Redis主从与哨兵模式构建与原理分析

Redis 集群

主从模式

一主多从,从节点会不断的从主节点同步数据。主节点提供读写功能,从节点提供读功能。一般来说会让主节点用于写操作,从节点用来读操作,读写分离减少服务器压力。

但是从节点在同步数据的时候需要时间,可能就会造成数据不一致的情况,最好是读写都在主节点,从节点用来做热备了。

添加主节点 redis 的配置文件

# 关闭保护模式
protected-mode no

#设置redis端口
port 6379

#设置非守护进程启动,不然跟 docker 的 -d 冲突
daemonize no

# 注释bing采用密码模式访问
#bind 127.0.0.1

#设置主节点的连接密码
masterauth 123456

#这个很重要!!!设置本redis的外网访问ip和端口
replica-announce-ip 外网ip
replica-announce-port 6379

#设置redis访问密码
requirepass 123456

添加从节点 redis 配置文件

# 关闭保护模式
protected-mode no

#设置redis端口
port 6380

#设置非守护进程启动,不然跟 docker 的 -d 冲突
daemonize no

# 注释bing采用密码模式访问
#bind 127.0.0.1

#设置主节点的连接密码
masterauth 123456

#设置关联主节点
replicaof 主节点外网访问ip 6379

#这个很重要!!!设置本redis的外网访问ip和端口
replica-announce-ip 外网ip
replica-announce-port 6379

#设置redis访问密码
requirepass 123456

新建一主二从:

docker run --name redis-1 -p 6379:6379 -d -v /data/redis-1:/usr/local/redis redis redis-server /usr/local/redis/redis-1.conf

docker run --name redis-2 -p 6380:6380 -d -v /data/redis-2:/usr/local/redis redis redis-server /usr/local/redis/redis-2.conf

docker run --name redis-3 -p 6381:6381 -d -v /data/redis-3:/usr/local/redis redis redis-server /usr/local/redis/redis-3.conf
哨兵模式
哨兵配置

在官网下载一个 sentinel.conf 文件,修改配置:

#设置哨兵端口
port 26379

#关闭守护进程启动
protected-mode no

#指定日志位置
logfile "/usr/local/redis/logs/sentinel.log"


#设置本哨兵外网访问ip和端口
sentinel announce-ip 外网ip
sentinel announce-port 26379

#设置监听的主节点地址和最小投票哨兵节点
sentinel monitor mymaster 外网访问ip 6379 2

#设置主节点的连接密码
sentinel auth-pass mymaster 123456

注意:哨兵当中的配置请勿设置密码 requirepass 123456,不然会造成异常 NOAUTH HELLO must be called with the client already authenticated, otherwise the HELLO AUTH <user> <pass> option can be used to authenticate the client and select the RESP protocol version at the same time

然后启动三个容器:

docker run -d --name=sentinel-1 --privileged=true -p 26379:26379 -v /data/sentinel-1:/usr/local/redis redis redis-sentinel /usr/local/redis/sentinel-1.conf

docker run -d --name=sentinel-2 --privileged=true -p 26380:26380 -v /data/sentinel-2:/usr/local/redis redis redis-sentinel /usr/local/redis/sentinel-2.conf

docker run -d --name=sentinel-3 --privileged=true -p 26381:26381 -v /data/sentinel-3:/usr/local/redis redis redis-sentinel /usr/local/redis/sentinel-3.conf

stop master redis 过一会就会自动进行故障转移了。

手动指定master

如果三台主节点都是 slave,如何重新指定 master 呢。

首先在准备 master 的 redis-cli 中输入

slaveof no one
config set  slave-read-only no

在其他从节点中关联 master:

# 如果master存在密码需要先配置密码
config set masterauth 123456

# 关联master的ip和端口
slaveof 192.168.1.12 6379
spring boot连接redis 哨兵

yml

spring:
  redis:
    database: 0
    sentinel:
      nodes: xxxx.xxxx.xxxx:26381,xxxx.xxxx.xxxx:26380,xxxx.xxxx.xxxx:26379 # 集群哨兵节点配置,多个节点之间用英文逗号分割
      master: mymaster # Redis主节点名称,哨兵配置文件中的名称
    password: 123456
    lettuce:
      pool:
        max-active: 20
        max-wait: 500
        max-idle: 10
        min-idle: 0

