用Redis踩过的坑,说几个印象深刻的。
大Key,Redis性能杀手第一名。
一个String存了10MB的JSON,一个Hash存了100万个field。你DEL这个Key的时候,整个Redis就卡住了。
为什么?Redis是单线程。你删一个大Key,可能要几百毫秒。这几百毫秒内所有请求都在排队等着。
更坑的是大Key过期。Redis定期清理过期Key,清到你那个大Key的时候,直接卡一下。你可能发现服务每隔一段时间抖一下,查半天才发现是这个原因。
规避方法:单个Key别超过10KB,大数据拆小数据。删大Key用UNLINK不要用DEL,UNLINK是后台异步删的。
热Key,打爆单节点。
秒杀商品的库存、微博热搜、爆款直播间的在线人数。所有流量打同一个Key。
Redis Cluster按Key分片,一个Key只落一个节点。你集群100个节点也没用,热Key那个节点照样被打爆。
解决办法就是打散。hot_key变成hot_key_1到hot_key_10,读的时候随机选。或者应用层加本地缓存,热数据缓存1秒,大部分请求就不用打Redis了。
缓存一致性,没有银弹。
先更新DB再删缓存?删缓存失败怎么办?
延迟双删?延迟多久合适?太短没用,太长浪费时间。
想了各种方案,最后发现都有漏洞。
本质问题是:缓存和数据库是两个独立存储,没有分布式事务,必然有不一致的窗口期。
你能做的是缩短这个窗口,不是消灭它。想清楚业务能容忍多久的不一致。金融场景不能容忍?那就别用缓存。电商展示几秒不一致无所谓?Cache Aside够用。
穿透、击穿、雪崩。
面试八股文三连,但真遇到还是能搞死人。
穿透:查不存在的数据,缓存没有DB也没有,每次都打DB。恶意攻击常用这招。解决:布隆过滤器,或者缓存空值。
击穿:热Key过期那一瞬间,几万请求同时打到DB。解决:热Key不设过期,或者加锁只让一个请求去加载。
雪崩:大量Key同时过期,或者Redis挂了。解决:过期时间加随机偏移,Redis做高可用。
分布式锁,看起来简单,坑多。
最基本的坑:锁没设过期时间,进程挂了锁永远不释放。
进阶的坑:锁过期了业务没执行完,另一个进程拿到锁了,两个进程同时跑。
更隐蔽的坑:DEL释放锁的时候没判断是不是自己的锁,把别人的锁删了。
正确做法:SET key value NX EX,value用UUID。释放锁用Lua脚本,先判断是不是自己的再删:
ifredis.call("get",KEYS[1])==ARGV[1]thenreturnredis.call("del",KEYS[1])elsereturn0end持久化。
RDB做快照要fork子进程。数据量大的时候fork很慢,fork期间主进程阻塞。
AOF文件越来越大,rewrite的时候也要fork。
建议:主节点用AOF,appendfsync everysec。从节点开RDB做冷备。别让RDB和AOF rewrite同时触发。
集群模式的坑。
MGET、事务、Lua脚本,要求所有Key在同一个slot。跨slot直接报错。
解决:Key加hash tag。{user_123}_profile和{user_123}_orders,花括号内的部分决定slot,这俩就能放一起操作。
还有主从切换会丢数据。Redis主从是异步复制,主节点挂了,没同步到从节点的数据就没了。不能接受就上WAIT命令,但性能会受影响。
踩这些坑的根本原因,是把Redis当成了它不是的东西。
它不是数据库,重要数据别只存Redis。
它不是消息队列,虽然有Stream,专业的事交给Kafka。
它就是个缓存,用来解决"快"的问题。
想清楚你要它干什么,坑就少一半。