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的做法是将更换主库的从库清空然后从新主库完整同步一份数据再进行命令传播。
整个从库重做流程是这样的:

  1. 主库bgsave自身数据到磁盘
  2. 主库发送rdb文件到从库
  3. 从库flushall后开始加载
  4. 加载完毕开始续传

很明显,在这个过程中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以内,实际也和元素字节数有关)。

注意两者不同:

  1. 原生是原子操作,pipeline是非原子操作。

  2. pipeline可以打包不同的命令,原生做不到。

  3. pipeline需要客户端和服务端同时支持,

  4. 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问题。

3. 客户端使用

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容