配置文件

@Configuration
public class RedisConfig {

    @Bean
    protected LettuceConnectionFactory redisConnectionFactory(RedisProperties redisProperties) {
        RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
                .master(redisProperties.getSentinel().getMaster());

        redisProperties.getSentinel().getNodes().forEach(s -> {
            String[] arr = s.split(":");
            sentinelConfig.sentinel(arr[0],Integer.parseInt(arr[1]));
        });

        sentinelConfig.setPassword(RedisPassword.of(redisProperties.getPassword()));
        sentinelConfig.setDatabase(redisProperties.getDatabase());
        return new LettuceConnectionFactory(sentinelConfig, LettuceClientConfiguration.defaultConfiguration());
    }

    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();

        template.setConnectionFactory(factory);

        Jackson2JsonRedisSerializer jacksonJsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper om = new ObjectMapper();

        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会抛出异常
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonJsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jacksonJsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jacksonJsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }


}
redis 配置参数注释
-------------------------------------
bind:本机的网卡对应的IP地址。而不是限制其他 ip 的应用访问。如果绑定了其他的 ip 就会启动不起来(不过我试着绑定了其他网卡的ip似乎也启动不起来~)
-------------------------------------
protected-mode yes yes是开启保护模式。当保护模式开启时并且未 bing ip,未设置密码,服务器只接受 127.0.0.1 和 ::1 的请求。
-------------------------------------
port 6379 设置 redis 连接的端口
-------------------------------------
tcp-backlog 511 此参数确定了TCP连接中已完成队列(完成三次握手之后)的长度, 当然此值必须不大于 Linux 系统定义的 /proc/sys/net/core/somaxconn 值,默认是 511,而Linux的默认参数值是 128
-------------------------------------
timeout 0 客户端超时时间,当客户端连接空闲超过当前时间会被自动关闭,0 表示不自动关闭。单位为秒。
-------------------------------------
loglevel notice 日志级别

debug:最详细的日志信息
verbose:比 debug 少一点的日志信息
notice:
warning:仅记录关键的日志信息
-------------------------------------
daemonize no 是否以守护进程启动,如果在 docker 中该参数是 yes 并且 docker run 参数中存在 -d 则会启动失败的。
-------------------------------------
logfile "" 指定日志文件名称
-------------------------------------
syslog-enabled no  是否启用将记录记载到系统日志功能
-------------------------------------
databases 16  设置数据库的数量
-------------------------------------
always-show-logo yes  启动是否显示logo
-------------------------------------
save 900 1  900 秒后检查一次,如果触发一次写入操作则将数据保存到磁盘当中。可以配置多个 save
-------------------------------------
stop-writes-on-bgsave-error yes 如果启用了 RDB 模式(至少一个保存点)并且最新的备份保存失败,Redis 将停止接受写入。恢复备份后继续接受。
-------------------------------------
stop-writes-on-bgsave-error yes dump .rdb 启用 LZF 压缩
-------------------------------------
rdbchecksum yes 是否进行 CRC64 校验
-------------------------------------
dbfilename dump.rdb 指定 dump 的文件名
-------------------------------------
dir ./ 指定 dump 的目录,这里只能指定目录
-------------------------------------
replicaof <masterip> <masterport> 5.0 之后设置主从的命令,5.0之前使用 slaveof,slaveof 可能在高版本后废弃。
-------------------------------------
masterauth <master-password> 指定主节点密码,在主从当中如果当前未正确配置则会停止复制。
-------------------------------------
replica-read-only yes 设置从节点只读。
-------------------------------------
repl-diskless-sync no 使用 Disk-backed 传输方式,master 将数据写入到 rdb 文件,然后使用增量备份的方式将数据传输给从节点。如果是 yes 这是无盘复制,redis 直接通过 socket 将数据发送给从节点不落地到磁盘。
-------------------------------------
repl-diskless-sync-delay 5 无盘传输延时等待时间。
-------------------------------------
replica-priority 100  从节点的优先级,优先级越高越可能在故障转移中成为主节点。
-------------------------------------
replica-announce-ip 5.5.5.5
replica-announce-port 1234
-------------------------------------
requirepass foobared 设置密码
-------------------------------------
maxclients 10000 设置redis同时可以与多少个客户端进行连接,如果达到了此限制,redis则会拒绝新的连接请求,并且向这些连接请求方法发出"max number of clients reached"以作回应
-------------------------------------
maxmemory <bytes> 设置可使用的最大容量
-------------------------------------
maxmemory-policy noeviction key 淘汰策略

   noeviction:当内存使用达到阈值的时候,所有引起申请内存的命令会报错。

   allkeys-lru:在主键空间中,优先移除最近未使用的key。

   volatile-lru:在设置了过期时间的键空间中,优先移除最近未使用的key。

   allkeys-random:在主键空间中,随机移除某个key。

   volatile-random:在设置了过期时间的键空间中,随机移除某个key。

   volatile-ttl:在设置了过期时间的键空间中,具有更早过期时间的key优先移除。
