六、Redis的高并发 & 高可用(主从+哨兵)

一、Redis的复制

Redis的复制是Redis主从结构、哨兵集群的重要实现依据,Redis在复制中数据的流向只能是由主节点  -->  从节点。

二、Redis复制的拓扑结构

1、一主一从

一主一从

结构:只有一个主节点和一个从节点,从节点同步主节点的数据,做主节点的数据备份。

场景:读写分离,降低单个节点的压力。

           故障转移,如果主节点宕机,从节点升级为主节点。

扩展:可以只开启从节点的数据持久化,提升主节点的性能。但是一旦主节点重启,主节点数据清空,且主节点不会向从节点拿数据,就会导致从节点也跟着同步主节点的数据,最终导致数据丢失。

2、一主多从

一主多从

结构:只有一台主节点,有多台从节点,所有从节点都从主节点同步数据。

场景:在读数据很多的场景下,为了减缓单台Redis的读压力,可以添加多台Redis从节点。

缺点:由于主节点每写入一次数据,就要向从节点推送一次数据,如果从节点过多,势必会对主节点造成影响,提高主节点压力。

3、树状主从

树状主从

结构:只有一台主节点,有多台从节点,从节点不是全部都从主节点复制数据,而是只有一部分从节点从主节点复制数据,剩下从节点从其他从节点复制数据。

场景:解决掉了多台从节点都从主节点复制数据的性能问题。

缺点:结构相对复杂,难以维护。

4、主从结构下的持久化

不管使用哪种主从结构,都建议全部开启持久化,减少数据的丢失量。

三、Redis主从结构的配置

Redis主从相关配置
命令作用
任何一台Redis启动时,不加干预,都默认是一台主节点。
info Replication

查询Redis服务的信息

可以看到role是master,表示当前节点是主节点。

flushdb

清空当前Redis节点的数据

在配置文件中加入如下配置

# slaveof <masterip> <masterport>
slaveof 127.0.0.1 6380

以这个配置文件启动Redis,当前Redis节点会变成从节点,并且以127.0.0.1:6380的Redis节点为主节点。

这时可以看到节点信息是slave。

Redis客户端输入:

slaveof 127.0.0.1 6380

如果Redis节点已经启动了,也可以在客户端输入以下命令实现从节点的转换。

命令表示先将当前节点变成从节点,再链接6380主节点。

Redis客户端输入:

salveof no one

先断开原有的主从关系,再把当前从节点变成主节点。

6380节点收到了 6381和6382两个从节点的请求。

从节点默认只有读权限,没有写权限。

config get slave-read-only

查询当前节点是否只有读权限,yes代表只能读不能写。

config set slave-read-only no

关闭从节点的只读限制,让从节点也能写入数据。

不过不建议开启从节点的写权限,容易造成主从数据不一致

配置文件配置

masterauth 可以设置连接主节点的密码

从库连接主库需要校验密码

掌握这些命令就可以愉快地搭建主从结构啦!下面我们来看看主从复制是如何实现数据同步的呢?

四、Redis主从复制原理

① slaveof ip port 命令只是让从节点记录主节点的信息。

② 第一次主从之间同步数据是需要同步主节点已存在的全量数据,比较费时间。

③ 2.8版本之后采用 psync 的方式同步数据,全量同步 + 部分同步,提高同步效率。

④ 从库的过期策略:不会进行定期扫描,key在主库过期后再同步给从库,从库做del操作。

1、psync命令

刚建立主从关系时,如果主节点的数据量太大,使用全量复制会很耗费时间,故加入了psync命令。psync 命令在Redis 2.8 版本以后加入,运行过程分 “全量同步” 和 “部分同步”。

psync 命令的执行需要以下支持:

① 主从节点各自复制偏移量。

② 主节点复制积压缓冲区。

③ 主节点运行id。

2、全量复制

全量复制原理图

一般用于初次复制场景(第一次建立slave后全量复制),主节点给从节点发送一个RDB数据包。

3、全量复制有哪些问题?

主节点数据量太大,打包RDB文件慢,网络发送RDB文件时间长,从节点解析RDB文件慢。

=====================================================================

解决办法:

配置文件调整 repl-timeout 60 参数,同步RDB时间达到60s使同步作废。

如果想提高全量复制成功率,可以适当增加过期时间,通过计算主节点数据量和带宽 可以得到一个大概的传输时间:数据量 ➗ 带宽 ✖ 2。

