一:简介
- redis的高可用
为了达到redis的高可用,有两种部署方式:
主从复制+哨兵机制。
集群模式。
哨兵机制是redis2.8开始支持。集群模式是redis3.0开始支持。 - 主从复制的意义
主从复制可以把主节点的数据复制给从节点。从节点可以备份主节点的数据,起到主节点挂调,
顶上来接替主节点工作的作用。也可以起到分担主节点读压力的作用。 - 哨兵机制存在的意义:
为了实现redis故障转移的自动化。自动发现,自动转移。不需要人工参与 - 什么是哨兵机制?
Redis的哨兵(sentinel) 系统用于管理多个 Redis 服务器,该系统执行以下三个任务:
a:监控(Monitoring): 哨兵(sentinel) 会不断地检查你的Master和Slave是否运作正常
b:提醒(Notification):当被监控的某个 Redis出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知
c:自动故障迁移(Automatic failover):
当一个Master不能正常工作时,哨兵(sentinel) 会开始一次自动故障迁移操作,它会将失效Master的其中
一个Slave升级为新的Master, 并让失效Master的其他Slave改为复制新的Master; 当客户端试图连接失效
的Master时,集群也会向客户端返回新Master的地址,使得集群可以使用Master代替失效Master。
哨兵(sentinel) 是一个分布式系统,你可以在一个架构中运行多个哨兵(sentinel) 进程,这些进程使用流言协议(gossipprotocols)
来接收关于Master是否下线的信息,并使用投票协议(agreement protocols)来决定是否执行自动故障迁移,以及选择哪个Slave
作为新的 Master。
每个哨兵(sentinel) 会向其它哨兵(sentinel)、master、slave定时发送消息,以确认对方是否”活”着,如果发现对方在指定时间(可配
置)内未回应,则暂时认为对方已挂(所谓的”主观认为宕机” Subjective Down,简称sdown).
若“哨兵群”中的多数sentinel,都报告某一master没响应,系统才认为该master"彻底死亡"(即:客观上的真正down机,
Objective Down,简称odown),通过一定的vote算法,从剩下的slave节点中,选一台提升为master,然后自动修改相关配置。
虽然哨兵(sentinel) 释出为一个单独的可执行文件 redis-sentinel ,但实际上它只是一个运行在特殊模式下的 Redis 服务器,
你可以在启动一个普通 Redis 服务器时通过给定 --sentinel 选项来启动哨兵(sentinel)。
二:两种数据丢失的情况(主备切换的过程,可能会导致数据丢失)
- 异步复制导致的数据丢失
问题详细描述:
当客户端请求过来写数据的时候,是和Master交互的,请求将数据写入到master内存中,那么这时候master按道理是要将
数据同步到slave节点的,但是因为master -> slave的复制是异步的,所以可能有部分数据还没复制到slave,master就宕机
了,此时这些部分数据就丢失了。那么这时候新的slave节点成为新的master,但是之前没有来得及同步的数据就丢失了。 - 脑裂导致的数据丢失
问题详细描述:
脑裂,也就是说,某个master所在机器突然脱离了正常的网络,跟其他slave机器不能连接,但是实际上master还运行着, 此时哨兵可能就会认为master宕机了,然后开启选举,将其他slave切换成了master。这个时候,集群里就会有两个master,也就是所谓的脑裂。此时虽然某个slave被切换成了master,但是可能client还没来得及切换到新的master,并且client和旧的master在一个网络,可以连通,还继续写向旧master的数据,因此旧master再次恢复的时候,会被作为一个slave挂到新的master上去,自己的数据会清空,重新从新的master复制数据,那么继续写入的数据也就丢失了。
三:解决异步复制和脑裂导致的数据丢失
- 在6379.conf中配置
min-slaves-to-write 1
min-slaves-max-lag 10 - 说明:
要求至少有1个slave,数据复制和同步的延迟不能超过10秒。
如果说一旦所有的slave,数据复制和同步的延迟都超过了10秒钟,那么这个时候,master就不会再接收任何请求了。
上面两个配置可以减少异步复制和脑裂导致的数据丢失
a:减少异步复制的数据丢失
有了min-slaves-max-lag这个配置,就可以确保说,一旦slave复制数据和ack延时太长,
就认为可能master宕机后损失的数据太多了,那么就拒绝写请求,这样可以把master宕
机时由于部分数据未同步到slave导致的数据丢失降低的可控范围内。
b:减少脑裂的数据丢失
如果一个master出现了脑裂,跟其他slave丢了连接,那么上面两个配置可以确保说
,如果不能继续给指定数量的slave发送数据,而且slave超过10秒没有给自己ack消息,
那么就直接拒绝客户端的写请求,这样脑裂后的旧master就不会接受client的新数据,也就避免了数据丢失
上面的配置就确保了,如果跟任何一个slave丢了连接,在10秒后发现没有slave给自己ack,那么就拒绝新的写请求,
因此在脑裂场景下,最多就丢失10秒的数据。
四:如何才能在保持主从复制+高可用的情况下,还能横向扩容
支撑1T的海量数据?
- 简介
假如现在我们的redis是一个读写分离的集群架构,我们现在有3 台服务器,一个master,2个slave,
那么比如master服务器最多只能容纳32G的数据,因为读写分离的集群架构就只有master服务器可以接受
写请求,并且将数据同步到slave节点上,那么master节点也将会成为一种限制,因为master节点的数据和
slave节点的数据是一模一样的,那么master最大能容纳的数据量也就是slave的数据量,一旦超过这个数据量,
就会使用LRU算法将旧的,很少使用的数据清除,从而去保证内存中只有32G数据。这就是一个master架构的
缺陷,如果我们要容纳更多的数据怎么办?我们想要1T的数据在缓存中去供给系统高性能的查询和运行...比如
在内存中的被清理的旧的数据,现在又新的请求过来去查询,那么这时候redis中有没有,那么就只能去关系型
数据库中查询,那么 关系型数据库的负载就会变大。 - 解决办法(redis cluster(多master + 读写分离 + 高可用))
集群中设置多个master节点,每个master node都可以挂载多个slave node,读写分离的架构,对于每个master来说,写就写到master,然后读就从mater对应的slave去读。高可用,因为每个master都有salve节点,那么如果mater挂掉,redis cluster这套机制,就会自动将某个slave切换成master。
- redis cluster vs. replication + sentinal
我们只要基于redis cluster去搭建redis集群即可,不需要手工去搭建replication复制+主从架构+读写分离+哨兵集群+高可用。
replication + sentinal:
如果你的数据量很少,主要是承载高并发高性能的场景,比如你的缓存一般就几个G,
那么就使用replication + sentinal,一个mater,多个slave,要几个slave跟你的要求的
读吞吐量有关系,然后自己搭建一个sentinal集群,去保证redis主从架构的高可用性,就可以了 。
redis cluster :
redis cluster,主要是针对海量数据+高并发+高可用的场景,海量数据,如果你的数据量很大,
那么建议就用redis cluster 。
五:redis cluster
- 简介
Redis3.0版本之前,可以通过Redis Sentinel(哨兵)来实现高可用 ( HA ),从3.0版本之后,官方推出了Redis Cluster,
它的主要用途是实现数据分片(Data Sharding),不过同样可以实现HA,是官方当前推荐的方案。 在Redis Sentinel模式中,每个节点需要保存全量数据,冗余比较多,而在Redis Cluster模式中,每个分片只需要保存一部分的数据,对于内存数据库来说,还是要尽量的减少冗余。在数据量太大的情况下,故障恢复需要较长时间,另外,内存实在是太贵了。。。
Redis Cluster的具体实现细节是采用了Hash槽的概念,集群会预先分配16384个槽,并将这些槽分配给具体的服务节点,通过对Key进行CRC16(key)%16384运算得到对应的槽是哪一个,从而将读写操作转发到该槽所对应的服务节点。当有新的节点加入或者移除的时候,再来迁移这些槽以及其对应的数据。在这种设计之下,我们就可以很方便的进行动态扩容或缩容,个人也比较倾向于这种集群模式。
cluster的出现是为了解决单机Redis容量有限的问题,将Redis的数据根据一定的规则分配到多台机器。
注意:redis cluster在实际生产的使用中,我们的读写操作都是在mster节点上进行的,不是做读写分离的。因为redis cluster
master+slave的架构对读写分离的支持不是很好,当然redis cluster也能做到,当然如果要做的比较好的话需要自己对源码进行改造,特别麻烦。
redis cluster: 自动做master+slave复制和读写分离,master+slave高可用和主备切换,支持多个master的hash slot支持数据分布式存储。
六:最经典的缓存+数据库读写的模式,cache aside pattern
- 简介
a:读的时候,先读缓存,缓存没有的话,那么就读数据库,然后取出数据后放入缓存,同时返回响应
b:更新的时候,先删除缓存,然后再更新数据库 - 为什么是删除缓存,而不是更新缓存呢?
原因很简单,很多时候,复杂点的缓存的场景,因为缓存有的时候,不简单是数据库中直接取出来的值,
商品详情页的系统,修改库存,只是修改了某个表的某些字段,但是要真正把这个影响的最终的库存计算出来,
可能还需要从其他表查询一些数据,然后进行一些复杂的运算,才能最终计算 。
比如可能更新了某个表的一个字段,然后其对应的缓存,是需要查询另外两个表的数据,并进行运算,
才能计算出缓存最新的值的,更新缓存的代价是很高的。
还有就是这个缓存到底会不会被频繁访问到?
举个例子,一个缓存涉及的表的字段,在1分钟内就修改了20次,或者是100次,那么缓存跟新20次,100次;
但是这个缓存在1分钟内就被读取了1次。实际上,如果你只是删除缓存的话,那么1分钟内,这个缓存不过就重新
计算一次而已,开销大幅度降低。
每次数据过来,就只是删除缓存,然后修改数据库,如果这个缓存,在1分钟内只是被访问了1次,那么只有那1次,缓存是要被重新计算的,用缓存才去算缓存。其实删除缓存,而不是更新缓存,就是一个lazy计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算
七:数据库与缓存双写不一致问题以及其解决方案
- 最初级的缓存不一致问题以及解决方案
问题:
先修改数据库,再删除缓存,如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据出现不一致.
解决思路:
先删除缓存,再修改数据库,如果删除缓存成功了,如果修改数据库失败了,那么数据库中是旧数据,缓存中是空的,
那么数据不会不一致,因为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中,
- 比较复杂的数据不一致问题分析,读写并发产生的问题
问题:
数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改,一个请求过来,去读缓存,发现缓存空了,
去查询数据库,查到了修改前的旧数据,放到了缓存中,后面数据变更的程序才完成了数据库的修改,最后完了,数据库
和缓存中的数据不一样了。。
为什么上亿流量高并发场景下,缓存会出现这个问题?
只有在对一个数据在并发的进行读写的时候,才可能会出现这种问题。其实如果说你的并发量很低的话,
特别是读并发很低,每天访问量就1万次,那么很少的情况下,会出现刚才描述的那种不一致的场景 ,但是问
题是,如果每天的是上亿的流量,每秒并发读是几万,每秒只要有数据更新的请求,就可能会出现上述的数据库+缓存
不一致的情况,高并发了以后,问题是很多的。