一、背景
最近对接了一个卧龙同事的接口,因为接口比较慢,所以打算对第三方接口加个缓存。但是会有大 key 的问题。设计过程中调研了一些解决方案,这里总结下。关键字:Redis;大Key问题;
二、大 key 会带来什么问题
我们都知道,redis 是单线程架构,日常的读写操作都是由一个线程完成。一旦某一个线程执行了大 key 的读写,就会影响之后所有命令的执行,进而影响 redis 实例甚至整个 redis 集群的稳定。
三、什么才叫大 key
那么什么才叫大 key?普遍认同的规范是:
value > 10kb,即认定为大 key
像list,set,hash 等容器类型的 redis key,元素数量 > 5000,即认定为大 key
现在我们知道了大 key 会来带什么问题,也知道了什么样的 key 才算大key。接下来我们看看都有哪些解决方案。
四、解决方案一:压缩
适用于字符串类型的 redis key。采用压缩算法,将 key 压缩至可接受的范围内。压缩也是有讲究的,首先要选择无损的压缩算法,然后在压缩速率和压缩率之间也要权衡。比较常用的压缩算法/工具如下:
google snappy:无损压缩,追求压缩速度而不是压缩率(Compression rate)
message pack:无损压缩,仅适用于 json 字符串的压缩,可以得到一个更小的 JSON,官网是:msgpack.org/
五、解决方案二:value 切片
适用于 list,set,hash 等容器类型的 redis key。规范要求容器的元素数量 < 5000,我们可以在写 redis 的时候做个逻辑,如果超过了 5000 的容器就做切片。
举个例子,现在有一个 list 类型的缓存 ,他包含 12000 个元素。是很典型的大key。
我们以 5000 为阈值,把 list 切分成三份:user_list_slice_1、user_list_slice_2、user_list_slice_3,另外还需要一个存储切片具体情况的key,所以还需要一个 user_list_ctl。 业务程序后续访问这个缓存的时候,先请求 user_list_ctl,解析出缓存的切分情况,再去请求具体的切片即可。
六、解决方案三:抛弃大 key(discard)
大多数场景,我们是把 redis 当缓存用,缓存失效了就走数据库查出数据。我们可以设定一个阈值,如果缓存对象特别大的话,我们就抛弃这个key,不缓存,直接走数据库。这样不会影响 redis 正常的运作。
当然,这是个取巧的方案,灵感是来自线程池的拒绝策略(DiscardPolicy)。采用这个方案得确认直接抛弃不会影响业务,还需要确保不走缓存后的性能业务上能够接受。
七、俯瞰一下,从架构的角度解决这个问题
千叮咛万嘱咐,大 key 问题造成的线上事故仍然没有断过,这个怎么解决? 我觉得有如下几个思路
完善监控机制,有大 key 出现就及时告警
封禁/限流能力,能够及时封禁大 key 的访问,降低业务影响(保命用)
在服务和 redis 集群之间建设 proxy 层,在 proxy 做大 key 的处理(压缩或者切片处理),让业务开发无需感知大key。
八、总结
总结一下,解决 redis 的大 key,我们常规有三种解决方案。一是压缩,而是切片,三是直接抛弃不缓存。