Redis第三篇: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
配置文件
在默认的之上修改的部分,每台都一样
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集群了。以下命令:
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。六台刚好。
>>> 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分配好了。
通过命令检查任意一节点:
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
<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>
配置文件
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通用工具类
@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);
}
}
测试用的控制层
@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;
}
}
启动类
@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
转载自:https://juejin.cn/post/6970550287241052190