Redis官方集群搭建&使用

来源:互联网 发布:主机mac地址怎么查 编辑:程序博客网 时间:2024/06/04 22:41

Redis官方集群设计目标

  • 高性能,并且多达1000个节点的线性可扩展性。没有代理,使用异步复制,并且在进行赋值时没有合并操作。
  • 可接受程度的写安全:当客户端与大多数master节点建立连接后,系统努力(使用最优的方式)保持来自客户端的写操作。通常有小窗口,其中确认的写操作可能会丢失。当客户端在一个小的分区中,窗口丢失写操作会更大。
  • 可用性:Redis集群支持网络分区——其中大部分主节点都可访问,并且不可访问的各master节点对应的从至少一个可访问。而且采用副本迁移,有多个从的主会提供一个从给没有从的主。

Redis集群特点

  • Redis集群不为客户端代理重定向服务,需要客户端自己重定向或缓存slot-node映射
  • Redis集群是无中心架构
  • Redis集群中存在Master-Slave结构
  • Redis集群的Re-sharding等管理需要管理员手动触发

几个约束

  • Redis版本需要>=3.0
  • Redis集群至少需要3个Master节点,考虑到基本的HA,至少需要3个Master节点+3个Slave节点
  • 创建集群使用安装Redis官方提供的Ruby实现的工具
  • 执行复杂的多键操作, 像set类型的合集或交集的命令,要求键必须是属于同一个节点
  • 事务只支持同一个节点的操作,不支持分布式事务

Redis集群示意

Redis集群示意图

一些重要的资料

  • Redis集群官方教程(译文)
  • Redis集群官方教程(原文)
  • Redis集群规范(译文)
  • Redis集群规范(原文)

创建Redis集群

在《Redis集群官方教程》中有相当详细的创建集群的方法,这里做一些摘要。

手动方式创建集群

要创建集群,首先需要以集群模式运行的空redis实例。也就说,以普通模式启动的redis是不能作为集群的节点的,需要以集群模式启动的redis实例才能有集群节点的特性、支持集群的指令,成为集群的节点。
下面是最小的redis集群的配置文件:

port 7000cluster-enabled yescluster-config-file nodes.confcluster-node-timeout 5000appendonly yes

开启集群模式只需打开cluster-enabled配置项即可。每一个redis实例都包含一个配置文件,默认是nodes.conf,用于存储此节点的一些配置信息。这个配置文件由redis集群的节点自行创建和更新,不能由人手动地去修改。
一个最小的集群需要最少3个主节点。第一次测试,强烈建议你配置6个节点:3个主节点和3个从节点。
开始测试,步骤如下:先进入新的目录,以redis实例的端口为目录名,创建目录,我们将在这些目录里运行我们的实例。
类似这样:

mkdir cluster-testcd cluster-testmkdir 7000 7001 7002 7003 7004 7005

在7000-7005的每个目录中创建配置文件redis.conf,内容就用上面的最简配置做模板,注意修改端口号,改为跟目录一致的端口。
把你的redis服务器(用GitHub中的不稳定分支的最新的代码编译来)拷贝到cluster-test目录,然后打开6个终端页准备测试。
在每个终端启动一个redis实例,指令类似这样:

cd 7000../redis-server ./redis.conf

在日志中我们可以看到,由于没有nodes.conf文件不存在,每个节点都给自己一个新的ID。

[82462] 26 Nov 11:56:55.329 * No cluster configuration found, I'm 97a3a64667477371c4479320d683e4c8db5858b1

这个ID将一直被此节点使用,作为此节点在整个集群中的唯一标识。节点区分其他节点也是通过此ID来标识,而非IP或端口。IP可以改,端口可以改,但此ID不能改,直到这个节点离开集群。这个ID称之为节点ID(Node ID)。
现在6个实例已经运行起来了,我们需要给节点写一些有意义的配置来创建集群。redis集群的命令工具redis-trib可以让我们创建集群变得非常简单。redis-trib是一个用ruby写的脚本,用于给各节点发指令创建集群、检查集群状态或给集群重新分片等。redis-trib在Redis源码的src目录下,需要gem redis来运行redis-trib。

gem install redis

创建集群只需输入指令:

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

这里用的命令是create,因为我们需要创建一个新的集群。选项”–replicas 1”表示每个主节点需要一个从节点。其他参数就是需要加入这个集群的redis实例的地址。
我们创建的集群有3个主节点和3个从节点。
redis-trib会给你一些配置建议,输入yes表示接受。集群会被配置并彼此连接好,意思是各节点实例被引导彼此通话并最终形成集群。最后,如果一切顺利,会看到类似下面的信息:

[OK] All 16384 slots covered

这表示,16384个哈希槽都被主节点正常服务着。

使用create-cluster脚本创建redis集群

如果你不想像上面那样,单独的手工配置各节点的方式来创建集群,还有一个更简单的系统(当然也没法了解到集群运作的一些细节)。
在utils/create-cluster目录下,有一个名为create-cluster的bash脚本。如果需要启动一个有3个主节点和3个从节点的集群,只需要输入以下指令

1. create-cluster start2. create-cluster create

在步骤2,当redis-trib要你接受集群的布局时,输入”yes”。

现在你可以跟集群交互,第一个节点的起始端口默认是30001。当你完成后,停止集群用如下指令:

1. create-cluster stop.

请查看目录下的README,它有详细的介绍如何使用此脚本。

使用Redis集群

使用命令行方式操作集群请参照《Redis官方文档》,这里主要描述一下在Java代码中如何操作Redis集群。
这里主要介绍两种大家熟悉的工具:Jedis和Spring data redis。

