iMisty的技术栈

iMisty的技术栈

Redis进阶提升与主从复制

1041
2020-07-21

Redis 的发布(pub)与订阅(sub)

img

基础语法:

  • publish channel message 对某个channel(主题)发布消息
  • subscribe channel channel2 ... 订阅某个或者多个主题
  • psubscribe [pattern] [pattern ...] 订阅多个主题,模糊匹配以某个关键字开头的主题,例如:psubscribe msg*订阅所有以msg开头的主题
    img

Redis的持久化机制 - RDB(Redis DataBase)

1. 什么是RDB
RDB:每隔一段时间把内存中的数据写入磁盘的临时文件,作为快照,恢复的时候把快照文件读进内存,如果宕机重启,那么内存里的数据肯定会没有的,那么再次重启Redis后,则会恢复;
2. 备份与恢复

  • 备份:内存备份->磁盘临时文件(默认dump.rdb)
  • 恢复:临时文件->恢复到内存

3. RDB优劣势

优势:

  • 每隔一段时间备份,全量备份
  • 灾备简单,可以远程传输
  • 子进程备份的时候,主进程不会有任何io操作(不会写入修改或者删除),保证数据备份的完整性
  • 相对于AOF来说,当有更大文件的时候可以快速重启恢复

劣势:

  • 发生故障时,与可能丢失最后一次的备份数据
  • 子进程所占用的内存会和父进程一模一样, 会造成CPU负担
  • 由于定时的全量备份是重量级操作,所以对于实时备份就无法处理了

查看文件的最后更新时间ll --full-time;可在Redis命令行内查看配置,例如获取Redis工作目录,127.0.0.1:6379> config get dir

4. RDB的相关配置

  1. 保存位置,可以在redis.conf主配置文件中自定义:/usr/local/redis/working/dump.rdb

  2. dbfilename dump.rdb 设置备份文件的名称,在工作目录/usr/local/redis/working里面

  3. 保存机制

    save 900 1  # 如果1个缓存更新,则15分钟后备份
    save 300 10  # 如果10个缓存更新,则5分钟后备份
    save 60 10000 # 如果10000个缓存更新,则1分钟后备份
    # save 10 3     # 更新3个缓存,10秒后备份
    
  • stop-writes-on-bgsave-error:

    • yes:如果save过程出错,则停止写操作
    • no:可能造成数据不一致
  • rdbcompression

    • yes:开启rdb压缩模式
    • no:关闭,会节约cpu损耗,但是文件会大,道理同nginx
  • rdbchecksum

    • yes:使用CRC64算法校验对rdb进行数据校验,有10%性能损耗
    • no:不校验

总结: RDB适合大量数据的恢复,但是数据的完整性和一致性可能会不足。

save和bgsave

save: save 命令执行一个同步保存操作,将当前 Redis 实例的所有数据快照(snapshot)以 RDB 文件的形式保存到硬盘,保存成功时返回ok,失败返回ERR
bgsave: 命令执行之后立即返回OK ,然后 Redis fork 出一个新子进程,原来的 Redis 进程(父进程)继续处理客户端请求,而子进程则负责将数据保存到磁盘,然后退出。

save保存会阻塞主进程,客户端无法连接Redis,等save完成之后主进程才开始工作,客户端可以连接;BGSAVE 是fork一个save的子进程,在执行save过程中,不影响主进程,客户端可以正常链接redis,等子进程fork执行save完成后,通知主进程,子进程关闭,更加适合线上的维护操作;

dump.rdb文件丢失恢复的办法

(error) MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.

之前恢复redis快照文件出现了问题,删除dump.rdb文件之后,重启redis,发现save命令出错,网上查找资料发现是强制停止Redis快照的问题,另外我用普通用户启动Redis服务,执行save因为部分文件夹没有权限也会导致无法生成dump.rdb,所以需要重新用管理员权限启动一下即可

Redis的持久化机制 - AOF(Append Only File)

RDB会丢失最后一次备份的rdb文件,但是其实也无所谓,可以忽略不计,毕竟是缓存,丢了就丢了,但是如果追求数据的完整性,那就得考虑使用aof了

AOF的特点

  1. 以日志的形式来记录用户请求的写操作,读操作不会被记录,因为写操作才会存储存;
  2. 文件以追加的形式而不是以修改的形式;
  3. redis的aof恢复其实就是把追加的文件从开始到结尾读取执行写操作;

