Featured image of post Redis三主三从分布式集群配置并整合SpringBoot

Redis三主三从分布式集群配置并整合SpringBoot

前言

推荐观看文章:redis安装并开机自启动整合springboot实战,安装Redis。

为什么要创建Redis分布式集群呢?Redis的部署有单机版,一主多从+哨兵。单机版就不用说了,节点挂了就直接不可用了。一主多从+哨兵,一般用作读写分离,并且由于哨兵的存在可以在Master不可用的时候,选择另外的Slave作为新的Master使得集群可以继续提供服务。

但是一主多从的架构,Slave只是Master的副本,一个Key在Master和所有的Slave上都是存在。在Key很多的情况下,就不够存储了,当然可以增大机器的内存。

而Redis提供Cluster的模式,可以横向扩展Key的存储。

服务器规划

服务器:

  • 192.168.79.120

  • 192.168.79.121

  • 192.168.79.122

  • 192.168.79.123

  • 192.168.79.124

  • 192.168.79.125

配置文件

在默认的之上修改的部分,每台都一样

1
2
3
4
5
6
bind 0.0.0.0
dir "/usr/local/redis/db"
requirepass "root1234"
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 5000appendonly yes

创建集群

在Redis5之前的版本,创建Redis的集群是需要Ruby脚本的,也就是需要一个Ruby的环境,但是Redis5版本之后,通过redis-cli就可以创建Redis集群了。以下命令:

1
redis-cli -a root1234 --cluster create 192.168.79.120:6379 192.168.79.121:6379 192.168.79.122:6379 192.168.79.123:6379 192.168.79.124:6379 192.168.79.125:6379 --cluster-replicas 1

敲完之后,输入yes创建三主三从的集群,--cluster-replicas 1说明每台master一个slave。六台刚好。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 192.168.79.124:6379 to 192.168.79.120:6379
Adding replica 192.168.79.125:6379 to 192.168.79.121:6379
Adding replica 192.168.79.123:6379 to 192.168.79.122:6379
M: fc45e8b102f1e3d689d1ca194593dc801689f28c 192.168.79.120:6379
   slots:[0-5460] (5461 slots) master
M: 6e6c3a530610c2e319e531ed6ba14a8a42ba5fe8 192.168.79.121:6379
   slots:[5461-10922] (5462 slots) master
M: 3cac41b0e8da1182f413ec2590430856fddab153 192.168.79.122:6379
   slots:[10923-16383] (5461 slots) master
S: b911d342eb181c2dee04f4bb49ead2246e58b070 192.168.79.123:6379
   replicates 3cac41b0e8da1182f413ec2590430856fddab153
S: be51a450c46fa33670f8a83e502ae5ead8d0e84b 192.168.79.124:6379
   replicates fc45e8b102f1e3d689d1ca194593dc801689f28c
S: 05724a6a68f7f24ac74e07900db269c136410fd3 192.168.79.125:6379
   replicates 6e6c3a530610c2e319e531ed6ba14a8a42ba5fe8
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
..
>>> Performing Cluster Check (using node 192.168.79.120:6379)
M: fc45e8b102f1e3d689d1ca194593dc801689f28c 192.168.79.120:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: b911d342eb181c2dee04f4bb49ead2246e58b070 192.168.79.123:6379
   slots: (0 slots) slave
   replicates 3cac41b0e8da1182f413ec2590430856fddab153
S: 05724a6a68f7f24ac74e07900db269c136410fd3 192.168.79.125:6379
   slots: (0 slots) slave
   replicates 6e6c3a530610c2e319e531ed6ba14a8a42ba5fe8
M: 6e6c3a530610c2e319e531ed6ba14a8a42ba5fe8 192.168.79.121:6379
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: be51a450c46fa33670f8a83e502ae5ead8d0e84b 192.168.79.124:6379
   slots: (0 slots) slave
   replicates fc45e8b102f1e3d689d1ca194593dc801689f28c
M: 3cac41b0e8da1182f413ec2590430856fddab153 192.168.79.122:6379
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

从上面的输出日子就可以看到,确实创建可三台主,三台从,一共16284跟slot被分配了。Master和Slave由Redis分配好了。

通过命令检查任意一节点:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
redis-cli -a root1234 --cluster check 192.168.79.120:6379


192.168.79.120:6379 (fc45e8b1...) -> 0 keys | 5461 slots | 1 slaves.
192.168.79.121:6379 (6e6c3a53...) -> 0 keys | 5462 slots | 1 slaves.
192.168.79.122:6379 (3cac41b0...) -> 0 keys | 5461 slots | 1 slaves.
[OK] 0 keys in 3 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 192.168.79.120:6379)
M: fc45e8b102f1e3d689d1ca194593dc801689f28c 192.168.79.120:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: b911d342eb181c2dee04f4bb49ead2246e58b070 192.168.79.123:6379
   slots: (0 slots) slave
   replicates 3cac41b0e8da1182f413ec2590430856fddab153
S: 05724a6a68f7f24ac74e07900db269c136410fd3 192.168.79.125:6379
   slots: (0 slots) slave
   replicates 6e6c3a530610c2e319e531ed6ba14a8a42ba5fe8
M: 6e6c3a530610c2e319e531ed6ba14a8a42ba5fe8 192.168.79.121:6379
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: be51a450c46fa33670f8a83e502ae5ead8d0e84b 192.168.79.124:6379
   slots: (0 slots) slave
   replicates fc45e8b102f1e3d689d1ca194593dc801689f28c