Jedis

Jedis从v2.3.0开始逐步支持Redis官方集群,到目前最新的v2.8.1对集群特性已经支持的相当完善了。考虑到我们一般只使用Redis做简单的缓存,这里只介绍基础应用。
1.添加依赖

<dependency>    <groupId>redis.clients</groupId>    <artifactId>jedis</artifactId>    <version>2.8.0</version>    <type>jar</type>    <scope>compile</scope></dependency>

2.创建Jedis集群客户端

public class RedisCluster {    static JedisCluster redisCluster = null;    public RedisCluster() {        if(redisCluster == null) {            Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();            jedisClusterNodes.add(new HostAndPort("127.0.0.1", 7000));            jedisClusterNodes.add(new HostAndPort("127.0.0.1", 7001));            jedisClusterNodes.add(new HostAndPort("127.0.0.1", 7002));            jedisClusterNodes.add(new HostAndPort("127.0.0.1", 7003));            jedisClusterNodes.add(new HostAndPort("127.0.0.1", 7004));            jedisClusterNodes.add(new HostAndPort("127.0.0.1", 7005));            redisCluster = new JedisCluster(jedisClusterNodes);        }        return;    }    public JedisCluster getRedisCluster() {        return redisCluster;    }}

3.操作Jedis集群

public class Application {    public static void main(String[] args) {        RedisCluster rc = new RedisCluster();        JedisCluster jc = rc.getRedisCluster();        jc.set("foo", "bar");        String value = jc.get("foo");        System.out.printf("\r\nValue is %s", value);    }}

4.说明
事实上,由于JedisCluster实现了JedisCommands接口,因此一般操作和使用单点的Redis服务器并无不同。Redis集群的分片、Slot-Node Cach等实现细节,Jedis客户端已经做了很好的封装,很多情况下使用者并无法感知到集群和单点的区别。
另一方面,对于线程安全的考虑,由于JedisCluster对每一个集群节点的连接都保存了一个JedisPool,而JedisPool是线程安全,因此JedisCluster天然线程安全(注:这一点是我主观猜测,网上没有很多讨论JedisCluster线程安全方面的讨论)。

Spring data redis

Spring data redis项目在最新的开发版本1.7.0 RC1中增加了对Redis集群的支持(注意,该版本还未发布,目前还在开发状态),当前的稳定版本1.6.4不支持集群特性。
1.添加依赖

<dependencies>    <dependency>        <groupId>org.springframework.data</groupId>        <artifactId>spring-data-redis</artifactId>        <version>1.6.4.RELEASE</version>    </dependency></dependencies>

2.创建RedisConnectionFactory
集群配置

spring:  redis:    cluster:      nodes:       - 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

3.通过RedisClusterConnection操作集群使用

@Component@ConfigurationProperties(prefix = "spring.redis.cluster")public class ClusterConfigurationProperties {    /*     * spring.redis.cluster.nodes[0] = 127.0.0.1:7379     * spring.redis.cluster.nodes[1] = 127.0.0.1:7380     * ...     */    List<String> nodes;    /**     * Get initial collection of known cluster nodes in format {@code host:port}.     *     * @return     */    public List<String> getNodes() {        return nodes;    }    public void setNodes(List<String> nodes) {        this.nodes = nodes;    }}@Configurationpublic class AppConfig {    /**     * Type safe representation of application.properties     */    @Autowired    ClusterConfigurationProperties clusterProperties;    public @Bean    RedisConnectionFactory connectionFactory() {        return new JedisConnectionFactory(new RedisClusterConfiguration(clusterProperties.getNodes()));    }}@Servicepublic class UseSdr {    @Autowired    private RedisConnectionFactory connectionFactory;    public void test() {        RedisClusterConnection connection = connectionFactory.getClusterConnection();        connection.set("a1".getBytes(), "111".getBytes());        connection.set("a2".getBytes(), "222".getBytes());        Set<byte[]> ret = connection.keys("a1".getBytes());        for(byte[] each:ret) {            System.out.printf("\r\n"  + new String(each));        }    }}

4.通过RedisTemplate方式操作集群
RedisTemplate提供了更高层次的抽象,提供了ValueOperations、ListOperations、SetOperations等接口

@Component@ConfigurationProperties(prefix = "spring.redis.cluster")public class ClusterConfigurationProperties {    /*     * spring.redis.cluster.nodes[0] = 127.0.0.1:7379     * spring.redis.cluster.nodes[1] = 127.0.0.1:7380     * ...     */    List<String> nodes;    /**     * Get initial collection of known cluster nodes in format {@code host:port}.     *     * @return     */    public List<String> getNodes() {        return nodes;    }    public void setNodes(List<String> nodes) {        this.nodes = nodes;    }}@Configurationpublic class AppConfig {    /**     * Type safe representation of application.properties     */    @Autowired    ClusterConfigurationProperties clusterProperties;    @Bean    public RedisConnectionFactory connectionFactory() {        return new JedisConnectionFactory(new RedisClusterConfiguration(clusterProperties.getNodes()));    }    @Bean    @Autowired    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {        RedisTemplate rt = new RedisTemplate();        rt.setConnectionFactory(redisConnectionFactory);        return rt;    }}@Servicepublic class UseSdr {    @Autowired    private RedisTemplate redisTemplate;    public void test() {        ValueOperations<String, String> valueOp = redisTemplate.opsForValue();        valueOp.set("hello", "world");        String ret = valueOp.get("hello");        System.out.printf("\r\nReturn value is " + ret);    }}
0 0
原创粉丝点击