-------------------------------------
maxmemory-samples 5 随机淘汰最久未被使用的 key 的个数
-------------------------------------
appendonly no 设置是否启用 AOF 模式
-------------------------------------
appendfilename "appendonly.aof" AOF 模式名称
-------------------------------------
# appendfsync always
appendfsync everysec
# appendfsync no
-------------------------------------
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
-------------------------------------
lua-time-limit 5000 lua 脚本可以运行的最长时间
-------------------------------------
cluster-enabled yes 是否启用 cluster
-------------------------------------
cluster-config-file nodes-6379.conf cluster 的配置文件
-------------------------------------
cluster-node-timeout 15000 cluster 节点超时时间
-------------------------------------
slowlog-log-slower-than 10000 慢日志时间
-------------------------------------
aof-rewrite-incremental-fsync yes
-------------------------------------
rdb-save-incremental-fsync yes
哨兵配置参数注释
#设置 redis 或者 sentinel 持续多少毫秒不可访问则将节点视为 S_DOWN 状态
sentinel down-after-milliseconds mymaster 30000
-------------------------------------
#设置 acl 最大的日志存储的最大值
acllog-max-len 128
-------------------------------------
#指定最多有能多少个 slave 节点对新的 master 进行同步
sentinel parallel-syncs mymaster 1
-------------------------------------
#指定故障转移超时时间
sentinel failover-timeout mymaster 180000
-------------------------------------
#指定通知脚本
sentinel notification-script mymaster /var/redis/notify.sh
-------------------------------------
#指定进行故障转移后进行通知的脚本
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
-------------------------------------
#配置是否可以使用 SENTINEL SET 重新配置 notification-script 和 client-reconfig-script 脚本
sentinel deny-scripts-reconfig yes
-------------------------------------
#配置是否启用域名解析配置
SENTINEL resolve-hostnames no
-------------------------------------
主从复制原理

全量同步

Redis 全量复制一般发生在 Slave 初始化阶段,这时 Slave 需要将 Master 上的所有数据都复制一份。

  • 从服务器连接主服务器,发送 SYNC 命令;
  • 主服务器接收到 SYNC 命名后,开始执行 BGSAVE 命令生成 RDB 文件并使用缓冲区记录此后执行的所有写命令;
  • 主服务器 BGSAVE 执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
  • 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
  • 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
  • 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;

master 节点会开启一个后台进程用于将 redis 中的数据生成一个 rdb 文件,与此同时,服务器会缓存所有接收到的来自客户端的写命令(包含增、删、改),当后台保存进程处理完毕后,会将该 rdb 文件传递给 slave 服务器,而slave 服务器会将 rdb 文件保存在磁盘并通过读取该文件将数据加载到内存,在此之后 master 节点会将在此期间缓存的命令通过 redis 传输协议发送给 slave 服务器,然后 slave 服务器将这些命令依次作用于自己本地的数据集上最终达到数据的一致性。

增量同步

Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。

增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

