分布式缓存中的七大经典问题

编程入门 行业动态 更新时间:2024-10-09 07:18:13

<a href=https://www.elefans.com/category/jswz/34/1770120.html style=分布式缓存中的七大经典问题"/>

分布式缓存中的七大经典问题

分布式缓存中的七大经典问题

1. 缓存失效

1.1. 解释

系统由于预热,把一批数据加载到缓存中,但是由于对缓存时间考虑不周,导致后期缓存在某一个时间节点突然集体失效,系统大量请求全部打在数据库上,造成数据库压力过大甚至宕机;

1.2. 解决方案

既然缓存实效是因为key的失效时间设置的不合理,那么解决这个问题也要从key的失效时间下手,我们可以让key的失效时间=原定的失效时间+随机时间,这样的话,key就不会集中失效了。

2. 缓存雪崩

2.1. 解释

就是由于突然缓存所在机器出现问题(可能是大流量打死其中的n个节点、大流量导致网卡异常),导致所有的请求直接打到了mysql库上去,造成mysql也被瞬间打死。(所谓雪崩就是服务与服务之间造成了级联故障,换句话就是一个服务死机导致了另外一个服务扛不住压力也死机了。)

2.2. 处理方案

中心思想就是防止redis被打死+应用程序限流熔断+应用程序提高响应能力,可分为两部分。在缓存部分,redis需要配置高可用+持久化+告警机制,高可用可以采用主从、哨兵、集群、异地多活等方式,持久化机制可以开启AOF、RDB、混合模式以便于在故障恢复后快速加载数据,告警机制可以人工提前介入进行动态扩容等;在应用程序部分,可以采用限流+熔断+本地缓存的方案;

3. 缓存穿透

3.1. 解释

就是多次请求缓存中没有的数据,导致直接查询数据库,导致数据库被打死;

3.2. 解决方案

  • 方案一: 如果从数据库中查询的结果为空,就在缓存中set一个值,再加上一个过期时间,这样就可以把请求拦截到缓存上,从而避免数据库被打死;但这种方式也有另外一个问题就是key如果很多,就会造成缓存的命中率下降,这时我们要定期清理key,或者将这些非法的key存入一个独立的公共缓存中,每次查询时先查询主缓存,如果不存在就查询独立缓存,再不存在就查询数据库,如果mysql返回结果为空,就把kv设置到独立缓存中,否则就放到主缓存中;

  • 方案二: 使用 Bloomfilter 来缓存全量的key,利用了 Bloomfilter 的位图数据结构特性,如果为ture,就一定存在,如果为false,不一定存在的特性;不过key的数量级要控制在10亿以内,大概占用1.2g缓存,又因为key越多误判率越高,因此还要定期清理key;

4. 缓存击穿

4.1. 解释

击穿的意思有点像在一道屏障上穿了一个孔。就是某一个key的访问非常频繁,但是某一个时刻,这个key突然失效,导致获取这个key的请求直接穿过缓存请求到数据库。

4.2. 处理方案

中心思想是对热点key的处理。 针对基本不会发生更新的场景,可以把key设置为永不过期,让key常驻缓存;针对偶尔需要更新的场景,可以对请求代码使用分布式互斥锁,是的少部分直接请求请求数据库后更新缓存,而剩余的其他请求直接使用新缓存即可,或者采用本地互斥锁保证仅有少量请求能够更新缓存,其余请求访问新缓存; 针对需要频繁更新的场景,可以使用额外的补偿程序来定时刷新缓存或者延长key的实效时间;

5. 双写一致性

5.1. 解释

在使用缓存时,往往是数据库中保存着一份数据,而缓存中也保存着一份数据,这就涉及到数据库与缓存中数据的一致性问题

