公司Redis规范
导读
Redis 是基于单线程模型实现的,但是再快的系统,也经不住疯狂的滥用。本文将从键值设计、命令使用、优化建议、运营平台等方面进行说明,介绍乐信Redis的开发规范,以期减少使用Redis过程带来的问题。
一、键值设计
1. key名设计
(1)【建议】: 可读性和可管理性
命令规范:以英文冒号分隔key,前缀概念的范围的返回从大到小,从不变到可变,从变化幅度小到变化幅度大。业务名:key用途:变量
1例如:yoga:user:1,表示 yoga:user:{userID},即瑜伽子系统ID=1的用户信息
(2)【建议】:简洁性
保证语义的前提下,控制key的长度,不超64个字符。
当key较多时,内存占用也不容忽视,例如:
1user:friends:messages:{mid} 简化为 u:fr:msg:{mid}
(3)【强制】:不要包含特殊字符
反例:包含空格、换行、单双引号以及其他转义字符
Redis 的 Key 一定要规范,这样在遇到问题时,能够进行方便的定位。Redis 属于无 scheme 的 KV 数据库,所以,我们靠约定来建立其 scheme 语义。其好处:
1、能够根据某类 key 进行数据清理
2、能够根据某类 key 进行数据更新
3、能够方面了解到某类 key 的归属方和应用场景
4、为统一化、平台化做准备,减少技术变更
2. value设计
(1)【强制】:拒绝bigkey
字符串类型:它的big体现在单个value值很大,一般认为超过10KB就是bigkey
非字符串类型:哈希、列表、集合、有序集合,它们的big体现在元素个数太多,元素个数不要超过5000
bigkey的危害
1、Redis集群的内存空间不均匀 由于Redis单线程的特性
2、操作bigkey的通常比较耗时,也就意味着阻塞Redis可能性越大
3、bigkey也就意味着每次获取要产生的网络流量较大,容易打满带宽
4、过期删除的时候会阻塞Redis
以下是压测结果,可直观看出bigkey对性能的影响:
(2)【推荐】:选择适合的数据类型
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)
反例:
1set user:1:name tom
2set user:1:age 19
3set user:1:favor football
正例:
1hmset user:1 name tom age 19 favor football
(3)【强制】:控制key的生命周期,redis不是垃圾桶。
如果应用将Redis定位为缓存Cache使用,对于存放的Key一定要设置超时时间!因为若不设置,这些Key会一直占用内存不释放,造成极大的浪费,而且随着时间的推移会导致内存占用越来越大,直到达到服务器内存上限!另外Key的超时长短要根据业务综合评估,而不是越长越好!(某些业务要求key长期有效。可以在每次写入时,都设置超时时间,让超时时间顺延。)
二、Redis命令使用规范
1.【强制】严禁不设置范围的批量操作
-
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值,有遍历的需求可以使用hscan、sscan、zscan代替。
-
zrange、 zrangebyscore等多个操作 zset 的函数,严禁使用 zrange myzset 0 -1 等这种不设置范围的操作。请指定范围,如 zrange myzset 0 100,如不确定长度,可使用 zcard 判断长度。
-
hgetall会取出相关 hash 的所有数据,如果数据条数过大,同样会引起阻塞,请确保业务可控。如不确定长度,可使用 hlen 先判断长度。
-
严禁使用 sunion, sinter, sdiff等一些聚合操作。
2.【强制】禁用 select 函数
select函数用来切换 database,对于使用方来说,这是很容易发生问题的地方,cluster 模式也不支持多个 database,且没有任何收益,dba已通过配置禁用。
3.【强制】禁用命令
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。dba已通过配置禁用这些命令。
4.【强制】不推荐使用事务
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上。
三、使用优化建议
1. 缩短键值对的存储长度
键值对的长度是和性能成反比的,比如我们来做一组写入数据的性能测试,执行结果如下:
不同key大小性能测试
从以上数据可以看出,在 key 不变的情况下,value 值越大操作效率越慢,因为 Redis 对于同一种数据类型会使用不同的内部编码进行存储,比如字符串的内部编码就有三种:int(整数编码)、raw(优化内存分配的字符串编码)、embstr(动态字符串编码),这是因为 Redis 的作者是想通过不同编码实现效率和空间的平衡,然而数据量越大使用的内部编码就越复杂,而越是复杂的内部编码存储的性能就越低。
2. 冷热数据分离
不要将所有数据全部都放到Redis中,建议根据业务只将高频热数据存储到Redis中【QPS大于5000】,重要性级别较低的但却需求容量较大的场景可申请完全兼容Redis操作的Pika,对于低频冷数据可以使用MySQL/ElasticSearch等基于磁盘的存储方式。
3. 业务数据分离
不要将不相关的数据业务都放到一个 Redis中。一方面避免业务相互影响,另一方面避免单实例膨胀,并能在故障时降低影响面,快速恢复。
4. 大文本数据要压缩
对于大文本【超过500 byte】写入到Redis时,一定要压缩后存储!大文本数据存入Redis,除了带来极大的内存占用外,在访问量高时,很容易就会将网卡流量占满,进而造成整个服务器上的所有服务不可用,并引发雪崩效应,造成各个系统瘫痪。
5. 使用连接池
使用带有连接池的客户端,可以有效控制连接,同时提高效率
6. 缓存 Key 设置失效时间的建议
如果在大型系统中有大量缓存在同一时间同时过期,那么会导致 Redis 循环多次持续扫描删除过期字典,直到过期字典中过期键值被删除的比较稀疏为止,而在整个执行过程会导致 Redis 的读写出现明显的卡顿,卡顿的另一种原因是内存管理器需要频繁回收内存页,因此也会消耗一定的 CPU。
为了避免这种卡顿现象的产生,我们需要预防大量的缓存在同一时刻一起过期,就简单的解决方案就是在过期时间的基础上添加一个指定范围的随机数。
7. 限制Redis 分片大小
乐信标准,Redis都有分片,最少一个分片,单分片的大小是4G ,为何不建议一直往上加内存?
-
单台服务器内存资源有限,不利于扩展
-
单个分片内存过大,服务器出故障时,影响面大
-
Redis持久化时,虽然操作是异步化,但会有fork进程的操作,这一步是由主进程来完成的,分片内存越大,页表就越大,fork执行时间就越长,就会给主线程带来阻塞风险
-
不利于平台标准化,后期迁移困难
-
Redis是单进程模型,只能利用一个CPU核心,多分片有利于提高Redis集群能力,充分利用多核性能。
8. 认真对待最大内存淘汰策略
根据自身业务类型选择好最大内存淘汰策略,可保证有用数据不被删除,也可避免Redis实例出现OOM,让Redis持续高效。
-
noeviction:不淘汰任何数据,当内存不足时,新增操作会报错,Redis 默认内存淘汰策略
-
allkeys-lru:淘汰整个键值中最久未使用的键值
-
allkeys-random:随机淘汰任意键值
-
volatile-lru:淘汰所有设置了过期时间的键值中最久未使用的键值(乐信默认)
-
volatile-random:随机淘汰设置了过期时间的任意键值
-
volatile-ttl:优先淘汰更早过期的键值
在 Redis 4.0 版本中又新增了 2 种淘汰策略:
-
volatile-lfu:淘汰所有设置了过期时间的键值中,最少使用的键值
-
allkeys-lfu:淘汰整个键值中最少使用的键值