AOF的优势

  1. AOF更加耐用,可以以秒级别为单位备份,如果发生问题,也只会丢失最后一秒的数据,大大增加了可靠性和数据完整性,所以aof可以每秒备份一次,使用fsync操作;
  2. 以log日志形式追加,如果磁盘满了,会执行redis-check-aof工具
  3. 当数据太大的时候,redis可以在后台自动重写aof,当Redis继续把日志追加到老的文件中去的时候,重写也是是非常安全的不会影响客户端的读写操作
  4. aof日志包含所有的写操作,会更加便于Redis的解析恢复;

AOF的劣势

  1. 相同的数据,同一份数据,AOF会比RDB大;
  2. 针对不同的同步机制,AOF会比RDB慢,因为AOF每秒都会备份做写操作,这样相对于RDB来说就略低,每秒备份fsync没毛病,但是如果客户端的每一次写入就做一次备份fsync的话,那么Redis的性能就会下降;
  3. AOF发生过bug,就是数据恢复的时候数据不完整,这样显得AOF比较脆弱,容易出现bug,因为aof没有rdb那么简单,但是为了防止bug的产生,aof就不会根据旧的指令去重构,而是根据当前缓存中存在的数据指令去做重构,这样就更加健壮和可靠了

AOF的配置

# AOF 默认关闭,yes可以开启
appendonly no

# AOF 的文件名
appendfilename "appendonly.aof"

# no:不同步
# everysec:每秒备份,推荐使用
# always:每次操作都会备份,安全并且数据完整,但是慢性能差
appendfsync everysec

# 重写的时候是否要同步,no可以保证数据安全
no-appendfsync-on-rewrite no

# 重写机制:避免文件越来越大,自动优化压缩指令,会fork一个新的进程去完成重写动作,新进程里的内存数据会被重写,此时旧的aof文件不会被读取使用,类似rdb
# 当前AOF文件的大小是上次AOF大小的100% 并且文件体积达到64m,满足两者则触发重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

到底采用RDB还是AOF呢?

  1. 如果能够接受一定时间内的缓存丢失,那么可以使用RDB
  2. 如果你对实时性的数据比较care,那么就用AOF;
  3. 使用RDB和AOF结合一起做持久化,RDB做冷备,可以在不同时期对不同版本做恢复,AOF做热备,保证数据仅仅只有1秒的损失。当AOF破损不可用了,那么再用RDB恢复,这样就做到了两者的相互结合,也就是说Redis恢复会先加载AOF,如果AOF有问题会再加载RDB,这样就达到冷热备份的目的了。

Redis 主从复制原理解析

img

注意: 主服务一定要开启持久化设置;

主从模式

img
如图:通常在主从模式的集群环境下,是一主双从的模式,如果一主多从,那么数据的同步需要消耗掉主节点计算机的性能,为了提高并发能力可以在从节点下部署从节点的从节点;

搭建Redis主从复制(读写分离)

注意:本地虚拟机配置一个环境之后可以直接克隆出多个虚拟机,但是需要注意修改静态IP地址和新的mac地址;

1. 可以通过info replication查看当前主机主从复制的节点信息
img

2. 在从节点下编辑redis.conf 主配置文件,添加主节点相关信息,设置从节点只读,来实现读写分离;

# replicaof <masterip> <masterport> 设置主节点的IP和端口
replicaof 192.168.1.105 6379
# masterauth <master-password> 设置主节点的验证密码
masterauth 123456
# 当前从节点只读
replica-serve-stale-data yes

3. 重启从节点下的Redis服务,如果配置没有问题的话这个时候R主Redis的数据全部都会同步过来了;
img
4. 测试主从复制 :将从节点主机working目录下dump.rdb和appendonly.aof文件删除,重启从服务器的Redis,数据同步正常则搭建成功

注意:主从同步方式在主节点挂掉之后,从节点与主节点连接断开,从节点不会变更为主节点对外提供服务

踩坑指南:

  1. 最好运行/启动Redis的主机有管理员权限,不然可能部分文件没有权限,导致同步失败
  2. 注意配置防火墙和安全组规则(云主机),或者干脆关闭防火墙自启动服务,不然也会出现无法同步的问题
  3. redis默认不记录log文件,需要在redis.conf文件,找到loglevel部分,设置logfile路径(默认为""),然后tail -f $redis_log_path监听错误日志

Redis无磁盘化复制原理解析

前言: 通常云服务磁盘是比较高效的,以阿里云为例,高效磁盘适合作为开发测试和系统盘,而ssd或者更加快的ESSD磁盘适合做数据库和日志文件的存储磁盘,来达到快速的读写提高吞吐量,但是如果自建服务器如果有机械硬盘但是网络带宽较高的话,可以使用无磁盘复制,避免磁盘读写,通过socket传输rdb文件内容实现无磁盘复制;无磁盘化复制的配置默认是关闭的,且当前处于试验阶段不推荐在生产环境使用

