Redis开发规范
1. 键值设计
1.1. key设计
(1)【强制】: 可读性和可管理性
以业务名(或数据库名)为前缀(防止key冲突),用冒号":"分隔域,用"."作为单词间的连接,比如业务名:表名:id, user:pay:1234
(2)【建议】:简洁性
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
(3)【强制】:不要包含特殊字符
反例:包含空格、换行、单双引号、逗号以及其他转义字符
1.2. value设计
(1)【强制】:拒绝bigkey(防止网卡流量、慢查询)
string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
反例:一个包含200万个元素的list,value的size太大的话会增加网络开销。
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞)
。
(2)【推荐】:选择适合的数据类型。
例如:用户实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)
1)原生字符串类型:每个属性一个键。
set user:1:name tom
set user:1:age 19
set user:1:city beijing
优点:简单直观,每个属性都支持更新操作。
缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差,所以此种方案一般不会在生产环境使用。
2)序列化字符串类型:将用户信息序列化后用一个键保存。
set user:1 serialize(userInfo)
优点:简化编程,如果合理的使用序列化可以提高内存的使用效率。
缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据取出进行反序列化,更新后再序列化到Redis中。
3)哈希类型:每个用户属性使用一对field-value,但是只用一个键保存。
hmset user:1 name tom age 23 city beijing
优点:简单直观,如果使用合理可以减少内存空间的使用。
缺点:要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。
1.3. 控制key的生命周期
建议Redis的key都能设置过期时间,条件允许的话还可以打散过期时间,防止集中过期,另外不过期的key要重点关注idletime。
1.4. 控制单个实例的大小
(1) 目前的策略:1、Master/Slave架构实例,单实例大小控制在10G以内,Redis-cluster架构中Redis实例大小尽量控制在10G以内。
(2) 为什么Redis实例不宜过大 ?
这是因为目前使用的Redis无法像Mysql、Mongodb那样基于同步的点位在主库发生变化后从新的主库继续同步数据。
在redis集群中一旦从库换主,redis的做法是将更换主库的从库清空然后从新主库完整同步一份数据再进行命令传播。
整个从库重做流程是这样的:
- 主库bgsave自身数据到磁盘
- 主库发送rdb文件到从库
- 从库flushall后开始加载
- 加载完毕开始续传
很明显,在这个过程中redis的内存体积越大以上每一个步骤的时间都会被拉长。
2. 命令使用
1.【推荐】 O(N)命令关注N的数量
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
2.【推荐】:禁用命令
禁止线上使用keys、flushall、flushdb、monitor等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理;使用scan需要控制每次增量迭代返回的key
数量(即scan命令的count选项,count值不宜太大,线上环境,不要超过10000),scan也是一个比较耗时的操作,不建议线上高频使用。
3.【推荐】合理使用select
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
4.【推荐】使用批量操作提高效率
原生命令:例如mget、mset。非原生命令:可以使用pipeline提高效率。
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
注意两者不同:
-
原生是原子操作,pipeline是非原子操作。
-
pipeline可以打包不同的命令,原生做不到。
-
pipeline需要客户端和服务端同时支持,
-
JedisCluster本身不支持pipeline,虽然可以利用CRC16算法计算出key对应的slot,以及Smart客户端保存了slot和节点对应关系的特性,将属于同一个Redis节点的key进行归档,然后分别对每个节点对应的子key列表执行mget或者pipeline操作。但是,不建议在生产环境这样操作。因为集群会有reshard和rebalance的操作,slot迁移数据期间由于键列表无法保证在同一节点,会导致大量错误。
5.【建议】Redis事务功能较弱,不建议过多使用
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个node上(可以使用hashtag功能解决)
6.【建议】Redis集群版本在使用Lua上有特殊要求:
1.所有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 arrayrn"
2.所有key,必须在1个slot上,否则直接返回error, "-ERR eval/evalsha command keys must in same slot"
7.【建议】必要情况下使用monitor命令时,要注意不要长时间使用。
8.【建议】谨慎全量操作Hash、Set等集合结构
在使用HASH结构存储对象属性时,开始只有有限的十几个field,往往使用HGETALL获取所有成员,效率也很高,但是随着业务发展,会将field扩张到上百个甚至几百个,此时还使用HGETALL会出现效率急剧下降、网卡频繁打满等问题,
9.【禁止】使用Jedis操作Redis-cluster
线上服务为了减少rtt时间,将属于同一个Redis节点的key进行归档,然后分别对每个节点对应的子key列表执行pipeline操作(Jedis + pipeline),服务初始化情况下,没有问题 ,如果遇到集群拓扑结构变更(包括添加/删除节点,主从切换),Jedis因为解析不了Redis-cluster协议,处理不了asking和moved异常,会导致Redis-cluster访问异常,需要使用JedisCluster客户端操作Redis-cluster
。
10.【禁止】不推荐线上服务使用Redis的key过期通知事件
Redis键过期通知事件,是基于Redis发布/订阅(pub/sub)功能实现,订阅过期channel的客户端连接是tcp长连接,如果Redis-server触发Failover(主从节点切换)后,Redis订阅连接不会自动重新建立到正确节点的连接。
3. 客户端使用
1.【推荐】避免多个应用使用一个Redis实例
不要将不相关的业务数据都放到一个Redis实例中,建议新业务申请新的单独实例。因为Redis为单线程处理,独立存储会减少不同业务相互操作的影响,提高请求响应速度;同时也避免单个实例内存数据量膨胀过大,在出现异常情况时可以更快恢复服务!
正例:不相干的业务拆分,公共数据做服务化。
2.【推荐】
使用带有连接池的数据库,可以有效控制连接,同时提高效率,标准使用方式:
Jedis jedis = null
try {
jedis = jedisPool.getResource();
//具体执行命令
} catch (Exception e) {
LoggUtil.ERROR.error("jedis error.",e)
} finally {
if(jedis!=null) {
jedis.close();
}
}
注意:
jedispool中,连接资源使用后,需要调用close()方法,归还资源到连接池。
对于JedisCluster的使用需要注意以下几点:
- JedisCluster包含了到所有节点的连接池(JedisPool)
- JedisCluster每次操作完成后,不需要管理连接池的借还,它在内部已经完成。
-
JedisCluster不要执行close()操作,它会将所有JedisPool执行destroy操作,引发性能问题
。
3.【建议】
高并发下建议客户端添加熔断功能(例如netflix hystrix)
4.【推荐】
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
5.【建议】
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。