哨兵架构
sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点。
工作原理
- sentinel集群启动完毕后,会将哨兵集群的元数据信息写入到所有sentinel的配置文件中(追加在文件的
最下面) - 当redis主节点如果宕机,哨兵集群会重新选举出新的redis主节点,同时会修改所有sentinel节点配置文件
的集群元数据信息 - 当宕机了的redis实例再次启动时,哨兵集群根据集群元数据信息就可以将6379端口的redis节点作为从节点
加入集群 - client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过
sentinel代理访问redis的主节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis
主节点通知给client端(这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)
环境安装
前提:续上一篇【分布式Redis集群–主从架构】,已有Redis集群有一主两从,master(6379), slave(6380, 6381)
- 进入redis安装目录,拷贝一份sentinel.conf配置文件更名为sentinel-26379.conf
cd /usr/local/redis-5.0.3
cp sentinel.conf sentinel-26379.conf
vi sentinel-26379.conf
- 修改sentinel-26379.conf
#修改端口
port 26379
#设置后台运行
daemonize yes
#将PID进程号写入到pidfile文件
pidfile “/var/run/redis‐sentinel‐26379.pid”
logfile “26379.log”
#指定数据存放目录
dir “/usr/local/redis-5.0.3/data”
#sentinel monitor master‐redis‐name master‐redis‐ip master‐redis‐port quorum
#master‐redis‐name名字随便取,客户端访问时会用到
#master‐redis‐ip master‐redis‐port 监控的master节点的ip和port
#quorum取数值,表示有多少个sentinel认为一个master失效才算真正失效(基于半数原则,一般取:sentinel总数/2 + 1)
sentinel monitor mymaster 192.168.126.130 6379 2
- 保存配置并退出,启动sentinel-26379实例
:wq
src/redis-sentinel sentinel-26379.conf
- 重复步骤2,3启动sentinel-26380实例和sentinel-26381实例(注意:步骤2中修改相应sentinel端口号即可)
- 检查sentinel集群是否都启动成功
ps -ef|grep redis
- 查看任何一个sentinel实例的配置文件(文件最下面有追加集群元数据信息),看是否有更新集群元数据信息(以sentinel-26379.conf为例)
cat sentinel-26379.conf
sentinel实例配置文件中追加了集群元数据信息,包含已感知到的slave节点信息以及其他sentinel节点信息
- 客户端连接sentinel实例(以sentinel-26379为例)
src/redis-cli -p 26379
info
通过info命令可以看出,sentinel已经识别出Redis主从信息
- 演示Redis 主节点6379宕机,看sentinel是否会选出其他从节点作为新的主节点
#杀掉主节点6379的进程
kill -9 9442
ps -ef|grep redis
cat sentinel-26379.conf
可以看到,6379主节点挂掉后,sentinel选举出新的主节点6381,原来的6379已变成从节点
info
- 演示已宕机的6379节点重新启动,sentinel集群会根据集群元数据信息将6379节点作为从节点加入集群
src/redis-server redis.conf_master
ps -ef|grep redis
SpringBoot整合Redis哨兵
引入依赖
<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>
修改application.yml
server:
port: 8080
#redis sentinel config
spring:
application:
name: redis-demo
redis:
database: 0
timeout: 3000
sentinel:
master: mymaster
nodes: 192.168.126.130:26379,192.168.126.130:26380,192.168.126.130:26381
lettuce:
pool:
max-idle: 50
min-idle: 10
max-active: 100
max-wait: 1000
RedisConfig
package com.itjeffrey.redis.test.service;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
/**
* Redis配置
* @From: Jeffrey
*/
@Slf4j
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
//string serializer
private RedisSerializer<String> redisSerializer = new StringRedisSerializer();
//object serializer
private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
public RedisConfig(){
setObjectMapper(jackson2JsonRedisSerializer);
}
//LettuceConnectionFactory实例化过程中会自动从spring.cache.redis中读取配置信息
@Autowired
private LettuceConnectionFactory lettuceConnectionFactory;
/**
* config redisTemplate---manually add caches
* @return RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置ObjectMapper(解决查询缓存转换异常问题)
setObjectMapper(jackson2JsonRedisSerializer);
// 设置连接工厂
template.setConnectionFactory(lettuceConnectionFactory);
// 配置key, value, hashValue序列化
template.setKeySerializer(redisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
/**
* 解决查询缓存转换异常问题
*/
private void setObjectMapper(Jackson2JsonRedisSerializer jackson2JsonRedisSerializer){
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
}
}
RedisUtil
package com.itjeffrey.redis.test.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Redis操作工具类
* @From: Jeffrey
*/
@Component
public class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
/**
* 写入缓存
*
* @param key
* @param value
* @return
*/
public boolean set(final String key, Object value) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 写入缓存设置时效时间
*
* @param key
* @param value
* @return
*/
public boolean set(final String key, Object value, Long expireTime, TimeUnit timeUnit) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
redisTemplate.expire(key, expireTime, timeUnit);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 批量删除对应的value
*
* @param keys
*/
public void remove(final String... keys) {
for (String key : keys) {
remove(key);
}
}
/**
* 批量删除key
*
* @param pattern
*/
public void removePattern(final String pattern) {
Set<Serializable> keys = redisTemplate.keys(pattern);
if (keys.size() > 0) {
redisTemplate.delete(keys);
}
}
/**
* 删除对应的value
*
* @param key
*/
public void remove(final String key) {
if (exists(key)) {
redisTemplate.delete(key);
}
}
/**
* 判断缓存中是否有对应的value
*
* @param key
* @return
*/
public boolean exists(final String key) {
return redisTemplate.hasKey(key);
}
/**
* 读取缓存
*
* @param key
* @return
*/
public Object get(final String key) {
Object result = null;
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
result = operations.get(key);
return result;
}
/**
* 哈希 添加
*
* @param key
* @param hashKey
* @param value
*/
public void hmSet(String key, Object hashKey, Object value) {
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
hash.put(key, hashKey, value);
}
/**
* 哈希获取数据
*
* @param key
* @param hashKey
* @return
*/
public Object hmGet(String key, Object hashKey) {
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
return hash.get(key, hashKey);
}
/**
* 列表添加
*
* @param k
* @param v
*/
public void lPush(String k, Object v) {
ListOperations<String, Object> list = redisTemplate.opsForList();
list.rightPush(k, v);
}
/**
* 列表获取
*
* @param k
* @param l
* @param l1
* @return
*/
public List<Object> lRange(String k, long l, long l1) {
ListOperations<String, Object> list = redisTemplate.opsForList();
return list.range(k, l, l1);
}
/**
* 集合添加
*
* @param key
* @param value
*/
public void add(String key, Object value) {
SetOperations<String, Object> set = redisTemplate.opsForSet();
set.add(key, value);
}
/**
* 集合获取
*
* @param key
* @return
*/
public Set<Object> setMembers(String key) {
SetOperations<String, Object> set = redisTemplate.opsForSet();
return set.members(key);
}
/**
* 有序集合添加
*
* @param key
* @param value
* @param scoure
*/
public void zAdd(String key, Object value, double scoure) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
zset.add(key, value, scoure);
}
/**
* 有序集合获取
*
* @param key
* @param scoure
* @param scoure1
* @return
*/
public Set<Object> rangeByScore(String key, double scoure, double scoure1) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
return zset.rangeByScore(key, scoure, scoure1);
}
}
测试
package com.itjeffrey.redis.test.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
/**
* @From: Jeffrey
*/
@Service
public class TestService {
private int count;
@Autowired
private RedisUtil redisUtil;
@Scheduled(cron = "0 0/2 * * * ?")
public void test(){
String key = "charset" + count;
redisUtil.set(key, "utf-" + count);
System.out.println("set redis cache, key:" + key + " - value:" + redisUtil.get(key));
count++;
}
}
结果
控制台打印:
连接其中一个从节点6380并查看数据