开源|如何开发一个高性能的redis-cluster-proxy

1次阅读

共计 4236 个字符,预计需要花费 11 分钟才能阅读完成。

文|曹佳俊

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

背    景

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

正文完
 0