「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」
Redis高可用架构
sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点。哨兵架构下client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过sentinel代理访问redis的主节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis主节点通知给client端(这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)
哨兵架构搭建
1、已经搭建好三个redis虚拟机(可以看我之前的文章),并设定master及slave 192.168.253.131:6379(master) 192.168.253.132:6380(slave) 192.168.253.133:6381(slave) 2、redis下有一个sentinel.conf文件,如下:
需修改以下内容:\
port 26379
daemonize yes
pidfile "/var/run/redis-sentinel.pid"
logfile "sentinel.log"
dir "/usr/local/redis/data"
# sentinel monitor <master-redis-name> <master-redis-ip> <master-redis-port> <quorum> # quorum是一个数字,指明当有多少个sentinel认为一个master失效时(值一般为:sentinel总数/2 + 1),master才算真正失效
sentinel monitor mymaster 192.168.253.131 6379 2
# mymaster这个名字随便取,客户端访问时会用到
2、启动sentinel哨兵实例
src/redis-sentinel sentinel.conf
3、查看sentinel的info信息
src/redis-cli -p 26379 127.0.0.1:26379>info
可以看到Sentinel的info里已经识别出了redis的主从
4、再配置另外两台sentinel,端口26380和26381,注意上述配置文件里的对应数字都要修改
查看131 master节点info(可以看到有两个slave节点,和节点信息)
查看132 slave节点info(当前节点role以及master节点信息)
查看133 slave节点info(当前节点role以及master节点信息)
集群信息查看
sentinel.conf从节点信息
sentinel集群都启动完毕后,会将哨兵集群的元数据信息写入所有sentinel的配置文件里去(追加在文件的最下面),我们查看下131 master配置文件sentinel.conf最下方,如下所示:
关闭131节点的redis服务。连接132及133节点redis客户端,查看info
132节点info信息如下:(已被哨兵选举为master节点,并可以看到slave就一个节点,节点信息为133)
再次打开132节点sentinel.conf文件,并到最下方,已经将挂掉的131,以及133作为从节点
sentinel.conf 主节点配置自动修改
哨兵在选举master的时候,同时还会修改sentinel.conf中主节点的信息
打开131 sentinel.conf配置文件如下:(可以看到之前131作为master的配置信息已更改为132)
打开132 sentinel.conf配置文件如下:(可以看到之前131作为master的配置信息已更改为132)
当131节点再次启动的时候就会被作为slave节点。
测试sentinel模式
public static void main(String[] args) throws IOException {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(20);
config.setMaxIdle(10);
config.setMinIdle(5);
String masterName = "mymaster";
Set<String> sentinels = new HashSet<String>();
sentinels.add(new HostAndPort("192.168.253.131",26379).toString());
sentinels.add(new HostAndPort("192.168.253.132",26380).toString());
sentinels.add(new HostAndPort("192.168.253.133",26381).toString());
//JedisSentinelPool其实本质跟JedisPool类似,都是与redis主节点建立的连接池
//JedisSentinelPool并不是说与sentinel建立的连接池,而是通过sentinel发现redis主节点并与其建立连接
JedisSentinelPool jedisSentinelPool = new JedisSentinelPool(masterName, sentinels, config, 3000, null);
Jedis jedis = null;
try {
jedis = jedisSentinelPool.getResource();
System.out.println(jedis.set("sentinel", "jony"));
System.out.println(jedis.get("sentinel"));
} catch (Exception e) {
e.printStackTrace();
} finally {
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
if (jedis != null)
jedis.close();
}
}
springboot 集成sentinel
1、配置pom.xml文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2、springboot配置文件添加
#连接超时时间\
spring.redis.timeout: 3000\
#哨兵master(在sentinel.conf中配置)\
spring.redis.sentinel.master=mymaster\
#哨兵节点\
spring.redis.sentinel.nodes=192.168.253.131:26379,192.168.253.132:26380,192.168.253.133:26381\
#Redis数据库索引(默认为0)\
spring.redis.database=0\
#连接池配置,springboot2.0中直接使用jedis或者lettuce配置连接池,默认为lettuce连接池\
#连接池最大连接数(使用负值表示没有限制)\
spring.redis.jedis.pool.max-active=8\
#连接池最大阻塞等待时间(使用负值表示没有限制)\
spring.redis.jedis.pool.max-wait=-1s\
#连接池中的最大空闲连接\
spring.redis.jedis.pool.max-idle=8\
#接池中的最小空闲连接\
spring.redis.jedis.pool.min-idle=0
3、添加Controller
@RestController
public class IndexController {
private static final Logger logger = LoggerFactory.getLogger(IndexController.class);
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 测试节点挂了哨兵重新选举新的master节点,客户端是否能动态感知到
* 新的master选举出来后,哨兵会把消息发布出去,客户端实际上是实现了一个消息监听机制,
* 当哨兵把新master的消息发布出去,客户端会立马感知到新master的信息,从而动态切换访问的masterip
*
* @throws InterruptedException
*/
@RequestMapping("/test_sentinel")
public void testSentinel() throws InterruptedException {
int i = 1;
while (true){
try {
stringRedisTemplate.opsForValue().set("jony"+i, i+"");
System.out.println("设置key:"+ "jony" + i);
i++;
Thread.sleep(1000);
}catch (Exception e){
logger.error("错误:", e);
}
}
}
}
浏览器访问测试,途中关闭master节点,测试redis可用性
测试结果
关闭master之后程序报错timeout,并且执行到了jony18
等待哨兵进行master选举,连接成功之后如下:(会显示上一次连接的maser节点,以及重新选举之后的master节点)
可以看到set的值,从jony19继续,无数据丢失
StringRedisTemplate与RedisTemplate详解
spring 封装了 RedisTemplate 对象来进行对redis的各种操作,它支持所有的 redis 原生的 api。
1、在RedisTemplate中提供了几个常用的接口方法的使用,分别是:
private ValueOperations<K, V> valueOps;
private HashOperations<K, V> hashOps;
private ListOperations<K, V> listOps;
private SetOperations<K, V> setOps;
private ZSetOperations<K, V> zSetOps;
2、RedisTemplate中定义了对5种数据结构操作
redisTemplate.opsForValue();//操作字符串 redisTemplate.opsForHash();//操作hash redisTemplate.opsForList();//操作list redisTemplate.opsForSet();//操作set redisTemplate.opsForZSet();//操作有序set
StringRedisTemplate继承自RedisTemplate,也一样拥有上面这些操作。
StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。
Redis客户端命令对应的RedisTemplate中的方法列表:
String 结构
Hash结构
List结构
Set结构
Redis存储数据序列化:
StringRedisTemplate一般用来存储字符串,默认用的序列化是StringRedisSerializer。
如果要存储对象我们一般用RedisTemplate,它底层用的序列化机制是JdkSerializationRedisSerializer,这种存储对象要求对象实现Serializable接口,它底层存的是二进制的序列化数组,不便于在redis里查看,所以我们一般用Jackson2JsonRedisSerializer,能将对象转成json存储,并且不需要对象实现Serializable接口,也便于在redis里查看。
当然,如果不需要在redis里查看一些数据,对性能要求较高的话,序列化可以采用protobuf。
还有,对于key的话一般都使用StringRedisSerializer,参考示例:
RedisTemplate<Object, Object> template = new RedisTemplate<>();
Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
// value的序列化采用jsonRedisSerializer
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
// key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());