Redis Cluster深刻与实践(续)

前文回顾

上一篇文章基于redis的分布式锁实现写了基于redis实现的分布式锁。分布式环境下,不会还使用单点的redis,作到高可用和容灾,起码也是redis主从。redis的单线程工做,一台物理机只运行一个redis实例太过浪费,redis单机显然是存在单点故障的隐患。内存资源每每受限,纵向不停扩展内存并非很实际,所以横向可伸缩扩展,须要多台主机协同提供服务,即分布式下多个Redis实例协同运行。前端

在以前的文章Redis Cluster深刻与实践介绍过Redis Cluster的相关内容,以前特意花时间在redis官网看了redis cluster的相关文档和实现。本文是那篇文章的续集,由于笔者最近在调研redis的主从切换到redis 集群的方案,将会讲下redis集群的几种方案选型和redis cluster的实践。java

redis集群的几种实现方式以下:node

  • 客户端分片,如redis的Java客户端jedis也是支持的,使用一致性hash
  • 基于代理的分片,如codis和Twemproxy
  • 路由查询, redis-cluster

下面咱们分别介绍下这几种方案。git

客户端分片

Redis Sharding是Redis Cluster出来以前,业界广泛使用的多Redis实例集群方法。其主要思想是采用哈希算法将Redis数据的key进行散列,经过hash函数,特定的key会映射到特定的Redis节点上。java redis客户端驱动jedis,支持Redis Sharding功能,即ShardedJedis以及结合缓存池的ShardedJedisPool。github

Sharding
Sharding

Redis Sentinel提供了主备模式下Redis监控、故障转移功能达到系统的高可用性。在主Redis宕机时,备Redis接管过来,上升为主Redis,继续提供服务。主备共同组成一个Redis节点,经过自动故障转移,保证了节点的高可用性。web

客户端sharding技术其优点在于很是简单,服务端的Redis实例彼此独立,相互无关联,每一个Redis实例像单服务器同样运行,很是容易线性扩展,系统的灵活性很强。redis

客户端sharding的劣势也是很明显的。因为sharding处理放到客户端,规模进一步扩大时给运维带来挑战。客户端sharding不支持动态增删节点。服务端Redis实例群拓扑结构有变化时,每一个客户端都须要更新调整。链接不能共享,当应用规模增大时,资源浪费制约优化。算法

基于代理的分片

客户端发送请求到一个代理组件,代理解析客户端的数据,并将请求转发至正确的节点,最后将结果回复给客户端。spring

该模式的特性以下:数据库

  • 透明接入,业务程序不用关心后端Redis实例,切换成本低。
  • Proxy 的逻辑和存储的逻辑是隔离的。
  • 代理层多了一次转发,性能有所损耗。

简单的结构图以下:

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、md五、crc1六、crc3二、fnv1_6四、fnv1a_6四、fnv1_3二、fnv1a_3二、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架构

  • 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节点中选择一个上升为主节点,整个集群继续对外提供服务。

Cluster
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现有的集群等。

./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。

配置JedisClusterConfig

@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

redis:
 cluster:
 enabled: true
 timeout: 2000
 max-redirects: 8
 nodes: 127.0.0.1:7000,127.0.0.1:7001
复制代码

主要配置了redis cluster的节点、超时时间等。

使用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"));
    }
}
复制代码

用法很简单,注入RedisTemplate便可进行操做,RedisTemplate用法比较丰富,能够自行查阅。

总结

本文主要讲了redis集群的选型,主要有三种:客户端分片、基于代理的分片以及路由查询。对于前两种方式,分别进行简单地介绍,最后选择redis官方提供的redis cluster方案,并进行了实践。虽然正式版的推出时间不长,目前成功实践的案例也还很少,可是整体来讲,redis cluster的整个设计是比较简单的,大部分操做均可以按照单点的操做流程进行操做。笔者使用的jedis客户端支持JedisCluster也是比较好,用起来也很方便。其实还有个压测的数据,后面再补上吧。

订阅最新文章,欢迎关注个人公众号

微信公众号

参考

  1. Redis集群方案应该怎么作?
  2. cluster-tutorial
  3. twemproxy
  4. CodisLabs
相关文章
相关标签/搜索