详细说明

# 1) Disk-backed: The Redis master creates a new process that writes the RDB
#                 file on disk. Later the file is transferred by the parent
#                 process to the replicas incrementally.
# 2) Diskless: The Redis master creates a new process that directly writes the
#              RDB file to replica sockets, without touching the disk at all.
...
# With slow disks and fast (large bandwidth) networks, diskless replication
# works better.

img

配置:将redis.conf配置文件的repl-diskless-sync改为开启无磁盘复制,然后配置repl-diskless-sync-delay(默认5)设置同步等待时间,重启服务即可配置完成

Redis 缓存过期处理与内存淘汰机制

**前言:**计算机内存有限,越大越贵,Redis的高并发高性能都是基于内存的,用硬盘无法发挥Redis的性能;

已过期的key如何处理?

设置了expire的key缓存过期了,但是服务器的内存还是会被占用,这是因为redis所基于的两种删除策略
(主动)定期删除: 定时随机的检查过期的key,如果过期则清理删除。(每秒检查次数在redis.conf中的hz配置,默认每秒检查10次,设值区间在1-500),主动删除会释放过期key所占用的内存;hz设置过低会导致cpu占用较高,设置过高可能无法及时释放内存并且过多的key会在同一时间释放,一般默认即可

(被动)惰性删除: 当客户端请求一个已经过期的key的时候,那么redis会检查这个key是否过期,如果过期了,则删除,然后返回一个nil,否则内存会一直被占用。这种策略对cpu比较友好,不会有太多的损耗,但是内存占用会比较高。
所以,虽然key过期了,但是只要没有被redis清理(用户不访问),那么其实内存还是会被占用着的。

如果内存被Redis缓存占用满了怎么办?

内存占满了,可以使用硬盘,来保存,但是没意义,因为硬盘没有内存块,会影响redis性能。
所以,当内存占用满了以后,redis提供了一套缓存淘汰机制: MEMORY MANAGEMENT

maxmemory: 当内存已使用率到达设置值,则开始清理缓存,默认是没有设置的

maxmemory-policy 内存满了之后的内存淘汰机制

  • noeviction:默认的淘汰机制,旧缓存永不过期,新缓存设置不了,返回错误
  • allkeys-lru:清除最少用的旧缓存,然后保存新的缓存(推荐使用)
  • allkeys-random:在所有的缓存中随机删除(不推荐)
  • volatile-lru:在那些设置了expire过期时间的缓存中,清除最少用的旧缓存,然后保存新的缓存
  • volatile-random:在那些设置了expire过期时间的缓存中,随机删除缓存
  • volatile-ttl:在那些设置了expire过期时间的缓存中,删除即将过期的

LRU means Least Recently Used
LFU means Least Frequently Used

Redis 的哨兵模式

**引言:**在主从同步模式下,一旦master节点挂了,如何保证可用性来实现继续读写

什么是哨兵?
Sentinel(哨兵)是用于监控Redis集群中Master状态的工具,是 Redis高可用解决方案,哨兵可以监视一个或者多个redis master服务,以及这些master服务的所有从服务;当某个master服务宕机后,会把这个master下的某个从服务升级为master来替代已宕机的master继续工作
img

哨兵模式部署实践

1.配置哨兵监控master: 将Redis的解压目录中的哨兵配置文件sentinel.conf 复制到安装目录,修改如下配置;

  • 常规配置
port 26379
pidfile "/usr/local/redis/sentinel/redis-sentinel.pid"
# 哨兵模式工作目录
dir "/usr/local/redis/sentinel"
# 设置后台运行
daemonize yes

# 设置非保护模式;
# 可以出于安全考虑开启保护模式然后设置bind 127.0.0.1 192.168.1.1 # 仅仅允许指定的IP访问
protected-mode no
logfile "/usr/local/redis/sentinel/redis-sentinel.log"
  • 核心配置
# 配置哨兵监听器, 配置主节点的命名,IP,端口;当有两个哨兵发现主节点挂掉即可标记主节点挂掉,两个从节点的其中一个哨兵即可开启故障转移
sentinel monitor <master-name> 192.168.1.101 6379 2
# 密码
sentinel auth-pass <master-name> <password>
# master被sentinel认定为失效的间隔时间,30s
sentinel down-after-milliseconds <master-name> 30000
# 剩余的slaves重新和新的master做同步的并行个数
sentinel parallel-syncs <master-name> 1
# 主备切换的超时时间,哨兵要去做故障转移,这个时候哨兵也是一个进程,如果他没有去执行,超过这个时间后,会由其他的哨兵来处理
sentinel failover-timeout <master-name> 180000

