文|曹佳俊

网易智慧企业资深服务端开发工程师

背    景

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服务器相干的开发工作。