zoukankan      html  css  js  c++  java
  • 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请求来了导致每次都要去数据库查一遍,恶意攻击直接把数据库打死了

    解决:数据库查不到的也根据条件写一个空值存进缓存,假请求再来直接从缓存返回空值

     

    布隆过滤器:  用来解决缓存穿透问题  (提前判断一下缓存中 和数据库中有没有要查的元素)

    原理:当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。

    存入缓存的数据的(主见) 先进行布隆过滤器K个散列函数运算..,将运算的结果映射成数组中的k个点置为1  

    下次来查询时,将先查一下布隆过滤器 经过散列函数映射的k个值是否都是1  如果都是1就表示存在缓存中,如果有0出现表示缓存中没有

     缺点:容易造成误判,正巧k的所有值都是1 但是并不存在

     

    缓存数据库双写一致性问题

    正常缓存读写机制:

    读数据 先读缓存,读不到再读数据库

    改数据 先删缓存原数据,再改数据库,最后有人读该数据时再存入缓存(懒加载思想)

    (直接修改缓存很浪费,因为有可能多次修改改了很多次也没人读)

     

    问题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拿了,可以每次获取锁之后,先判断一下时间戳,版本号这种,自己的值是不是最新的,再决定是否写入

     

     

     

  • 相关阅读:
    测试 多线程 实现 callable 带返回值
    给定一个 hashMap 最终输出最大值的键
    正则判断输入的字符(英文、数字、空格、其他)的个数
    当返回值为json字符串时 如何获得其中的json数组
    thread run 和 start 的区别
    docker 构建dockerfile
    jsonp 跨域
    springsession 实现session 共享
    通过反射获得 spring 的 RequestMapping value值
    redis 集群搭建 以及 报错解决
  • 原文地址:https://www.cnblogs.com/ttaall/p/13274217.html
Copyright © 2011-2022 走看看