注意: 主节点的名称需要保持一致

简化配置:可以清空哨兵配置文件,仅保留如下配置

protected-mode no
port 26379
daemonize yes
pidfile "/var/run/redis-sentinel.pid"
logfile "/usr/local/redis/sentinel/redis-sentinel.log"
dir "/usr/local/redis/sentinel"

sentinel monitor mymaster 192.168.1.101 6379 2
sentinel auth-pass mymaster 123456
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000

2. 配置完成之后直接启动即可

  • redis-sentinel sentinel.conf

配置完成之后直接启动即可,sentinel会自动在配置文件中生成相应的配置

3. 用同样的配置文件将从节点的哨兵模式打开:查看主节点日志信息如下

img

可以使用scp sentinel.conf root@imisty.cn:~ 将配置文件传输给从节点

3. 检查与测试

  • 启动成功之后可以通过ps -ef |grep redis查看进程,
  • tail -f redis-sentinel.log监听日志
  • 配置成功之后将主节点关闭,此时查看从节点的Redis终端,发现主节点变更,再启动原来主节点的Redis服务,上线成为了从节点,说明配置ok
    img

进入哨兵终端查看信息:redis-cli -p 26379

  • 查看mymaster下的master节点信息 : sentinel master mymaster

  • 查看mymaster下的slaves节点信息 :sentinel slaves mymaster

  • 查看mymaster下的哨兵节点信息 : sentinel sentinels mymaster

解决原Master恢复后不同步问题

如果原来的Master(101)恢复成Slave后,他的同步状态不OK,状态为master_link_status:down,这是为什么呢?

这是因为可能我们只设置了两个从节点的的masterauth,这是用于同步master的数据,但是一开始是master是不受影响的,当master转变为slave后,由于他没有设置masterauth,所以他不能从新的master同步数据,随之导致info replication的时候,同步状态为down,所以只需要修改redis.conf中的masterauth即可。

一般master数据无法同步给slave的方案检查为如下:

  • 网络通信问题,要保证互相ping通,内网互通。
  • 关闭防火墙,对应的端口开发(虚拟机中建议永久关闭防火墙,云服务器的话需要保证内网互通)。
  • 统一所有的密码,不要漏了某个节点没有设置。

SpringBoot 集成Redis哨兵-配置

spring:
  redis:
    database: 1
    password: 123456
    sentinel:
      master: mymaster
      nodes:192.168.1.105:26379,192.168.1.104:26379,192.168.1.106:26379

图解哨兵模式

在一主多从的主从复制模式下,一旦主节点挂掉直接影响集群的功能,可用性低下;
哨兵监控
img

故障转移
img

选举leader: 获胜的从节点变为主节点,然后按照设置的并行同步数量依次将数据同步到其他从节点
img

原master恢复: 只能变成从节点
img

哨兵部署的约定

  • 哨兵节点要有至少三个或者奇数个节点,这样可以保证客观下线和选举的准确性
  • 哨兵分布式部署在不同的计算机节点,防止因为单个节点故障影响这整个集群
  • 一组哨兵只监听一组主从;虽然哨兵可以监听多组,但是防止发生故障的时候产生错误,最好本节点哨兵监听本节点的Redis

Redis-Cluster 集群

引言: 在主从复制以及哨兵集群环境下,可以提高读的并发,但是单个master容量有限,数据达到一定的程度会有瓶颈,这个时候可以通过水平扩展为多master-slave成为集群;

Redis-cluster 集群: 他可以支撑多个master-slave,支持海量数据,实现高可用与高并发;

img

哨兵模式其实也是一种集群,他能够提高读请求的并发,但是容错方面可能会有一些问题,比如master同步数据给slave的时候,这其实是异步复制吧,这个时候master挂了,那么slave上的数据就没有master新.数据同步需要时间的,1-2秒的数据会丢失.master恢复并转换成slave后,新数据则丢失。

特点:

  1. 每个节点知道彼此之间的关系,也会知道自己的角色,当然他们也会知道自己存在于一个集群环境中,他们彼此之间可以交互和通信,比如ping pong.那么这些关系都会保存到某个配置文件中,每个节点都有,这个我们在搭建的时候会做配置的.
  2. 客户端要和集群建立连接的话,只需要和其中一个建立关系就行。
  3. 某个节点挂了,也是通过超过半数的节点来进行的检测,客观下线后主从切换,和我们之前在哨兵模式中提到的是一个道理。
  4. Redis中存在很多的插槽,又可以称之为槽节点,用于存储数据;

