开源|如何开发一个高性能的redis cluster proxy?
文|曹佳俊
网易智慧企业资深服务端开发工程师
背 景
redis cluster简介
Redis cluster是redis官方提供集群方案,设计上采用非中心化的架构,节点之间通过gossip协议交换互相的状态,redis cluster使用数据分片的方式来构建集群,集群内置了16384个哈希槽,每个key都属于这16384这个哈希槽中的一个,通过crc16算法计算哈希值,再取余可得每个key归属的哈希槽;redis cluster支持动态加入新节点,动态迁移slot,自动的故障转移等。
Redis cluster的架构要求客户端需要直接与redis集群中的每个节点建立连接,并且当出现新增节点加入、节点宕机failover、slot迁移等事件时,客户端需要能够通过redis cluster协议去更新本地的slot映射表,并且能处理ASK/MOVE语义,因此,我们一般称实现了redis cluster协议的客户端为smart redis client。
Redis cluster最多可以构建超过100个主节点的集群(超过之后gossip协议开销过大,且可能引起集群不稳定),按照单节点10G容量(单实例内存过大可能导致性能下降),单集群最多可以支撑1T左右的容量。
问 题
Redis cluster有很多优点(比如可以构建大容量集群,性能好,扩缩容灵活),但是当一些项目工程期望从redis迁移到redis cluster时,客户端却面临着大量的改造工作,与此同时带来的是需要大量的测试工作以及引入的新风险,这对于一些稳定运行的线上工程代价无疑是巨大的。
需 求
为了更方便的将业务迁移到redis cluster,最期望的是客户端SDK的API完全兼容redis/redis-cluster,spring提供的RedisTemplate是一个很好实现,但是对于没有使用SpringRedisTemplate的项目,很多客户端实现的redis和redis-cluster访问API是不一致的(比如Java中流行的Jedis),这无形中提高了迁移工作的工作量和复杂性,此时redis cluster proxy是不错的选择,有了proxy,就可以像操作单实例redis一样操作redis cluster,客户端程序就不需要做任何的修改。
当然,增加一层proxy,必然会导致性能有一定程度的下降,但是proxy作为无状态的服务,理论上可以水平扩展,并且由于proxy层的存在减少了后端redis server的连接数,在某些极限场景下甚至能提高redis集群整体的吞吐量。此外,基于proxy,我们还可以做很多额外的事情:
- 比如可以在proxy层做分片逻辑,这样当单集群的redis cluster不满足需求(内存/QPS)时,就可以通过proxy层实现透明的同时访问多个redis cluster集群。
- 再比如可以在proxy层做双写逻辑,这样在迁移或者拆分缓存类型的redis时,就不需要使用redis-migrate-tool之类的工具进行全量迁移,而只需要按需双写,即可完成迁移。
- 此外因为proxy实现了redis协议,因此可以在proxy层利用其它存储介质实现redis相关命令,从而可以模拟成redis对外服务。一个典型的场景就是冷热分离存储。
功 能
介于上述各种原因和需求,我们基于netty开发了camellia-redis-proxy这样一个中间件,支持如下特性:
- 支持设置密码
- 支持代理到普通redis,也支持代理到redis cluster
- 支持配置自定义的分片逻辑(可以代理到多个redis/redis-cluster集群)
- 支持配置自定义的双写逻辑(服务器会识别命令的读写属性,配置双写之后写命令会同时发往多个后端)
- 支持外部插件,从而可以复用协议解析模块(当前包括camellia-redis-proxy-hbase插件,实现了zset命令的冷热分离存储)
- 支持在线变更配置(需引入camellia-dashboard)
- 支持多个业务逻辑共享一套proxy集群,如:A业务配置转发规则1,B业务配置转发规则2(需要在建立redis连接时通过client命令设置业务类型)
- 对外提供了一个spring-boot-starter,3行代码即可快速搭建一个proxy集群
如何提升性能?
客户端向camellia-redis-proxy发起一条请求,到收到请求回包的过程中,依次经历了如下过程:
- 上行协议解析(IO读写)
- 协议转发规则匹配(内存计算)
- 请求转发(IO读写)
- 后端redis回包解包(IO读写)
- 后端redis回包下发到客户端(IO读写)
可以看到作为一个proxy,大量的工作是在进行网络IO的操作,为了提升proxy的性能,做了以下工作:
多线程
我们知道redis本身是单线程的,但是作为一个proxy,完全可以使用多线程来充分利用多核CPU的性能,但是过多的线程引起不必要的上下文切换又会引起性能的下降。camellia-redis-proxy使用了netty的多线程reactor模型来确保服务器的处理性能,默认会开启cpu核心数的work线程。 此外,如果服务器支持网卡多队列,开启它,能避免CPU不同核心之间的load不均衡;如果不支持,那么将业务进程绑核到非CPU0的其他核心,从而让CPU0专心处理网卡中断而不被业务进程过多的影响。
异步非阻塞
异步非阻塞的IO模型一般情况下都是优于同步阻塞的IO模型,上述5个过程中,除了协议转发规则匹配这样的内存计算,整个转发流程都是异步非阻塞的,确保不会因为个别流程的阻塞影响整个服务。
流水线
我们知道redis协议支持流水线(pipeline),pipeline的使用,可以有效减少网络开销。camellia-redis-proxy也充分利用了这样的特性,主要包括两方面:
- 上行协议解析时尽可能的一次性解析多个命令,从而进行规则转发时可以批量进行
- 往后端redis节点进行转发时尽可能的批量提交,这里除了对来自同一个客户端连接的命令进行聚合,还可以对来自不同客户端连接,但转发目标redis相同时,也可以进行命令聚合
当然,所有这些批量和聚合的操作都需要保证请求和响应的一一对应。
TCP分包和大包处理
不管是上行协议解析,还是来自后端redis的回包,特别是大包的场景,在碰到TCP分包时,利用合适的checkpoint的机制可以有效减少重复解包的次数,提升性能。
异常处理和异常日志合并
如果没有有效的处理各种异常,在异常发生时也会导致服务器性能迅速下降。想象一个场景,我们配置了90%的流量转发给A集群,10%的流量转发到B集群,如果B集群发生了宕机,我们期望的是来自客户端的90%的请求正常执行,10%的请求失败,但是实际上却可能远远超过10%的请求都失败了,原因是多方面的:
- 后端操作系统层面的突然宕机proxy层可能无法立即感知(没有收到TCP fin包),导致大量请求在等待回包,虽然proxy层没有阻塞,但是客户端表现为请求超时
- proxy在尝试转发请求到B集群时,针对B集群的重新连接请求可能拖慢整个流程
- 宕机导致的大量异常日志可能会引起服务器性能下降(这是一个容易忽视的地方)
- pipeline提交上来的请求,99个请求指向A集群,1个请求指向B集群,但是由于B集群的不可用,导致指向B集群的请求迟迟不回包或者异常响应过慢,客户端的最终表现是100个请求全部失败了
camellia-redis-proxy在处理上述问题时,采取了如下策略:
- 设置对异常后端节点的快速失败降级策略,避免拖慢整个服务
- 异常日志统一管理,合并输出,在不丢失异常信息的情况下,减少异常日志对服务器性能的影响
- 增加对后端redis的定时探活探测,避免宕机无法立即感知导致业务长时间异常
部署架构
proxy作为无状态的服务,可以做到水平扩展,为了服务的高可用,也至少要部署两个以上的proxy节点,对于客户端来说,想要像使用单节点redis一样访问proxy,可以在proxy层之前设置一个LVS代理服务,此时,部署架构图如下:
当然,还有另外一个方案,可以将proxy节点注册到zk/Eureka/Consul等注册中心,客户端通过拉取和监听proxy的列表,然后再向访问单节点redis一样访问每个proxy即可。以Jedis为例,仅需将JedisPool替换为封装了注册发现逻辑的RedisProxyJedisPool,即可像访问普通redis一样使用proxy了,此时,部署架构图如下:
应用场景
- 需要从redis迁移到redis-cluster,但是客户端代码不方便修改
- 客户端直连redis-cluster,导致cluster服务器连接过多,导致服务器性能下降
- 单个redis/redis-cluster集群容量/QPS不满足业务需求,使用camellia-redis-proxy的分片功能
- 缓存类redis/redis-cluster集群拆分迁移,使用camellia-redis-proxy的双写功能
- 使用双写功能进行redis/redis-cluster的灾备
- 混合使用分片和双写功能的一些业务场景
- 基于camellia-redis-proxy的插件功能,开发自定义插件
结 语
Redis cluster作为官方推荐的集群方案,越来越多的项目已经或正在迁移到redis cluster,camellia-redis-proxy正是在这样的背景下诞生的;特别的,如果你是一个Java开发者,camellia还提供了CamelliaRedisTemplate这样的方案,CamelliaRedisTemplate拥有和普通Jedis一致的API,提供了mget/mset/pipeline等原生JedisCluster不支持的特性,且提供了和camellia-redis-proxy功能一致的分片/双写等特性。
为了回馈社区,camellia已经正式开源了,想详细了解camellia项目的请点击【阅读原文】访问github,同时附上地址:
https://github.com/netease-im...
如果你有什么好的想法或者提案,或者有什么问题,欢迎提交issue与我们交流!
关于作者
曹佳俊。网易智慧企业资深服务端开发工程师。中科院研究生毕业后加入网易,一直在网易云信负责IM服务器相关的开发工作。
作者:网易云信
链接:https://segmentfault.com/a/1190000023210717
来源:SegmentFault 思否
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
开源|如何开发一个高性能的redis cluster proxy?的更多相关文章
- 如何利用开源思想开发一个SEO友好型网
如果你有一个网站需要去做SEO优化的时候,不要期望你的努力能立即得到回报.耐心等待并更正内容营销策略,最终会发现你的网站很受用户欢迎.下面就教你如何利用开源思维开发一个SEO友好型网站! 首先,你应该 ...
- 高级开发不得不懂的Redis Cluster数据分片机制
Redis 集群简介 Redis Cluster 是 Redis 的分布式解决方案,在 3.0 版本正式推出,有效地解决了 Redis 分布式方面的需求. Redis Cluster 一般由多个节点组 ...
- 高性能kv存储之Redis、Redis Cluster、Pika:如何应对4000亿的日访问量?
一.背景介绍 随着360公司业务发展,业务使用kv存储的需求越来越大.为了应对kv存储需求爆发式的增长和多使用场景的需求,360web平台部致力于打造一个全方位,适用于多场景需求的kv解决方案.目前, ...
- 谈谈如何使用Netty开发实现高性能的RPC服务器
RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络,从远程计算机程序上请求服务,而不必了解底层网络技术的协议.说的再直白一点,就是客户端在不必知道 ...
- Netty开发实现高性能的RPC服务器
Netty开发实现高性能的RPC服务器 RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络,从远程计算机程序上请求服务,而不必了解底层网络技术的协 ...
- Redis Cluster数据分片机制
复制粘贴自: https://www.e-learn.cn/content/redis/2344485, 点击链接访问原文 仅供个人学习参考之用, 如有侵权, 请联系删除! 高级开发不得不懂的Redi ...
- 部署Redis Cluster 6.0 集群并开启密码认证 和 Redis-cluster-proxy负载
部署Redis Cluster集群并开启密码认证 如果只想简单的搭建Redis Cluster,不需要设置密码和公网访问,可以参考官方文档. 节点介绍 Cluster模式推荐最少有6个节点,本次实验搭 ...
- Redis Cluster部署、管理和测试
背景: Redis 3.0之后支持了Cluster,大大增强了Redis水平扩展的能力.Redis Cluster是Redis官方的集群实现方案,在此之前已经有第三方Redis集群解决方案,如Twen ...
- Redis for OPS 06:Redis Cluster 集群
写在前面的话 前面的主从,HA 都只是解决我们数据安全性方面的问题,并没有解决我们业务瓶颈的问题.当业务并发到达一定瓶颈的时候,我们需要对服务进行横向扩展,而不是纵向扩展.这就需要引入另外一个东西,R ...
随机推荐
- 其他函数-web_concurrent
web_concurrent_start函数是并发组开始的标记.组中所有的函数是并发执行的,并发组的结束符为web_concurrent_end 函数. 在并发组中,可以包含的函数有: web_url ...
- IntelliJ IDEA中项目import与open的区别
场景: 从原来公司离职来到新的公司,接手公司项目,先将项目从git或svn项目版本管理上clone下来,如果项目原先是用Eclipse开发的,而你更习惯于使用IntelliJ IDEA,下面是针对使用 ...
- EPLAN文本输入怎么转行,路径功能文本换行方法
EPLAN文本输入怎么转行,路径功能文本换行方法 在输入文本时,按CTRL+ENTER可以将字体换行 Ctrl+回车键
- JAVA设计模式 1 设计模式介绍、单例模式的理解与使用
数据结构我们已经学了一部分了.是该了解了解设计模式了.习惯了CRUD的你,也该了解了解这一门神器.我为啥要说是神器呢? 因为在大厂的面试环节.以及很多的比如 Springboot Mybatis 等开 ...
- JS新界面关闭原界面刷新的多种形式
------------恢复内容开始------------ 1.新界面为当前界面弹出的形式: 第一步:首先原界面的原生JS事件需要放到 (function () { ...... })(); 中 ...
- Javascript数组迭代精髓,拿去花
数组迭代 数组迭代是处理各数组的利器,编写代码时常常会用到,为我们提供了大大的便利.如果还不知道,真的别告诉别人你知道js哈哈. 以下迭代方法均不会改变原数组,带*为必选对象. 1.arr.forEa ...
- 【解读】TCP粘包拆包
一.TCP粘包.拆包图解 假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到字节数是不确定的,故可能存在以下四种情况: 1)服务端分两次读取到了两个独立的数据包,分别是D1和D2, ...
- 微信小程序-超出部分显示省略号(单行与多行)
/*单行*/ .work_font{ display:block /*这里设置inline-block或者block:根据使用情况来定(行内元素需要加这个,块级元素和行内块级可以不用)*/ white ...
- webpack的入门实践,看这篇就够了
webpack的入门实践 我会将所有的读者概括为初学者,即使你可能有基础,学习本节之前我希望你具有一定的JavaScript和node基础 文中的 ... ...代表省略掉部分代码,和上面的代码相同 ...
- electron打造桌面应用
Electron 将网页打包成桌面应用(web页面生成exe) http://m.blog.csdn.net/u014563989/article/details/75045052 Electron学 ...