一、Redis集群
由于单机 Redis 存储能力受单机限制,以及无法实现读写操作的负载均衡和读写分离,无法保证高可用。
本篇就来介绍 Redis 集群搭建方案及实现原理,实现 Redis 对数据的冗余备份,从而保证数据和服务的高可用。
主从复制是哨兵和集群的基石,因此我们循序渐进,由浅入深一层层的将 Redis 高可用方案抽丝剥茧展示在大家面前。
1.主从复制
主从复制,是指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器,主从是哨兵和集群模式能够实施 的基础。前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点。默认 情况下,每台 Redis 服务器都是主节点;且一个主节点可以有零个或多个从节点(0+个从节点),但一个从节点只 能有一个主节点。一般主节点负责接收写请求,从节点负责接收读请求,从而实现读写分离。主从一般部署在不 同机器上,复制时存在网络延时问题,使用参数 repl-disable-tcp-nodelay 选择是否关闭 TCP_NODELAY,默认为关闭:
# 关闭:
无论数据大小都会及时同步到从节点,占带宽,适用于主从网络好的场景。
# 开启:
主节点每隔指定时间合并数据为 TCP 包节省带宽,默认为 40 毫秒同步一次,适用于网络环境复 杂或带宽紧张,如跨机房
2.作用
# 1、数据冗余:
主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
# 2、故障恢复:
当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
# 3、负载均衡:
在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,分担服 务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高 Redis 服务器的并发量。
# 4、读写分离:
主库写、从库读,读写分离不仅可以提高服务器的负载能力,同时可根据需求的变化,改变从库 的数量。
# 5、高可用基石:
除了上述作用以外,主从复制还是哨兵和集群能够实施的基础。
3.开启主从配置
配置主从可以在命令行或配置文件中配置,上面提到主节点负责写,从节点负责读,因此推荐开启从服务器 的只读配置,否则的话在从节点的写操作不会同步到主节点会导致数据不一致。
只配置从库,不用配置主库
#查看当前库的信息
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0 #没有从机
master_replid:625361c542744a006584fa2766a7eba2928320da
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
#1.准备主从环境(一主两从)
[root@redis ~]# cd /usr/local/redis/conf/
[root@redis01 conf]# cp redis.conf redis-6380.conf
[root@redis01 conf]# cp redis.conf redis-6381.conf
[root@redis01 conf]# ll
总用量 256
-rw-r--r-- 1 root root 84893 5月 7 06:24 redis-6380.conf
-rw-r--r-- 1 root root 84893 5月 7 06:24 redis-6381.conf
-rw-r--r-- 1 root root 84893 5月 1 20:33 redis.conf
-rw-r--r-- 1 root root 217 5月 7 05:52 users.acl
#改配置文件对应端口号
[root@redis01 conf]# vim redis-6380.conf
port 6380
[root@redis01 conf]# vim redis-6381.conf
port 6381
#将三个配置文件中aclfile一行内容注释掉
#2.启动三个Redis服务
[root@redis01 ~]# /usr/local/redis/bin/redis-server /usr/local/redis/conf/redis.conf
[root@redis01 ~]# /usr/local/redis/bin/redis-server /usr/local/redis/conf/redis-6380.conf
[root@redis01 ~]# /usr/local/redis/bin/redis-server /usr/local/redis/conf/redis-6381.conf
[root@redis01 ~]# netstat -lntp|grep redis
tcp 0 0 127.0.0.1:6379 0.0.0.0:* LISTEN 1297/redis-server 1
tcp 0 0 127.0.0.1:6380 0.0.0.0:* LISTEN 1426/redis-server 1
tcp 0 0 127.0.0.1:6381 0.0.0.0:* LISTEN 1432/redis-server 1
#3.redis-cli连接三个服务端
[root@redis01 ~]# redis-cli -p 6379
127.0.0.1:6379>
[root@redis01 conf]# redis-cli -p 6380
127.0.0.1:6380>
[root@redis01 ~]# redis-cli -p 6381
127.0.0.1:6381>
#4.从节点加入主节点
127.0.0.1:6381> slaveof 127.0.0.1 6379
OK
127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
127.0.0.1:6379> info replication #查看集群信息
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6381,state=online,offset=84,lag=0
slave1:ip=127.0.0.1,port=6380,state=online,offset=84,lag=0
master_replid:8b8e3f607542628afc9ba358a7881764d4081e9a
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:84
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:84
127.0.0.1:6380> info replication #从节点查看集群信息
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up #up
master_last_io_seconds_ago:4
master_sync_in_progress:0
slave_repl_offset:406
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:8b8e3f607542628afc9ba358a7881764d4081e9a
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:406
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:57
repl_backlog_histlen:350
127.0.0.1:6380>
#5.撤销集群主节点
SLAVEOF NO ONE
config set masteruser #从节点设置主节点用户命令
config get masteruser #从节点查看主节点用户命令
[root@redis01 conf]# cp redis.conf redis-6381.conf
[root@redis01 conf]# ll
total 256
-rw-r--r-- 1 root root 84979 Jul 20 16:32 redis-6380.conf
-rw-r--r-- 1 root root 84979 Jul 20 16:33 redis-6381.conf
-rw-r--r-- 1 root root 84979 Jul 20 16:20 redis.conf
-rw-r--r-- 1 root root 32 Jul 20 16:20 user.acl
#改配置文件对应端口号
[root@redis01 conf]# vim redis-6380.conf
port 6380
[root@redis01 conf]# vim redis-6381.conf
port 6381
#将三个配置文件中aclfile一行内容注释掉
4.主从复制的原理
# 1、主从复制过程大体分为3个阶段:
1、连接建立阶段(即准备阶段)
2、数据同步阶段
3、命令传播阶段
# 2、在从节点执行slaveof命令后,复制过程便开始按下面的流程运作:
1、保存主节点信息:
配置slaveof之后会在从节点保存主节点信息
2、主从建立socket连接:
定时发现主节点以及尝试建立连接
3、发送ping命令:
从节点定时发送 ping 给主节点,主节点返回 PONG。若主节点没有返回 PONG 或因阻 塞无法响应导致超时,则主从断开,在下次定时任务时会从新 ping 主节点
4、权限验证:
若主节点开启了 ACL 或配置了 requirepass 参数,则从节点需要配置 masteruser 和 masterauth 参数才能保证主从正常连接(密码主从)
requirepass 123 3台配置开启密码
masteruser <username> 打开改成 masteruser default
masterauth <master-password> 打开,改成 masterauth 123
5、同步数据集:
首次连接,全量同步
6、命令持续复制:
全量同步完成后,保持增量同步
# 3、当节点被当做从节点的时候,需要注意将从节点设置为只读,这样做的目的是为了保证集群的数据一致性。
5.全量复制与部分复制
1)、全量复制
redis 全量复制的原理是,首先将 master 本身的 RDB 文件同步给 slave,而在同步期间,master 写入的命令 也会记录下来(master 内部有一个复制缓冲区,会记录同步时 master 新增的写入),当 slave 将 RDB 加载完后, 会通过偏移量的对比将这期间 master 写入的值同步给 slave
2)、部分复制
当 master 和 slave 断开连接时,master 会将期间所做的操作记录到复制缓存区当中(可以看成是一个队列, 其大小默认 1M)。待 slave 重连后,slave 会向 master 发送 psync 命令并传入 offset 和 runId,这时候,如果 master 发现 slave 传输的偏移量的值,在缓存区队列范围中,就会将从 offset 开始到队列结束的数据传给 slave,从而达 到同步,降低了使用全量复制的开销
6.全量复制的开销
bgsave 的开销,每次 bgsave 需要 fork 子进程,对内存和 CP的开销很大
RDB 文件网络传输的时间(网络带宽)
从节点清空数据的时间
从节点加载 RDB 的时
可能的 AOF 重写时间(如果我们的从节点开启了 AOF,则加载完 RDB 后会对 AOF 进行一个重写,保证 AOF 是最新的)
7.主从复制存在的问题
# 1、手动故障转移# 2、写能力和存储能力受限
二、哨兵
哨兵(sentinel),用于对主从结构中的每一台服务器进行监控,当主节点出现故障后通过投票机制来挑选 新的主节点,并且将所有的从节点连接到新的主节点上。前面的主从是最基础的提升 Redis 服务器稳定性的一种 实现方式,但我们可以看到 master 节点仍然是一台,若主节点宕机,所有从服务器都不会有新的数据进来,如何让主节点也实现高可用,当主节点宕机的时候自动从从节点中选举一台节点提升为主节点就是哨兵实现的功能。
1.作用
# 1、监控:监控主从节点运行情况
# 2、通知:当监控节点出现故障,哨兵之间进行通讯
# 3、自动故障转移:当监控到主节点宕机后,断开与宕机主节点连接的所有从节点,然后在从节点中选取一个作为主节点,将其他的从节点连接到这个最新的主节点。最后通知客户端最闲的服务地址
2.哨兵的工作方式
1.每个 Sentinel 以每秒钟一次的频率向它所知的 Master,Slave 以及其他 Sentinel 实例发送一个 PING 命令。
2.如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 own-after-milliseconds 选项所指定 的值,则这个实例会被 Sentinel 标记为主观下线。(主观下线不影响其他功能)
3.如果一个 Master 被标记为主观下线,则正在监视这个 Master 的所有 Sentinel 要以每秒一次的频率确认 Master 的确进入了主观下线状态。
4.当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认 Master 的确进入了主观下 线状态,则 Master 会被标记为客观下线。 (客观下线直接杀死)
5.在一般情况下,每个 Sentinel 会以每 10 秒一次的频率向它已知的所有 Master,Slave 发送 INFO 命令。
6.当 Master 被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会 从 10 秒一次改为每秒一次。
7.若没有足够数量的 Sentinel 同意 Master 已经下线,Master 的客观下线状态就会被移除。 若 Master 重新向Sentinel 的 PING 命令返回有效回复,Master 的主观下线状态就会被移除。
3.哨兵架构图
4.配置哨兵
#1.配置文件详解:
# 端口
port 26379
# 是否后台启动
daemonize yes
# pid 文件路径
pidfile /var/run/redis-sentinel.pid
# 日志文件路径
logfile "/var/log/sentinel.log"
# 定义工作目录
dir /tmp
# 定义 Redis 主的别名, IP, 端口,这里的 2 指的是需要至少 2 个 Sentinel 认为主 Redis 挂了才最终会采取下一步行为
# sentinel monitor [集群名称] [集群主节点 IP] [断开] []
sentinel monitor mymaster 127.0.0.1 6379 2
# 如果 mymaster 30 秒内没有响应,则认为其主观失效
sentinel down-after-milliseconds mymaster 2000
# 如果 master 重新选出来后,其它 slave 节点能同时并行从新 master 同步数据的台数有多少个,显然该值越大,所有 slave 节点完成同步切换的整体速度越快,但如果此时正好有人在访问这些 slave,可能造成读取失败,影响面会更广。最保守的设置为 1,同一时间,只能有一台干这件事,这样其它 slave 还能继续服务,但是所有 slave 全部完成缓存更新同步的进程将变慢。
sentinel parallel-syncs mymaster 1
# 该参数指定一个时间段,在该时间段内没有实现故障转移成功,则会再一次发起故障转移的操作,单位毫秒
sentinel failover-timeout mymaster 180000
# 不允许使用 SENTINEL SET 设置 notification-script 和 client-reconfig-script。
sentinel deny-scripts-reconfig yes
#1.添加配置文件
[root@redis01 ~]# cd /usr/local/redis/conf/
[root@redis01 conf]# vim sentinel-26379.conf
port 26379
daemonize yes
pidfile "/var/run/redis-sentinel-26379.pid"
logfile "/var/log/sentinel-26379.log"
dir "/tmp"
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 2000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
sentinel deny-scripts-reconfig yes
# sentinel auth-pass mymaster 123
[root@redis01 conf]# vim sentinel-26380.conf
port 26380
daemonize yes
pidfile "/var/run/redis-sentinel-26380.pid"
logfile "/var/log/sentinel-26380.log"
dir "/tmp"
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 2000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
sentinel deny-scripts-reconfig yes
[root@redis01 conf]# vim sentinel-26381.conf
port 26381
daemonize yes
pidfile "/var/run/redis-sentinel-26381.pid"
logfile "/var/log/sentinel-26381.log"
dir "/tmp"
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 2000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
sentinel deny-scripts-reconfig yes
#2.启动
[root@redis01 redis]# ./bin/redis-sentinel ./conf/sentinel-26379.conf
[root@redis01 redis]# ./bin/redis-sentinel ./conf/sentinel-26380.conf
[root@redis01 redis]# ./bin/redis-sentinel ./conf/sentinel-26381.conf
#3.验证
[root@redis01 redis]# ps -ef |grep redis
root 2107 1 0 11:57 ? 00:00:00 ./bin/redis-sentinel *:26379 [sentinel]
root 2113 1 0 11:57 ? 00:00:00 ./bin/redis-sentinel *:26380 [sentinel]
root 2119 1 0 11:57 ? 00:00:00 ./bin/redis-sentinel *:26381 [sentinel]
#4.查看集群主节点监控
[root@redis01 redis]# redis-cli -p 26379
127.0.0.1:26379> sentinel master mymaster
1) "name"
2)"mymaster"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "6379
7) "runid"
8) "50d713000de317940645802862c2d53a6c116189" #有runid ID号就证明监控成功
5.三个定时任务
# 1.每 10 秒每个 sentinel 会对 master 和 slave 执行 info 命令,这个任务达到两个目的:
a) 发现 slave 节点
b) 确认主从关系
# 2.每 2 秒每个 sentinel 通过 master 节点的 channel 交换信息(pub/sub)。
master 节点上有一个发布订阅的频 道(sentinel:hello)。sentinel 节点通过__sentinel__:hello 频道进行信息交换(对节点的"看法"和自身的信息), 达成共识。`subscribe __sentinel__:hello`
# 3.每 1 秒每个 sentinel 对其他 sentinel 和 redis 节点执行 ping 操作(相互监控),这个其实是一个心跳检测, 是失败判定的依据
6.主观下线与客观下线
1)、主观下线
所谓主观下线(Subjectively Down, 简称 SDOWN)指的是单个 Sentinel 实例对服务器做出的下线判断,即 单个 sentinel 认为某个服务下线(有可能是接收不到订阅,之间的网络不通等等原因)。如果服务器在 down-after-milliseconds 给定的毫秒数之内, 没有返回 Sentinel 发送的 PING 命令的回复, 或者返回一个错误, 那么 Sentinel 将这个服务器标记为主观下线(SDOWN )。sentinel 会以每秒一次的频率向所有与其建立了命令 连接的实例(master,从服务,其他 sentinel)发 ping 命令,通过判断 ping 回复是有效回复,还是无效回复来判 断实例时候在线(对该 sentinel 来说是“主观在线”)。sentinel 配置文件中的 down-after-milliseconds 设置了判 断主观下线的时间长度,如果实例在 down-after-milliseconds 毫秒内,返回的都是无效回复,那么 sentinel 回认为 该实例已(主观)下线,修改其 flags 状态为 SRI_S_DOWN。如果多个 sentinel 监视一个服务,有可能存在多个 sentinel 的 down-after-milliseconds 配置不同,这个在实际生产中要注意。
2)、客观下线
客观下线(Objectively Down, 简称 ODOWN)指的是多个 Sentinel 实例在对同一个服务器做出 SDOWN 判断,并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后,得出的服务器下线判断,然后开启 failover。
客观下线就是说只有在足够数量的 Sentinel 都将一个服务器标记为主观下线之后, 服务器才会被标记为客 观下线(ODOWN)。
只有当 master 被认定为客观下线时,才会发生故障迁移。
当 sentinel 监视的某个服务主观下线后,sentinel 会询问其它监视该服务的 sentinel,看它们是否也认为该服 务主观下线,接收到足够数量(这个值可以配置)的 sentinel 判断为主观下线,既任务该服务客观下线,并对其 做故障转移操作。
客观下线条件只适用于主服务器: 对于任何其他类型的 Redis 实例, Sentinel 在将它们判断为下线前不需 要进行协商, 所以从服务器或者其他 Sentinel 永远不会达到客观下线条件。只要一个 Sentinel 发现某个主服务 器进入了客观下线状态, 这个 Sentinel 就可能会被其他 Sentinel 推选出, 并对失效的主服务器执行自动故障 迁移操
三、Redis缓存穿透和雪崩
Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题。从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么久不能使用缓存。
另外的一些典型问题就是,缓存穿透,缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案
1、缓存穿透(查不到导致)
概念
用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这个时候就相当于出现了缓存穿透
解决方案
方案一:
方案二:
但是这种方法会存在两个问题:
1.如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键
2.即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响
2、缓存击穿(量太大,缓存过期)
概述
是指一个key非常热点,在不停地扛着大并发,大并发集中对这一个点进行访问,当这个key在失败的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导致数据库瞬间压力过大
解决方案
1.设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题
2.加互斥锁
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大
3、缓存雪崩
概念
解决方案