Redis缓存分布式高可用架构原理
Redis
传统数据库事务 ACID 原子性 一致性 隔离性 持久性
NOSQL CAP 强一致性(准确) 可用性(并发) 分区容错性
(一个分布式系统不可能完全满足三个CAP 最多同时满足其二) 传统关系型数据库满足CA Redis满足AP
为甚么要用缓存?
复杂查询结果存入缓存 查询效率大大提高
高性能:高频数据从数据库查询出结果存入缓存,用户直接从缓存查提高性能
高并发:替数据库分担一部分数据压力
Redis,Memcached区别.
Redis 支持数据结构更多,更好用
Memecached没有原生集群模式,都是单机的,Redis原生支持cluster集群模式
Redis单线程模型
- 客户端建立Socket请求连接Redis服务端
- Redis建立多个serverSocket用来与客户端建立连接,用过IO多路复用监听serverSocket事件
- IO多路复用监听到不同事件然后传入Queue中顺序处理时间,送到文件事件分派器
- 文件分派器根据不同的事件找到对应的处理器(连接应答处理器,命令请求处理器,命令回复处理器)
命令请求处理器与命令回复处理器关联使用。命令请求处理器处理完请求事件调用命令回复处理器返回处理结果
Redis单线程为什么可以处理高并发?
(1) 非阻塞IO多路复用机制
(2) 纯内存操作
(3) 避免多线程切换等消耗
Redis过期删除策略
到了过期时间后
- 定期删除,redis每100ms随机抽一些数据检测是否过期,过期就删除
- 惰性删除,查某条数据时才检查该数据是否过期,过期就删除
Redis内存淘汰机制
- 如果内存不足,新写入则报错
- 内存不足时,自动移除最近最少使用的key(常用) LRU思想最近最久未使用(一个队列,每一个数据读取一次就把他拿出来重新加到队头,直到数据满了再新加数据直接把队尾数据删除 队尾代表最近最久未使用)
- 内存不足时,随即删除某个key
Redis主从复制架构
单主多从,主从复制 主写入 从读出
redis replication 单机redis 单master 单slave主从复制
slave在本地保存master节点的host和ip(在redis.conf里slaveof配置)
slave内部有定时任务,每秒check是否有master要连接
Master节点必须持久化(RDB AOF) 否则master挂了重启后会把空数据同步到所有slave,全没数据了
主从同步机制:
1第一次全量将RDB发给slave做同步 , 或者间隔很久数据量较大时也通过RDB同步
2 master和slave都会维护一个offset,slave每秒都会上报offset给master保证数据一致
3 master有一个backlog,用于全量复制中断后增量复制
4增量同步,master 服务器增减数据的时候,就会触发 replicationFeedSalves()函数,接下来在 Master 服务器上调用的每一个命令都会使用replicationFeedSlaves() 函数来同步到Slave服务器。
5 master runid相当于标识现在master的版本号
6 psync 根据情况去判断进行全量复制还是增量复制,psync中保存了runid和offset
主从复制断点续传(靠维护offset实现):
主从复制中途断掉时,master中存了一个backlog,master和slave都存了一个offset,重启后可以从上次offset处继续复制
处理过期key:
Master过期的key或淘汰一条key,会发送一条del命令给slave
主从复制丢数据问题:
1异步复制:master还没来得及同步数据到slave就挂了,slave被选举成新master数据丢失
2 master脑裂:redis集群由于网络等问题master与slave连接断了,master其实没挂,slave认为master挂了选举了新master 出现了两个master! 客户端还在给老master传数据 新master少数据.
解决办法:尽量少丢数据
参数:
min-slaves-to-write 1
Min-slaves-max-lag 10
只要有1台机器复制数据的时间超过10秒,master暂时暂停接数据,一旦发生丢失数据不会丢太多
Redis哨兵
(1) 监控master,slave是否异常,如果一场报警给管理员
(2) master故障,分布式选举 选举新master
哨兵至少需要三个实例,才能正常工作 (经典三节点哨兵集群)
哨兵的两个属性:
quorum = n 表示n个哨兵发现了master故障就能触发选举(触发客观宕机)
majority 表示绝大多数哨兵都是正常运行的才能投票,
当哨兵集群两个时 majority = 2 两个代表绝大多数
当哨兵集群三个时 majority = 2 两个代表绝大多数
当哨兵集群五个时 majority = 3 三个代表绝大多数
所以当哨兵集群小于等于两个时,任何一个master挂了 剩下的哨兵都不能代表绝大多数
哨兵底层原理:
sdown主观宕机,一个哨兵自己觉得master宕机了(一个哨兵ping一个master,超过is-master-down-after-millisecond指定毫秒级后,主观认为master宕机)
odown客观宕机,quorum数量的哨兵都觉得主观宕机
Redis持久化机制(RDB与AOF核心)
RDB是几分钟完成一次当前内存全量快照,存入dump.rdb文件中,每个文件代表一时刻的数据快照,RDB崩了会丢失最后一次数据
AOF以日志的形式记录每次操作,每秒一次,存的是每次指令,写入时写到cache中,每1秒fsync刷到AOF日志,最多丢1秒数据。AOF数据量大
当AOF文件过大,会产生rewrite操作,基于当前redis内存中的数据 新构建一份较小的AOF文件.(例如对同一个key的多次操作,归成最后一条)
RDB适合做冷备份,每过一段时间自动的将dump.rdb文件上传到云服务或者什么地方做存储(可以存一个月一次的redis快照,一天一次的redis快照等)
Redis cluster / Redis集群模式 / 分布式存储
前提:单master到达瓶颈,用删除策略淘汰数据,用户从redis查不到去MySQL查
Redis cluster(多master+读写分离+高可用)
每个master有多个slave节点
写入master读出salve
分布式数据存储算法
Hash算法:
(1) 对传入的key进行hash运算 例如hello算出14212
(2) 对Hash值进行取模运算,算出落入容器的位置
应用:hashmap 分库分表 分布式节点路由算法
问题:只要任何一个master挂了 取模规则就变了,所有的数据都要重新取模再落入
一致性Hash算法(可以解决热点分布不均):
(1) 将整个哈希值空间组织成一个虚拟的圆环,根据Key算出hash值落在圆环上
(2) 将服务器节点按照运算散落在圆环上,hash值顺时针找到的第一个服务器存入value
某个节点挂了 或者加节点减节点 整体的圆环取模规则不变
问题:负载不均衡,缓存热点问题,大量数据可能集中在一个某个服务器导致性能瓶颈
解决:给每个master做均匀分布的虚拟节点,保证大量数据能均匀分布到不同节点
实在是热点太热 可以使用本地缓存hashMap先存储 或者本地缓存Ehcache小缓存 先查ehcache 再差redis 再查数据库然后存入redis和本地缓存
Redis cluster的hash slot算法
Redis cluster有固定的16384个hash slot,平均分给所有master(取模规则变成16384),
如果增加一个master,将其他master部分slot过去
如果减少一个master,将他得slot分给各个master
元数据存储:
1 hashslot -> node 映射关系
2 master -> slave关系
3故障信息
集中式集群元数据存储 例如使用zookeper大数据存储
优点:元数据的更新和读取,一旦元数据变更其他可以马上感知
缺点:所有元数据全集中在一起存储有压力
gossip协议通信(各master节点间通信机制)
所有节点都持有一份元数据,
不同节点如果出现元数据变更会同步到其他节点
优点:元数据分散在各个节点
缺点:元数据更新有延迟,导致集群一些操作滞后
gossip协议通讯中包含信息: ping pong meet fail 用来节点间交换状态等信息,同步更新元数据
Meet:老节点发送meet给新节点,欢迎其加入到集群中
Ping:节点间频繁的发送ping互相交换状态和元数据信息
Pong:返回自己的状态和其他信息
Fail:节点发现另一个节点宕机,发送fail给其他节点
类似redis的哨兵机制 主观宕机和客观宕机
Jedis Cluster
Jedis cluster API与redis cluster集群交互
请求重定向,
客户端发送命令给任一个redis节点
节点接收到命令后计算key对应的hash slot
如果hash slot对应的节点是本节点直接处理,否则返回moved给客户端重定向
缺点:多次重定向找对应节点 加大网络IO开销
Smart Jedis
在本地维护了一个hash slot -> node 的映射表关系
同时为每个节点创建一个JedisPool连接池
Jedis找节点步骤:
(1)JedisCluster在本地计算key的hash slot,然后根据本地映射表找node节点
(2)去节点找如果找不到 说明hashslot已经不再那个节点 返回moved
(3)Jedis Cluster发现返回moved 利用该节点返回的元数据 更新本地映射表
(4)再次重复利用hash slot去新映射表查找,重复五次找不到直接报错!
当查找时 hash slot如果正在迁移,会返回ask,代表要查询的hash slot正在迁移。等迁移完,通过moved更新本地映射表
redis cluster集群选举
1. 各节点间通过ping pong 判断是否活着,如果一个节点长时间不返回pong,认为主管宕机。 如果多个节点认为一个节点宕机,则是客观宕机。
2. 主节点宕机,会选举他的从节点成为主节点,主要还是根据与master断开连接时间,复制offset的值来判断
3. 所有的master节点投票,投票给每个salve节点,当一个salve节点投票数超过半数,选举成功
缓存雪崩
Redis缓存挂了,导致大量请求直接走数据库 打崩数据库,数据库重启一次挂一次
解决:
雪崩前:缓存高可用架构(主从,哨兵,Redis cluster) 让redis尽量别崩
雪崩时:限流加降级,hystrix限流组件,将大请求限流成小请求(例如,5000个请求只有2000个可以通过,剩下的3000做降级处理,不做请求直接返回异界默认值或友好提示)
雪崩后:redis持久化机制,保证redis快速回复
缓存穿透(一般黑客干的)
每秒5000个请求,有4000个是redis 和数据库都不存在的数据(黑客发的)
4000请求来了导致每次都要去数据库查一遍,恶意攻击直接把数据库打死了
解决:数据库查不到的也根据条件写一个空值存进缓存,假请求再来直接从缓存返回空值
(同时会产生一个问题,如果大量不同的key查询会生产大量的空值更加占用资源)
布隆过滤器: 用来解决缓存穿透问题 (提前判断一下缓存中 和数据库中有没有要查的元素)
原理:当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。
存入缓存的数据的(主见) 先进行布隆过滤器K个散列函数运算..,将运算的结果映射成数组中的k个点置为1
下次来查询时,将先查一下布隆过滤器 经过散列函数映射的k个值是否都是1 如果都是1就表示存在缓存中,如果有0出现表示缓存中没有
缺点:容易造成误判,正巧k的所有值都是1 但是并不存在
缓存击穿问题
特别热点key突然过期失效的问题
1.可能会有大量的请求穿过缓存打到数据层
2.每个请求从数据层拿到数据后重新构建缓存,同一时间大量的请求都重新构建缓存导致的问题
解决:
1.避免热点key设计成过期失效,可设计成永不过期,并通过定时任务等手段来更新key
2.加互斥锁,每个请求通过获取互斥锁来执行重构缓存的操作,拿不到锁则直接返回,拿到锁判断缓存是否已被更新
3.限流,只限制一个请求能打到数据层,并在查询完成后更新缓存。 其他的请求都做降级处理,直到缓存完成更新
缓存数据库双写一致性问题
正常缓存读写机制:
读数据 先读缓存,读不到再读数据库
改数据 先删缓存原数据,再改数据库,最后有人读该数据时再存入缓存(懒加载思想)
(直接修改缓存很浪费,因为有可能多次修改改了很多次也没人读)
问题1:删缓存数据失败,由于网络等问题没删了,导致缓存数据和数据库改后数据不一样
解决:事务控制删缓存和改数据库 / 删库之前做一下校验缓存是否删除
问题2:更新一个数据的同时,另一边读这个数据,造成并发读写问题
A在删除完缓存还没等改数据库 B来读数据 把数据库老数据又刷入了缓存,造成不一致
解决:搞一个队列,对同一数据例如某个商品Id进行hash取值再取模进行唯一标识的读改操作串行化执行(可能会有数据压根数据库缓存都没有的情况,读操作进一个队列前,可以判断队列中是否存在改操作)
(多个读操作同时加入队列等待时可以设置一个20ms过期,预计20ms数据库改操作执行完所有读操作全都可以从队列里拿出来并发执行)
对同一个商品的读写请求,全部路由到同一台机器上,可以利用nginx,hash路由,同一商品独写路由到同一机器上
分布式并发竞争问题
当多个写redis操作并发执行,可能会造成先后顺序不一致问题 (例如 服务A:set key1 a1 服务B:set key2 a2 服务C:set key3 v3 并发执行可能会导致服务B最后写入,结果为a2)
解决方案:分布式锁,确保同一时间只有一个写操作,对同一个key的写操作串行执行
因为写入redis的数据一般都是先从mysql拿了,可以每次获取锁之后,先判断一下时间戳,版本号这种,自己的值是不是最新的,再决定是否写入