缓存雪崩、穿透如何解决,如何确保Redis只缓存热点数据?

原创 吴就业 136 0 2019-12-12

本文为博主原创文章,未经博主允许不得转载。

本文链接:https://wujiuye.com/article/52a5dc10390345dd94323f1f2a531dfd

作者:吴就业
链接:https://wujiuye.com/article/52a5dc10390345dd94323f1f2a531dfd
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。

本篇文章写于2019年12月12日,从公众号同步过来(博客搬家),本篇为原创文章。

缓存雪崩

缓存雪崩,简单说就是所有请求都从缓存中拿不到数据,比如大批量数据同一时间过期。对于大批量数据同时过期的场景,可以为数据设置过期时间指定一个时间范围内的随机值,比如一天到一天零一小时之间的随机值,但不适用于集合类型,比如hash。

还有小数场景,比如高峰流量导致Redis集群崩溃;未配置持久化的redis无从节点Cluster集群重启、集群迁移。当Redis集群发生故障时,可先启用内存缓存方案,比如Ehcache,同时根据情况做限流与降级,最后快速重启集群,必须配置持久化策略,根据流量情况扩展集群。

缓存穿透

缓存穿透简单理解就是数据库中也没有对应的记录,永远都不会命中缓存。比如表中的记录只有id从1000到100000,请求查询id为10000000的记录。一般是恶意攻击,针对这种情况最好的处理方式就是判断id的有效范围,其它情况可以针对查询的key缓存一个null值,并设置ttl过期时间。

如何确保Redis缓存的都是热点数据

A、为key设置ttl过期时间

适用于对实时性要求不高的业务场景;适用于可以容忍获取到的是过期数据的业务场景。过期时间会在每次读写key时刷新。为确保缓存中不遗留垃圾数据,一般都会为key设置过期时间,除了那些不会改变且一直会用到,也不会更新的数据,比如笔者前几篇文章提到的IP库。

B、选择缓存淘汰策略

选择淘汰最近最少使用的缓存淘汰策略可以保证缓存中都是热点数据,但这个策略只会在内存吃紧的情况下起效果,一般要保证缓存的数据都是热点数据就是在redis内存不够用的情况下。建议及时做缓存数据清理,依靠缓存淘汰策略的时候性能也会有所下降。

C、缓存访问次数,定时清除访问次数少的记录

比如用Sorted Set缓存key的读次数,周期性的去删除访问次数小于多少的key。适用于hash等集合类型,计录field的读次数,缺点是每次请求都有统计次数的性能开销。

如何更新缓存数据

A、在数据库修改记录时使用MQ队列通知更新

适用于那种比较少改动的缓存记录,比如用户信息;适用于要求数据修改及时更新缓存的业务场景,如一些配置的修改要求及时生效。但不适用于要求非常实时的场景,比如商品库存。

B、在修改数据库记录时直接更新缓存

这种方法与前一种方法都可利用AOP方式去更新,区别在于,前者解决多个服务之间的耦合问题,用于跨服务数据更新。小公司为考虑成本问题不会为每个服务使用独立的Redis集群,后者只能用于单个服务内的数据更新。即便是多个微服务使用同一个Redis集群,也不要通过共用key的方式共享缓存,否则耦合性太大,容易出问题。

C、定时任务批量更新

配合ttl使用,ttl的时间设置比定时任务周期长一点,避免数据过期了新的任务还没执行完成。适用于实时性要求不是很高,且短时间内大量数据更新的业务场景。比如数据库有10w数据,每15分钟都会有百分七八十的数据变更,且变更时间只在一分钟内。

如果是集合类型、Hash类型,一般会配合Rename使用,只有所有数据写入到redis成功,才原子性替换旧数据。且数据量大的情况下使用pipeline批量写入,避免使用hmset这类批量操作。使用hash这类集合类型时,一定要考虑到脏数据的问题。

如何处理请求倾斜问题

Cluster分槽会导致缓存数据倾斜,从而导致请求倾斜。假设一个三个小主从的Cluster集群,平均分配槽位,大量的key落到第二个节点上,导致请求都偏向第二个节点。导致这个问题的主要原因是,大量key为hash、set、sorted sort类型,且每个集合数据量都比较大。其次是HashTag的不合理使用。

解决方案,一是将大hash分段存储,二是减少HashTag的使用,三是重新分配槽位,将第二个节点的槽位根据实际情况分配一些给其它两个节点。

实际业务场景下,如何选择缓存数据结构

拿我最熟悉的广告行业,举几个简单例子。

a、判断一个广告单是否过期

使用hash、bitmap都可实现。bitmap适用于判断true or false的业务需求。bitmap的读写速度都优于hash,且内存占用少。但出于其它需求,我选择hash。bitmap用于其它业务需求,如快速判断offer每日展示数是否达到上限。

b、统计每个渠道的拉取广告次数

简单的key-value以及hash都支持incr自增,且操作原子性。为减少缓存中key的数据,我选择hash,同时也因为hash支持hgetall,用于实时统计以及方便问题排查。

c、根据标签限CAP

Capacity,即容量,如根据国家、城市、渠道、广告主等标签限制广告的展示次数,一个广告可能同时会匹配到多个标签,当达到最小Capacity时,即判定为true。通过Sorted Set存储一个广告匹配的所有标签,根据当前展示次数通过zcount获取匹配的标签总数,判断zcount结果是否大于零即可。

d、过滤每日重复ip

如用于过滤短时间内重复点击广告的用户,只是举个例子。这时就可以利用HyperLogLog存储IP,HyperLogLog会过滤重复数据,准确率有误差,但对业务影响甚微。

#后端

声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。

文章推荐

Dubbo RPC远程调用过程源码分析(服务消费者)

本篇继续分析服务提供者发起一个远程RPC调用的全过程,也是跳过信息交换层和传输层,但发起请求的逻辑会复杂些,包括负载均衡和失败重试的过程,以及当消费端配置与每个服务提供端保持多个长连接时的处理逻辑。

Dubbo分层架构之服务注册中心层的源码分析(下)

由于我在实际项目中并未使用Redis作为服务注册中心,所以一直没有关注这个话题。那么,使用Redis作为服务注册中心有哪些缺点,希望本篇文章能给你答案。

Dubbo分层架构之服务注册中心层的源码分析(上)

服务注册与发现是Dubbo核心的一个模块,假如没有注册中心,我们要调用远程服务,就必须要预先配置,就像调用第三方http接口一样,需要知道接口的域名或者IP、端口号才能调用。

线上RPC远程调用频繁超时问题排查,大功臣Arthas

项目不断新增需求,难免不会出现问题,特别是近期新增的增加请求处理耗时的需求。以及一些配置的修改而忽略掉的问题,如dubbo工作线程数调增时,忽略了redis连接池的修改。由于redis可用连接远小于工作线程数,就会出现多个线程竞争redis连接,影响性能。

如何让JedisCluster支持Pipeline

pipeline提升性能的关键,一是RTT,节省往返时间,二是I/O系统调用,read系统调用,需要从用户态,切换到内核态。

Redis性能问题如何排查

并发数上升,到底是哪个服务处理能力到了瓶颈,还是Redis性能到了瓶颈,只有找出是哪里的性能问题,才能对症下药。所以,了解redis的一些运维知识能够帮助我们快速判定是否Redis集群的性能问题。