前文回顾
上一篇文章 基于 redis 的分布式锁实现 写了基于 redis 实现的分布式锁.分布式环境下,不会还使用单点的 redis,做到高可用和容灾,起码也是 redis 主从.redis 的单线程工作,一台物理机只运行一个 redis 实例太过浪费,redis 单机显然是存在单点故障的隐患.内存资源往往受限,纵向不停扩展内存并不是很实际,因此横向可伸缩扩展,需要多台主机协同提供服务,即分布式下多个 Redis 实例协同运行.
在之前的文章 Redis Cluster 深入与实践 介绍过 Redis Cluster 的相关内容,之前特地花时间在 redis 官网看了 redis cluster 的相关文档和实现.本文是那篇文章的续集,因为笔者最近在调研 redis 的主从切换到 redis 集群的方案,将会讲下 redis 集群的几种方案选型和 redis cluster 的实践.
redis 集群的几种实现方式如下:
客户端分片,如 redis 的 Java 客户端 jedis 也是支持的,使用一致性 hash
基于代理的分片,如 codis 和 Twemproxy
路由查询, redis-cluster
下面我们分别介绍下这几种方案.
客户端分片
Redis Sharding 是 Redis Cluster 出来之前,业界普遍使用的多 Redis 实例集群方法.其主要思想是采用哈希算法将 Redis 数据的 key 进行散列,通过 hash 函数,特定的 key 会映射到特定的 Redis 节点上.java redis 客户端驱动 jedis,支持 Redis Sharding 功能,即 ShardedJedis 以及结合缓存池的 ShardedJedisPool.
Sharding
Redis Sentinel 提供了主备模式下 Redis 监控,故障转移功能达到系统的高可用性.在主 Redis 宕机时,备 Redis 接管过来,上升为主 Redis,继续提供服务.主备共同组成一个 Redis 节点,通过自动故障转移,保证了节点的高可用性.
客户端 sharding 技术其优势在于非常简单,服务端的 Redis 实例彼此独立,相互无关联,每个 Redis 实例像单服务器一样运行,非常容易线性扩展,系统的灵活性很强.
客户端 sharding 的劣势也是很明显的.由于 sharding 处理放到客户端,规模进一步扩大时给运维带来挑战.客户端 sharding 不支持动态增删节点.服务端 Redis 实例群拓扑结构有变化时,每个客户端都需要更新调整.连接不能共享,当应用规模增大时,资源浪费制约优化.
基于代理的分片
客户端发送请求到一个代理组件,代理解析客户端的数据,并将请求转发至正确的节点,最后将结果回复给客户端.
该模式的特性如下:
透明接入,业务程序不用关心后端 Redis 实例,切换成本低.
Proxy 的逻辑和存储的逻辑是隔离的.
代理层多了一次转发,性能有所损耗.
简单的结构图如下:
proxy
主流的组件有:Twemproxy 和 Codis.
Twemproxy
Twemproxy 也叫 nutcraker,是 twtter 开源的一个 redis 和 memcache 代理服务器程序.redis 作为一个高效的缓存服务器,非常具有应用价值.但在用户数据量增大时,需要运行多个 redis 实例,此时将迫切需要一种工具统一管理多个 redis 实例,避免在每个客户端管理所有连接带来的不方便和不易维护,Twemproxy 即为此目标而生.
Twemproxy 有以下几个特点:
快
轻量级
维持永久的服务端连接
支持失败节点自动删除;可以设置重新连接该节点的时间,还可以设置连接多少次之后删除该节点
支持设置 HashTag;通过 HashTag 可以自己设定将同一类型的 key 映射到同一个实例上去.
减少与 redis 的直接连接数,保持与 redis 的长连接,可设置代理与后台每个 redis 连接的数目
自带一致性 hash 算法,能够将数据自动分片到后端多个 redis 实例上;支持多种 hash 算法,可以设置后端实例的权重,目前 redis 支持的 hash 算法有:one_at_a_time,md5,crc16,crc32,fnv1_64,fnv1a_64,fnv1_32,fnv1a_32,hsieh,murmur,jenkins.
支持 redis pipelining request,将多个连接请求,组成 reids pipelining 统一向 redis 请求.
支持状态监控;可设置状态监控 ip 和端口,访问 ip 和端口可以得到一个 json 格式的状态信息串;可设置监控信息刷新间隔时间.
TwemProxy 官网介绍了如上的特性.TwemProxy 的使用可以像访问 redis 客户端一样访问 TwemProxy.然而 Twitter 已经很久放弃了更新 TwemProxy.Twemproxy 最大的痛点在于,无法平滑地扩容 / 缩容.Twemproxy 另一个痛点是,运维不友好,甚至没有控制面板.
Codis
Codis 是豌豆荚开源的 redis 集群方案,是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生的 Redis Server 没有显著区别 , 上层应用可以像使用单机的 Redis 一样使用, Codis 底层会处理请求的转发, 不停机的数据迁移等工作, 所有后边的一切事情, 对于前面的客户端来说是透明的, 可以简单的认为后边连接的是一个内存无限大的 Redis 服务.
Codis 当前最新 release 版本为 codis-3.2,codis-server 基于 redis-3.2.8.有一下组件组成:
Codis 架构
Codis Server:基于 redis-3.2.8 分支开发.增加了额外的数据结构,以支持 slot 有关的操作以及数据迁移指令.
Codis Proxy:客户端连接的 Redis 代理服务, 实现了 Redis 协议. 除部分命令不支持以外 (不支持的命令列表),表现的和原生的 Redis 没有区别(就像 Twemproxy).
Codis Dashboard:集群管理工具,支持 codis-proxy,codis-server 的添加,删除,以及据迁移等操作.在集群状态发生改变时,codis-dashboard 维护集群下所有 codis-proxy 的状态的一致性. 对于同一个业务集群而言,同一个时刻 codis-dashboard 只能有 0 个或者 1 个;所有对集群的修改都必须通过 codis-dashboard 完成.
Codis Admin:集群管理的命令行工具. 可用于控制 codis-proxy,codis-dashboard 状态以及访问外部存储.
Codis FE:集群管理界面. 多个集群实例共享可以共享同一个前端展示页面; 通过配置文件管理后端 codis-dashboard 列表,配置文件可自动更新.
Storage:为集群状态提供外部存储. 提供 Namespace 概念,不同集群的会按照不同 product name 进行组织;目前仅提供了 Zookeeper,Etcd,Fs 三种实现,但是提供了抽象的 interface 可自行扩展.
至于具体的安装与使用,见官网 CodisLabs ,不在此涉及.
Codis 的特性:
Codis 支持的命令更加丰富,基本支持 redis 的命令.
迁移成本低,迁移到 codis 没这么麻烦,只要使用的 redis 命令在 codis 支持的范围之内,只要修改一下配置即可接入.
Codis 提供的运维工具更加友好,提供 web 图形界面管理集群.
支持多核心 CPU,twemproxy 只能单核
支持 group 划分,组内可以设置一个主多个从,通过 sentinel 监控 redis 主从,当主 down 了自动将从切换为主
路由查询
Redis Cluster 是一种服务器 Sharding 技术,3.0 版本开始正式提供.Redis Cluster 并没有使用一致性 hash,而是采用 slot(槽) 的概念,一共分成 16384 个槽.将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行.当客户端操作的 key 没有分配到该 node 上时,就像操作单一 Redis 实例一样,当客户端操作的 key 没有分配到该 node 上时,Redis 会返回转向指令,指向正确的 node,这有点儿像浏览器页面的 302 redirect 跳转.
Redis 集群,要保证 16384 个槽对应的 node 都正常工作,如果某个 node 发生故障,那它负责的 slots 也就失效,整个集群将不能工作.为了增加集群的可访问性,官方推荐的方案是将 node 配置成主从结构,即一个 master 主节点,挂 n 个 slave 从节点.这时,如果主节点失效,Redis Cluster 会根据选举算法从 slave 节点中选择一个上升为主节点,整个集群继续对外提供服务.
Redis Cluster
特点:
无中心架构,支持动态扩容,对业务透明
具备 Sentinel 的监控和自动 Failover 能力
客户端不需要连接集群所有节点, 连接集群中任何一个可用节点即可
高性能,客户端直连 redis 服务,免去了 proxy 代理的损耗
缺点是运维也很复杂,数据迁移需要人工干预,只能使用 0 号数据库,不支持批量操作,分布式逻辑和存储模块耦合等.
选型最后确定 redis cluster.主要原因是性能高,去中心化支持扩展.运维方面的数据迁移暂时业内也没有特别成熟的方案解决,redis cluster 是 redis 官方提供,我们期待 redis 官方在后面能够完美支持.
安装
官方推荐集群至少需要六个节点,即三主三从.六个节点的配置文件基本相同,只需要修改端口号.
启动后,可以看到如下的日志.
port 7000
cluster-enabled yes #开启集群模式
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
[82462] 26 Nov 11:56:55.329 * No cluster configuration found, I'm 97a3a64667477371c4479320d683e4c8db5858b1
由于没有 nodes.conf 存在,每个实例启动后都会给自己分配一个 ID.为了在集群的环境中有一个唯一的名字,该 ID 将会被永久使用.每个实例都会保存其他节点使用的 ID,而不是通过 IP 和端口.IP 和端口可能会改变,但是唯一的 node ID 将不会改变直至该 node 的死亡.
我们现在已经启动了六个 redis 实例, 需要通过写一些有意义的配置信息到各个节点来创建集群. redis cluster 的命令行工具 redis-trib,利用 Ruby 程序在实例上执行一些特殊的命令,很容易实现创建新的集群,检查或者 reshard 现有的集群等.
--replicas 1 参数是将每个 master 带上一个 slave.
./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 \
127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
配置 JedisClusterConfig
可以配置密码,cluster 对密码支持不太友好,如果对集群设置密码,那么 requirepass 和 masterauth 都需要设置,否则发生主从切换时,就会遇到授权问题.
@Configuration
public class JedisClusterConfig {
private static Logger logger = LoggerFactory.getLogger(JedisClusterConfig.class);
@Value("${redis.cluster.nodes}")
private String clusterNodes;
@Value("${redis.cluster.timeout}")
private int timeout;
@Value("${redis.cluster.max-redirects}")
private int redirects;
@Autowired
private JedisPoolConfig jedisPoolConfig;
@Bean
public RedisClusterConfiguration getClusterConfiguration() {
Map<String, Object> source = new HashMap();
source.put("spring.redis.cluster.nodes", clusterNodes);
logger.info("clusterNodes: {}", clusterNodes);
source.put("spring.redis.cluster.max-redirects", redirects);
return new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));
}
@Bean
public JedisConnectionFactory getConnectionFactory() {
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(getClusterConfiguration());
jedisConnectionFactory.setTimeout(timeout);
return jedisConnectionFactory;
}
@Bean
public JedisClusterConnection getJedisClusterConnection() {
return (JedisClusterConnection) getConnectionFactory().getConnection();
}
@Bean
public RedisTemplate getRedisTemplate() {
RedisTemplate clusterTemplate = new RedisTemplate();
clusterTemplate.setConnectionFactory(getConnectionFactory());
clusterTemplate.setKeySerializer(new StringRedisSerializer());
clusterTemplate.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
return clusterTemplate;
}
}
配置 redis cluster
主要配置了 redis cluster 的节点,超时时间等.
redis:
cluster:
enabled: true
timeout: 2000
max-redirects: 8
nodes: 127.0.0.1:7000,127.0.0.1:7001
使用 RedisTemplate
用法很简单,注入 RedisTemplate 即可进行操作,RedisTemplate 用法比较丰富,可以自行查阅.
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisConfigTest {
@Autowired
RedisTemplate redisTemplate;
@Test
public void clusterTest() {
redisTemplate.opsForValue().set("foo", "bar");
System.out.println(redisTemplate.opsForValue().get("foo"));
}
}
总结
本文主要讲了 redis 集群的选型,主要有三种:客户端分片,基于代理的分片以及路由查询.对于前两种方式,分别进行简单地介绍,最后选择 redis 官方提供的 redis cluster 方案,并进行了实践.虽然正式版的推出时间不长,目前成功实践的案例也还不多,但是总体来说,redis cluster 的整个设计是比较简单的,大部分操作都可以按照单点的操作流程进行操作.笔者使用的 jedis 客户端支持 JedisCluster 也是比较好,用起来也很方便.其实还有个压测的数据,后面再补上吧.
来源: https://juejin.im/post/5a54a6fbf265da3e3f4c9048