M: 3cac41b0e8da1182f413ec2590430856fddab153 192.168.79.122:6379
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

通过检查可以清楚的知道Redis Cluster中各个Redis实例之间的关系了。

关于Redis Cluster中的Slot(槽)

一个key是会通过Redis中CRC算法,去计算出一个的key是属于哪一个Slot。在上面的例子中,一共有16384个slot被平均地分配到了三台Master上(Slave不分配Slot),每个Key在集群中只会存在一份。

当往Redis Cluuster中插入一个Key时,就通过CRC16(key) mod 16384算法取模,假如:执行redis命令,SET test 123。将执行crc16(test)=63534,然后mod取余,63534%16384=14382,得出14382。由于10923-16383是位于192.168.79.122:6379上,test这个key就会被存储在192.168.79.122:6379的Redis实例上。

SpringBoot整合,连接Redis Cluster

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
	<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

配置文件

1
2
3
4
5
spring:
  redis:
    cluster:
      nodes: 192.168.79.120:6379,192.168.79.121:6379,192.168.79.122:6379,192.168.79.123:6379,192.168.79.124:6379,192.168.79.125:6379
    password: root1234

redis通用工具类

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
@Component
public class RedisUtil {

    private final StringRedisTemplate redisTemplate;

    public RedisUtil(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    // Key(键),简单的key-value操作

    /**
     * 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。
     *
     * @param key
     * @return
     */
    public long ttl(String key) {
        return redisTemplate.getExpire(key);
    }

    /**
     * 实现命令:expire 设置过期时间,单位秒
     *
     * @param key
     * @return
     */
    public void expire(String key, long timeout) {
        redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 实现命令:INCR key,增加key一次
     *
     * @param key
     * @return
     */
    public long incr(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key
     */
    public Set<String> keys(String pattern) {
        return redisTemplate.keys(pattern);
    }

    /**
     * 实现命令:DEL key,删除一个key
     *
     * @param key
     */
    public void del(String key) {
        redisTemplate.delete(key);
    }

    // String(字符串)

    /**
     * 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key)
     *
     * @param key
     * @param value
     */
    public void set(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 实现命令:SET key value EX seconds,设置key-value和超时时间(秒)
     *
     * @param key
     * @param value
     * @param timeout (以秒为单位)
     */
    public void set(String key, String value, long timeout) {
        redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
    }

    /**
     * 实现命令:GET key,返回 key所关联的字符串值。
     *
     * @param key
     * @return value
     */
    public String get(String key) {
        return (String) redisTemplate.opsForValue().get(key);
    }

    /**
     * 批量查询,对应mget
     *
     * @param keys
     * @return
     */
    public List<String> mget(List<String> keys) {
        return redisTemplate.opsForValue().multiGet(keys);
    }

    /**
     * 批量查询,管道pipeline
     *
     * @param keys
     * @return
     */
    public List<Object> batchGet(List<String> keys) {

//		nginx -> keepalive
//		redis -> pipeline

        List<Object> result = redisTemplate.executePipelined(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                StringRedisConnection src = (StringRedisConnection) connection;

                for (String k : keys) {
                    src.get(k);
                }
                return null;
            }
        });

        return result;
    }


    // Hash(哈希表)

    /**
     * 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value
     *
     * @param key
     * @param field
     * @param value
     */
    public void hset(String key, String field, Object value) {
        redisTemplate.opsForHash().put(key, field, value);
    }

    /**
     * 实现命令:HGET key field,返回哈希表 key中给定域 field的值
     *
     * @param key
     * @param field
     * @return
     */
    public String hget(String key, String field) {
        return (String) redisTemplate.opsForHash().get(key, field);
    }

    /**
     * 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
     *
     * @param key
     * @param fields
     */
    public void hdel(String key, Object... fields) {
        redisTemplate.opsForHash().delete(key, fields);
    }

    /**
     * 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。
     *
     * @param key
     * @return
     */
    public Map<Object, Object> hgetall(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    // List(列表)

    /**
     * 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头
     *
     * @param key
     * @param value
     * @return 执行 LPUSH命令后,列表的长度。
     */
    public long lpush(String key, String value) {
        return redisTemplate.opsForList().leftPush(key, value);
    }

    /**
     * 实现命令:LPOP key,移除并返回列表 key的头元素。
     *
     * @param key
     * @return 列表key的头元素。
     */
    public String lpop(String key) {
        return (String) redisTemplate.opsForList().leftPop(key);
    }

    /**
     * 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。
     *
     * @param key
     * @param value
     * @return 执行 LPUSH命令后,列表的长度。
     */
    public long rpush(String key, String value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }
}

测试用的控制层

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RestController
@RequestMapping("test")
public class TestRedisController {

    @Autowired
    private RedisUtil redisUtil;

    @GetMapping("add")
    public void add(@RequestParam("key") String key, @RequestParam("value") String value) {
        redisUtil.set(key, value);
    }

    @GetMapping("list")
    public Object listAllKeys() {
        Set<String> keys = redisUtil.keys("*");
        HashMap<String, Object> map = new HashMap<>();
        for (String key : keys) {
            String value = redisUtil.get(key);
            map.put(key, value);
        }
        return map;
    }
}

启动类

1
2
3
4
5
6
7
@SpringBootApplication
public class RedisApplication {

    public static void main(String[] args) {
        SpringApplication.run(RedisApplication.class, args);
    }
}

测试

运行启动类

发送GET请求

http://localhost;8080/test/add?key=age&value=18

http://localhost;8080/test/add?key=name&value=tom

http://localhost;8080/test/list