1、更新KEY异常,先删再存还是直接覆盖
案例:
查询退改XX起原来线上是每8小时刷新一次缓存,查询去每8小时去查退改签库拉退改签数据,根据航司作为KEY存到Redis中,再放到各机器内存中使用
这次调整为退改签有改动就发mq消息给查询,查询收到消息,则重新去拉全量数据
开发设计时有变动就全量更新,且是先删除后存入,这样可能存在大量请求过来没有拿到数据
推动修改放案:从退改数据库拉数据之后,先和Redis原来的key(航司)做hash比对,如果原来的Redis多了,则清除,剩下的再做更新操作
2、KEY删除和丢失区分
案例:
政策先同步到Redis,再发消息给数据同步build站,build站收到消息后,会去拿Redis的数据更新到mongodb,如果没有查到key会做删除操作
如果Redis 数据丢失,key不会存在,则会造成误删
解决方案:
删除key时,生成key,数据为[],查到KEY的数据为[]则删除数据;数据丢了,不生成KEY,没查到KEY去实时调接口查
build收到消息后,去更新mongdb,如果查到的key是[],则删除该条航线
build收到消息后,去更新mongdb,如果没查到该key,则在去实时调官网DSF接口
3、KEY 过期策略不当造成内存泄漏
TTL KEY TTL过期时间为秒
当 key 不存在时,返回 -2 。
当 key 存在但没有设置剩余生存时间时,返回 -1 。
否则,以秒为单位,返回 key 的剩余生存时间。
大多数业务redis都会设置过期时间,key过期时如何清理的,也需要了解下
惰性清理(被动清理)
某个KEY过期后,不会立马被删除,下次使用时检查时候过期,过期就删除
缺点:浪费内存,长期不访问没法清理,垃圾数据过多,可能引起内存泄漏
定期清理(主动清理)
Redis会定期主动淘汰一批已过期的key(随机抽取一批key检查)
缺点:KEY已过期,仍未清理,需要等待JOB扫
内存淘汰机制
当前已用内存超过maxmemory限定时,触发主动清理策略:大多数会设置一个阀值,达到一定阀值自动扩容,除非自动扩容失败,则会出问题
noeviction(默认策略):当内存不足以容纳新写入数据时,新写入操作会报错。
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。
allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
目前公司一般采取惰性和定期清理配合使用
4、查询Redis异常,是否实时调接口/数据库
很多情况redis只是做一个缓存机制,如果redis异常或者未取到数据,是否有实时获取数据的兜底方案,需要考虑
5、Redis击穿
某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。
解决方案:可以将热点数据设置为永远不过期;或者基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据;或者用Hash方法对比KEY进行增删改
6、缓存雪崩
对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。
解决方案:
事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据
7、缓存穿透
对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。
黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。
解决方案:
每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 set -999 UNKNOWN。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。
8、Redis锁,使用不当造成锁不能释放,陷入死锁
目前常用的2种锁:
1)SET Key UniqId Seconds
仅在单实例的场景下是安全的,不用setnx+expire+del 中间断了仍可能造成死锁;不用SET Key UnixTimestamp Seconds NX,高并发可能存在相同时间戳
2)分布式Redis锁:Redlock
此种方式比原先的单节点的方法更安全
安全性:在同一时间不允许多个Client同时持有锁。
活性死锁:锁最终应该能够被释放,即使Client端crash或者出现网络分区(通常基于超时机制)。
容错性:只要超过半数Redis节点可用,锁都能被正确获取和释放。
案例:
综合推荐获取航班动态的redis数据时,某个航线没有推荐出来,高并发生成相同时间戳redis的KEYLOCK,redis解锁失败导致
9、Redis持久化
当Redis数据需要长久有效时,需要考虑是否做RDB和AOF持久化,一般RDB和AOF配合使用,但做持久化,会影响性能,目前做持久化的很少见
比如如来Redis数据是长久有效的,但却为了响应快不影响性能,未做持久化;采用了其他的降级方案Hbase,以及业务的兜底
10、缓存与数据库双写时的数据一致性
一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,最好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况
串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
还有一种方式就是可能会暂时产生不一致的情况,但是发生的几率特别小,就是先更新数据库,然后再删除缓存。
并行写数据库和缓存,可以加个事务都写成功才成功,有一个环节失败了就回滚事务,全失败
问题场景
描述
先写缓存,再写数据库,缓存写成功,数据库写失败
缓存写成功,但写数据库失败或者响应延迟,则下次读取(并发读)缓存时,就出现脏读
先写数据库,再写缓存,数据库写成功,缓存写失败
写数据库成功,但写缓存失败,则下次读取(并发读)缓存时,则读不到数据
需要缓存异步刷新
指数据库操作和写缓存不在一个操作步骤中,比如在分布式场景下,无法做到同时写缓存或需要异步刷新(补救措施)时候