十、主从复制#
概念:和MySQL的主从复制的概念大同小异,分成leader节点和follower节点,主节点承接线上的写流量,从节点承接线上的读流量为主库分流减压,从库的数据从主库中同步过来
主从复制的作用:
- 理论上主库从库的数据是需要保持的,这也是一种数据冗余热备份的机制
- 故障恢复:当leader节点出现故障时,可以由从节点提供服务,保证应用的可用性
- 负载均衡:在主从复制的接触上,可以将客户端的读写不同类型的流量分摊到不同的机器上,分流减压
- 主从复制+哨兵,构建高可用的redis集群,解决了单点故障问题
十一、集群搭建及小实验#
redis默认自己就是一个主库,所以我们搭建主从架构的redis,只需要配置Redis从库。
11.1、架构一:一主两从#
下面搭建这样的一主两从的redis集群
role | ip | port |
主库 | xxx | 16379 |
从库 | xxx | 16378 |
从库 | xxx | 16377 |
如果是在一台服务器上启动多台Redis,需要修改一下配置文件中的端口、pid文件名、日志名、dump.db名
启动三台redis
让16378、16377认16379为leader,执行如下命令:
搭建完主从环境之后,查看是否可以从slave中写入数据:
# 结果很明显,不能写入 127.0.0.1:16378> set k1 v0 (error) READONLY You can't write against a read only replica.
MySQL中只要你不设置从库read only,从库也是可以写入的并产生自己的binlog的
测试:从master写入,从slave读出
测试:主机宕机,slave有什么表现
主机宕机后,从库的role依然是slave,并且显示master的状态为down
测试:主机宕机后又重启了,slave有什么表现
主机重启后,slave会重连主机,主机的状态为up,salve可以正常在主库上同步数据
测试:从机宕机,然后有新数据写到了主库,从机再重启问:重启后的从机能不能获取到她宕机期间主库的增量数据?
答案是:获取不到了,因为如果是通过命令行搭建的主从,从库一旦重启,角色会变回master
如果这时再把16378变成16379的从库,问能不能获取到增量数据呢?
全量复制和增量复制
从库第一次连接到主库上肯定会进行一次全量复制,即:master会启动后台的存盘进程,同时收集所有用于修改数据集的命令,在后台完成同步,然后将整个数据文件发送给slave,让slave完成一次数据的全量复制
除第一次复制数据之外的主从复制都是增量复制,即master仅仅会将收到的增量写命令发送给slave。
11.2、架构二#
其中的17378既是Master又是Slave
对于16377来说,它确实认了16378为主,但是16378本身又是16379的slave,所以他们之间数据同步的走向是 : 16379 --> 16378 --> 16377 ,对于16378来说,即使有实例认它当master,它依然是不能写
如果主库16379宕机了,16378的状态依然是slave,并且它能察觉master已经挂了,执行 slave no one, 可以将自己提升为master。 在整个过程中,16377不受影响
即使旧master开机重启了,旧的master依然是master,也不能自动的加入到 16377 16378集群中
11.3、架构三:Sentinel#
上面的两种架构模式中,主库挂了之后都需要人为的去选举一个的新的master来承接读流量
redis2.8之之后,提供了哨兵模式:哨兵监控到当主服务器挂了,发起投票选新主库,实现自动的完成选主,承接线上写流量,完成止损
哨兵作为一个独立的进程存在,原理是:哨兵通过发送命令和redis服务器交互,从而监控运行整个集群中的多个Redis实例
Redis的哨兵在Redis 的安装目录下可以找到
为了防护哨兵出现单点故障,所以通常使用多个哨兵对集群进行监控
集群中的每个哨兵彼此相互监控,每个哨兵也都监控着集群中的所有Redis实例
主观下线和客观下线
当一个哨兵发现master不可用时,系统不会马上进行failover,仅仅是这一个哨兵主观意义上认为这个master不可用,这时如果其他的哨兵也来探测master,并且大部分的哨兵都主观认为master确实不可用了,哨兵们就会投票在slave中选出一个当得票最多的slave作为新的master。进行failover操作。
通过发布订阅的模式,哨兵告诉自己监控的那些服务器将master切换为刚刚的票最多的那个实例,这个过程就叫做客观下线。
集群搭建
首先是创建一主二从的redis集群, 此处省略,参照上面架构1部分即可
编写sentinel的配置文件,配置文件的名称、配置项不能写错~
# myredis1 监控的这个redis实例啥 # 127.0.0.1 监控的这个redis实例的ip # 16379 监控的这个redis实例的端口 # 1 监控的这个redis实例的挂了后,自动投票选主 sentinel monitor myredis1 127.0.0.1 16379 1
启动sentinel
这样,当master宕机后,哨兵会自动选择一个新的slave作为新主,主库重启后,sentinel会将其作为slave自动加入到现有的redis集群中
更多更详细的sentinel配置文件可以看看这个博文:https://www.cnblogs.com/heroinss/p/10340925.html
十二、缓存穿透、缓存击穿、雪崩#
缓存穿透
比如这种应用场景:使用redis缓存用户信息,当有新用户注册时先将用户的信息写入Mysql,然后写入Redis。有修改操作时,修改完MySQl中的数据后,同步的也会修改Redis中的数据,而且我们也没有给Redis中的key设置过期时间。(这就意味着:数据库中有指定的KV的信息的话,缓存中也会有。那当用户查询时缓存中没有的话,说明数据库中99.999%也不会有)
这时候有大量的请求去访问MySQL中都没有都数据时,请求先打向了Redis,Redis中肯定也没有存储用户查询的数据,所以大量的请求一下子打到了数据库上,瞬间击垮数据库,这种现象称为缓存穿透。
解决方案:
布隆过滤器:
布隆过滤器可以理解成一个bit数组,数组中每一个非0即1
客户端的请求统一先打向布隆过滤器,布隆过滤器放在应用的控制层,布隆过滤器中存在多个hash函数,分别对这个key进行hash得到hashcode,然后将hashcode%数组长度,将算出来的下标标记为1。
key以此经过所有hash,再%size算出的下标对应的值,只要存在一个不为1的数,我们就认为key没在缓存中,直接丢弃用户的这次请求,符合要求把请求打向Redis。从而避免这个请求对底层存储的查询压力。
缓存空对象:
当用户查询的时候,如果发现缓存中没有,就往缓存中放置一个空的对象,然后返回给用户这个控对象,也能避免用户的请求直接打向数据库。
缓存击穿:
缓存击穿指的是Redis中确确实实存在用户查询的key,但是呢用户的访问频率太猛烈了,导致Redis扛不住挂了,导致大量的请求直接打向数据库,或者当某一个key的过期时间到了的瞬间,大量的请求打向数据库导致数据库直接挂了
解决方案:
设置key永不过期
加互斥锁:对这个查询操作添加分布式锁,将原来的大并发直接访问缓存转换成了并发获取分布式锁,只有获取到分布式锁后才能去查询缓存。
雪崩:
比我们启动redis进行一些数据预热,就是将一些数据库中的数据提前导入到redis中,然后给这些数据设置了过期时间。
抢购时间一到系统迎来了一大批并发,但是由于缓存中的数据充足,所以能扛住这波并发。一段时间后,redis中的key集中式的过期了,这时再来一大批并发请求可能就直接将redis打垮。redis挂了后,大量的请求直接打向MySQL,导致MySQL跟着雪崩式的垮掉
解决方法:
异地多活,添加redis的实例的数量
加分布式锁
在应用和缓存之间添加消息中间件做缓冲
合理为不同的key设置不同的过期时间,放置缓存中的key出现集中式过期的情况