如果主节点的写入流量太大,很可能将【客户端缓冲区】冲垮。

=====================================================================

解决办法:

配置文件调整 client-output-buffer-limit slave 256mb 64mb 60 参数

slave:表示主从复制。

256mb:表示如果客户端缓冲区的总数据量达到256mb,停止第7步传输。

64mb 60:表示如果在60s内的数据量达到64mb,停止第7步传输。

既然全量复制不能完全满足初始数据的同步,那我们该怎么办呢?

4、部分复制

部分复制原理图

部分复制是应对全量复制过程中出现网络中断时,从节点再次连接主节点时,主节点补发缺少的数据。

5、心跳

主从节点之间有长连接心跳,主节点默认每10s向从节点发送ping命令,来检查从节点是否存活。

repl-ping-slave-period 控制发送频率。

从节点每1s向主节点发送一次心跳 ping ,通知主节点自己存活。

6、总结

无论是全量复制还是部分复制,底层都是异步执行的,实时数据进入主节点后,都要暂存到客户端缓冲区或复制积压缓冲区,再启动其他任务发送这些缓冲区的数据,不会阻塞主线程。

所以主从复制中数据同步具有一定的延迟,部分复制在正常情况下延迟在1s内,具体看网络问题。

但是在这样的主从集群下,一旦主节点宕机,整套集群就会失去写入数据的能力,不能达到高可用。

五、Redis哨兵 + 主从

1、原理

当主节点出现故障时,由Redis Sentinel自动完成故障发现和转移,并通知应用方,实现高可用。

说人话就是主节点挂掉时哨兵会选一个从节点出来当新的主节点,即使出现数据丢失,一般也只会丢失1s的数据。

哨兵集群节点关系​​​​

2、Redis哨兵的配置

一般配置3台哨兵节点,防止哨兵掉线,而且配置3台哨兵也是为了更快地选出领导者哨兵。

Redis哨兵的配置与启动
配置作用
sentinel monitor mymaster 127.0.0.1 6379 2

配置当前哨兵监控的主节点信息,包括ip和端口。

最后这个 2 要注意,后面要考。表示该节点有2票就能被哨兵投票出局。

windows版本启动:

redis-server.exe redis.windows.conf --sentinel

以哨兵身份启动一个哨兵节点,本质上启动的还是redis,但是职责在配置文件中变了

linux版本启动:

sh redis-centinel.sh redis.conf

sentinel down-after-milliseconds mymaster 3000主观下线时间,配置节点超过3s未响应哨兵的ping命令,就认为这台redis节点主观下线了
Redis哨兵启动后控制台输出​​​​​
在配置文件中回写的id

这样就配置好并且启动了redis哨兵啦!

3、为什么需要启动多个Redis哨兵?

Redis加入哨兵机制是为了处理单点故障问题,避免单台Redis节点故障影响整个集群。Redis哨兵本质上也是一个Redis节点,如果只有一台Redis哨兵,那么这台哨兵自己就可能出现单点故障,还是没达到高可用。

4、启动多台Redis哨兵会影响性能吗?

肯定是会的,但是Redis哨兵的职责只是监控其他Redis节点,不负责数据吞吐,并不会产生特别大的性能影响,可以容忍这样的性能消耗。

5、Redis哨兵的三个定时任务:

info

第一个:info

机制:每隔10s,哨兵给集群中每个节点发送info指令,可以拿到当前集群中的拓扑结构,同步到主从之间的信息。

publish/subscribe(发布/订阅)

第二个:publish/subscribe(发布/订阅)

机制:每隔2s,所有的哨兵都向主节点发送一次发布订阅。

功能:① 通过master节点,哨兵之间就可以感知到其他哨兵的存在。

           ② 哨兵之间可以交换主节点的信息,为将来出现master的下线或主节点的选举提供数据支撑。

ping

第三个:ping

机制:每隔1s,哨兵节点都向所有节点发送一个ping命令。

功能:检查每个节点是否故障,如果某个节点过了很长时间未响应,那就认为这个节点故障了。

6、Redis哨兵判断主从节点下线

哨兵检测节点下线流程图

主观下线:

哨兵ping某个数据节点,超过配置文件中 down-after-milliseconds 的时长未响应,那么哨兵就认为这个数据节点下线了,这时还不能确认数据节点是不是真的下线了,因为有可能是这个哨兵下线了。这时不会做故障转移,而且去继续确认。

