[Java复习] 缓存Cache part2

7. Redis持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?

为什么要持久化?

  如果只是存在内存里,如果redis宕机再重启,内存数据就丢失了,所以要用持久化机制。

  将数据写入内存的同时,异步的慢慢将数据写入磁盘文件,定期同步或备份到云存储服务上,进行持久化。

  如果redis宕机重启,自动从磁盘加载之前持久化的一些数据,也许会丢失少量数据,但至少不会丢所有数据。

Redis持久化的两种方式: RDBAOF

RDB(Redis Database): 是对redis中的数据执行周期性的持久化。

          简单说就是每个几分钟或几个小时,生成redis内存中数据的一份全量快照副本。

AOF(Append-Only-File):是对每条写入命令作为日志,以append-only的模式写入一个日志文件中,在redis重启的时候,可以通过回放AOF日志中的写入指令来重新构建整个数据集。

   现在操作系统中,写文件不是直接写磁盘,会先写os cache(linux),然后到一定时间从os cache写到磁盘文件。

如果同时使用 RDB 和 AOF 两种持久化机制,那么在 redis 重启的时候,会使用 AOF 来重新构建数据,因为 AOF 中的数据更加完整。

RDB优缺点:

优点:

1. 非常适合做冷备份。

    RDB生成多个文件,每个文件代表一个时刻的完整快照。

    有redis去控制固定时长生成快照文件,比较方便,隔一段时间备份数据到远程安全的存储上。

2. 对redis影响小,让redis保持高性能。

    因为redis主进程只需fork一个子进程,让子进程进行IO操作完成RDB持久化。

    RDB每次写,都是直接写redis内存,只是在一定时候,才会将数据写入磁盘中;

    AOF每次都是要写文件,虽然可以快速写os cache,但是还是有一定时间开销,速度肯定比RDB慢一些。

3. 相对于AOF,RDB数据文件重启和恢复redis进程更快

     AOF是存放的指令日志,做数据恢复时,其实是回放和执行所有指令日志来恢复内存中数据;RDB本身就是一份数据文件,恢复时,直接加载到内存即可。

缺点:

1. RDB丢失数据更多。

     一般RDB设置间隔5分钟一次,那么这时宕机需要接受丢失最近5分钟数据。

2. 不适合做第一优先的恢复方案。

      如果数据文件特别大,会导致对客户端提供服务暂停数毫秒甚至数秒,一般不要让RDB间隔太长时间。

AOF优缺点:

优点:

  1. 更好保证数据不丢失或丢得很少。

       每个1秒做一次fsync,保证os cache中数据写入磁盘,最多丢1秒数据。

  2. 写入性能高。

      以append-only写入,没有磁盘寻找开销,即使文件尾部破损,也容易恢复。

  3. AOF日志文件即使过大也不影响客户端读写

      因为在rewrite log时,会压缩指令,创建一份需要恢复数据的最小日志。

      创建新日志时,老日志继续写,当新的merge后的日志文件ready时,再交换新老日志,删除老日志。

   4. AOPF日志是人类易读的,非常适合做灾难性的误删除的紧急恢复。

      比如某人不小心flushall命令清空了所有数据,只要rewrite还没有发生,可立即拷贝AOF文件,将最后一个flushall命令删掉,再将AOF放回去,

      通过恢复机制,自动恢复所有数据。(很少用)

缺点:

    1. 占用磁盘比RDB多。

        对同一份数据,AOF文件比RDB数据快照文件大。

    2. QPS比RDB低一些。

        AOF开启,写QPS比RDB的写QPS低。因为AOF一般会配置成每秒fsync一次日志文件,性能还OK。

    3. 唯一的比较大缺点,做数据恢复比较慢;还有做冷备不合适,需要写复制脚本算法。

RDB和AOF的选择:

  1. 不能仅仅用RDB,会导致丢失很多数据

  2. 也不能只用AOF,其一AOPF做冷备,没有RDB恢复快;其二,RDB每次通过生成数据快照,恢复更健壮,避免AOF的复制备份和恢复机制的bug。

  3. 综合用AOF和RDB,用AOF保证数据基本不丢,作为数据恢复第一选择;

      用RDB做不同时间间隔的冷备,在AOF文件都丢失时或损坏不可用时,从远程云服务等备份的RDB来进行快速数据恢复,作为Redis数据可用的最后一道防线。

