原始数据存储在DB中(如MySQL、Hbase等),但DB的读写性能低,延迟高。比如MySQL在4核8G上TPS=5000,QPS=10000左右,平均读写耗时10~100ms。使用Redis作为缓存系统正好可以弥补DB的不足。“码哥”在自己的MacBookPro2019上进行了Redis性能测试如下:$redis-benchmark-tset,get-n100000-qSET:107758.62requestspersecond,p50=0.239msecGET:108813.92requestspersecond,p50=0.239msecTPS和QPS达到了10万,所以我们引入了缓存架构,将原始数据存储在数据库中,同时在缓存中存储一??份副本。当有请求进来时,先从缓存中取出数据,如果有则直接返回缓存中的数据。如果缓存中没有数据,就去数据库读取数据写入缓存,然后返回结果。这是无缝的吗?缓存设计不当会导致严重的后果。本文将介绍三种缓存使用中常见的问题及解决方法:缓存击穿(失效);缓存穿透;缓存雪崩。缓存击穿(失效)高并发流量,访问的数据是热点数据,请求的数据在DB中存在,但是Redis中保存的副本已经过期,后端需要从DB中加载数据写入Redis。关键词:单热点数据,高并发,数据失败但由于高并发,DB可能不堪重负,导致服务不可用。如下图:解决方案过期时间+随机值对于热点数据,我们不设置过期时间,这样所有的请求都可以在缓存中处理,充分利用Redis的高吞吐性能。或者在到期时间上添加一个随机值。在设计缓存的过期时间时,使用公式:过期时间=baes时间+随机时间。即当相同的业务数据写入缓存时,在基础过期时间之上加上一个随机的过期时间,让数据在以后慢慢过期,避免所有瞬间过期,造成过大的压力数据库。数据预先存储在Redis中,热门数据的过期时间设置为最大值。当发现缓存无效时,使用锁而不是立即从数据库中加载数据。而是先获取分布式锁,获取锁成功后才进行数据库查询和写入缓存。如果获取锁失败,说明当前有线程在执行数据库查询操作,当前线程休眠一段时间后重试。这只会向数据库发出一次读取数据的请求。伪代码如下:publicObjectgetData(Stringid){Stringdesc=redis.get(id);//缓存为空,过期if(desc==null){//互斥锁,只有一次请求才能成功if(redis(lockName)){try//从数据库中取数据desc=getFromDB(id);//写入Redisredis.set(id,desc,60*60*24);}catch(Exceptionex){LogHelper.错误(例如);}finally{//确保在最后删除并释放锁redis.del(lockName);返回描述;}}else{//否则休眠200ms,然后获取锁Thread.sleep(200);返回getData(id);}}}缓存穿透缓存穿透:是指有一个特殊的请求去查询一个不存在的数据,即Redis或数据库中不存在数据。这样一来,每次请求都会渗透到数据库中,缓存就成了摆设,给数据库造成很大的压力,影响正常的服务。如图:解决方案缓存空值:当请求的数据在Redis或数据库中不存在时,设置一个默认值(例如:None)。后续再次查询时,直接返回空值或默认值。Bloomfilter:当数据写入数据库时??,将这个ID同步到Bloomfilter。如果请求的id在Bloomfilter中不存在,则说明请求查询到的数据肯定没有保存在数据库中。不要去数据库查询。BloomFilter需要缓存全量的key,这就要求全key的个数不多,最好少于10亿条数据,因为10亿条数据会占用1.2GB左右的内存。下面说说布隆过滤器的原理。BloomFilter的算法是先分配一块内存空间作为位数组,数组的位的初始值全部设置为0。添加元素时,使用k个独立的Hash函数进行计算,并且然后将元素Hashmap的K个位置全部置1。检测key是否存在,仍然用这k个Hash函数计算k个位置。如果所有位置都为1,则说明key存在,否则不存在。如下图所示:hash函数会发生碰撞,所以Bloomfilter会误判。这里的误报率指的是BloomFilter判断一个key存在,但实际上并不存在的概率,因为它存储的是key的Hash值,而不是key的值。所以有概率存在这样的key,虽然内容不一样,但是多次Hash后的Hash值是一样的。对于BloomFilter判断不存在的key,100%不存在。如果key存在,那么它在每个Hash之后对应的Hash值位置一定是1,而不是0。Bloomfilter不一定存在。缓存雪崩缓存雪崩是指Redis缓存系统无法处理的大量请求,所有请求都打到数据库,导致数据库压力激增,甚至宕机。这主要有两个原因:大量热点数据同时过期,导致大量请求查询数据库和写入缓存;Redis故障宕机,缓存系统异常。大量数据被缓存,过期数据存储在缓存系统中,并设置了过期时间,但同时,大量数据同时过期。系统将所有请求都发送到数据库获取数据,如果并发量很大,数据库的压力会急剧增加。缓存雪崩是大量数据同时失效的场景,而缓存击穿(失效)是数据在某个热点失效的场景。这是他们之间最大的区别。如下图:解决方案过期时间加一个随机值,避免大量数据设置相同的过期时间,过期时间=baes时间+随机时间(一个小的随机数,比如加1加5分钟随机)。这样一来,所有热点数据不会同时失效,过期时间也不会相差太大,既保证了过期时间相近,又满足了业务需求。接口限流当访问的不是核心数据时,在查询方法中增加接口限流保护。例如,设置10000req/s。如果你访问的是核心数据接口,缓存是不存在的,让你从数据库中查询,设置到缓存中。这样,只有部分请求会发送到数据库,减轻了压力。限流是指我们在业务系统的请求入口前端控制每秒进入系统的请求数,防止过多的请求发送到数据库。如下图:Redis宕机,一个Redis实例可以支持10万QPS,而一个数据库实例只能支持1000QPS。Redis一旦宕机,大量的请求会打到数据库,造成缓存雪崩。解决方案缓存系统故障导致缓存雪崩的解决方案有两种:服务熔断和接口限流;构建高可用的缓存集群系统。服务熔断与限流在业务系统中,服务熔断用于高并发破坏提供服务,保证系统可用性。服务熔断是指当从缓存中获取的数据发现异常时,将错误的数据直接返回给前端,避免所有流量都打到数据库造成宕机。服务熔断和限流属于缓存雪崩发生时如何降低雪崩对数据库影响的方案。搭建高可用缓存集群所以缓存系统一定要搭建Redis高可用集群,比如《Redis 哨兵集群》或者《Redis Cluster 集群》。如果Redis的主节点出现故障宕机,从节点也可以切换成为主节点,继续提供缓存服务,避免缓存实例宕机导致的缓存雪崩问题。总结缓存穿透是指数据库没有这个数据,请求直奔数据库,缓存系统就没用了。缓存击穿(失效)是指数据库有数据,缓存应该有数据,但是缓存过期了,Redis的流量保护屏障被击穿,请求直奔数据库。缓存雪崩是指Redis缓存无法处理大量热数据(大面积热数据缓存失效,Redis宕机),所有流量都打到数据库上,导致数据库压力很大。参考资料https://segmentfault.com/a/11...https://cloud.tencent.com/dev...https://learn.lianglianglee.com/https://time.geekbang.org/
