目录
一、Redis主从复制
full resync(全量复制)
partial resync(增量复制)
二、Redis主从复制配置
三、哨兵机制原理
每个 Sentinel 都需要定期执行的任务
自动发现 Sentinel 和从服务器
Sentinel 选主规则
四、哨兵机制配置
五、哨兵机制整合SpringBoot
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者为主节点(master),后者称为从节点(slave);数据的复制只能由主节点到从节点。主从复制保证了数据的备份,高可用(配合哨兵机制)、负载均衡(读写分离分担master压力)。
Redis主从数据同步有两种方式,(full resync)全量复制和增量复制(partial resync)
Redis通过psync命令进行全量复制的过程如下:
由于网络原因导致主从服务器断开连接,当主从重新连接之后,不需要全量复制,只需要进行增量复制。因为主从服务器都会维持一个offset(偏移量),当连接恢复之后,对比两者的偏移量,把不同的数据同步过来。
在命令传播阶段,主节点除了将写命令发送给从节点,还会发送一份给积压缓冲区,作为写命令的备份;除了存储写命令,积压缓冲区中还存储了其中的每个字节对应的复制偏移量(offset)。由于复制积压缓冲区定长且是先进先出(队列),所以它保存的是主节点最近执行的写命令;时间较早的写命令会被挤出缓冲区。
由于该缓冲区长度固定且有限,因此可以备份的写命令也有限,当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。反过来说,为了提高网络中断时部分复制执行的概率,可以根据需要增大复制积压缓冲区的大小(通过配置repl-backlog-size 1M 默认是1M);
每个Redis节点(无论主从),在启动时都会自动生成一个随机ID(每次启动都不一样),由40个随机的十六进制字符组成;runid用来唯一识别一个Redis节点。通过info Server命令,可以查看节点的runid。
主从节点初次复制时,主节点将自己的runid发送给从节点,从节点将这个runid保存起来;当断线重连时,从节点会将这个runid发送给主节点;主节点根据runid判断能否进行部分复制:
配置方式有两种,这种方式不推荐,如果从服务器过多,数据同步效率很差
采用树状结构
搭建Redis:Redis入门&Redis安装(传统方式&Docker方式)_熟透的蜗牛的博客-CSDN博客
配置主从复制
由于我是在一台虚拟机上模拟所以需要修改响应的端口号为6379,6380,6381
修改响应的 pidfile /var/run/redis_6379.pid,pidfile /var/run/redis_6380.pid,pidfile /var/run/redis_6381.pid
在6380配置文件添加指向主节点
slaveof 192.168.139.154 6379
#密码
masterauth xiaojie
在6381上修改配置文件如下
slaveof 192.168.139.154 6380
masterauth xiaojie
启动redis实例
#由于是同一台虚拟机,启动时候需要指定端口,不然默认是6379
[root@bogon bin]# ./redis-cli -p 6379
[root@bogon bin]# ./redis-cli -p 6380
[root@bogon bin]# ./redis-cli -p 6381
输入指令
127.0.0.1:6379> info replication
此时我们Redis主从复制已经搭建好了,在6379写入数据,其他两个节点能同步数据,从节点从2.6版本之后默认只能读不能写,这个可以配置replica-read-only yes(yes只能读不能写)。这个时候如果主节点宕机之后,从节点不能写,然后服务就不能用了,于是引入哨兵机制。
Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务:
Redis Sentinel 是一个分布式系统, 你可以在一个架构中运行多个 Sentinel 进程(progress), 这些进程使用流言协议(gossip protocols)来接收关于主服务器是否下线的信息, 并使用投票协议(agreement protocols)来决定是否执行自动故障迁移, 以及选择哪个从服务器作为新的主服务器。
一个 Sentinel 可以与其他多个 Sentinel 进行连接, 各个 Sentinel 之间可以互相检查对方的可用性, 并进行信息交换。
你无须为运行的每个 Sentinel 分别设置其他 Sentinel 的地址, 因为 Sentinel 可以通过发布与订阅功能来自动发现正在监视相同主服务器的其他 Sentinel , 这一功能是通过向频道 sentinel:hello 发送信息来实现的。
与此类似, 你也不必手动列出主服务器属下的所有从服务器, 因为 Sentinel 可以通过(info)询问主服务器来获得所有从服务器的信息。
复制redis解压文件中的sentinel.conf文件到安装目录的bin下面
[root@bogon bin]# cp /usr/local/redis-6.2.5/sentinel.conf /usr/local/redis6379/bin/
[root@bogon bin]# cp /usr/local/redis-6.2.5/sentinel.conf /usr/local/redis6380/bin/
[root@bogon bin]# cp /usr/local/redis-6.2.5/sentinel.conf /usr/local/redis6381/bin/
[root@bogon bin]#
修改sentinel.conf
#后台启动
daemonize yes
#修改端口
port 26379-26381
#指定pid
pidfile /var/run/redis-sentinel-6379.pid
#指定监听的主节点 2是至少有2个哨兵认为宕机的个数
sentinel monitor mymaster 192.168.139.154 6379 2
#指定主节点密码
sentinel auth-pass mymaster xiaojie
#指定哨兵的密码
requirepass xiaojie
#打开这个注释
protected-mode no
启动哨兵
./redis-sentinel sentinel.conf
然后手动宕机6379服务节点。
启动6379的节点再检查info replication
此时6380节点被选举为master,6379原来的master成为了slave。
来 上才艺,不对 !上代码
4.0.0
com.xiaojie
springboot-redis
1.0-SNAPSHOT
8
8
spring-boot-starter-parent
org.springframework.boot
2.4.2
org.springframework.boot
spring-boot-starter-data-redis-reactive
org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.4
mysql
mysql-connector-java
com.alibaba
druid
1.1.12
com.alibaba
fastjson
1.2.47
com.fasterxml.jackson.core
jackson-databind
2.10.4
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-configuration-processor
2.2.7.RELEASE
org.projectlombok
lombok
1.18.20
provided
spring:
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
datasource:
name: iot-home
url: jdbc:mysql://127.0.0.1:3306/my_test?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
password: xiaojie #这个密码一定要加,然后才能保证能连接到redis服务器,如果没有配置密码则不需要
connect-timeout: 5000
database: 0
sentinel:
master: mymaster
nodes: 192.168.6.137:26379,192.168.6.137:26380,192.168.6.137:26381
password: xiaojie #这个密码是哨兵的密码,如果在sentinel.conf中没有配置requirepass则不需要
package com.xiaojie.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.net.UnknownHostException;
/*
*
* @param null
* @redis配置
* @author xiaojie
* @date 2021/9/8
* @return
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper=new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
public User getUserByName(String name) {
JSONObject obj= (JSONObject) redisUtil.get(USERKEY + ":" + name);
if (null==obj){
System.out.println("缓存中没有该值,查询数据库");
User resultUser = userMapper.selectByName(name);
if (null!= resultUser) {
redisUtil.set(USERKEY+":"+resultUser.getName(), JSONObject.toJSON(resultUser),30);
return resultUser;
}
}
User user = JSONObject.toJavaObject(obj,User.class);
return user;
}
测试:宕机主节点,然后还能继续写入缓存,搭建完成。
完整代码 :spring-boot: Springboot整合redis、消息中间件等相关代码
参考:
深入学习Redis(3):主从复制 - 编程迷思 - 博客园
REDIS sentinel-old -- Redis中国用户组(CRUG)