键值设计
key设计
- (1)【建议】: 可读性和可管理性
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
o2o:order:1
- (2)【建议】:简洁性
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
user:{uid}:friends:messages:{mid} 简化为 u:{uid}🇫🇷m:{mid}
- (3)【强制】:不要包含特殊字符
反例:包含空格、换行、单双引号以及其他转义字符
value设计
- (1)【强制】:拒绝bigkey(防止网卡流量、慢查询)
- (1)(2)【推荐】:选择适合的数据类型。
例如:实体类型(要合理控制和使用数据结构,但也要注意节省内存和性能之间的平衡)
反例:
set user:1:name tom set user:1:age 19 set user:1:favor football
- 正例:
hmset user:1 name tom age 19 favor football
- (1)3.【推荐】:控制key的生命周期,redis不是垃圾桶
建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期)
big key
我们知道,Redis 的一个字符串最大512M,一个二级数据结构(比如 hash、list、set 、zset)可以存储2^32-1 个元素 ,约40亿个元素。 但是不是以为着我们可以任意存储元素呢? 时刻牢记,在读写这个角度上,目前Redis还是单线程的。
定义
其实不然,按照经验来说 ,如何定义bigKey 呢?
- 字符串类型:它的big体现在单个value值很大,一般认为超过10KB就是bigkey。
- 非字符串类型:哈希、列表、集合、有序集合,它们的big体现在元素个数太多。
一般来说,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。当然了这不是绝对的,请依据场景,灵活处理。
反例
我们来看个反例: 一个hash 存储用户信息,我有100万用户,我全都放到一个key里。。。。这不管从哪个角度看 ,bigkey无疑。
bigkey的产生
一般来说,bigkey的产生都是由于程序设计不当,或者对于数据规模预料不清楚造成的,来看几个例子:
- 社交类:粉丝列表,如果某些明星的粉丝数据,如果不精心设计下,一个明星的粉丝 百万很少了吧,你都把这百万的粉丝数据放到一个key中存储,毫无疑问是bigkey
- 统计类:比如按天存储某项功能或者网站的用户集合,用户很少,倒是没多大问题,一旦用户多了起来,必是bigkey
- 缓存类:将数据从数据库加载出来以后序列化放到Redis里,这个方式非常常用,但有两个地方需要注意,第一,是不是有必要把所有字段都缓存;第二,有没有相关关联的数据,不要为了图方便把相关数据都存一个key下,产生bigkey。
如何优化bigkey
核心思想: 分治 拆分
- 拆
big list: list1、list2、…listN
big hash:可以讲数据分段存储,比如一个大的key,假设存了1百万的用户数据,可以拆分成 200个key,每个key下面存放5000个用户数据 - 如果bigkey不可避免,也要思考一下要不要每次把所有元素都取出来(例如有时候仅仅需要
hmget,而不是hgetall),删除也是一样,尽量使用优雅的方式来处理 - 【推荐】:选择适合的数据类型
例如:实体类型(要合理控制和使用数据结构,但也要注意节省内存和性能之间的平衡)
反例:
set user:1:name tom set user:1:age 19 set user:1:favor football
- 正例:
hmset user:1 name tom age 19 favor football
- 【推荐】:控制key的生命周期,redis不是垃圾桶
建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期)。
删除bigKey的注意事项
对于非字符串的bigkey,比如 hash list set zset , 不要使用del 删除, 请使用 hscan 、sscan、zscan方式渐进式删除。
同时要注意防止bigkey过期时间自动删除问题(例如一个100万的hash设置1小时过期,会触发del操作,造成阻塞)
bigkey的危害
- 导致redis阻塞
这个也很好理解: 我们知道Redis 官方号称10万QPS, 我们通常打不到这个值,但是大几万的QPS还是没问题的,这也就意味着 redis 的执行速度 1秒几万条, 速度相当的快的。 假设你有个bigKey , 操作一次耗时1秒,那Redis 单线程 在这1秒钟就只能处理你这个Key, 后面堵了一堆请求。。。。 并且你的应用 序列化和反序列化这种大key , 也消耗CPU 。 - 导致网络拥塞
假设我们的交换机,千兆网络(小b),那么 实际带宽 1024 / 8 = 128M . 假设你的这个key的大小 500KB, 客户端并发 1000获取这个key, 那么就意味着 1000 * 500KB = 500M ,那就是每秒产生500M的流量。先不说你的Redis能不能处理的过来这个并发下的bigKey,单说你的这个千兆网络, 你说你这个网络I/O能扛得住吗? 一般服务器会采用单机多实例的方式来部署,也就是说一个bigkey可能会对其他实例也造成影响,其后果不堪设想。
- 过期删除- Redis4.0新特性(三)-Lazy Free
针对那种我们设置了过期时间的big key , 在redis4.0前,没有lazy free功能,我们只能通过类似scan big key,每次删除少量的元素,分多次删除;但在面对“被动”删除键的场景,这种取巧的删除就无能为力。
举个例子:Redis Cluster大集群,业务缓慢地写入一个带有TTL的2000多万个字段的Hash键,当这个键过期时,redis开始被动清理它时,导致redis被阻塞20多秒,结果发生了fail over ,造成故障。
Redis 4.0提供了过期异步删除(lazyfree-lazyexpire yes)
lazy free 惰性删除或延迟释放: 当删除键的时候,redis提供异步延时释放key内存的功能,把key释放操作放在Background I/O单独的子线程处理中,减少删除big key对redis主线程的阻塞,有效地避免删除big key带来的性能和可用性问题。
redis4.0有lazy free功能后,这类主动或被动的删除big key时,时间复杂度O(1)。