集群容错
构建Redis集群,需要至少3个节点作为master,以此组成一个高可用的集群,此外每个master都需要配备一个slave,所以整个集群需要6个节点,这也是最经典的Redis集群,也可以称之为三主三从,容错性更佳。所以在搭建的时候需要有6台虚拟机。请各自准备6台虚拟机,可以通过克隆去构建,使用单实例的Redis 去克隆即可 。

  • 集群也可以在单服务器构建,称之为伪集群,但是生产环境肯定是真的,所以建议用6台。
  • 虚拟机克隆后务必关闭Redis,配置完成再启动

搭建Redis的三主三从集群模式

1. redis.conf 配置

# 开启集群模式
cluster-enabled yes
# 每一个节点需要有一个配置文件,需要6份。每个节点处于集群的角色都需要告知其他所有节点,彼此知道,这个文件用于存储集群模式下的集群状态等信息,这个文件是由redis自己维护,我们不用管。如果你要重新创建集群,那么把这个文件删了就行
cluster-config-file nodes1-6379.conf
# 超时时间,超时则认为master宕机,随后主备切换
cluster-node-timeout 5000
# 开启AOF
appendonly yes

2. 创建&启动6个redis实例

  • 启动6台Redis实例,这里采用三个虚拟机,一台主机启动两台Redis对应端口,6379,6380,在redis.conf基础上创建redis2.conf

    1. 修改port为6380
    2. 修改 pidfile "/var/run/redis_6380.pid"
    3. 修改日志路径,需要创建新的目录 logfile "/var/log/redis2/redis.log";
    4. 创建新的工作目录 dir "/usr/local/redis/working2"
    5. 启动RedisServer2 /usr/local/bin/redis-server ./redis2.conf,关闭Redis redis-cli -a 123456 -p 6380 shutdown

    img

最好创建单独的目录,不然得修改相应的文件名来区分本机6379和6380的Redis服务,如果启动不了注意查看/var/log/redis2/redis.log日志

  • 在配置完成之后启动,如果启动过程出错,把rdbaof等文件删除清空

3. 创建集群

#####
# 注意1:如果你使用的是redis3.x版本,需要使用redis-trib.rb来构建集群,最新版使用C语言来构建了,这个要注意
# 注意2:以下为新版的redis构建方式
#####

# 创建集群,主节点和从节点比例为1,1-3为主,4-6为从,1和4,2和5,3和6分别对应为主从关系,这也是最经典用的最多的集群模式;
# 这里的1代表比值,主节点和从节点之间的比值是1:1
redis-cli [-a 123456] --cluster create ip1:port1 ip2:port2 ip3:port3 ip4:port4 ip5:port5 ip6:port6 --cluster-replicas 1

img

等待集群环境创建完成
img
slots: 槽,用于装数据,主节点有,从节点没有

redis-cli --cluster help 查看集群的帮助文档

4. 检查集群信息
redis-cli --cluster check 192.168.1.101:6379
在Redis终端执行:

  • cluster info 查看集群信息;
  • cluster nodes 查看节点信息;

img

集群环境分析

  • 如果一个master挂掉,这个集群还可以正常工作,对应的slave变为master
  • 如果一个slave挂掉对集群没有影响
  • 集群中读写由master负责,slave是备份,当然有一个readonly是可以配置的,这样salve也可以读了;
  • 集群的扩容:针对master做水平扩展即可,如此一来,读操作和写操作的吞吐量会随之提高
  • 集群的数据备份其实同理AOF和RDB,设置好了既可。当然了,虽然是集群,当一个节点挂了以后,在极端的情况下执行也是有可能会造成一部分数据的丢失的

常见异常解决

1. 没有清空工作空间

[ERR] Node 192.168.1.106:6380 is not empty. 
Either the node already knows other nodes (check with CLUSTER NODES) or 
contains some key in database 0.

将对应节点的工作目录内容删除,然后重新启动

  1. 没有采用集群的方式启动客户端,导致无法访问集群
    (error) MOVED 5798 192.168.1.106:6379
    在集群中的任意节点,采用如下方式启动客户端即可redis-cli -c -a 123456 -h 192.168.1.101 -p 6379

什么是slot槽节点

**简介:**槽节点总共有16384个进行分配,平均分配到集群中的master节点下,备用节点没有槽节点的;
img

槽节点如何存储: 数据真正是放在槽节点里面的而不是Redis里面去的;hsah(key)%16384key经过hash计算之后取模算出对应槽的信息,然后进行存储的;
img

Springboot集成Redis集群

spring:
  redis:
    password: 123456
    cluster:
      nodes: 192.168.1.101:6379,192.168.1.104:6379,192.168.1.106:6379... # 将所有的节点加入即可****