SpringBoot 2.x 整合 Redis Sentinel 集群

JavaFamily

共 8009字,需浏览 17分钟

 · 2022-05-10

 SpringBoot 2.x 整合 Redis Sentinel 集群实现自动故障转移.

上一篇我们讲解了 Redis Sentinel 集群搭建的详细过程, 作为一个 Java 程序员, 我们还是需要从代码层面来操作 Redis 的.

1. 环境准备

  • 192.168.3.26: Master 实例, Sentinel 1

  • 192.168.3.27: Slave1 实例, Sentinel 2

  • 192.168.3.28:Slave2 实例, Sentinel 3

1.1 Redis Sentinel 集群(一主二从三个 Sentinel)

Redis Sentinel 集群的搭建过程可以参考帅帅之前的文章 Redis 高可用专题-- Sentinel 哨兵模式

注意: 目前的 master 是 192.168.3.27, 即经过故障转移后 slave1 目前是 Master

1.2 SpringBoot 环境

  • SpringBoot 版本: 2.3.2.RELEASE

2. 引入依赖

         

org.springframework.boot
spring-boot-starter-data-redis




org.apache.commons
commons-pool2



org.springframework.boot
spring-boot-starter-web



org.springframework.boot
spring-boot-starter-test
test


org.junit.vintage
junit-vintage-engine


SpringBoot 2.x 的 Redis 客户端已经从 Jedis 切换到了 Lettuce

3. 配置

  • application.yml 配置文件中编辑 Sentinel 信息


spring:
application:
name: sb-redis-sentinel

# redis 配置
redis:
database: 1
# 数据节点密码
password: javaFamily123!!

sentinel:
master: mymaster
# Sentinel 连接密码, 如果没配置就不写
# password:
nodes:
- 192.168.3.26:26379
- 192.168.3.27:26379
- 192.168.3.28:26379

# 配置 lettuce 客户端连接池信息
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 5
# 连接池中的最大空闲连接
max-idle: 10
# 连接池的最大数据库连接数
max-active: 100
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1

这里只需要配置 Sentinel 节点. 客户端会自动从 sentinel 获取数据节点信息.

  • 配置 Redis 连接池、RedisTemplate

package club.javafamily.sbredissentinel.conf;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.lettuce.*;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.ArrayList;
import java.util.List;

/**
* @author Jack Li
* @date 2022/5/8 下午6:42
* @description Redis 哨兵配置
*/
@Configuration
public class RedisConfig {

private final RedisProperties redisProperties;

public RedisConfig(RedisProperties redisProperties) {
this.redisProperties = redisProperties;
}

/**
* 创建 Redis 连接池配置
*/
@Bean
public GenericObjectPoolConfig poolConfig() {
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setMinIdle(redisProperties.getLettuce().getPool().getMinIdle());
config.setMaxIdle(redisProperties.getLettuce().getPool().getMaxIdle());
config.setMaxTotal(redisProperties.getLettuce().getPool().getMaxActive());
config.setMaxWaitMillis(redisProperties.getLettuce().getPool().getMaxWait().toMillis());

return config;
}

/**
* 声明哨兵配置, 将哨兵信息放到配置中
*/
@Bean
public RedisSentinelConfiguration sentinelConfig() {
RedisSentinelConfiguration redisConfig = new RedisSentinelConfiguration();
redisConfig.setMaster(redisProperties.getSentinel().getMaster());
redisConfig.setPassword(redisProperties.getPassword());

if(redisProperties.getSentinel().getNodes()!=null) {
List sentinelNode = new ArrayList();

for(String sen : redisProperties.getSentinel().getNodes()) {
String[] arr = sen.split(":");
sentinelNode.add(new RedisNode(arr[0],Integer.parseInt(arr[1])));
}

redisConfig.setSentinels(sentinelNode);
}

return redisConfig;
}

/**
* 创建连接工厂 LettuceConnectionFactory
*/
@Bean("lettuceConnectionFactory")
public LettuceConnectionFactory lettuceConnectionFactory(
@Qualifier("poolConfig") GenericObjectPoolConfig config,
@Qualifier("sentinelConfig") RedisSentinelConfiguration sentinelConfig)
{
LettuceClientConfiguration clientConfiguration
= LettucePoolingClientConfiguration.builder()
.poolConfig(config)
.build();

return new LettuceConnectionFactory(sentinelConfig, clientConfiguration);
}

/**
* 创建 RedisTemplate
*/
@Bean("stringObjectRedisTemplate")
public RedisTemplate stringObjectRedisTemplate(
@Qualifier("lettuceConnectionFactory") LettuceConnectionFactory connectionFactory)
{
RedisTemplate redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);

StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer
= new GenericJackson2JsonRedisSerializer();

//设置序列化器
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();

return redisTemplate;
}
}

