作为一个稀有的Java妹子,所写的所有博客都只是当作自己的笔记,留下证据自己之前是有用心学习的~哈哈哈哈(如果有不对的地方,也请大家指出,不要悄悄咪咪的不告诉我)
概述
redis在实际应用中安装部署的方式有所不同,如果对数据没有持久化的要求,只是为了提高读取数据效率,可能会采用单机模式;但是生产环境中基本上都会要求持久化数据,备份数据,节点失效转移等,那么这时候单节点部署就不能满足了,下面就介绍一下redis的高可用方案。
redis的高可用方案主要有两种:
1.主从复制(Replication-Sentinel模式)
2.集群(Redis-Cluster模式)
主从复制(Replication-Sentinel模式)
Redis Sentinel 是 Redis 高可用 的实现方案。Sentinel 是一个管理多个 Redis 实例的工具,它可以实现对 Redis 的 监控、通知、自动故障转移。下面先对 Redis Sentinel 的 基本概念 进行简单的介绍。
基本名词 | 逻辑结构 | 物理结构 |
---|---|---|
Redis数据节点 | 主节点和从节点 | 主节点和从节点的进程 |
主节点(master) | Redis主数据库 | 一个独立的Redis进程 |
从节点(slave) | Redis从数据库 | 一个独立的Redis进程 |
Sentinel节点 | 监控Redis数据节点 | 一个独立的Sentinel进程 |
Sentinel节点集合 | 若干Sentinel节点的抽象组合 | 若干Sentinel节点进程 |
Redis Sentinel | Redis高可用实现方案 | Sentinel节点集合和Redis数据节点进程 |
1.Redis Sentinel的主要功能
1.从节点分担主节点的读压力
2.Sentinel监控主节点和从节点服务器的状态
3.当被监控的某个 Redis 服务器出现问题,Sentinel 通过 API 脚本 向 管理员 或者其他的 应用程序 发送通知。
4.当 主节点 不能正常工作时,Sentinel 会开始一次 自动的故障转移操作,它会将与 失效主节点 是 主从关系 的其中一个 从节点 升级为新的 主节点,并且将其他的 从节点 指向 新的主节点,然后还会修改配置文件。
5.在 Redis Sentinel 模式下,客户端应用 在初始化时连接的是 Sentinel 节点集合,从中获取 主节点 的信息,因为如果直接连接主节点信息,主节点挂掉后还要修改配置。
2.主观下线和客观下线
默认情况下,每个 Sentinel 节点会以 每秒一次 的频率对 Redis 节点和 其它 的 Sentinel 节点发送 PING 命令,并通过节点的 回复 来判断节点是否在线。
主观下线
主观下线 适用于所有 主节点 和 从节点。如果在 down-after-milliseconds 毫秒内,Sentinel 没有收到 目标节点 的有效回复,则会判定 该节点 为 主观下线。
客观下线
客观下线 只适用于 主节点。如果 主节点 出现故障,Sentinel 节点会通过 sentinel is-master-down-by-addr 命令,向其它 Sentinel 节点询问对该节点的 状态判断。如果超过 个数的节点判定 主节点 不可达,则该 Sentinel 节点会判断 主节点 为 客观下线。
主观下线只是其中一个哨兵节点探测到主节点不可达了,客观下线指当主节点主观下线后,超过指定个数额哨兵也去探测到主节点宕机了。主节点客观下线后就开始了故障转移,从新选举主节点的操作。
3.Redis Sentinel搭建
1.一个稳健的 Redis Sentinel 集群,应该使用至少 三个 Sentinel 实例,并且保证讲这些实例放到 不同的机器 上,甚至不同的 物理区域。
2.Sentinel 无法保证 强一致性。
3.常见的 客户端应用库 都支持 Sentinel。
4.Sentinel 需要通过不断的 测试 和 观察,才能保证高可用。
Redis Sentinel的配置文件
# 哨兵sentinel实例运行的端口,默认26379
port 26379
# 哨兵sentinel的工作目录
dir ./
# 哨兵sentinel监控的redis主节点的
## ip:主机ip地址
## port:哨兵端口号
## master-name:可以自己命名的主节点名字(只能由字母A-z、数字0-9 、这三个字符".-_"组成。)
## quorum:当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
# 当在Redis实例中开启了requirepass <foobared>,所有连接Redis实例的客户端都要提供密码。
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster 123456
# 指定主节点应答哨兵sentinel的最大时间间隔,超过这个时间,哨兵主观上认为主节点下线,默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
# 指定了在发生failover主备切换时,最多可以有多少个slave同时对新的master进行同步。这个数字越小,完成failover所需的时间就越长;反之,但是如果这个数字越大,就意味着越多的slave因为replication而不可用。可以通过将这个值设为1,来保证每次只有一个slave,处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间failover-timeout,默认三分钟,可以用在以下这些方面:
## 1. 同一个sentinel对同一个master两次failover之间的间隔时间。
## 2. 当一个slave从一个错误的master那里同步数据时开始,直到slave被纠正为从正确的master那里同步数据时结束。
## 3. 当想要取消一个正在进行的failover时所需要的时间。
## 4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来同步数据了
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
# 当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本。一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
# 对于脚本的运行结果有以下规则:
## 1. 若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10。
## 2. 若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
## 3. 如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
3.1 Redis Sentinel的节点规划
这里是模拟,所以是在一台物理机上部署的,生产环境上应该在不同的服务器上部署。特别是哨兵节点和redis节点不同部署在同一物理机上,不然redis挂了,哨兵节点也挂了,就检测不到redis的状态了。
角色 | IP地址 | 端口号 |
---|---|---|
Redis Master | 127.0.01 | 16379 |
Redis Slave1 | 127.0.01 | 26379 |
Redis Slave2 | 127.0.01 | 36379 |
Redis Sentinel1 | 127.0.01 | 16380 |
Redis Sentinel2 | 127.0.01 | 26380 |
Redis Sentinel3 | 127.0.01 | 36380 |
3.2 Redis-Server的配置管理
1.分别拷贝三份 redis.conf 文件到 /usr/local/redis-sentinel 目录下面。三个配置文件分别对应 master、slave1 和 slave2 三个 Redis 节点的 启动配置。
cp /usr/local/redis-5.0.7/redis.conf /usr/local/redis-sentinel/redis-16379
cp /usr/local/redis-5.0.7/redis.conf /usr/local/redis-sentinel/redis-26379
cp /usr/local/redis-5.0.7/redis.conf /usr/local/redis-sentinel/redis-36379
2.分别修改三份配置文件如下:
主节点:redis-16379.conf
daemonize yes
pidfile /var/run/redis-16379.pid
logfile /var/log/redis/redis-16379.log
port 16379
bind 0.0.0.0
timeout 300
databases 16
dbfilename dump-16379.db
dir ./redis-workdir
masterauth 123456
requirepass 123456
从节点1:redis-26379.conf
daemonize yes
pidfile /var/run/redis-26379.pid
logfile /var/log/redis/redis-26379.log
port 26379
bind 0.0.0.0
timeout 300
databases 16
dbfilename dump-26379.db
dir ./redis-workdir
masterauth 123456
requirepass 123456
replicaof 127.0.0.1 16379
从节点2:redis-36379.conf
daemonize yes
pidfile /var/run/redis-36379.pid
logfile /var/log/redis/redis-36379.log
port 36379
bind 0.0.0.0
timeout 300
databases 16
dbfilename dump-36379.db
dir ./redis-workdir
masterauth 123456
requirepass 123456
replicaof 127.0.0.1 16379
如果要做 自动故障转移,建议所有的 redis.conf 都设置 masterauth。
因为自动故障只会重写 主从关系,即 slaveof,不会自动写入
masterauth。如果 Redis 原本没有设置密码,则可以忽略
3.按顺序分别启动 16379,26379 和 36379 三个 Redis 节点,启动命令和启动日志如下:
redis-server /usr/local/redis-sentinel/redis-16379.conf
redis-server /usr/local/redis-sentinel/redis-26379.conf
redis-server /usr/local/redis-sentinel/redis-36379.conf
主节点的启动日志如下:
6282:M 06 Mar 2020 14:19:05.966 * Replica 127.0.0.1:26379 asks for synchronization
6282:M 06 Mar 2020 14:19:08.738 * Replica 127.0.0.1:36379 asks for synchronization
这两行表明从节点从主节点同步信息
从节点启动日志:
6285:S 06 Mar 2020 14:19:05.964 * Connecting to MASTER 127.0.0.1:16379
从节点的这句话表明已经连接到主节点上
3.3 Sentinel的配置管理
1.分别拷贝三份 redis-sentinel.conf 文件到 /usr/local/redis-sentinel 目录下面。三个配置文件分别对应 master、slave1 和 slave2 三个 Redis 节点的 哨兵配置。
cp /usr/local/redis-5.0.7/sentinel /usr/local/redis-sentinel/sentinel-16380.conf
cp /usr/local/redis-5.0.7/sentinel /usr/local/redis-sentinel/sentinel-26380.conf
cp /usr/local/redis-5.0.7/sentinel /usr/local/redis-sentinel/sentinel-36380.conf
三个节点的配置信息如下
protected-mode no
bind 0.0.0.0
port 16380
daemonize yes
sentinel monitor mymaster 127.0.0.1 16379 2
sentinel down-after-milliseconds master 5000
sentinel failover-timeout master 180000
sentinel parallel-syncs master 1
sentinel auth-pass master 123456
logfile /var/log/redis/sentinel-16380.log
protected-mode no
bind 0.0.0.0
port 26380
daemonize yes
sentinel monitor mymaster 127.0.0.1 16379 2
sentinel down-after-milliseconds master 5000
sentinel failover-timeout master 180000
sentinel parallel-syncs master 1
sentinel auth-pass master 123456
logfile /var/log/redis/sentinel-26380.log
protected-mode no
bind 0.0.0.0
port 36380
daemonize yes
sentinel monitor mymaster 127.0.0.1 16379 2
sentinel down-after-milliseconds master 5000
sentinel failover-timeout master 180000
sentinel parallel-syncs master 1
sentinel auth-pass master 123456
logfile /var/log/redis/sentinel-26380.log
2.按顺序分别启动 16380,26380 和 36380 三个 Sentinel 节点,启动命令和启动日志如下:
MacBook-Pro:redis-5.0.7 root# tail -200f sentinel-16380.log
8684:X 06 Mar 2020 16:02:11.450 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
8684:X 06 Mar 2020 16:02:11.451 # Redis version=5.0.5, bits=64, commit=00000000, modified=0, pid=8684, just started
8684:X 06 Mar 2020 16:02:11.451 # Configuration loaded
8685:X 06 Mar 2020 16:02:11.453 * Increased maximum number of open files to 10032 (it was originally set to 4864).
8685:X 06 Mar 2020 16:02:11.454 * Running mode=sentinel, port=16380.
8685:X 06 Mar 2020 16:02:11.455 # Sentinel ID is 1188a54e05eed0df0e230bfa1c9cec01929252a2
//这里显示监控的主节点信息
8685:X 06 Mar 2020 16:02:11.455 # +monitor master mymaster 127.0.0.1 16379 quorum 2
//监控的从节点的信息
8685:X 06 Mar 2020 16:02:11.458 * +slave slave 127.0.0.1:26379 127.0.0.1 26379 @ mymaster 127.0.0.1 16379
8685:X 06 Mar 2020 16:02:11.459 * +slave slave 127.0.0.1:36379 127.0.0.1 36379 @ mymaster 127.0.0.1 16379
//当另外两个哨兵节点启动后,也会加入到该哨兵节点的配置中去
8685:X 06 Mar 2020 16:02:17.138 * +sentinel sentinel 9fd6cb78ef74b46530f9ce74c7d7abf53a606c12 127.0.0.1 26380 @ mymaster 127.0.0.1 16379
8685:X 06 Mar 2020 16:02:19.946 * +sentinel sentinel 1a47466bf6142d00039cf6983ba1e860db4cfe00 127.0.0.1 36380 @ mymaster 127.0.0.1 16379
MacBook-Pro:redis-5.0.7 root# tail -200f sentinel-26380.log
8686:X 06 Mar 2020 16:02:15.086 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
8686:X 06 Mar 2020 16:02:15.086 # Redis version=5.0.5, bits=64, commit=00000000, modified=0, pid=8686, just started
8686:X 06 Mar 2020 16:02:15.086 # Configuration loaded
8687:X 06 Mar 2020 16:02:15.089 * Increased maximum number of open files to 10032 (it was originally set to 4864).
8687:X 06 Mar 2020 16:02:15.091 * Running mode=sentinel, port=26380.
8687:X 06 Mar 2020 16:02:15.092 # Sentinel ID is 9fd6cb78ef74b46530f9ce74c7d7abf53a606c12
8687:X 06 Mar 2020 16:02:15.092 # +monitor master mymaster 127.0.0.1 16379 quorum 2
8687:X 06 Mar 2020 16:02:15.094 * +slave slave 127.0.0.1:26379 127.0.0.1 26379 @ mymaster 127.0.0.1 16379
8687:X 06 Mar 2020 16:02:15.095 * +slave slave 127.0.0.1:36379 127.0.0.1 36379 @ mymaster 127.0.0.1 16379
8687:X 06 Mar 2020 16:02:15.503 * +sentinel sentinel 1188a54e05eed0df0e230bfa1c9cec01929252a2 127.0.0.1 16380 @ mymaster 127.0.0.1 16379
8687:X 06 Mar 2020 16:02:19.946 * +sentinel sentinel 1a47466bf6142d00039cf6983ba1e860db4cfe00 127.0.0.1 36380 @ mymaster 127.0.0.1 16379
MacBook-Pro:redis-5.0.7 root# tail -200f sentinel-36380.log
8688:X 06 Mar 2020 16:02:17.917 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
8688:X 06 Mar 2020 16:02:17.917 # Redis version=5.0.5, bits=64, commit=00000000, modified=0, pid=8688, just started
8688:X 06 Mar 2020 16:02:17.917 # Configuration loaded
8689:X 06 Mar 2020 16:02:17.920 * Increased maximum number of open files to 10032 (it was originally set to 4864).
8689:X 06 Mar 2020 16:02:17.921 * Running mode=sentinel, port=36380.
8689:X 06 Mar 2020 16:02:17.922 # Sentinel ID is 1a47466bf6142d00039cf6983ba1e860db4cfe00
8689:X 06 Mar 2020 16:02:17.922 # +monitor master mymaster 127.0.0.1 16379 quorum 2
8689:X 06 Mar 2020 16:02:17.923 * +slave slave 127.0.0.1:26379 127.0.0.1 26379 @ mymaster 127.0.0.1 16379
8689:X 06 Mar 2020 16:02:17.924 * +slave slave 127.0.0.1:36379 127.0.0.1 36379 @ mymaster 127.0.0.1 16379
8689:X 06 Mar 2020 16:02:19.151 * +sentinel sentinel 9fd6cb78ef74b46530f9ce74c7d7abf53a606c12 127.0.0.1 26380 @ mymaster 127.0.0.1 16379
8689:X 06 Mar 2020 16:02:19.554 * +sentinel sentinel 1188a54e05eed0df0e230bfa1c9cec01929252a2 127.0.0.1 16380 @ mymaster 127.0.0.1 16379
4.springboot整合Redis Sentinel
1.配置文件
java.redis.pool.max-total=10
java.redis.pool.max-idle=5
java.redis.pool.min-idle=5
# 多个用逗号隔开,host:port,host2:port2
java.redis.sentinel.nodes=127.0.0.1:16380,127.0.0.1:26380,127.0.0.1:36380
java.redis.sentinel.password=123456
java.redis.sentinel.master-name=mymaster
java.redis.sentinel.pool.max-total=10
java.redis.sentinel.pool.max-idle=5
java.redis.sentinel.pool.min-idle=5
2.配置类
@Slf4j
@Component
public class RedisNodeConfig implements InitializingBean {
@Value("${java.redis.pool.max-total:10}")
private Integer redisMaxTotal;
@Value("${java.redis.pool.min-idle}")
private Integer redisMaxIdle;
@Value("${java.redis.pool.min-idle}")
private Integer redisMinIdle;
@Value("${java.redis.sentinel.nodes}")
private String redisSentinelNodes;
@Value("${java.redis.sentinel.password}")
private String redisSentinelPassword;
@Value("${java.redis.sentinel.master-name}")
private String redisSentinelMasterName;
@Value("${java.redis.sentinel.pool.max-total}")
private Integer redisSentinelMaxTotal;
@Value("${java.redis.sentinel.pool.max-idle}")
private Integer redisSentinelMaxIdle;
@Value("${java.redis.sentinel.pool.min-idle}")
private Integer redisSentinelMinIdle;
private JedisSentinelPool jedisPool;
@Override
public void afterPropertiesSet() throws Exception {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(redisMaxTotal);
jedisPoolConfig.setMaxIdle(redisMaxIdle);
jedisPoolConfig.setMinIdle(redisMinIdle);
String[] hosts = redisSentinelNodes.split(",");
Set<String> sentinels = new HashSet<>(Arrays.asList(hosts));
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(redisSentinelMaxTotal);
poolConfig.setMaxIdle(redisSentinelMaxIdle);
poolConfig.setMinIdle(redisSentinelMinIdle);
jedisPool = new JedisSentinelPool(redisSentinelMasterName, sentinels, jedisPoolConfig);
if (isConnectSuccess()) {
// 初始化成功
log.info("++++++ Init Redis Sentinel Pool SUCCESS! ++++++");
} else {
// 初始化失败
log.info("++++++ Init Redis Sentinel Pool FAILURE! ++++++");
}
}
public boolean isConnectSuccess() {
Jedis jedis = getRedis();
String ping = jedis.ping();
recycleRedis(jedis);
return ping.equals("PONG");
}
/**
* @return 连接池中的Jedis实例
*/
public Jedis getRedis() {
return jedisPool.getResource();
}
/**
* 资源回收
*
* @param jedis jedis实例
*/
public void recycleRedis(Jedis jedis) {
jedis.close();
}
/**
* 销毁Redis连接池
*/
public void close() {
if (null != jedisPool) {
jedisPool.close();
}
}
}
3.服务类
@Component
public class RedisNodeService implements InitializingBean {
@Autowired
private RedisNodeConfig redisNodeConfig;
private Jedis jedis;
@Override
public void afterPropertiesSet() throws Exception {
jedis = redisNodeConfig.getRedis();
}
public void set(String key,String value){
jedis.set(key,value);
}
}
4.测试
@Service
@Slf4j
public class TestService{
@Autowired
private RedisNodeService redisNodeService;
public void testRedisService() {
redisNodeService.set("testnode","testnode");
}
}
连接redis的主节点:
127.0.0.1:16379> get testnode
"testnode"
5.模拟主节点失效
1.将主节点进程手动杀掉
MacBook-Pro:redis-5.0.7 root# ps -ef|grep 16379
0 8208 1 0 3:38下午 ?? 0:15.52 redis-server 0.0.0.0:16379
0 9683 6027 0 4:54下午 ttys000 0:00.01 grep 16379
MacBook-Pro:redis-5.0.7 root# kill -9 8208
2.查看sentinel日志
//判断主节点主观下线
8685:X 06 Mar 2020 16:54:16.817 # +sdown master mymaster 127.0.0.1 16379
8685:X 06 Mar 2020 16:54:16.916 # +new-epoch 1
8685:X 06 Mar 2020 16:54:16.917 # +vote-for-leader 9fd6cb78ef74b46530f9ce74c7d7abf53a606c12 1
//超过2个哨兵节点认同主节点失效,主节点变为客观下线
8685:X 06 Mar 2020 16:54:17.887 # +odown master mymaster 127.0.0.1 16379 #quorum 3/2
8685:X 06 Mar 2020 16:54:17.887 # Next failover delay: I will not start a failover before Fri Mar 6 17:00:17 2020
8685:X 06 Mar 2020 16:54:18.045 # +config-update-from sentinel 9fd6cb78ef74b46530f9ce74c7d7abf53a606c12 127.0.0.1 26380 @ mymaster 127.0.0.1 16379
//开始更换主节点为36379
8685:X 06 Mar 2020 16:54:18.045 # +switch-master mymaster 127.0.0.1 16379 127.0.0.1 36379
//然后把原来的从节点26379和原来的主节点16379都变为36379的从节点
8685:X 06 Mar 2020 16:54:18.046 * +slave slave 127.0.0.1:26379 127.0.0.1 26379 @ mymaster 127.0.0.1 36379
8685:X 06 Mar 2020 16:54:18.046 * +slave slave 127.0.0.1:16379 127.0.0.1 16379 @ mymaster 127.0.0.1 36379
8685:X 06 Mar 2020 16:54:23.092 # +sdown slave 127.0.0.1:16379 127.0.0.1 16379 @ mymaster 127.0.0.1 36379
这时候去看redis的配置文件会发现replicaof 127.0.0.1 16379这一行变为了replicaof 127.0.0.1 36379,sentinel的配置文件中的sentinel monitor mymaster 127.0.0.1 16379 2也变为了sentinel monitor mymaster 127.0.0.1 36379 2
3.因为哨兵模式的节点故障转移,现在主节点已经变为了端口号为36379的服务了,再测试下springboot项目是否能正常连接redis,所有的配置项都没有动,
@Service
@Slf4j
public class TestService{
@Autowired
private RedisNodeService redisNodeService;
public void testRedisService() {
redisNodeService.set("redis-sentinel","redis-sentinel");
}
}
127.0.0.1:36379> get redis-sentinel
"redis-sentinel"
集群(Redis-Cluster模式)
主从模式下从节点只能分担读压力,不能分担写压力,而且主节点的写能力和存储能力受单机的限制。集群模式可以做到从节点分担写压力,可以扩充主节点的写能力和存储能力。
具体如何实现还未研究