背景

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模型,对于proxy场景尤其如此,上述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功能一致的分片/双写等特性

作者:网易云信
链接:https://juejin.im/post/5e9fbd3c51882573c508eb80
来源:掘金

服务器如何识别同一客户端的各种不同的命令_开源项目|如何开发一个高性能的redis cluster proxy?...相关推荐

  1. Redis集群的方案总结:客户端Sharding/Redis Cluster/Proxy

    转载:redis sentinel设计与实现 转载:分布式一致性算法(一)一致性哈希算法(consistent hashing) 转载:Jedis下的ShardedJedis(分布式)使用方法(一) ...

  2. 最佳 开源 人脸识别算法_开源项目的最佳社会结构是什么?

    最佳 开源 人脸识别算法 代码审查是一种实践,可促进开源项目中的快速协作,知识共享和最高质量. 代码审查的社会结构是项目的定义特征. 开源项目的最佳社会结构是什么? 在本文中,我们通过分析三个常见模型 ...

  3. 服务器关闭重启后客户端socket能自动连接吗_用Python 撸一个 Web 服务器

    从一个 Hello World 程序说起 要编写 Web 服务器,需要用到一个 Python 内置库 socket.Socket 是一个比较抽象的概念,中文叫套接字,它代表一个网络连接.两台计算机之间 ...

  4. 求生之路2服务器模式修改参数,求生之路2参数修改控制台命令_求生之路2参数修改控制台命令方法_牛游戏网...

    求生之路2是一款著名的僵尸恐怖题材的动作生存类游戏,游戏注重策略性和队友之间的默契配合团队精神.那么大家知道怎么对游戏参数进行控制台修改命令吗?不知道没关系,下面小编就为大家带来了其相关介绍,一起来看 ...

  5. 谈谈如何使用Netty开发实现高性能的RPC服务器

    RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络,从远程计算机程序上请求服务,而不必了解底层网络技术的协议.说的再直白一点,就是客户端在不必知道 ...

  6. 类似问卷星的开源项目Tduck填鸭部署到Linux服务器

    dddd 1.去gitee上把前后端代码下载到本地. 后端源码:https://gitee.com/TDuckApp/tduck-platform 前端源码:https://gitee.com/TDu ...

  7. OCR识别开源项目对比

    OCR技术路线图总体上可分为五步: 图像预处理.切割字符.识别字符.恢复版面.后处理文字. OCR识别常见的八大开源工具-51CTO.COM https://www.toutiao.com/artic ...

  8. GitHub 6大热门实时人脸识别开源项目!哪个最适合初级开发者?

    实时人脸识别系统在计算机视觉领域仍然是一个非常热门的话题,许多公司已经开发了自己的解决方案来尝试进入不断增长的市场. 与传统的识别方法相比,实时人脸识别系统的优势在于在连续帧中使用同一个人的多个实例. ...

  9. 带有服务器回调的通用客户端——服务器 DotNet远程处理

    目录 介绍 DotNet 远程处理框架 约束.限制 客户端--服务器定义 接口 服务器通知参数 服务器异常 服务器实现 客户端实现 如何测试示例 本文中的项目 结论 下载演示 - 194 KB 下载源 ...

最新文章

  1. 与英特尔抢市场,英伟达的数据中心业务能增长到多大?
  2. 对 Kafka 和 Pulsar 进行性能测试后,拉卡拉将消息平台统一换成了 Pulsar
  3. 我诞生了!祝贺我吧。
  4. mtk android lcm调试
  5. java正则表达式 s报错_Java基础--正则表达式的规则
  6. jupyter ipython display_Tensorflow从jupyter / Ipython运行动画
  7. P6624-[省选联考2020A卷]作业题【矩阵树定理,欧拉反演】
  8. C++ :stringstream介绍,使用方法与例子(转)
  9. linux cron网址,cron 计划任务问题,我的cron要求是点击网站一个网址
  10. mac java jni_Mac OS上编译JNI的动态库
  11. c语言如何框出视频或图像中的车辆,视频图像中的车辆检测跟踪和分类
  12. umijs 出错 TypeError: rawData.some is not a function
  13. python蓝牙模块教程_通过蓝牙将HC05蓝牙模块与python连接时出错
  14. 为什么要创业,有人盆满钵满、有人别无选择,区别在创业思维
  15. 毕业设计:基于java的小区物业信息管理系统的设计与实现(1)
  16. 【0049】报错:fe_sendauth: invalid authentication request from server: AUTH_REQ_SASL_CONT without
  17. “百度杯”CTF比赛 九月场Upload 之菜刀的使用
  18. 公司招了个五年经验的测试员,见识到了真正的测试天花板
  19. 本周白银价格走势仍关注美经济数据
  20. iPhone创富志:日进千金 无限遐想的盈利空间

热门文章

  1. java spliterator,Java 8 之Stream Spliterator
  2. 搭建卷积神经网络怎么确定参数_汽车冲压模具中拉延件各参数要怎么确定?本文教你确定方法!...
  3. php 检测密码,php 判断密码是否简单
  4. 5动态显示图片_单片机入门 数码管的静态显示和动态显示 壁纸
  5. matlab角点坐标获取,MatLab角点检测(harris经典程序) | 学步园
  6. 计算机程序设计 大作业 vb,成人《VB程序设计》标准化大作业(答案)1
  7. java spring源码_spring源码分析-spring中的bean
  8. 基于JAVA+Servlet+JSP+MYSQL的毕业生离校管理系统
  9. (转)从开发小白到音视频专家
  10. linux buffer/cache手动释放