从 redis 2.8 版本以前,并不支持增量同步,当主从服务器之间的连接断掉之后,master 节点和slave 节点之间都是进行全量数据同步,但是从 redis 2.8 开始,即使主从连接中途断掉,也不需要进行全量同步,因为从这个版本开始融入了部分同步的概念。部分同步的实现依赖于在 master 节点内存中给每个 slave 节点维护了一份同步日志和同步标识,每个 slave 节点在跟 master 节点进行同步时都会携带自己的同步标识和上次同步的最后位置。

当主从连接断掉之后,slave 节点隔断时间(默认1s)主动尝试和 master 节点进行连接,如果从节点携带的偏移量标识还在 master 节点的同步备份日志中,那么就从 slave 发送的偏移量开始继续上次的同步操作,如果 slave 发送的偏移量已经不在 master 的同步备份日志中(可能由于主从之间断掉的时间比较长或者在断掉的短暂时间内 master 节点接收到大量的写操作),则必须进行一次全量更新。

在部分同步过程中,master 会将本地记录的同步备份日志中记录的指令依次发送给 slave 服务器从而达到数据一致。

主从节点都维护一个复制偏移量(replication offset)和 master run id ,当连接断开时,从节点会重新连接上主节点,然后请求继续复制,假如主从节点的两个 master run id 相同,并且指定的偏移量在内存缓冲
区中还有效,复制就会从上次中断的点开始继续。如果其中一个条件不满足,就会进行完全重新同步(在2.8版本之前就是直接进行完全重新同步)。因为 master run id 不保存在磁盘中,如果从节点重启了的话就只能进行完全同步了。

Redis主从同步策略

主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

redis 哨兵模式是如何进行故障转移的

故障发现

主观下线:指某个节点认为另一个节点不可用,即下线状态,这个状态并不是最终的故障判定,只能代表一个节点的意见,可能存在误判情况。
客观下线:指标记一个节点真正的下线,集群内多个节点都认为该节点不可用,从而达成共识的结果。如果是持有槽的主节点故障,需要为该节点进行故障转移。

选举

  • 发送 is-master-down-by-addr 进行要求将自己设置为 leader.
  • 收到命令的 sentinel 会判断,如果其没有同意过其他的 sentinel 节点的命令则同意,否则拒绝
  • 如果该 sentinel 发现自己的票数大于等于 max(quorum,num(sentinels)/2) ;则将成为领导者将进入下一次选举

故障转移

  • 过滤:“不健康”(主观下线、断线)、5 秒内没有回复过 Sentinel 节点 ping 响应、与主节点失联超过 down-after-milliseconds * 10 秒。
  • 选择 slave-priority(从节点优先级)最高的从节点列表,如果存在则返回,不存在则继续。
  • 选择复制偏移量最大的从节点(复制的最完整),如果存在则返回,不存在则继续。
  • 选择 runid 最小的从节点。

Sentinel leader 节点会对第一步选出来的从节点执行 slaveof no one 命令让其成为主节点,然后向剩余的从节点发送命令,让它们成为新主节点的从节点,复制规则和parallel-syncs参数有关。Sentinel节点集合会将原来的主节点更新为从节点,并保持着对其关注,当其恢复后命令它去复制新的主节点。

redis 脑裂问题

脑裂现象是主节点出现假的客观下线但是和客户端又还能通信进而导致数据丢失的情景。

出现原因:

1、旧的主节点在哨兵节点选举时突然恢复和客户端的通信,客户端将数据写入到旧的主节点,导致新的主节点产生后丢失这部分的写入数据。
2、网络分区让主节点和客户端在一个分区,哨兵和从节点在一个分区,这时候哨兵就会进行重新选举产生新的主节点。

解决方案:

min-slaves-to-write:与主节点通信的从节点数量必须大于等于该值主节点,否则主节点拒绝写入。

min-slaves-max-lag:主节点与从节点通信的ACK消息延迟必须小于该值,否则主节点拒绝写入。

这两个配置项必须同时满足,不然主节点拒绝写入。

但是这个只是规避脑裂,不能完全解决脑裂问题。

cluster 模式
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值