客观下线:

哨兵在发现有节点主观下线后,会去询问其他哨兵节点,检测目标节点是否下线,最后哨兵之间投票确认,如果达到配置文件中【sentinel monitor mymaster 127.0.0.1 6379 2(前面提到要考)】配置的 2 个哨兵认为下线了,那么就废调这个节点,执行故障转移。

7、领导者哨兵选举

如果master节点客观下线了,要做的第一件事不是选举新的master节点,而是确定出让哪个哨兵节点来做这件事。这个过程叫领导者哨兵选举。

比如哨兵1 和 哨兵2 同时发现master节点下线了,哨兵1 向 哨兵2 和哨兵3 进行询问,哨兵2 向哨兵1 和哨兵3 进行询问,哨兵2 最先获得2票,那么就选哨兵2 做领导者,让它来选出新的master节点。

哨兵领导者选举涉及一些深层次的算法,raft算法,我还不会,就先略过。

8、哨兵故障转移流程

哨兵故障转移流程

哨兵故障转移流程

9、在Java程序中连接哨兵+主从

在客户端程序中,需要知道最新的主节点和从节点的关系。如果出现故障转移,那么Java客户端就需要及时发现新的主节点。以下是在Java程序中配置哨兵的代码示例。

只要配置好 JedisSentinelPool 以后,再使用 jedisSentinelPool.getResource() 方法拿到Jedis实例,使用起来就和Jedis一模一样了。

但是这里的从节点只是做数据备份,还不能做读写分离。因为 JedisSentinelPool 连接池只能连接到master节点。

 先看配置文件:

# jedis Sentinel Pool
# host:port
redis.sentinels=127.0.0.1:16380,127.0.0.1:16381,127.0.0.1:16382
redis.sentinel.master=mymaster
# unit:Millis
redis.sentinel.timeout=5000
# unit:s
redis.sentinel.maxIdle=10
redis.sentinel.maxWaitMillis=2000
redis.sentinel.blockWhenExhausted=true
redis.sentinel.jmxEnabled=true

再看配置类:

package sentinel.config;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisSentinelPool;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * redis哨兵集群的连接配置
 *
 * @Date: 2024/07/26/8:01
 */
@Configuration
@PropertySource("classpath:application.properties")
public class RedisSentinelConfig {
    @Value("${redis.sentinels}")
    private String hosts;

    @Value("${redis.sentinel.master}")
    private String master;

    @Value("${redis.sentinel.timeout}")
    private int timeout;

    @Value("${redis.sentinel.maxIdle}")
    private int maxIdle;

    @Value("${redis.sentinel.maxWaitMillis}")
    private int maxWaitMillis;

    @Value("${redis.sentinel.blockWhenExhausted}")
    private Boolean blockWhenExhausted;

    @Value("${redis.sentinel.jmxEnabled}")
    private Boolean jmxEnabled;


    @Bean
    public JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
        // 连接耗尽时是否阻塞, false报异常,true阻塞直到超时, 默认true
        jedisPoolConfig.setBlockWhenExhausted(blockWhenExhausted);
        // 是否启用pool的jmx管理功能, 默认true
        jedisPoolConfig.setJmxEnabled(jmxEnabled);

        jedisPoolConfig.setTestOnBorrow(true);
        jedisPoolConfig.setTestOnReturn(true);

        return jedisPoolConfig;
    }

    @Bean
    public JedisSentinelPool jedisSentinelPoolFactory() {
        if (StrUtil.isBlank(hosts)) {
            throw new RuntimeException("请配置redis哨兵集群的节点");
        }

        List<String> hostList = Arrays.asList(hosts.split(","));
        if (CollectionUtil.isEmpty(hostList)) {
            throw new RuntimeException("hostList is empty");
        }

        Set<String> hostSet = new HashSet<>(hostList);
        return new JedisSentinelPool(master, hostSet, jedisPoolConfig(), timeout);
    }
}

10、哨兵+主从如何实现读写分离?

我没有找到现成的哨兵+主从实现读写分离插件,不过理论上可以使用Java代码实现读写分离。只要拿到哨兵节点后就可以读取到主从之间的拓扑结构,拿到从节点的host+port就可以对从节点进行读操作了。

  • 17
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值