Redis哨兵高可用架构

sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点。
哨兵架构下client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过sentinel代理访问redis的主节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis主节点通知给client端(这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)
redis哨兵架构搭建步骤:
1、复制一份sentinel.conf文件
cp sentinel.conf sentinel-26379.conf
2、将相关配置修改为如下值:
port 26379
daemonize yes
pidfile "/var/run/redis-sentinel-26379.pid"
logfile "26379.log"
dir "/usr/local/redis-5.0.3/data"
# sentinel monitor
# quorum是一个数字,指明当有多少个sentinel认为一个master失效时(值一般为:sentinel总数/2 + 1),master才算真正失效
sentinel monitor mymaster 192.168.0.60 6379 2 # mymaster这个名字随便取,客户端访问时会用到
3、启动sentinel哨兵实例
src/redis-sentinel sentinel-26379.conf
4、查看sentinel的info信息
src/redis-cli -p 26379
127.0.0.1:26379>info
可以看到Sentinel的info里已经识别出了redis的主从
5、可以自己再配置两个sentinel,端口26380和26381,注意上述配置文件里的对应数字都要修改
sentinel集群都启动完毕后,会将哨兵集群的元数据信息写入所有sentinel的配置文件里去(追加在文件的最下面),我们查看下如下配置文件sentinel-26379.conf,如下所示:
sentinel known-replica mymaster 192.168.0.60 6380 #代表redis主节点的从节点信息
sentinel known-replica mymaster 192.168.0.60 6381 #代表redis主节点的从节点信息
sentinel known-sentinel mymaster 192.168.0.60 26380 52d0a5d70c1f90475b4fc03b6ce7c3c56935760f #代表感知到的其它哨兵节点
sentinel known-sentinel mymaster 192.168.0.60 26381 e9f530d3882f8043f76ebb8e1686438ba8bd5ca6 #代表感知到的其它哨兵节点
当redis主节点如果挂了,哨兵集群会重新选举出新的redis主节点,同时会修改所有sentinel节点配置文件的集群元数据信息,比如6379的redis如果挂了,假设选举出的新主节点是6380,则sentinel文件里的集群元数据信息会变成如下所示:
sentinel known-replica mymaster 192.168.0.60 6379 #代表主节点的从节点信息
sentinel known-replica mymaster 192.168.0.60 6381 #代表主节点的从节点信息
sentinel known-sentinel mymaster 192.168.0.60 26380 52d0a5d70c1f90475b4fc03b6ce7c3c56935760f #代表感知到的其它哨兵节点
sentinel known-sentinel mymaster 192.168.0.60 26381 e9f530d3882f8043f76ebb8e1686438ba8bd5ca6 #代表感知到的其它哨兵节点
同时还会修改sentinel文件里之前配置的mymaster对应的6379端口,改为6380
sentinel monitor mymaster 192.168.0.60 6380 2
当6379的redis实例再次启动时,哨兵集群根据集群元数据信息就可以将6379端口的redis节点作为从节点加入集群
哨兵的Jedis连接代码:
public class JedisSentinelTest {
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 sentinels = new HashSet();
sentinels.add(new HostAndPort("192.168.0.60",26379).toString());
sentinels.add(new HostAndPort("192.168.0.60",26380).toString());
sentinels.add(new HostAndPort("192.168.0.60",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", "zhuge"));
System.out.println(jedis.get("sentinel"));
} catch (Exception e) {
e.printStackTrace();
} finally {
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
if (jedis != null)
jedis.close();
}
}
}
哨兵的Spring Boot整合Redis连接代码见示例项目:redis-sentinel-cluster
1、引入相关依赖:
org.springframework.boot
spring-boot-starter-data-redis
org.apache.commons
commons-pool2
springboot项目核心配置:
1 server:
2 port: 8080
3
4 spring:
5 redis:
6 database: 0
7 timeout: 3000
8 sentinel: #哨兵模式
9 master: mymaster #主服务器所在集群名称
10 nodes: 192.168.0.60:26379,192.168.0.60:26380,192.168.0.60:26381
11 lettuce:
12 pool:
13 max-idle: 50
14 min-idle: 10
15 max-active: 100
16 max-wait: 1000
17
访问代码:
1 @RestController
2 public class IndexController {
3
4 private static final Logger logger = LoggerFactory.getLogger(IndexController.class);
5
6 @Autowired
7 private StringRedisTemplate stringRedisTemplate;
8
9 /**
10 * 测试节点挂了哨兵重新选举新的master节点,客户端是否能动态感知到
11 * 新的master选举出来后,哨兵会把消息发布出去,客户端实际上是实现了一个消息监听机制,
12 * 当哨兵把新master的消息发布出去,客户端会立马感知到新master的信息,从而动态切换访问的masterip
13 *
14 * @throws InterruptedException
15 */
16 @RequestMapping("/test_sentinel")
17 public void testSentinel() throws InterruptedException {
18 int i = 1;
19 while (true){
20 try {
21 stringRedisTemplate.opsForValue().set("zhuge"+i, i+"");
22 System.out.println("设置key:"+ "zhuge" + i);
23 i++;
24 Thread.sleep(1000);
25 }catch (Exception e){
26 logger.error("错误:", e);
27 }
28 }
29 }
30 }
StringRedisTemplate与RedisTemplate详解
spring 封装了 RedisTemplate 对象来进行对redis的各种操作,它支持所有的 redis 原生的 api。在RedisTemplate中提供了几个常用的接口方法的使用,分别是:
1 private ValueOperations valueOps;
2 private HashOperations hashOps;
3 private ListOperations listOps;
4 private SetOperations setOps;
5 private ZSetOperations zSetOps;
RedisTemplate中定义了对5种数据结构操作
1 redisTemplate.opsForValue();//操作字符串
2 redisTemplate.opsForHash();//操作hash
3 redisTemplate.opsForList();//操作list
4 redisTemplate.opsForSet();//操作set
5 redisTemplate.opsForZSet();//操作有序set
StringRedisTemplate继承自RedisTemplate,也一样拥有上面这些操作。
StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。
Redis客户端命令对应的RedisTemplate中的方法列表:
|
String类型结构
|
|
Redis
|
RedisTemplate rt
|
|
set key value
|
rt.opsForValue().set("key","value")
|
|
get key
|
rt.opsForValue().get("key")
|
|
del key
|
rt.delete("key")
|
|
strlen key
|
rt.opsForValue().size("key")
|
|
getset key value
|
rt.opsForValue().getAndSet("key","value")
|
|
getrange key start end
|
rt.opsForValue().get("key",start,end)
|
|
append key value
|
rt.opsForValue().append("key","value")
|
| |
|
|
Hash结构
|
|
hmset key field1 value1 field2 value2...
|
rt.opsForHash().putAll("key",map) //map是一个集合对象
|
|
hset key field value
|
rt.opsForHash().put("key","field","value")
|
|
hexists key field
|
rt.opsForHash().hasKey("key","field")
|
|
hgetall key
|
rt.opsForHash().entries("key") //返回Map对象
|
|
hvals key
|
rt.opsForHash().values("key") //返回List对象
|
|
hkeys key
|
rt.opsForHash().keys("key") //返回List对象
|
|
hmget key field1 field2...
|
rt.opsForHash().multiGet("key",keyList)
|
|
hsetnx key field value
|
rt.opsForHash().putIfAbsent("key","field","value"
|
|
hdel key field1 field2
|
rt.opsForHash().delete("key","field1","field2")
|
|
hget key field
|
rt.opsForHash().get("key","field")
|
|
|
|
|
List结构
|
|
lpush list node1 node2 node3...
|
rt.opsForList().leftPush("list","node")
|
|
rt.opsForList().leftPushAll("list",list) //list是集合对象
|
|
rpush list node1 node2 node3...
|
rt.opsForList().rightPush("list","node")
|
|
rt.opsForList().rightPushAll("list",list) //list是集合对象
|
|
lindex key index
|
rt.opsForList().index("list", index)
|
|
llen key
|
rt.opsForList().size("key")
|
|
lpop key
|
rt.opsForList().leftPop("key")
|
|
rpop key
|
rt.opsForList().rightPop("key")
|
|
lpushx list node
|
rt.opsForList().leftPushIfPresent("list","node")
|
|
rpushx list node
|
rt.opsForList().rightPushIfPresent("list","node")
|
|
lrange list start end
|
rt.opsForList().range("list",start,end)
|
|
lrem list count value
|
rt.opsForList().remove("list",count,"value")
|
|
lset key index value
|
rt.opsForList().set("list",index,"value")
|
| |
|
|
Set结构
|
|
sadd key member1 member2...
|
rt.boundSetOps("key").add("member1","member2",...)
|
|
rt.opsForSet().add("key", set) //set是一个集合对象
|
|
scard key
|
rt.opsForSet().size("key")
|
|
sidff key1 key2
|
rt.opsForSet().difference("key1","key2") //返回一个集合对象
|
|
sinter key1 key2
|
rt.opsForSet().intersect("key1","key2")//同上
|
|
sunion key1 key2
|
rt.opsForSet().union("key1","key2")//同上
|
|
sdiffstore des key1 key2
|
rt.opsForSet().differenceAndStore("key1","key2","des")
|
|
sinter des key1 key2
|
rt.opsForSet().intersectAndStore("key1","key2","des")
|
|
sunionstore des key1 key2
|
rt.opsForSet().unionAndStore("key1","key2","des")
|
|
sismember key member
|
rt.opsForSet().isMember("key","member")
|
|
smembers key
|
rt.opsForSet().members("key")
|
|
spop key
|
rt.opsForSet().pop("key")
|
|
srandmember key count
|
rt.opsForSet().randomMember("key",count)
|
|
srem key member1 member2...
|
rt.opsForSet().remove("key","member1","member2",...)
|