Cache要怎么更新?

代码 · 06-11

前几天看自己一个关于用户信息的代码,突然发现组里一个人加了一段与之并列的逻辑,看着像是重构的方法,心想这个模块上线才不到一个月,也没发现有什么问题,为什么要重新搞?于是去问提交的人原因,他的意思是原来有两点问题,需要优化,现在新产品用户量还不高,等高了可能有问题,我觉得有必要记下来,聊一下这个cache需要怎么更新

数据结构

数据其实就是一个用户信息表,包含用户id,用户名,各种用户资料之类的,包含一个DB表,DB之前会有一个redis用于cache信息。很典型的cache方式

原cache-set流程(双删更新)

  1. 删除redis中对应修改用户key的数据
  2. update用户信息表
  3. delay 1s左右再次删除对应用户key的数据
  4. 请求用户数据时判断cache存在,则直接返回
  5. 请求用户数据时判断cache不存在,读db
  6. 读db后将数据json_encode到字符串set到redis中
  7. 返回db结果

新cache-set流程

  1. update用户信息表
  2. 订阅用户信息表binlog
  3. 解析更新内容,修改redis中对应用户的hash结果中的修改内容的字段key
  4. 请求用户数据判断cache存在,则直接返回
  5. 请求用户数据判断cache不存在,读db
  6. 读db后将数据一一映射到hash表的key字段,hmset到redis
  7. 返回db结果

优化原因

通过两种更新方式可以理解组内同学说的优化原因:原来的redis cache是一个大的string,用户修改其中一段就要全删了重新set,现在调整为hashmap,修改哪个改哪个,不用删掉整个key。

似乎很合理,但原来的问题算是个问题吗?新的解决了这个问题吗?新的方式会带来别的问题吗?

前置:redis字符串(string)和集合(hashmap)区别

首先redis整体所有的key都是存在于一个大的hashmap下,所有的key对应到这个根hashmap下一个key,而集合(hashmap结构)就相当于在根hashmap下嵌了另一个hashmap

效率:get/set都是O(1)的效率,hset/hget对于单一字段都是O(1),但对于当前场景用的最多的hgetall来说却是O(N),总体来说hashmap相对于string是有劣势的

问题是否存在

通过对日志的分析,当前用户信息的update与get的调用比例是1:300左右,同时公司内另一个百万级用户的app上修改用户信息的调用量也就在10qps以下,所以update本身占比很低,del并不是系统高频操作,正常不会有太大影响;

那会不会就算请求量虽然小,但del大value非常占用资源,可能阻塞其它请求呢?当前一个用户信息encode后长度为700个byte,我们按4倍冗余预估,算2800byte,通过本地测试,10000次set string为780ms左右,长度调整4个byte后10000次set为730ms左右,这个级别的长度看来是不会有太大影响的,所以如果抛去网络时间,单纯对于redis来看这个级别长度的key长度不用太在意,所以以key太长来决策使用新流程也不成立;

新流程将redis的set/del操作修改成为了hset,这个操作会有影响吗?我们再试下,10000次hset数据耗时750ms左右,所以set和hset这两种操作也没有什么影响;

综上来看,将原先的set大value并没有比hash map更耗时,流程修改没有必要

新流程会带来新问题吗?

  1. 修改用户cache的一个来源是用户表的binlog,这会导致一个问题--消息丢失和时序问题,消息一丢那就会导致错误数据一定时间得不到更新,时序问题还会导致更新的结果无法与db完全保持一致,解决时序本身也是一项成本
  2. 由于hashmap是共享一个过期时间,所以导致更新某个字段时有两种选择,一是给用户key重新设定一个过期时间,这样会导致一个极端情况下的问题,那就是某个key更新失败了,正常情况key在过期后会重新set所有字段,但在这期间,用户不停更新其它字段,导致过期时间一直往后延长,无法修正数据;二是不给用户重新设定过期时间,只在读不到数据时统一set一个过期时间,那就会导致另一个极端情况,当前在修改某个key时,这个用户数据过期了,但hset成功了,于是整体的key没有了过期时间,数据再也得不到全量刷新的逻辑

应该怎么更新?

我觉得延迟双删已经算是个不错的cache策略了,不需要瞎搞。我们在解决问题时还是不能自以为是的设想场景,需要理论和数据做支撑

Theme Jasmine by Kent Liao Modified by eLangX