8. Redis 集群模式的工作原理能说一下么?在集群模式下,redis key 是如何寻址?分布式寻址都有哪些算法?了解一致性 hash 算法吗?RedisCluster高可用?

 

  8.1. redis单master架构面对海量数据的容量问题

  问题背景:master节点的数据和slave节点的 数据一模一样,slave容量只能最大容纳和master一样容量的数据。

                    数据超过后,会采取LRU清除算法,把旧的很少使用的数据清除出内存,不可能超过内存上限。

  解决方案:Redis Cluster架构 = 多master + 读写分离 + 高可用

                    支持N个redis master节点,每个master节点可以挂多个slave。

                    读写分离:写就写到master,读从master对应的slave读。

                    高可用:每个master都有slave节点,如果master挂了,redis cluster这套机制会自动将某个salve切换成master。

  Redis Cluster   vs.  Replication + sentinel 比较:

    如果数据量少,主要承载高并发高性能场景,如果缓存只有几个G,单机足矣。

    Replication:一个 master 多个 slave,要几个 slave 跟你要求的读吞吐量有关,然后自己搭建一个 Sentinel 集群去保证 redis 主从架构的高可用性

    Redis Cluster:主要是针对海量数据+高并发+高可用的场景。

    Redis Cluster 支撑 N 个 redis master node,每个master node都可以挂载多个 slave node。这样整个 redis 就可以横向扩容了。

 8.2. 分布式数据存储的核心算法

      hash算法 -> 一致性hash算法(memecached)  ->  Redis Cluster的hash slot算法

      

      hash算法(缓存里现在用的少):计算key的hash值,对节点数量取模。

      最大问题:某一个master宕机,所有请求会基于最新2个master取模,会导致几乎大部分请求无法拿到有效缓存。

      原来对3取模,现在对2取模,缓存没有命中,就会涌向数据库。

  

      一致性hash算法:

      将整个 hash 值空间组织成一个虚拟的圆环,整个空间按顺时针方向组织,下一步将各个 master 节点(使用服务器的 ip 或主机名)进行 hash,

      这样确定每个节点在哈希环上的位置。

      来一个key,计算hash值,确定在环上的位置,从此位置沿环顺时针行走,遇到第一个master节点就是key所在位置。

      如果3台中1台挂了,只有1/3的数据会找不到,涌向DB。只有1/N的流量在master宕机会失效。

      受影响的数据仅仅时此节点到环的前一个节点之间的数据,增加一个节点也同理。

      一致性哈希算法在节点太少时,容易因为节点分布不均匀而造成缓存热点的问题。

      为了解决这种热点问题,一致性 hash 算法引入了虚拟节点机制,即对每一个节点计算多个 hash,每个计算结果位置都放置一个虚拟节点。

      这样就实现了数据的均匀分布,负载均衡。

      Redis Cluster的hash slot算法

      Redis Cluster固定16384个hash slot, 对每个key计算出对应的hash slot。

      hash slot让节点的增加和移除很简单,移动hash slot成本很低。

      任何一台节点宕机,另外两个节点不受影响,因为key找到的是hash slot,不是机器。redis cluster会快速把宕机节点的hash slot均匀分布到其他节点。

 8.3. Redis节点内部通信机制

    Redis Cluster节点间采用gossip协议进行通信

      集群元数据集中式存储:    

          集群有很多元数据,包括hash slot,节点之间映射表,还有master-slave关系等。

          通过zookeeper集中式元数据的维护和存储,典型是大数据的Storm,底层基于zookeeper(分布式协调中间件)。

      gossip协议存储:

          所有节点都持有一份元数据,不同节点如果出现元数据变更,就不断将元数据发送给其他节点,让其他节点也进行元数据更新。

    集中式的优点: 元数据的更新和读取,时效性好。一旦元数据更新,立即更新到集中式存储,其他节点读取时能立刻感知。

    集中式的缺点:所有元数据都集中在一个地方,有存储压力(单点故障?)

    gossip优点:元数据分布式存储,不是集中在一个地方,更新请求时陆陆续续,有一定延迟,减轻压力。

    gossip缺点:元数据更新有延迟,可能导致集群的一些操作会有滞后。

  gossip协议:

    包含多种消息,包括ping, pong, meet, fail等等。

    meet: 某个节点发送meet给新加入节点,让新节点加入集群中,然后新节点就开始与其他节点通信。

    redis-trib.rb(官方提供的Redis Cluster的管理工具) add-node – 添加新节点

    ping: 每个节点频繁与其他节点发送ping交换数据,包含自己状态和维护的集群元数据。

    pong: 返回ping和meet,包含自己状态和其他信息,也可用于信息广播和更新

    fail: 某个节点判断另外一个节点fail之后,发送fail给其他节点,通知其他节点,指定的节点宕机了。

 8.4. Jedis Cluster API与Redis Cluster集群交互的一些基本原理

  1. 基于重定向的客户端

      1)请求重定向

       客户端可以挑选任意redis实例去发送命令,每个实例接收到命令后,会计算key对应的hash slot。

       如果在本地就在本地处理,否则返回moved给客户端,让客户端进行重定向

       redis-cli -c,-c参数,支持自动请求重定向,redis-cli接收到moved之后,会自动重定向到对应节点执行命令。

       cluster keyslot mykey, 可以查看一个key对应的hash slot是多少。

       2)计算hash slot

           根据key计算CRC16值,然后对16384取模,拿到对应的hash slot。

           用hash tag可以手动指定key对应的slot, 同一个hash tag下的key,都会在同一个hash slot中。

           比如set key1:{100}和set key2:{100} 

          100:hash tag的值

       3) hash slot查找

           节点间通过gossip协议进行数据交换,就知道每个hash slot在哪个节点上。

  2. Smart Jedis

   1) 什么是Smart Jedis

       基于重定向客户端,很消耗网络IO,因为大部分情况下,可能都会出现一次请求重定向,才能找到正确的节点。

       所以大部分客户端,比如java redis客户端(jedis),都是smart的。

       本地维护一份hash slot -> node的映射表缓存,大部分情况下,直接走本地缓存就可以找到hashslot->node,不需要通过节点进行moved重定向。

    2) JedisCluster的工作原理

       在JedisCluster初始化时,就会随机选择一个node, 初始化hashslot->node映射表,同时为每个节点创建一个JedisPool连接池。

      每次基于JedisCluster执行操作,首先JedisCluster都会在本地计算key的hashslot,然后在本地映射表找到对应的节点。

      如果那个node正好还是持有那个hashslot,那没问题;如果说进行了reshard这样的操作,可能hashslot已经不在那个node上,就会返回moved。

      如果发现JedisCluster api发现对应的节点返回moved,那么利用该节点的元数据,更新hashslot->node映射表。

      重复上述步骤,直到找到对应节点。如果重试5次,就报错JedisClusterMaxRedirectException。

     3)hash slot迁移和ask重定向

        如果hash slot正在迁移,那么会返回ask重定向给jedis。

        jedis接收到ask重定向后,会重新定位到目标节点去执行,但是因为ask发生在hash slot迁移过程中,

        所以JedisCluster api收到ask是不会更新hash slot本地缓存,确定hash slot已经迁移完成,moved是会更新本地hashslot->node映射缓存的。

 8.5 Redis Cluster高可用和主备切换原理

    高可用原理与哨兵类似。

    判断节点宕机:

        在cluster-node-timeout内,某个节点一直没有返回pong,则被认为pfail。

        如果一个节点认为某个节点 pfail 了,那么会在 gossip ping 消息中,ping 给其他节点,如果超过半数的节点都认为 pfail 了,那么就会变成 fail。

    从节点过滤:

        对宕机的master node,从其所有的slave node中,选择一个切换成master node。

        检查每个slave node与master node断开连接的时间,如果超过了 cluster-node-timeout * cluster-slave-validity-factor,那么就没有资格切换成 master。

    从节点选举:

         每个从节点,都根据自己对 master 复制数据的 offset,来设置一个选举时间,offset 越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举。

         所有的 master node 开始 slave 选举投票,给要进行选举的 slave 进行投票,

        如果大部分 master node(N/2 + 1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成 master。

        从节点执行主备切换,从节点切换为主节点。

    与哨兵比较

        整个流程与哨兵非常类似,redis cluster 功能强大,直接集成了 replication 和 sentinel 的功能。

9. 了解什么是 redis 的缓存雪崩、穿透和击穿?redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 redis 的穿透?

缓存雪崩:

   假设高峰QPS=5000,缓存可以抗4000,但是缓存以外宕机,全部5000全都落在DB,DB扛不住,报警后,挂了。

   如果没有特别方案来处理,就算DBA重启DB,还是会被新的流量打死。

解决方案:

  事前:Redis高可用,主从+哨兵,或Redis Cluster, 避免全盘崩溃。(仍存在全盘崩溃可能性)

  事中:本地ehcache缓存 + hystrix 限流&降级,避免DB被流量打死。

    1. 用户发送请求,先查本地ehcache缓存,如果没有再查redis。如果echache和redis都没有,查DB。将DB的结果写入ehcache和redis。

    2. 通过限流组件,设置每秒2000个请求,那么一秒过来5000个请求时,只有2000个通过限流组件,到达DB。

         剩下的3000个请求走降级,会调用自定义的降级组件。比如返回默认值,友情提示,其他自定义处理等等。

         虽然用户体验下降,但保证DB不会挂,系统仍可用!

  事后:Redis持久化机制,尽快恢复缓存集群,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

                   

缓存穿透:

    某个key(可能是恶意攻击的)非常热点,访问非常频繁,数据库和缓存都没有这个数据,缓存穿透后把数据库打死。

解决方案:比如黑客发送查id(id应该都是正数)的请求,都是负数(-99, -999),每次从DB中没有查到的,就写一个空值到缓存。

                  比如set -99 UNKNOWN。这样再查就是走缓存。

10. 如何保证缓存与数据库的双写一致性?

  1. Cache Aside Pattern

    最经典的缓存 + 数据库读写的模式,就是Cache Aside Pattern。

  • 读的时候,先读缓存,缓存没有,就读数据库,然后取出数据后放入缓存,同时返回响应。
  • 更新的时候,先更新数据库,然后再删除缓存。

   2. 为什么是删除缓存,而不是更新缓存

     原因是很多复杂缓存计算场景,因为缓存不简单是数据库中直接取出,而是需要涉及多个表。如果频繁修改缓存涉及的多个表,缓存也会频繁更新。

     如果这个缓存是冷数据,用到缓存才去算缓存,降低开销。删除缓存,而不是更新缓存,就是一个lazy计算的思想(懒加载),它到需要被使用的时候再重新计算。

  双写一致的解决方案:

    1. 最初级的缓存不一致问题及解决方案

        场景:先修改数据库,再删除缓存。如果某个原因(网络超时等)删除缓存失败,就出现数据库与缓存双写不一致。

        解决:调整顺序,先删除缓存,再修改数据库。

    2. 比较复杂的数据不一致问题分析

        场景:数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改。一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。随后数据变更的程序完成了数据库的修改。结果数据库和缓存中的数据不一样了。

        并发低时很少出现这种不一致,如果时高并发上亿流量,每秒并发几万,每秒只要有数据库更新请求,就可能出现上述数据库和缓存不一致。

        解决:数据库与缓存更新进行异步串行化

        更新数据库时,根据数据唯一标识,将操作路由之后,发送到一个JVM内部队列中

        读取数据时,如果发现数据不在缓存中,将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个JVM内部队列中

        一个队列对应一个工作线程,每个工作线程串行拿到对应的操作,然后一条一条执行

        优化点:一个队列中,如果有一个读请求,不用再放入同样的读请求进入队列,排除重复读请求。

       

        该方案在高并发场景要注意的问题:

           1. 读请求长时阻塞

               可能数据更新频繁,导致队列中积压大量更新操作,然后读请求发生大量超时,导致大量请求直接走数据库。

           2. 读请求并发量过高

               做好压力测试,看服务能不能抗住大量读请求,需要多少机器才能抗住最大极限峰值。

           3. 多服务实例部署的请求路由

               对同一个业务的读写请求,保证路由到相同的服务实例上。可自己实现请求参数hash路由,也可以用Nginx的hash路由功能。

           4. 热点商品路由问题,导致请求倾斜

               某个业务读写请求特别高,全部打到相同机器的相同队列了,操作某台机器压力过大。

          总结,一般来说,如果系统不是严格要求缓存+数据库必须一致的话,最好不采用这个方案。

          读写请求串行化,会导致系统吞吐量大幅度下降,需要用比正常情况下多几倍的机器来支撑。

11. Redis 的并发竞争问题是什么?如何解决这个问题?了解 redis 事务的 CAS 方案吗?

  分布式锁确保同一个时间只有一个系统实例在操作某个key,其他实例不允许读和写。

  每次写之前,判断当前value的时间戳是否比缓存里的value的时间戳更新,如果更新则可以写,如果更旧,就不能用旧数据覆盖新数据。

12. 生产环境中的redis是怎么部署的?

   看看你了解不了解你们公司的 redis 生产集群的部署架构,如果你不了解,那么确实你就很失职了。

   你的 redis 是主从架构?集群架构?用了哪种集群方案?有没有做高可用保证?有没有开启持久化机制确保可以进行数据恢复?

   线上 redis 给几个 G 的内存?设置了哪些参数?压测后你们 redis 集群承载多少 QPS?

  Redis Cluster, 10台机器, 5主5从。每个主实例挂一个从实例。5个节点对外提供读写服务,每个节点读写高峰QPS可能达5W/S,5台最多25W QPS/S。

  8核CPU+32G内存+1T磁盘。分给Redis进程10G内存。一般生产环境,Redis内存不要超过10G。

猜你喜欢

转载自www.cnblogs.com/fyql/p/11909790.html