缓存穿透
有人恶意循环请求不存在的key,导致所有的请求都怼到数据库上,从而造成数据库链接异常。
解决方案:
- 异步更新:无论key是否取到都立即返回。异步更新缓存。
- 使用布隆过滤器:key经过布隆过滤器可得出两个结果:“一定不存在”、“可能存在”。对于“一定不存在”的key直接返回。
- 互斥锁:缓存穿透的时候,先获取锁才能访问数据库。
缓存雪崩
缓存同时大面积失效,恰巧来了一波请求,结果全怼到数据库上了,导致数据库异常。
解决方案:
- 避免key集体失效:给缓存失效时间加随机数,避免集体失效。
- 双缓存:A缓存和B缓存。A缓存正常设置过期时间,B缓存过期时间超级长。
- 互斥锁:加锁请求数据库。明显吞吐量下降。
redis并发竞争key
Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念。
但是由于多客户端连接服务器混乱,可能会出现连接超时,数据转换错误,阻塞,客户端关闭等问题。
- 客户端角度:连接池化。读写redis使用锁(lock、suynchronized)
- 服务端角度:用setnx实现锁。
setnx加锁,通过del或者expire解锁。但是由于客户端崩溃或者阻塞等原因,加锁了没有解锁就会出问题。
场景1:c1客户端用setnx lock timestamp加锁,del解锁。
悲催的是c1加锁后就崩溃了。这时,c2客户端检测到这个锁已经过期,尝试del在setnx加锁,这是有问题的:
C2和 C3执行SETNX 都返回 0, 读取 lock 并检查时间戳已过期
C2 向 lock.foo 发送 DEL 命令。
C2 向 lock.foo 发送 SETNX 并成功。
C3 向 lock.foo 发送 DEL 命令。
C3 向 lock.foo 发送 SETNX 并成功。
出错:因为竞争条件的关系,C2 和 C3 两个都获得了锁。
用GETSET可以避免以上问题
c4执行SETNX 返回 0。
c4执行GET检查lock是否过期,如果过期,c4执行GETSET,再检查lock是否过期