Sentinel 哨兵Sentinel 哨兵介绍
Sentinel 哨兵本质上是一个运行在特殊模式下的Redis实例,只是初始化的过程和工作与普通的 Redis不同,本质上也是一个单独的进程。
Sentinel 哨兵 是Redis的高可用解决方案:一个或多个Sentinel实例(instance)组成的Sentinel系统(system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在主服务器下线时可以自动切换从服务器升级为主服务器。
下图是一个简单的Sentinel系统架构图,一个Sentinel系统监视一个主从集群,其中server1是Redis主服务器,server2/3/4是Redis 从服务器。主从之间利用上面的主从复制来达到主从一致。而Sentinel系统监视整个主从集群。
当Sentinel系统察觉到Server1主服务器下线时,就会终止server2/3/4的复制。
同时Sentinel将server2升级为主服务器,server3/4从新的主服务器进行复制。同时等待server1的再次上线。
Sentinel系统也可以主动降级主服务为从服务器,将从服务器升级为主服务器。
Sentinel 哨兵监控集群过程:
Sentinel 故障转移:
优点:
1、哨兵模式基于主从复制,因此主从复制的优点哨兵都具备
2、哨兵具备了主从切换和故障转移,因此集群有了更高的可用性
缺点:
1、Redis较难支持在线扩容,在线扩容比较复杂。
总结:
sentinel 哨兵主要用来监控redis主从集群,提高了redis 主从集群的可用性。
建议先参考主从模式搭建教程:
Sentinel模式是基于主从模式的嘛,因此要实现Sentinel集群模式的简单搭建需要提前做好主从集群模式的搭建。
想尝试主从怎么搭建的参考上一篇博客:
http://t.csdnimg.cn/emWe0
注意点:注意点:注意点:
**【必须】
**参考主从只需参考centos下的redis.conf配置文件那里即可,让三个redis服务跑起来就ok了,其他的无需参考,主从归主从,哨兵归哨兵嘛。
通俗点:从标题1看到标题5即可
sentinel01.conf sentinel02.conf sentinel03.conf
sentinel01.conf
bind 0.0.0.0
port 1001
sentinel announce-ip "192.168.6.128"
# 后面的2 表示有2个哨兵认为主库挂了就是客观下线
# 就会开始选举
# 5000 表示每5秒钟检测一次主库是否挂掉
sentinel monitor mymaster 192.168.6.128 6380 2
sentinel failover-timeout mymaster 5000
# linux中 解除redis保护 允许外部连接
protected-mode no
# 后台访问
daemonize yes
sentinel02.conf
bind 0.0.0.0
port 1002
sentinel announce-ip "192.168.6.128"
# 后面的2 表示有2个哨兵认为主库挂了就是客观下线
# 就会开始选举
# 5000 表示每5秒钟检测一次主库是否挂掉
sentinel monitor mymaster 192.168.6.128 6380 2
sentinel failover-timeout mymaster 5000
# linux中 解除redis保护 允许外部连接
protected-mode no
# 后台访问
daemonize yes
sentinel03.conf
bind 0.0.0.0
port 1003
sentinel announce-ip "192.168.6.128"
# 后面的2 表示有2个哨兵认为主库挂了就是客观下线
# 就会开始选举
# 5000 表示每5秒钟检测一次主库是否挂掉
sentinel monitor mymaster 192.168.6.128 6380 2
sentinel failover-timeout mymaster 5000
# linux中 解除redis保护 允许外部连接
protected-mode no
# 后台访问
daemonize yes
前提
:redis主从配置的三个服务必须开启
启动命令如下:
redis-sentinel sentinel01.conf
redis-sentinel sentinel02.conf
redis-sentinel sentinel03.conf
# 查看redis进程
ps -ef|grep redis|grep -v grep
其他两个同理
最后连接效果
这时切换到主库往下滑找到role这一行,
此效果证明主从配置搭建没问题
gitee代码测试地址:https://gitee.com/crqyue/springboot-redis-sentinel.git
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.10.0version>
dependency>
spring:
datasource: # oracle数据库连接 - 可以切换为自己的数据库
driver-class-name: oracle.jdbc.driver.OracleDriver
url: jdbc:oracle:thin:@localhost:1521:orcl
username: oa
password: root
type: com.alibaba.druid.pool.DruidDataSource
redis: # redis哨兵配置
sentinel:
master: mymaster
nodes: 192.168.6.128:1001,192.168.6.128:1002,192.168.6.128:1003
mybatis-plus:
type-aliases-package: com.cy.entity
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory); // 启动时注入连接对象
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // 设置序列化器
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
return redisTemplate; // 返回模板对象
}
}
@Component
public class RedisUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key , long time) {
try {
if (time > 0) {
redisTemplate.expire(key , time , TimeUnit.SECONDS);
}
return true;
} catch ( Exception e ) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key , TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch ( Exception e ) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(Arrays.asList(key));
}
}
}
// ============================= key相关操作 =============================
/**
* 可以实现分布式锁
*
* @param key
* @param value
* @return key存在则返回false,不存在则存储key-value并返回true
*/
public boolean setnx(String key , String value) {
return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key , value));
}
/**
* 可以实现分布式锁
*
* @param key
* @param value
* @param timeout
* @param unit
* @return key存在则返回false,不存在则存储key-value并返回true
*/
public boolean setnx(String key , String value , long timeout , TimeUnit unit) {
return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key , value , timeout , unit));
}
/**
* 删除key
*
* @param key
*/
public void delete(String key) {
redisTemplate.delete(key);
}
/**
* 批量删除key
*
* @param keys
*/
public void delete(Collection<String> keys) {
redisTemplate.delete(keys);
}
/**
* 设置过期时间
*
* @param key
* @param timeout
* @param unit
* @return
*/
public Boolean expire(String key , long timeout , TimeUnit unit) {
return redisTemplate.expire(key , timeout , unit);
}
/**
* 设置过期时间
*
* @param key
* @param date
* @return
*/
public Boolean expireAt(String key , Date date) {
return redisTemplate.expireAt(key , date);
}
/**
* 查找匹配的key
*
* @param pattern
* @return
*/
public Set<String> keys(String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 将当前数据库的 key 移动到给定的数据库 db 当中
*
* @param key
* @param dbIndex
* @return
*/
public Boolean move(String key , int dbIndex) {
return redisTemplate.move(key , dbIndex);
}
/**
* 移除 key 的过期时间,key 将持久保持
*
* @param key
* @return
*/
public Boolean persist(String key) {
return redisTemplate.persist(key);
}
/**
* 返回 key 的剩余的过期时间
*
* @param key
* @param unit
* @return
*/
public Long getExpire(String key , TimeUnit unit) {
return redisTemplate.getExpire(key , unit);
}
/**
* 从当前数据库中随机返回一个 key
*
* @return
*/
public String randomKey() {
return redisTemplate.randomKey();
}
/**
* 修改 key 的名称
*
* @param oldKey
* @param newKey
*/
public void rename(String oldKey , String newKey) {
redisTemplate.rename(oldKey , newKey);
}
/**
* 仅当 newkey 不存在时,将 oldKey 改名为 newkey
*
* @param oldKey
* @param newKey
* @return
*/
public Boolean renameIfAbsent(String oldKey , String newKey) {
return redisTemplate.renameIfAbsent(oldKey , newKey);
}
/**
* 返回 key 所储存的值的类型
*
* @param key
* @return
*/
public DataType type(String key) {
return redisTemplate.type(key);
}
// ============================String=============================
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key , Object value) {
try {
redisTemplate.opsForValue().set(key , value);
return true;
} catch ( Exception e ) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key , Object value , long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key , value , time , TimeUnit.SECONDS);
} else {
set(key , value);
}
return true;
} catch ( Exception e ) {
e.printStackTrace();
return false;
}
}
/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key , long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key , delta);
}
/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key , long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key , -delta);
}
// ================================Map=================================
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key , String item) {
return redisTemplate.opsForHash().get(key , item);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key , Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key , map);
return true;
} catch ( Exception e ) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key , Map<String, Object> map , long time) {
try {
redisTemplate.opsForHash().putAll(key , map);
if (time > 0) {
expire(key , time);
}
return true;
} catch ( Exception e ) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key , String item , Object value) {
try {
redisTemplate.opsForHash().put(key , item , value);
return true;
} catch ( Exception e ) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key , String item , Object value , long time) {
try {
redisTemplate.opsForHash().put(key , item , value);
if (time > 0) {
expire(key , time);
}
return true;
} catch ( Exception e ) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key , Object... item) {
redisTemplate.opsForHash().delete(key , item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key , String item) {
return redisTemplate.opsForHash().hasKey(key , item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key , String item , double by) {
return redisTemplate.opsForHash().increment(key , item , by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key , String item , double by) {
return redisTemplate.opsForHash().increment(key , item , -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
*
* @param key 键
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch ( Exception e ) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key , Object value) {
try {
return redisTemplate.opsForSet().isMember(key , value);
} catch ( Exception e ) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key , Object... values) {
try {
return redisTemplate.opsForSet().add(key , values);
} catch ( Exception e ) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key , long time , Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key , values);
if (time > 0) {
expire(key , time);
}
return count;
} catch ( Exception e ) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch ( Exception e ) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key , Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key , values);
return count;
} catch ( Exception e ) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key , long start , long end) {
try {
return redisTemplate.opsForList().range(key , start , end);
} catch ( Exception e ) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch ( Exception e ) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public Object lGetIndex(String key , long index) {
try {
return redisTemplate.opsForList().index(key , index);
} catch ( Exception e ) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key , Object value) {
try {
redisTemplate.opsForList().rightPush(key , value);
return true;
} catch ( Exception e ) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key , Object value , long time) {
try {
redisTemplate.opsForList().rightPush(key , value);
if (time > 0) {
expire(key , time);
}
return true;
} catch ( Exception e ) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key , List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key , value);
return true;
} catch ( Exception e ) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key , List<Object> value , long time) {
try {
redisTemplate.opsForList().rightPushAll(key , value);
if (time > 0) {
expire(key , time);
}
return true;
} catch ( Exception e ) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key , long index , Object value) {
try {
redisTemplate.opsForList().set(key , index , value);
return true;
} catch ( Exception e ) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key , long count , Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key , count , value);
return remove;
} catch ( Exception e ) {
e.printStackTrace();
return 0;
}
}
// ============================= zSet相关操作 =============================
/**
* 添加元素,有序集合是按照元素的score值由小到大排列
*
* @param key
* @param value
* @param score
* @return
*/
public Boolean zAdd(String key , String value , double score) {
return redisTemplate.opsForZSet().add(key , value , score);
}
/**
* 有序set添加元素 不存在就添加
*
* @param key
* @param value
* @param score
* @return
*/
public boolean zAddIf(String key , Object value , double score) {
return redisTemplate.opsForZSet().addIfAbsent(key , value , score);
}
/**
* @param key
* @param values
* @return
*/
public Long zRemove(String key , Object... values) {
return redisTemplate.opsForZSet().remove(key , values);
}
/**
* 为指定的key加上指定的分数,并返回增加后的值
*
* @param key
* @param value
* @param delta
* @return
*/
public Double zIncrementScore(String key , String value , double delta) {
return redisTemplate.opsForZSet().incrementScore(key , value , delta);
}
/**
* 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列
*
* @param key
* @param value
* @return 0表示第一位
*/
public Long zRank(String key , Object value) {
return redisTemplate.opsForZSet().rank(key , value);
}
/**
* 取出指定范围的元素 按下标 降序 按分数从高到低
*
* @param key
* @param value
* @return
*/
public Long zReverseRank(String key , Object value) {
return redisTemplate.opsForZSet().reverseRank(key , value);
}
/**
* 获取集合的元素, 从小到大排序
*
* @param key
* @param start 开始位置
* @param end 结束位置, -1查询所有
* @return
*/
public Set<Object> zRange(String key , long start , long end) {
return redisTemplate.opsForZSet().range(key , start , end);
}
/**
* 根据Score值查询集合元素
*
* @param key
* @param min 最小值
* @param max 最大值
* @return
*/
public Set<Object> zRangeByScore(String key , double min , double max) {
return redisTemplate.opsForZSet().rangeByScore(key , min , max);
}
/**
* 获取集合的元素, 从大到小排序
*
* @param key
* @param start
* @param end
* @return
*/
public Set<Object> zReverseRange(String key , long start , long end) {
return redisTemplate.opsForZSet().reverseRange(key , start , end);
}
/**
* 获取集合大小
*
* @param key
* @return
*/
public Long zZCard(String key) {
return redisTemplate.opsForZSet().zCard(key);
}
/**
* 查询指定key中某成员的分数
*
* @param key
* @param item
* @return
*/
public int zScore(String key , Object item) {
int score = 0;
try {
score = redisTemplate.opsForZSet().score(key , item).intValue();
} catch ( Exception ex ) {
}
return score;
}
/**
* 移除指定索引位置的成员
*
* @param key
* @param start
* @param end
* @return
*/
public Long zRemoveRange(String key , long start , long end) {
return redisTemplate.opsForZSet().removeRange(key , start , end);
}
/**
* 根据指定的score值的范围来移除成员
*
* @param key
* @param min
* @param max
* @return
*/
public Long zRemoveRangeByScore(String key , double min , double max) {
return redisTemplate.opsForZSet().removeRangeByScore(key , min , max);
}
}
import com.cy.springbootredissentinel.SpringbootRedisSentinelApplication;
import com.cy.springbootredissentinel.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
/**
* RedisTest
*
* @author wxhn1
* @since 2024-01-15 12:04
*/
@SpringBootTest(classes = SpringbootRedisSentinelApplication.class)
@Slf4j
public class RedisTest {
@Resource
RedisUtil redisUtil;
@Test
public void test() {
redisUtil.set("keyTest", "valueTest");
System.out.println("redisUtil = " + redisUtil.get("keyTest"));
}
}
单元测试效果:
像redis添加了一个keyTest键的valueTest值,查看主库从库效果:
这时我们先把主库服务给停了,就是redis6380.conf配置启动的这个服务
使用哨兵模式的一个**注意点
:假设启动了三个redis服务,如果主库挂了,那么根据哨兵原则,它会在剩余的两个从库中选一个做为主库,这时从库的配置文件会有所更改
**。如若之前挂了的主库重新上线也只就只能充当从库了
OK !!! 收尾!!!
若有误区或不能解决,私信我,远程协助你!!!