Redis开发规范
- 总体规约以《阿里云Redis开发规范》为主,请开发人员至少阅读一遍该手册。
总体要求
Redis主要用于缓存处理,加快读取效率,但在使用过程中需要注意合理的使用,一般存储全局配置数据和一些访问非常频繁的较为静态的数据,另外注意过期时间控制,减少资源的不必要消耗。
-
模块级固定段:服务简码:模块简码: 例:
hpfm:fnd:
-
服务级固定段:服务简码: 例:
hpfm:
key名设计
- 可读性和可管理性
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如平台服务:基础模块:配置文件(Hash结构的key)
hpfm:fnd:profile
- 简洁性
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
user:{uid}:friends:messages:{mid} 简化为 u:{uid}:fr:m:{mid}
- 不要包含特殊字符
反例:包含空格、换行、单双引号以及其他转义字符
value设计
-
拒绝bigkey(防止网卡流量、慢查询)
string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
反例:一个包含200万个元素的list。
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法 -
选择适合的数据类
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)
反例:
set user:1:name tom
set user:1:age 19
set user:1:favor football
- 控制key的生命周期
建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。
- Redis操作
建议使用Spring提供的RedisTemplate对象进行操作,项目中进行一定的封装,是操作和使用保持一致的风格,便于后续的维护。
先操作缓存,还是先操作数据库
希望保证操作缓存和操作数据库的原子性,要么同时成功,要么同时失败。这演变为一个分布式事务的问题,保证原子性十分困难,很有可能出现一半成功,一半失败。
1.先操作数据库,再操作缓存
正常情况下:
(1) 先操作数据库,成功;
(2) 再操作缓存(delete或者set),也成功
如果第一步就失败,可以返回调用方50X,不会出现数据不一致。但如果这两个动作原子性被破坏:第一步成功,第二步失败,会导致,数据库里是新数据,而缓存里是旧数据,业务无法接受。
2.先操作缓存,再操作数据库;
正常情况下:
(1) 先操作缓存(delete或者set),成功;
(2) 再操作数据库,也成功;
如果第一步就失败,也可以返回调用方50X,不会出现数据不一致。
如果原子性被破坏,这里分了两种情况:
(1) 操作缓存使用set
第一步成功,第二步失败,会导致,缓存里是set后的数据,数据库里是之前的数据,数据不一致,业务无法接受。
(2) 操作缓存使用delete
第一步成功,第二步失败,会导致,缓存里没有数据,数据库里是之前的数据,数据没有不一致,对业务无影响。只是下一次读取,会多一次cache miss。
3.淘汰缓存和修改缓存中的数据,有什么差别
- 淘汰某个key,操作简单,直接将key置为无效,但下一次该key的访问会cache miss
- 修改某个key的内容,逻辑相对复杂,但下一次该key的访问仍会cache hit
建议淘汰(delete)缓存,而不是更新(set)缓存:在两个并发写发生时,由于无法保证时序,此时不管先操作缓存还是先操作数据库,都可能导致数据库与缓存之间的数据不一致。
4.缓存中的value数据一般是怎么修改的
- 简单类型(String、int等)的数据,直接set修改后的值即可
- 序列化后的对象:一般需要先get数据,反序列化成对象,修改其中的成员,再序列化为binary,再set数据
- json或者html数据:一般也需要先get文本,parse成doom树对象,修改相关元素,序列化为文本,再set数据
因此对于对象类型,或者文本类型,修改缓存value的成本较高,一般选择直接淘汰缓存(delete key)。如果还在纠结,总是淘汰缓存,问题也不大。
5.总结
(1) 读请求:先读cache,再读db;如果 cache hit,则直接返回数据;如果 cache miss,则访问db,并将数据set回缓存。
(2) 更新请求:先删缓存,再操作数据库。
(3) 如果先操作数据库,再删缓存,因为可能存在删除缓存失败的问题,可以提供一个补偿措施,例如利用消息队列。
(4) 对于对象类型,或者文本类型,修改缓存value的成本较高,一般选择直接淘汰缓存(delete key)
(5) 建议淘汰(delete)缓存,而不是更新(set)缓存。
(6) 一般来说,数据最终以数据库为准,写缓存成功,其实并不算成功。
(7) 如果对数据有强一致性要求,就不能放缓存。我们所做的一切,只能保证最终一致性。
(8) 缓存尽量加上失效时间,可以避免永久性的脏数据。
(9) 缓存失效时间不要一样,可以加上一个随机值,避免集体失效。否则容易导致缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。