阿里云某客户发现本身使用读写分离实例,master的cpu特别高,而读写分离中承担读流量的slave节点却相对空闲。用户CPU打满后,访问到主节点的的线上服务受到了较大影响。git
Redis读写分离实例的原理是:key统一写入到master,而后经过主从复制同步到slave,用户的请求经过proxy作判断,若是是写请求,转发到master;若是是读请求,分散转发到slave,这种架构适合读请求数量远大于写请求数量的业务,读写分离架构示意图以下所示。
图1. 阿里云Redis读写分离版读写命令转发示例github
通过和客户沟通查看后,客户使用了大量的bitfield作读取,首先介绍一下这个命令的用法和场景,bitfield 是针对bitmap数据类型操做的命令,bitmap一般被用来在极小空间消耗下经过位的运算(AND/OR/XOR/NOT)实现对状态的判断,常见的使用场景例如:redis
图2. 一个使用Redis BITMAP设计的答题游戏系统
答题系统设计如:数据库
可见,Redis的bitmap接口能够用很是高的存储效率和计算加速效果。回到bitfiled命令,它的语法以下所示:segmentfault
BITFIELD key [GET type offset] // 获取指定位的值 [SET type offset value] // 设置指定位的值 [INCRBY type offset increment] // 增长指定位的值 [OVERFLOW WRAP|SAT|FAIL] // 控制INCR的界限
从上文可知,bitfield的子命令中,GET命令是读属性,SET/INCRBY命令为写属性,所以Redis将其归类为写属性,从而只能被转发到master实例,以下图所示为bitfield的路由状况。
这就是为何客户使用了读写分离版,而只有master节点cpu使用高,其他slave节点却没有收到这个命令的打散的缘由。后端
• 方案一:改造Redis内核,将bitfield命令属性标记为读属性,可是当其包含SET/INCRBY等写属性的子命令时候,仍旧将其同步到slave等。此方案优势是外部组件(proxy和客户端)不须要作修改,缺点是须要对bitfiled命令作特殊处理,破坏引擎命令统一处理的一致性。架构
• 方案二:增长bitfield_ro命令,相似于georadius_ro命令,用来只支持get选项,从而做为读属性,这样就避免了slave没法读取的问题。此方案优势是方案清晰可靠,缺点是须要proxy和客户端作适配才能使用。性能
通过讨论,最终采起了方案二,由于这个方案更优雅,也更标准化。阿里云
{"bitfield_ro",bitfieldroCommand,-2, "read-only fast @bitmap", 0,NULL,1,1,1,0,0,0},
完成以后,下图是在slave上执行bitfield_ro命令,能够看到被正确执行。url
tair-redis > SLAVEOF 127.0.0.1 6379 OK tair-redis > set k v (error) READONLY You can't write against a read only replica. tair-redis > BITFIELD mykey GET u4 0 (error) READONLY You can't write against a read only replica. tair-redis > BITFIELD_RO mykey GET u4 0 1) (integer) 0
为了保持用户不作代码修改,咱们在proxy上对bitfiled命令作了兼容,即若是用户的bitfield命令只有get选项,proxy会将此命令转换为bitfield_ro分散转发到后端多个节点上,从而实现加速,用户不用作任何改造便可完成加速,以下图所示。
图4. 添加BITFIELD_RO命令后处理BITFIELD逻辑流程
2.4 贡献社区
咱们将本身的修改回馈给了社区,而且被Redis官方接受(https://github.com/antirez/redis/pull/6951)
值得一提的是,阿里云在国内是最大的Redis社区contributer,如在新发布的Redis-6.0rc中,阿里云的贡献排第三,仅次于做者和Redis vendor(Redis Labs)。阿里云仍旧在不断的回馈和贡献社区。
图5. Redis6.0 RC commit数目榜
阿里云Redis经过增长bitfield_ro命令,解决了官方bitfield get命令没法在slave上加速执行的问题。
除过bitfield命令,阿里云Redis也同时对georadius命令作了兼容转换,即在读写分离实例上,若是georadius/georadiusbymember命令没有store/storedist选项,将会被自动判断为读命令转发到slave加速执行。
3.2 思考
咱们思考读写分离版的场景,为何用户须要读写分离呢?为何不是用集群版呢?咱们作一下简单对比,好比设置社区版的服务能力为K,那么表的对好比下(咱们只添加了加强版Tair的主备作对比,集群版能够直接乘以分片数):
方式
Redis社区版集群
Redis社区版读写分离
Redis(Tair加强版)主备
写(key均匀状况)
K*分片数
K
K*3
读(key均匀状况)
K*分片数
K*只读节点数
K*3
写(单key或热key)
K(最坏状况)
K
K*3
读(单key或热key)
K(最坏状况)
K*只读节点数
K*3
表1. Redis社区版(集群/读写分离)和加强版(主备)简单场景对比
可见,其实读写分离版属于对单个key和热key的读能力的扩展的一种方法,比较适合中小用户有大key的状况,它没法解决用户的突发写的瓶颈,好比在这个场景下,若是用户的bitfield命令是写请求(子命令中带有INCRBY和SET),就会遇到没法解决的性能问题。
从表的对比看,这种状况下,用户若是能把key拆散,或者把大key拆成不少小key,就可使用集群版得到良好的线性加速能力。大key带来的问题包含但不只限于:
这也是Tair加强版在阿里集团内各个应用建议的:“避免设计出大key和慢查,能避免90%以上的Redis问题”。
可是在实际使用中,用户仍旧不可避免的遇到热点问题,好比抢购,好比热剧,好比超大型直播间等;尤为是不少热点具有“突发性”的特色,事先并不知晓,冲击随时可达。Redis加强版的性能加强实例具有单key在O(1)操做40~45w ops的服务能力和极强的抗冲击能力,单机主备版就足够应对一场中大型的秒杀活动!同时若是用户没有大key,加强性能集群版可以近乎赋予用户千万甚至几千万OPS的服务能力,这也是Tair做为阿里重器,支持每次平稳渡过双11购物节秒杀的关键,欢迎你们试用!
最后,打一个小广告~若是对KV存储系统,图数据库有兴趣的小伙伴,欢迎加入咱们团队,简历发送至:zongdai at taobao dot com