4. 正常测试

  • 读写测试

package club.javafamily.sbredissentinel;

import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

@SpringBootTest
class SbRedisSentinelApplicationTests {

@Autowired
private RedisTemplate redisTemplate;

ValueOperations opsForValue;

@BeforeEach
public void init() {
opsForValue = redisTemplate.opsForValue();
}

@Test
void contextLoads() {
Assertions.assertNotNull(redisTemplate);
}

@Test
void writeTest() {
opsForValue.set("tsb1", "springboot");
}

@Test
void readTest() {
final String value = (String) opsForValue.get("tsb1");

System.out.println(value);
}

}

  • 在 slave 节点读取

可以看到主从复制及读写都没问题.

5. 主节点 Master 宕机测试

哨兵的一大作用即就是自动故障转移, 因此, 我们测试一下主节点宕机后 SpringBoot 应用不重启的情况下, SpringBoot 应用是否还可以继续正常工作.

  • 写一个 Controller 接收读写请求, 启动并让 SpringBoot 处于运行状态

package club.javafamily.sbredissentinel.controller;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

/**
* @author Jack Li
* @date 2022/5/8 下午11:43
* @description
*/
@RestController
public class RedisTestController {

private final RedisTemplate redisTemplate;

public RedisTestController(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}

@GetMapping("/write")
public String write(@RequestParam("key") String key,
@RequestParam("value") String val)
{
// 写入
redisTemplate.opsForValue().set(key, val);

// 读取并返回
return (String) redisTemplate.opsForValue().get(key);
}
}

  • 下线当前的 master

通过 info 指令可以看到当前集群的 master 是 slave1 节点(192.168.3.27)

执行下线

观察 SpringBoot 后台也会重新连接

  • 再次执行写入

  • 查看从节点数据

目前 master 为 slave 节点, slave1 已经下线, slave2 为 master

至此, SpringBoot 连接 Sentinel 集群及自动故障转移测试就讲完了, 这里再补充一个知识点, 也是一道字节面试题哦:

哨兵集群完成了主从切换,客户端如何感知?

我们知道Redis有pub/sub机制,为了便于外部知道当前的切换进度,哨兵提供了多个订阅频道。其中就有一个新主库切换频道,(switch-master)

SUBSCRIBE +switch-master

订阅对应频道,就可以获得切换后的新主库ip、port,并与之建立连接,继续使用 Redis 服务。

        如果有任何相关的问题都可以加入 QQ/微信群一起讨论, 学习, 进步. 此外如果有任何对于本公众号的意见和建议也欢迎大家留言积极批评指正, 最后, 愿你我都能成为更好的自己. 


        我是帅帅, 一个集帅气, 幽默与内涵, 并且热爱编程, 拥抱开源, 喜欢烹饪与旅游的暖男, 我们下期再见. 拜了个拜!

        老规矩别忘了哦, 点击原文链接跳转到我们官方的博客平台哦.



悄悄话




每文一句



Don't aim for success if you really want it. Just stick to what you love and believe in, and it will come naturally.

少一些功利主义的追求, 多一些不为什么的坚持.


日常求赞

      你们白漂的力量就是我拖更的史诗级动力, 点赞, 评论, 再看, 赞赏, 看都看到这了, 随便点一个咯.



关注加好友


拉你进大佬交流群





浏览 99
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报