【Redis】Codis

Posted by 西维蜀黍 on 2020-05-12, Last Modified on 2022-12-10

Codis

Codis 是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生的 Redis Server 没有显著区别 (不支持的命令列表), 上层应用可以像使用单机的 Redis 一样使用, Codis 内部(其实是Codis Proxy)会处理请求的转发(转发到Codis Server), 不停机的数据迁移等工作, 所有后边的一切事情, 对于前面的客户端来说是透明的, 可以简单的认为后边连接的是一个内存无限大的 Redis 服务。

Architecture

Components

Codis 3.x 由以下组件组成:

  • Codis Server(Codis-server):基于 redis-3.2.8 分支开发。增加了额外的数据结构,以支持 slot 有关的操作以及数据迁移指令。具体的修改可以参考文档 redis 的修改
  • Codis Proxy(Codis-proxy):客户端连接的 Redis 代理服务, 实现了 Redis 协议。 除部分命令不支持以外(不支持的命令列表),表现的和原生的 Redis 没有区别(就像 Twemproxy)。
    • 对于同一个业务集群而言,可以同时部署多个 Codis-proxy 实例;
    • 不同 Codis-proxy 之间由 Codis-dashboard 保证状态同步。
  • Codis Dashboard(Codis-config):Codis 集群的管理工具,支持 Codis-proxy、Codis-server 的添加、删除,以及据迁移等操作。在集群状态发生改变时,Codis-dashboard 维护集群下所有 Codis-proxy 的状态的一致性。
    • 对于同一个业务集群而言,同一个时刻 Codis-dashboard 只能有 0个或者1个;
    • 所有对集群的修改都必须通过 Codis-dashboard 完成。
    • Codis-config 本身还自带了一个 http server,会启动一个 dashboard,用户可以直接在浏览器上观察 Codis 集群的运行状态;
  • Codis Admin:集群管理的命令行工具。
    • 可用于控制 Codis-proxy、Codis-dashboard 状态以及访问外部存储。
  • Codis FE:集群管理界面。
    • 多个集群实例共享可以共享同一个前端展示页面;
    • 通过配置文件管理后端 Codis-dashboard 列表,配置文件可自动更新。
  • Storage:为集群状态提供外部存储。
    • 提供 Namespace 概念,不同集群的会按照不同 product name 进行组织;
    • 目前仅提供了 Zookeeper、Etcd、Fs 三种实现,但是提供了抽象的 interface 可自行扩展;
    • Codis-config 发起的命令都会通过 ZooKeeper 同步到各个存活的 Codis-proxy(Codis 依赖 ZooKeeper 来存放数据路由表和 Codis-proxy 节点的元信息)。

High Availability and Horizontal Scalability

因为 Codis-proxy 是无状态的,可以比较容易的搭多个实例,达到高可用性和横向扩展。

对 Java 用户来说,可以使用基于 Jedis 的实现 Jodis ,来实现 proxy 层的 HA(如下图所示,本质上其实是访问Coordinator,如ZooKeeper):

  • 它会通过监控 zookeeper 上的注册信息来实时获得当前可用的 proxy 列表,既可以保证高可用性;
  • 也可以通过轮流请求所有的proxy实现负载均衡。

Codis 的优点

  • 轻松地实现高可用性和横向扩展
  • 不停机的数据迁移(当增加 Redis server的数量时)

相对于twemproxy的优劣?

codis和twemproxy最大的区别有两个:

  • 一个是codis支持动态水平扩展,对client完全透明不影响服务的情况下可以完成增减redis实例的操作;
  • 一个是codis是用go语言写的并支持多线程而twemproxy用C并只用单线程。 后者又意味着:codis在多核机器上的性能会好于twemproxy;codis的最坏响应时间可能会因为GC的STW而变大,不过go1.5发布后会显著降低STW的时间;如果只用一个CPU的话go语言的性能不如C,因此在一些短连接而非长连接的场景中,整个系统的瓶颈可能变成accept新tcp连接的速度,这时codis的性能可能会差于twemproxy。

相对于redis cluster的优劣?

redis cluster基于smart client和无中心的设计,client必须按key的哈希将请求直接发送到对应的节点。这意味着:使用官方cluster必须要等对应语言的redis driver对cluster支持的开发和不断成熟;client不能直接像单机一样使用pipeline来提高效率,想同时执行多个请求来提速必须在client端自行实现异步逻辑。 而codis因其有中心节点、基于proxy的设计,对client来说可以像对单机redis一样去操作proxy(除了一些命令不支持),还可以继续使用pipeline并且如果后台redis有多个的话速度会显著快于单redis的pipeline。同时codis使用zookeeper来作为辅助,这意味着单纯对于redis集群来说需要额外的机器搭zk,不过对于很多已经在其他服务上用了zk的公司来说这不是问题:)

Pre-sharding

Codis 采用 Pre-sharding 的技术来实现数据的分片(sharding),默认分成 1024 个槽(slots) (0-1023),对于每个key来说,通过以下公式确定所属的 Slot Id:

SlotId = crc32(key) % 1024

每一个 slot 都会有一个且必须有一个特定的 server group id 来表示这个 slot 的数据由哪个 Codis-group(里面有且只有一个Codis-server master,可能还有一个或多个Codis-server slave) 来提供。数据的迁移也是以slot为单位的。

  • slot:分片信息,在redis当中仅仅表示一个数字,代表分片索引。每个分片会归属于具体的redis实例
  • group:主要是虚拟结点,由多台redis机器组成,形成一主多从的模式,是逻辑意义上的结点

Proxy 请求处理细节

如下图所示:该部分主要涉及到proxy的处理细节,涉及到如何接受一个请求到响应回包的过程:

proxy接收客户端的连接之后,新建一个session,同时启动session中reader与writer两个协程,reader主要用于接收客户端请求数据并解析,对多key的场景下进行命令的拆分,然后将请求通过router进行分发到具体的redis实例,并将redis处理的数据结果写到通道到中,writer从通道中接收对应的结果,将写回给客户端。

Router层主要是通过crc命令得到key对应的路由信息,从源码可以看到hashtag的特性,codis其实也是支持的。

高可用&容灾&故障转移

哨兵集群如何保证redis高可用

Sentinel(哨岗,哨兵)是Redis的高可用解决方案:由一个或多个Sentinel实例组成的Sentinel系统,可以监视任意多个主服务器,以及这些主服务器属下的所有的从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由主服务器代替已下线的主服务器继续处理命令请求。

通常来说要达到服务的高可用的效果需要做2个事情:故障探测与故障转移(即选主并做主从切换)。

codis的架构本身分成proxy集群+redis集群,redis集群的高可用是由哨兵集群来保证的,那么proxy是如何感知redis主机故障,然后切换新主保证服务高可用的呢?

如上图所示,proxy本身会监听sentinle集群的+switch-master事件,该事件发出,意味着redis集群主机出现问题,sentinel集群开始进行选举并切换主机,proxy监听了sentinel的主从切换事件,收到主从切换事件之后,proxy会做一个动作,就是把所有sentinel上的集群所感知的当前认为的主机拉取出来,选取过半sentinel认为的主机当作目前的集群主机。

Reference