上一篇文章基于redis的分布式锁实现写了基于redis实现的分布式锁。分布式环境下,不会还使用单点的redis,作到高可用和容灾,起码也是redis主从。redis的单线程工做,一台物理机只运行一个redis实例太过浪费,redis单机显然是存在单点故障的隐患。内存资源每每受限,纵向不停扩展内存并非很实际,所以横向可伸缩扩展,须要多台主机协同提供服务,即分布式下多个Redis实例协同运行。前端
在以前的文章Redis Cluster深刻与实践介绍过Redis Cluster的相关内容,以前特意花时间在redis官网看了redis cluster的相关文档和实现。本文是那篇文章的续集,由于笔者最近在调研redis的主从切换到redis 集群的方案,将会讲下redis集群的几种方案选型和redis cluster的实践。java
redis集群的几种实现方式以下:node
下面咱们分别介绍下这几种方案。git
Redis Sharding是Redis Cluster出来以前,业界广泛使用的多Redis实例集群方法。其主要思想是采用哈希算法将Redis数据的key进行散列,经过hash函数,特定的key会映射到特定的Redis节点上。java redis客户端驱动jedis,支持Redis Sharding功能,即ShardedJedis以及结合缓存池的ShardedJedisPool。github
Redis Sentinel提供了主备模式下Redis监控、故障转移功能达到系统的高可用性。在主Redis宕机时,备Redis接管过来,上升为主Redis,继续提供服务。主备共同组成一个Redis节点,经过自动故障转移,保证了节点的高可用性。web
客户端sharding技术其优点在于很是简单,服务端的Redis实例彼此独立,相互无关联,每一个Redis实例像单服务器同样运行,很是容易线性扩展,系统的灵活性很强。redis
客户端sharding的劣势也是很明显的。因为sharding处理放到客户端,规模进一步扩大时给运维带来挑战。客户端sharding不支持动态增删节点。服务端Redis实例群拓扑结构有变化时,每一个客户端都须要更新调整。链接不能共享,当应用规模增大时,资源浪费制约优化。算法
客户端发送请求到一个代理组件,代理解析客户端的数据,并将请求转发至正确的节点,最后将结果回复给客户端。spring
该模式的特性以下:数据库
简单的结构图以下:
主流的组件有:Twemproxy和Codis。
Twemproxy也叫nutcraker,是twtter开源的一个redis和memcache代理服务器程序。redis做为一个高效的缓存服务器,很是具备应用价值。但在用户数据量增大时,须要运行多个redis实例,此时将迫切须要一种工具统一管理多个redis实例,避免在每一个客户端管理全部链接带来的不方便和不易维护,Twemproxy即为此目标而生。
Twemproxy有如下几个特色:
TwemProxy 官网介绍了如上的特性。TwemProxy的使用能够像访问redis客户端同样访问TwemProxy。然而Twitter已经好久放弃了更新TwemProxy。Twemproxy最大的痛点在于,没法平滑地扩容/缩容。Twemproxy另外一个痛点是,运维不友好,甚至没有控制面板。
Codis是豌豆荚开源的redis集群方案,是一个分布式 Redis 解决方案, 对于上层的应用来讲, 链接到 Codis Proxy 和链接原生的 Redis Server 没有显著区别 , 上层应用能够像使用单机的 Redis 同样使用, Codis 底层会处理请求的转发, 不停机的数据迁移等工做, 全部后边的一切事情, 对于前面的客户端来讲是透明的, 能够简单的认为后边链接的是一个内存无限大的 Redis 服务。
Codis当前最新release 版本为 codis-3.2,codis-server 基于 redis-3.2.8。有一下组件组成:
至于具体的安装与使用,见官网CodisLabs,不在此涉及。
Codis的特性:
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节点中选择一个上升为主节点,整个集群继续对外提供服务。
特色:
缺点是运维也很复杂,数据迁移须要人工干预,只能使用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现有的集群等。
./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
复制代码
--replicas 1
参数是将每一个master带上一个slave。
@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;
}
}
复制代码
能够配置密码,cluster对密码支持不太友好,若是对集群设置密码,那么requirepass和masterauth都须要设置,不然发生主从切换时,就会遇到受权问题。
redis:
cluster:
enabled: true
timeout: 2000
max-redirects: 8
nodes: 127.0.0.1:7000,127.0.0.1:7001
复制代码
主要配置了redis cluster的节点、超时时间等。
@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"));
}
}
复制代码
用法很简单,注入RedisTemplate便可进行操做,RedisTemplate用法比较丰富,能够自行查阅。
本文主要讲了redis集群的选型,主要有三种:客户端分片、基于代理的分片以及路由查询。对于前两种方式,分别进行简单地介绍,最后选择redis官方提供的redis cluster方案,并进行了实践。虽然正式版的推出时间不长,目前成功实践的案例也还很少,可是整体来讲,redis cluster的整个设计是比较简单的,大部分操做均可以按照单点的操做流程进行操做。笔者使用的jedis客户端支持JedisCluster也是比较好,用起来也很方便。其实还有个压测的数据,后面再补上吧。