Redis缓存分布式高可用架构原理

Redis

传统数据库事务  ACID  原子性 一致性 隔离性 持久性

NOSQL  CAP  强一致性(准确)  可用性(并发)  分区容错性       

 (一个分布式系统不可能完全满足三个CAP 最多同时满足其二)  传统关系型数据库满足CA   Redis满足AP

 

为甚么要用缓存?

复杂查询结果存入缓存 查询效率大大提高

高性能:高频数据从数据库查询出结果存入缓存,用户直接从缓存查提高性能

高并发:替数据库分担一部分数据压力

 

Redis,Memcached区别.

Redis 支持数据结构更多,更好用

Memecached没有原生集群模式,都是单机的,Redis原生支持cluster集群模式

 

 

Redis单线程模型

  1. 客户端建立Socket请求连接Redis服务端
  2. Redis建立多个serverSocket用来与客户端建立连接,用过IO多路复用监听serverSocket事件
  3. IO多路复用监听到不同事件然后传入Queue中顺序处理时间,送到文件事件分派器
  4. 文件分派器根据不同的事件找到对应的处理器(连接应答处理器,命令请求处理器,命令回复处理器)

命令请求处理器与命令回复处理器关联使用。命令请求处理器处理完请求事件调用命令回复处理器返回处理结果

 

 

Redis单线程为什么可以处理高并发?

(1) 非阻塞IO多路复用机制

(2) 纯内存操作

(3) 避免多线程切换等消耗

 

 

Redis过期删除策略

到了过期时间后

  1. 定期删除,redis100ms随机抽一些数据检测是否过期,过期就删除
  2. 惰性删除,查某条数据时才检查该数据是否过期,过期就删除

 

 

Redis内存淘汰机制

  1. 如果内存不足,新写入则报错
  2. 内存不足时,自动移除最近最少使用的key(常用)  LRU思想最近最久未使用(一个队列,每一个数据读取一次就把他拿出来重新加到队头,直到数据满了再新加数据直接把队尾数据删除 队尾代表最近最久未使用)
  3. 内存不足时,随即删除某个key

 

Redis主从复制架构

单主多从,主从复制  主写入 从读出

redis replication 单机redis master slave主从复制

 

slave在本地保存master节点的hostip(redis.confslaveof配置)

slave内部有定时任务,每秒check是否有master要连接

 

Master节点必须持久化(RDB AOF) 否则master挂了重启后会把空数据同步到所有slave,全没数据了

主从同步机制:

1第一次全量将RDB发给slave做同步 , 或者间隔很久数据量较大时也通过RDB同步

2 masterslave都会维护一个offsetslave每秒都会上报offsetmaster保证数据一致

3 master有一个backlog用于全量复制中断后增量复制

4增量同步,master 服务器增减数据的时候,就会触发 replicationFeedSalves()函数,接下来在 Master 服务器上调用的每一个命令都会使用replicationFeedSlaves() 函数来同步到Slave服务器。

5 master runid相当于标识现在master的版本号

6 psync 根据情况去判断进行全量复制还是增量复制,psync中保存了runidoffset

 

主从复制断点续传(靠维护offset实现):

主从复制中途断掉时,master中存了一个backlogmasterslave都存了一个offset,重启后可以从上次offset处继续复制

 

处理过期key

Master过期的key或淘汰一条key,会发送一条del命令给slave

 

主从复制丢数据问题:

1异步复制:master还没来得及同步数据到slave就挂了,slave被选举成新master数据丢失

2 master脑裂:redis集群由于网络等问题masterslave连接断了,master其实没挂,slave认为master挂了选举了新master 出现了两个master! 客户端还在给老master传数据 新master少数据.

解决办法:尽量少丢数据

参数:

min-slaves-to-write 1

Min-slaves-max-lag 10

只要有1机器复制数据的时间超过10master暂时暂停接数据,一旦发生丢失数据不会丢太多

 

 

 

Redis哨兵

(1) 监控masterslave是否异常,如果一场报警给管理员

(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持久化机制(RDBAOF核心)

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 clusterhash slot算法

Redis cluster有固定的16384hash 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 APIredis cluster集群交互

请求重定向,

客户端发送命令给任一个redis节点

节点接收到命令后计算key对应的hash slot

如果hash slot对应的节点是本节点直接处理,否则返回moved给客户端重定向

缺点:多次重定向找对应节点 加大网络IO开销

 

Smart Jedis

在本地维护了一个hash slot -> node 的映射表关系

同时为每个节点创建一个JedisPool连接池

Jedis找节点步骤:

(1)JedisCluster在本地计算keyhash 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数据库改操作执行完所有读操作全都可以从队列里拿出来并发执行)

 

对同一个商品的读写请求,全部路由到同一台机器上,可以利用nginxhash路由,同一商品独写路由到同一机器上

 

 

分布式并发竞争问题

当多个写redis操作并发执行,可能会造成先后顺序不一致问题 (例如 服务A:set key1 a1 服务B:set key2 a2 服务C:set key3 v3 并发执行可能会导致服务B最后写入,结果为a2)

解决方案:分布式锁,确保同一时间只有一个写操作,对同一个key的写操作串行执行

因为写入redis的数据一般都是先从mysql拿了,可以每次获取锁之后,先判断一下时间戳,版本号这种,自己的值是不是最新的,再决定是否写入

 

 

 

posted @ 2020-07-09 16:01  六小扛把子  阅读(851)  评论(0编辑  收藏  举报