本文针对Redis以及redis的几种集群方案的调研日期在2016年8月,后续如果各框架有更新与本文不符,请自行甄别。
Redis回顾
Redis支持的数据结构
- 字符串(String)
- 哈希(Hash)
- 列表(List)
- 集合(Set)
- 有序集合(Sorted Set)
- 支持针对score作范围查询
- 位数组
- HyperLogLog
- 做基数统计的算法
Redis支持的操作
- 基本操作
- Set get add push pop…
- 发布/订阅
- Pipeline操作
- 事务
- 事务支持不完整。不提供回滚命令。
- ……
Redis适用场景
- KV存储
- 缓存(ttl LRU...)
- Lru是redis达到maxmemory之后,可选的key删除策略。
- 消息中间件
- 分布式锁
- ......
redis集群实现方式
实现基础——分区
- 分区是分割数据到多个Redis实例的处理过程,因此每个实例只保存key的一个子集。
- 通过利用多台计算机内存的和值,允许我们构造更大的数据库。
- 通过多核和多台计算机,允许我们扩展计算能力;通过多台计算机和网络适配器,允许我们扩展网络带宽。
集群的几种实现方式
- 客户端分片
- 基于代理的分片
- 路由查询
客户端分片
- 由客户端决定key写入或者读取的节点。
- 包括jedis在内的一些客户端,实现了客户端分片机制。
原理如下所示:
特性
- 优点
- 简单,性能高。
- 缺点
- 业务逻辑与数据存储逻辑耦合
- 可运维性差
- 多业务各自使用redis,集群资源难以管理
- 不支持动态增删节点
基于代理的分片
- 客户端发送请求到一个代理,代理解析客户端的数据,将请求转发至正确的节点,然后将结果回复给客户端。
- 开源方案
- Twemproxy
- codis
基本原理如下所示:
特性
- 透明接入
- 业务程序不用关心后端Redis实例,切换成本低。
- Proxy 的逻辑和存储的逻辑是隔离的。
- 代理层多了一次转发,性能有所损耗。
路由查询
- 将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行。
- 开源方案
- Redis-cluster
基本原理如下所示:
集群的挑战
- 涉及多个key的操作通常是不被支持的。
- 举例来说,当两个set映射到不同的redis实例上时,你就不能对这两个set执行交集操作。
- 涉及多个key的redis事务不能使用。
- 不能保证集群内的数据均衡。
- 分区的粒度是key,如果某个key的值是巨大的set、list,无法进行拆分。
- 增加或删除容量也比较复杂。
- redis集群需要支持在运行时增加、删除节点的透明数据平衡的能力。
redis集群各种方案原理
Twemproxy
- Proxy-based
- twtter开源,C语言编写,单线程。
- 支持 Redis 或 Memcached 作为后端存储。
Twemproxy高可用部署架构
Twemproxy特性
- 支持失败节点自动删除
- 与redis的长连接,连接复用,连接数可配置
- 自动分片到后端多个redis实例上
- 多种hash算法:能够使用不同的分片策略和散列函数
- 支持一致性hash,但是使用DHT之后,从集群中摘除节点时,不会进行rehash操作
- 可以设置后端实例的权重
- 支持redis pipelining 操作
- 支持状态监控
- 支持select切换数据库
Twemproxy不足
- 性能低:代理层损耗 && 本身效率低下
- Redis功能支持不完善
- 不支持针对多个值的操作,比如取sets的子交并补等(MGET 和 DEL 除外)
- 不支持Redis的事务操作
- 出错提示还不够完善
- 集群功能不够完善
- 仅作为代理层使用
- 本身不提供动态扩容,透明数据迁移等功能
- 失去维护
- 最近一次提交在一年之前。
Twitter内部已经不再使用。
redis-cluster
- redis官网推出,可线性扩展到1000个节点
- 无中心架构
- 一致性哈希思想
- 客户端直连redis服务,免去了proxy代理的损耗
Redis Cluster模型
Redis-cluster原理
- Hash slot。
- key的空间被分到16384个hash slot里;
- 计算key属于哪个slot,CRC16(key) & 16384。
- 集群内的每个redis实例监听两个tcp端口,6379(默认)用于服务客户端查询,16379(默认服务端口 + 10000)用于集群内部通信。
- 节点间状态同步:gossip协议,最终一致性。节点间通信使用轻量的二进制协议,减少带宽占用。
Redis-cluster请求路由方式
- redis-cluster借助客户端实现了混合形式的路由查询
查询路由并非直接从一个redis节点到另外一个redis,而是借助客户端转发到正确的节点。根据客户端的实现方式,可以分为以下两种:- dummy client
- smart client
- 包括Jedis在内的许多redis client,已经实现了对Redis Cluster的支持。
查询路由的流程如下所示:
Redis cluster采用这种架构的考虑:
- 减少redis实现的复杂度
- 降低客户端等待的时间。Smart client可以在客户端缓存 slot 与 redis节点的映射关系,当接收到 MOVED 响应时,会修改缓存中的映射关系。请求时会直接发送到正确的节点上,减少一次交互。
Redis Cluster特性
- 高性能
- 支持动态扩容,对业务透明
- 具备Sentinel的监控和自动Failover能力
Redis Cluster不足
- 官方未提供图形管理工具,运维比较复杂
- 比如数据迁移,纯手动操作,先分配slot,再将slot中对应的key迁移至新的节点。
- 要求客户端必须支持cluster协议
- Redis命令支持不完整
- 对于批量的指令,如mget支持不完整;不支持事务;不支持数据库切换,只能使用0号数据库……
- 集群管理与数据存储耦合
- 比如如果集群管理有bug,需要升级整个redis。
Codis
- 豌豆荚开源的proxy-based的redis集群方案
- 支持透明的扩/缩容
- 运维友好
- GUI监控界面和管理工具
- 同类产品中最全面的redis命令支持
Codis部署拓扑
Codis数据存储
- 数据根据key分布到1024个slot内
- Key的slotid计算方法 crc32(key) % 1024
- 每个 codis-server 负责一部分数据
- 比如后端有3组codis-server,每个server负责的slot范围可以这样设置:
- group0 0-300
- group1 301-600
- group2 601-1023
Codis模块简介
- Codis Server
- 基于 redis-2.8.21 分支开发。增加了额外的数据结构,以支持 slot 有关的操作以及数据迁移指令。
- Codis Proxy
- 客户端连接的 Redis 代理服务, 实现了 Redis 协议。
- 对于同一个业务集群而言,可以同时部署多个 codis-proxy 实例
- 不同 codis-proxy 之间由 codis-dashboard 保证状态同步。
- Codis Dashboard
- 集群管理工具,支持 codis-proxy、codis-server 的添加、删除,以及据迁移等操作。
- 所有对集群的修改都必须通过 codis-dashboard 完成。
- Codis Admin
- 集群管理的命令行工具。
- 可用于控制 codis-proxy、codis-dashboard 状态以及访问外部存储。
- Codis FE
- 集群管理界面。
- Codis HA
- 为集群提供高可用。
- 依赖 codis-dashboard 实例,自动抓取集群各个组件的状态;
- 会根据当前集群状态自动生成主从切换策略,并在需要时通过 codis-dashboard 完成主从切换。
- Storage
- 为集群状态提供外部存储。
- 目前仅提供了 Zookeeper 和 Etcd 两种实现,但是提供了抽象的 interface 可自行扩展。
Codis主从切换
- Codis-HA:自动切换主从的工具。
- 通过RESTful api从 codis-dashboard 中拉取集群状态
- 默认以 5s 为周期
- 该工具会在检测到 master 挂掉的时候主动应用主从切换策略,向codis-dashboard发送主从切换命令。
- 对自动主从切换的支持比较弱
- 主从切换的限制较多,必须在系统其他组件状态健康,且距离上次主从同步数据在一定时间间隔内才可以执行。
- 多slave场景,提升其中的一个slave为master,其他的slave仍然会从旧的master同步数据,需要管理员手动操作。
Codis数据迁移流程
- 前提:Codis单条数据迁移是原子操作
- 单条数据迁移过程:
- 随机选取指定 slot 中的一个 <K,V>
- 将这个<K,V>传输给另外一个 codis-server
- 传输成功后,把本地的这个 <K,V>删除
迁移过程如下所示:
迁移过程中,Codis-dashboard与proxy通过zk通信,路由表信息全部存放在zk,保证所有proxy的视图一致。
Codis如何保证数据迁移过程的正确及透明?
- codis-config 在实际修改slot状态之前,会确保所有的 proxy 收到这个迁移请求。
- 迁移过程中, 如果客户端请求 slot 的 key 数据,proxy 会将请求转发到group2上。
- proxy会先在group1上强行执行一次 MIGRATE key 将这个键值提前迁移过来,
- 然后再到group2上正常读取
Codis VS Redis
对比参数 | Codis | Redis-cluster |
---|---|---|
Redis版本 | 基于2.8分支开发 | >= 3.0 |
部署 | 较复杂。 | 简单 |
运维 | Dashboard,运维方便。 | 运维人员手动通过命令操作。 |
监控 | 可在Dashboard里监控当前redis-server节点情况,较为便捷。 | 不提供监控功能。 |
组织架构 | Proxy-Based, 类中心化架构,集群管理层与存储层解耦。 | P2P模型,gossip协议负责集群内部通信。去中心化 |
伸缩性 | 支持动态伸缩。 | 支持动态伸缩 |
主节点失效处理 | 自动选主。 | 自动选主。 |
数据迁移 | 简单。支持透明迁移。 | 需要运维人员手动操作。支持透明迁移。 |
升级 | 基于redis 2.8分支开发,后续升级不能保证;Redis-server必须是此版本的codis,无法使用新版本redis的增强特性。 | Redis官方推出,后续升级可保证。 |
可靠性 | 经过线上服务验证,可靠性较高。 | 新推出,坑会比较多。遇到bug之后需要等官网升级。 |
- 理论上,redis-cluster的性能更高,单次请求的延时低。另外,经过实测,两种架构后端单台redis-server的条件下,TPS基本没有差别。
- Codis的高可用依赖jodis,或者使用LVS进行高可用部署。
我们公司选择Codis的原因
- Redis-cluster没有成熟的应用案例
- Codis支持的命令更加丰富
- 迁移至redis cluster需要修改现有实现;而迁移到codis没这么麻烦,只要使用的redis命令在codis支持的范围之内,只要修改一下配置即可接入。
- Codis提供的运维工具更加友好
Codis简单压测
单台Codis Server压测结果
三台Codis-Server的集群压测结果
压测结论
- 针对codis集群压测过程中,后端codis server平均CPU占用约为70%~80%。
- 单台codis的TPS在6w~7w左右,集群的TPS在17w左右,可以达到近乎线性扩展的能力
- 测试期间后台codis-server的负载没有跑满,仍然具有继续压测的潜力。
- 测试期间proxy机器负载低,可以支撑更多后端codis server实例。
使用codis注意事项
- 不支持的命令
- 防止key冲突
- 建议现有业务接入codis时,加入项目前缀以作区分。
- 使用spring data redis操作codis时,只能使用RedisTemplate系列接口,Cache系列接口不可用
- Cache系列的接口使用了redis的事务指令,事务在Codis以及Redis Cluster中均未提供支持。
- 避免key value巨大的数据
- 吞吐量提升不明显
- 可能造成各codis实例资源占用不均衡