缓存重构 – 减少Redis Key的数量

前不久重构系统的时候,发现redis的key已经超过5000万个了,已经没法用keys做遍历了,即使用迭代器*scan做遍历,开销也大到无法接受了。对业务我是相当熟悉的,我很确定我们不需要这么多的key,于是着手开始清理。

首先我跑了个脚本,统计出最常见的key的前缀,发现有两类最多,都超过1000万,分别是

  • carbrand_udid_,缓存的是每个用户的绑定了车牌的车型
  • 1100_341_,缓存的是某个URL对应的图片是否允许被展示

这些key都没有过期时间,也没有清理机制。而业务本身是要求carbrand_udid_永不过期的,1100_341_的时效则比较短,通常在半个月以内,最长也不会超过三个月。

针对第一种情况,原先一个carbrand_udid_$userId对应一个车型列表,通常这个列表长度不超过10,毕竟拥有10个以上车牌且在我们平台上绑定过的用户是极其稀有的。而且每次对这个列表都是整体存取的,不需要单独访问其中一个车型。这种情况其实很好改造,我们放弃每个用户一个key的做法,改用hashtable,table.carbrand,用$userId做hashtable的field,然后把JSON序列化后的车型ID作为value,Redis的hashtable可以存储40亿个键值对,我们用户数才3亿,绑定了牌照的用户不过几千万,在可以预见的未来,不存在超过40亿的可能性。第一种情况的改造,把数千万个key塞到了hashtable内,变成了一个key加这个key内部的数千万个field。

本质上这种改造,是把二维的数据压缩成一维的,data[][]变成data[],消失的那个维度是通过JSON编码成字符串完成的。

针对第二种情况,原先是1100_341_$URL,值是0或1,用来存储一个广告图片是否能展示,0是不能展示,1是可以展示。直接用上面那种方法也能大大减少key的数量,但是我需要做的更好,增加一个过期的功能。由于redis只能对key设置过期时间,不能对二维数据结构内部元素做过期设置,只能想别的办法了。能不能好好利用有效期最长也不会超过三个月这个特性呢?当然可以,而且很简单。我们把日期编码进key里面就行了,比如这样

  • image.audit.white.2018.Q1
  • image.audit.black.2018.Q2

  • image.audit.white.2018.Q1

  • image.audit.black.2018.Q2

每个季度一个白名单set和黑名单set,把URL存储在set里面,再通过判断集合中是否存在某个元素决定URL是否能访问。我们每次最多需要查4个名单,可以确定URL在哪个集合,利用redis的pipeline特性,可以在一个报文里批量查询,一次查完。4条O(1)复杂度的命令执行起来是很快的。还能不能继续优化一些呢?考虑到大部分URL都很长,在空间利用上有些浪费,所以我选择用sha1把URL做个签名,只存储这个签名就行了。第二种改造,成功把数千万个key,减少到每个季度2个,然后定期删除上上季度的key,就能完成清理工作了。

Redis有丰富的数据结构,需要因地制宜好好使用。如若不然,还不如用memcached算了。

标签:缓存Redis 发布于:2019-10-27 01:45:34