5.2. 处理方案

  • 方案一: 使用 Cache Aside 模式,就是在读缓存的时候,先读缓存,如果缓存中没有读到,那就读数据库,然后把读到的数据再放入缓存中,最后返回响应;更新操作时,就先更新数据库,然后再删除缓存;【更新操作是有问题的,下面会讲到】;

    • 优缺点: 这种方案使用了懒加载的思想,适用于数据一致性要求较高的业务场景,或者缓存更新较为复杂的业务场景; 但是这种方案需要同时关注cache和db的数据变更,有些繁琐;

    • 更新操作为什么是删除缓存而不是更新缓存?
      • 这里涉及到懒加载的思想,事实上,更新缓存的性能损耗要大于删除缓存的性能损耗,如果读操作不多,那每次都要更新缓存所带来的性能损耗一定大于删除缓存的性能损耗,让第一次读操作从数据库中获取数据后更新缓存,之后所有的读操作直接请求缓存,性能损耗就会大幅度下降;

    • 如果更新操作时,先更新数据库,然后删除缓存,如果缓存删除失败呢?
      • 这同样会造成缓存与数据库不一致。解决办法就是先删除缓存,然后更新数据库。这样读操作时,如果缓存为空,就去读数据库,然后更新缓存,虽然读到的数据是旧数据,但是缓存更新后也是旧数据,就保证缓存与数据库一致了。

    • 如果更新操作时,瞬间有大量请求发送过来,会造成什么情况?仍然会造成缓存与数据库不一致问题。
      • 因为一个更新请求过来,我们先执行删除缓存,然后更新数据库,但是在删除缓存之后还没有来得及更新数据库,另一个读请求也过来了,然后它发现缓存中没有数据,它就先去数据库中读取数据然后再更新到缓存,这时之前的更新请求再更新数据库,此时缓存和数据库不一致了【这个问题的本质原因是高并发请求和更新单个key】。解决方案是: 可以根据key的唯一性标识把相同参数的请求路由到同一台机器上,然后创建JVM内部队列,使更新操作放入一个队列,读操作也放入一个队列。目的是hang住读操作一些时间,等更新操作完成之后再进行读操作。但这种方式有可能会造成读操作的时间过长并且还有可能会造成某个单台机器负载过高的情况,这个时候要严格执行性能测试,一方面要看一下这种方式下读操作请求时长是否是可以忍受的,如果不可忍受,那就只能加机器;但单台机器负载过高的情况不可避免;

  • 方案二: 使用 Read/Write Through 模式,就是提供一个存储服务,查询数据和修改数据都通过这个服务来完成,这样可以屏蔽对数据的访问细节,在存储服务内部,针对查询数据的操作,可以直接去cache中查询,如果不存在就去db中查询,然后回种到cache中后返回,针对写数据的操作,先去查询cache,如果key存在,就更新缓存再更新db,如果缓存中key不存在就只更新db;

    • 优缺点: 这种方案使用起来更加方便,因为它提供了一套操作cache和db的API,等同于封装了cache和db的操作细节,使业务系统不必关注cache和db的读写操作实现;此外,由于同样适用了懒加载方式,使得这种方式也适用于数据有冷热区分的业务场景;

    • 更新操作是怎么实现的?
      • 是通过cas算法实现的,这样利用了算法锁的方式避免了高并发带来的问题;

  • 方案三: 使用 Write Behind Caching 模式,这种模式跟 方案二模式差不多,都是提供了一个存储服务,封装对cache和db的操作细节,让外部业务系统无感知的访问缓存。在其内部,读操作的实现原理与 方案二 是一样的,同样是先去cache中读,如果cache中不存在,就去db中读取,然后回种到cache后返回;与方案二不同的是更新操作的实现原理, 这种方案的更新操作是只更新cache,并提供异步批量的方案来根据cache来更新db;

    • 优缺点: 这种方案的写性能是最大的,但是数据不一致性发生的几率最大,极端场景下可能会丢失数据,因此这种方案适合写合并的场景,比如微博的点赞数量,如果采用方案二,那势必是点赞一次就需要写db一次,这对db是很大压力的,在方案三中,可以点赞到1w后再写db,这样db压力就大大减小了;

    • 如何选择?
      • 高性能与强一致性本来就不可兼得,不同模式的选择就是针对高并发与强一致性的取舍;

      • 不存在最佳方案,只有最符合业务场景的方案;

6. 热点key

6.1. 解释

某些业务在某一瞬间或某一时间段内可能会成为热点业务,热点业务的数据可能会产生热点key,比如微博上热榜数据;

6.2. 方案

先找出哪些key是热点key,可以通过spark的流计算或Hadoop的批处理来得出热点key,然后中心思想就是把这些热点key打散到不同的节点中以应付高并发请求;总的实现方案有加入二级缓存和加冗余节点,这个问题的关键在于如何发现热点key,4.0之后,可以使用 redis-cli --hotkeys 命令获取;业内著名处理方案有有赞透明多级缓存解决方案(TMC)

7. 大key

7.1. 解释

缓存中某些key的value的值过大,导致写操作超时、加载速度缓慢等问题;

7.2. 方案

主要的处理思路是先找出哪些key是大key,然后再对大key进行操作;

  • 如何找到大key?
    • 使用报警机制可以查看带宽与qps的关系,来判断是否有大key产生;

    • 利用 redis-cli --bigkeys 命令可以异步获取大key

    • 使用 redis-rdb-tools 离线分析工具来扫描RDB持久化文件

  • 找到大key后如何处理大key?
    • 可删除:
      • 小于4.0, 使用 scan 命令扫描出key后进行删除

      • 大于4.0, 使用 UNLINK 命令异步删除

    • 不可删除:
      • value 是 string,比较难拆分,则使用序列化、压缩算法将key的大小控制在合理范围内,但是序列化和反序列化都会带来更多时间上的消耗; 如果压缩之后仍然是大key,则需要进行拆分,一个大key分为不同的部分,记录每个部分的key,使用multiget等操作实现事务读取;

      • value 是 list/set 等集合类型时,根据预估的数据规模来进行分片,不同的元素计算后分到不同的片;

更多推荐

分布式缓存中的七大经典问题

本文发布于:2024-03-09 18:52:49,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1725800.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:分布式   缓存   经典

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!