键值设计
key 名设计
(1)【建议】: 可读性和可管理性 以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
1
ugc:video:1
(2)【建议】:简洁性 保证语义的前提下,控制 key 的长度,当 key 较多时,内存占用也不容忽视,例如:
1
2
3
user:{uid}:friends:messages:{mid}
简化为:
u:{uid}:fr:m:{mid}
(3)【强制】:不要包含特殊字符 反例:包含空格、换行、单双引号以及其他转义字符
key 最优长度(从内部编码角度分析)
Redis3.2 之前 key 长度小于等于 39 字节最佳,Redis3.2+ 长度小于等于 44 个字节最佳。
Redis中的字符串类型,有三种内部编码:raw、embstr、int。当值小于 44 字节(Redis 3.2+),使用 embstr,否则使用 raw(这里不讨论int),例如 下图展示了两者的区别,可以看到 embstr 将 redisObject 和 SDS 保存在连续的 64 字节空间内,这样可以只需要一次 jemalloc 分配,而对于 raw 来说,SDS 和 redisObject分离,需要两次 jemalloc,而且占用更多的内存空间。 Redis 3.2+ 对SDS进行优化:
可以看到 embstr 在 3.2+ 中使用了叫 sdshdr8 的结构,在该结构下,元数据只需要 3 个字节,而 Redis3.2之前 需要 8 个字节,所以总共64个字节,减去redisObject(16字节),再减去SDS的原信息,最后的实际内容就变成了44字节和39字节。
Redis 3.0 和 Redis 3.2+的sds有很大不同,新版本的sds会根据字符串长度使用不同的原信息,sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64。
value 设计
(1)【强制】:拒绝 bigkey (防止网卡流量、慢查询) string 类型控制在 10KB 以内,hash、list、set、zset 元素个数不要超过 5000。
反例:一个包含 200 万个元素的 list。
一般来说,对于string类型使用del命令不会产生阻塞。非字符串的 bigkey,不要使用 del 删除,使用 hscan、sscan、zscan 方式渐进式删除,同时要注意防止 bigkey 过期时间自动删除问题(例如一个 200 万的 zset 设置 1 小时过期,会触发 del 操作,造成阻塞,而且该操作不会不出现在慢查询中( latency 可查))
(2)【推荐】:选择适合的数据类型。 例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)
反例:
1
2
3
set user:1:name tom
set user:1:age 19
set user:1:favor football
正例:
1
hmset user:1 name tom age 19 favor football
(3)【推荐】:控制 key 的生命周期,redis 不是垃圾桶。 建议使用 expire 设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注 idle time。
大key问题
由于 redis 简单的单线程模型,业务在获取或者删除大 key 的时候都会有一定的影响,另外在集群模式下由于大 key 的产生还很容易导致某个子节点的内存满。
大 key 带来的危害体现在三个方面:
- 内存空间不均匀:统一集群中不同节点间内存使用量差异较大,不利于集群对内存的统一管理,存在丢失数据的隐患
- 操作耗时,存在阻塞风险:由于 Redis 单线程的特性,操作 bigkey 的通常比较耗时,也就意味着阻塞 Redis 可能性越大,这样会造成客户端阻塞或者引起故障切换,它们通常出现在慢查询中。
- 网络阻塞,每次获取大key产生的网络流量较大
- 过期删除:bigkey过期删除时,如果没有使用Redis 4.0的过期异步删除(lazyfree-lazy-expire yes),就会存在阻塞Redis的可能性。
- 迁移困难:当需要对bigkey进行迁移(例如Redis cluster的迁移slot),实际上是通过migrate命令来完成的,migrate实际上是通过dump + restore + del三个命令组合成原子命令完成,如果是bigkey,可能会使迁移失败,而且较慢的migrate会阻塞Redis
大key查找
大key删除
下面操作可以使用 pipeline 加速,此外 redis 4.0 已经支持 key 的异步删除,一条异步删除unlink就解决,无需下面的方式。
- Hash删除(hscan+hdel): 使用 hscan 命令,每次获取部分(例如100个)field-value,在利用hdel删除每个field(为了快速可以使用pipeline)。
- List删除(ltrim): Redis并没有提供 lscan 这样的 API 来遍历列表类型,但是提供了 ltrim 这样的命令可以渐进式的删除列表元素,直到把列表删除
- Set删除(sscan+srem): 使用 sscan 命令,每次获取部分(例如100个)元素,在利用 srem 删除每个元素
- SortedSet删除(zscan+zrem)使用 zscan 命令,每次获取部分(例如 100个)元素,在利用zremrangebyrank删除元素
键值数量
Redis虽然可以承担2^32个(大约40亿个)键值对,但是它建议你最佳实践是存放2.5亿个键值对,没有给出具体的原因
命令使用
-
【推荐】 O(N)命令关注 N 的数量 例如
hgetall、lrange、smembers、zrange、sinter
等并非不能使用,但是需要明确 N 的值。有遍历的需求可以使用hscan、sscan、zscan
代替。 -
【推荐】:禁用命令 禁止线上使用
keys、flushall、flushdb
等,通过 redis 的 rename 机制禁掉命令,或者使用 scan 的方式渐进式处理。 -
【推荐】合理使用
select
redis 的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。 -
【推荐】使用批量操作提高效率
- 原生命令:例如mget、mset。
- 非原生命令:可以使用pipeline提高效率。
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
注意两者不同:
- 原生是原子操作,pipeline是非原子操作。
- pipeline可以打包不同的命令,原生做不到
- pipeline需要客户端和服务端同时支持。
- 【建议】Redis事务功能较弱,不建议过多使用 Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)
- 【建议】Redis集群版本在使用Lua上有特殊要求:
- 所有key都应该由 KEYS 数组来传递,redis.call/pcall 里面调用的redis命令,key的位置,必须是KEYS array, 否则直接返回error,”-ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS array”
- 所有key,必须在1个slot上,否则直接返回error, “-ERR eval/evalsha command keys must in same slot”
- 【建议】必要情况下使用monitor命令时,要注意不要长时间使用。