Redis 的主从和哨兵以及集群架构

Posted 超的博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis 的主从和哨兵以及集群架构相关的知识,希望对你有一定的参考价值。

Redis 的主从架构

主从架构搭建步骤

1. 添加从节点

复制一份 redis.conf 文件,将相关配置修改如下:

# 如果在不同机器上部署,端口可以不用修改
port 6380
# 把 pid 进程号写入 pidfile 配置的文件
pidfile /var/run/redis_6380.pid  
logfile "6380.log"
# 指定数据存放目录
dir /usr/local/redis-5.0.3/data/6380  

# 需要注释掉 bind
# bind 127.0.0.1(bind 绑定的是自己机器网卡的 ip,如果有多块网卡可以配多个 ip,代表允许客户端通过机器的哪些网卡 ip 去访问,内网一般可以不配置 bind,注释掉即可)

# 配置主从复制
# 从 192.168.2.10 6379 的 redis 实例复制数据,Redis 5.0 之前使用 slaveof
replicaof 192.168.2.10 6379   
# 配置从节点只读
replica-read-only yes  

2. 启动从节点

redis-server redis.conf

3. 连接从节点

redis-cli -p 6380

4. 测试

在主节点 6379 上写如数据,然后在 6380 上看看是否能读取到

5. 同理,再添加一个 6381 节点

Redis 主从工作原理

  1. 如果为一个 master 配置一个 slave ,不管这个 slave 是否第一次连接上 Master, 它都会发送一个 PSYNC 命令给 master, 请求复制数据。
  2. master 接收到 PSYNC 命令后,会在后台通过 bgsave 命令,生成最新的 rdb 快照文件,在持久化期间,master 会继续接收客户端请求,把可能修改数据的请求记录在内存中,master 会把 rdb 文件发给 slave,salve 接收到 rdb 文件后,保存在本地,然后加载到内存中。 之后,master 会把刚刚记录在内存中的修改数据的命令再发给 slave, slave 再依次执行这些命令。达到数据一致。
  3. 当 master 与 slave 之间因为网络问题而断开时, slave 能够自动连接到 master。 如果 master 收到多个 slave 的连接请求,它只会进行一次持久化动作,而不是每个连接一次,然后再把这持久化文件发送给各个 slave。

主从全量复制的流程

数据的部分复制

当主节点与从节点断开重连后,一般都会进行全量的数据复制,从 2.8 版本开始,redis 可以支持部分数据复制的命令与 master 同步,也就是断点续传。

主节点会在内存中维护一个复制数据用的缓存队列,这个队列保存这最近一段时间的数据,master 和 slave 都会维护复制数据的下标 offset,和 master 的进程 id。因此当网络重连后,slave 会请求 master 继续未完成的复制,从所记录的下标位置开始,如果 master 的进程 id 变了,或者在 master 中的缓存队列中找不到这个下标,(意味着从节点的下标 offset 太旧了) 那么将会进行一次全量的数据复制。

流程图如下:

主从复制风暴

如果有很多个从节点,多个从节点同时从主节点复制数据,导致主节点压力过大,可以做如下阶梯式架构,让部分从节点从其他从节点复制数据:

Redis 哨兵高可用架构

sentinel 哨兵是特殊的 redis 服务,不提供读写服务,主要用来监控 redis 节点。

哨兵架构下的 client 端第一次从哨兵中找出主节点,后续就直接访问 redis 的主节点,不会每次都通过哨兵代理访问主节点,当 redis 的主节点发生变化时,哨兵会第一时间感知到,并将新的 redis 主节点通知给客户端,这里的 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 <master-redis-name> <master-redis-ip> <master-redis-port> <quorum>
# quorum 是一个数字,指明当有多少个 sentinel 认为一个 master 失效时(值一般为:sentinel 总数/2 + 1),master 才算真正失效
sentinel monitor mymaster 192.168.1.32 6379 2   # mymaster 这个名字随便取,客户端访问时会用到
  1. 启动哨兵实例
src/redis-sentinel sentinel-26379.conf
  1. 连接 redis 客户端执行 info 命令查看哨兵信息
  2. 再配置另外两个 sentinel, 端口为 26380, 26381

哨兵集群启动完毕后,会将哨兵集群的元信息写入所有的 sentinel 的配置文件中,例如:

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  #代表感知到的其它哨兵节点

如果主节点(6379)宕机,哨兵集群会重新选举新的主节点,同时修改所有 sentinel 节点配置文件的集群元信息, 同时还会修改 sentinel 配置文件中的 mymaster 对应的 6379 端口,改为 6380:

sentinel monitor mymaster 192.168.0.60 6380 2

当 6379 节点再次启动时,哨兵集群根据集群元信息就可以将 6379 作为从节点加入集群中。

Redis 集群

TODO:

java 使用 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<String> sentinels = new HashSet<String>();
        sentinels.add(new HostAndPort("192.168.1.32",26379).toString());
        sentinels.add(new HostAndPort("192.168.1.32",26380).toString());
        sentinels.add(new HostAndPort("192.168.1.32",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", "dc"));
            System.out.println(jedis.get("sentinel"));
         catch (Exception e) 
            e.printStackTrace();
         finally 
            //注意这里不是关闭连接,在 JedisPool 模式下,Jedis 会被归还给资源池。
            if (jedis != null)
                jedis.close();
        
    

spting-boot 连接集群

  1. 引入依赖
dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-pool2</artifactId>
</dependency>
  1. 配置信息
server:
  port: 8080

spring:
  redis:
    database: 0
    timeout: 3000
    sentinel:    #哨兵模式
      master: mymaster #主服务器所在集群名称
     nodes: 192.168.1.32:26379,192.168.1.32:26380,192.168.1.32:26381
   lettuce:
      pool:
        max-idle: 50
        min-idle: 10
        max-active: 100
        max-wait: 1000
  1. 访问代码
@RestController
public class IndexController 

    private static final Logger logger = LoggerFactory.getLogger(IndexController.class);

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 测试节点挂了哨兵重新选举新的 master 节点,客户端是否能动态感知到
     * 新的 master 选举出来后,哨兵会把消息发布出去,客户端实际上是实现了一个消息监听机制,
     * 当哨兵把新 master 的消息发布出去,客户端会立马感知到新 master 的信息,从而动态切换访问的 masterip
     *
     * @throws InterruptedException
     */
    @RequestMapping("/test_sentinel")
    public void testSentinel() throws InterruptedException 
        int i = 1;
        while (true)
            try 
                stringRedisTemplate.opsForValue().set("zhuge"+i, i+"");
                System.out.println("设置 key:"+ "zhuge" + i);
                i++;
                Thread.sleep(1000);
            catch (Exception e)
                logger.error("错误:", e);
            
        
    

StringRedisTemplate 与 RedisTemplate 详解

spring 封装了 RedisTemplate 对象来进行对 redis 的各种操作,它支持所有的 redis 原生的 api。在 RedisTemplate 中提供了几个常用的接口方法的使用,分别是:

private ValueOperations<K, V> valueOps;
private HashOperations<K, V> hashOps;
private ListOperations<K, V> listOps;
private SetOperations<K, V> setOps;
private ZSetOperations<K, V> zSetOps;
// RedisTemplate 中定义了对 5 种数据结构操作
redisTemplate.opsForValue();//操作字符串
redisTemplate.opsForHash();//操作 hash
redisTemplate.opsForList();//操作 list
redisTemplate.opsForSet();//操作 set
redisTemplate.opsForZSet();//操作有序 set

StringRedisTemplate 继承自 RedisTemplate,也一样拥有上面这些操作。
StringRedisTemplate 默认采用的是 String 的序列化策略,保存的 key 和 value 都是采用此策略序列化保存的。
RedisTemplate 默认采用的是 JDK 的序列化策略,保存的 key 和 value 都是采用此策略序列化保存的。

Redis 客户端命令对应的 RedisTemplate 中的方法列表:

String 类型结构
RedisRedisTemplate rt
set key valuert.opsForValue().set(“key”,“value”)
get keyrt.opsForValue().get(“key”)
del keyrt.delete(“key”)
strlen keyrt.opsForValue().size(“key”)
getset key valuert.opsForValue().getAndSet(“key”,“value”)
getrange key start endrt.opsForValue().get(“key”,start,end)
append key valuert.opsForValue().append(“key”,“value”)
Hash 结构
hmset key field1 value1 field2 value2…rt.opsForHash().putAll(“key”,map) //map 是一个集合对象
hset key field valuert.opsForHash().put(“key”,“field”,“value”)
hexists key fieldrt.opsForHash().hasKey(“key”,“field”)
hgetall keyrt.opsForHash().entries(“key”) //返回 Map 对象
hvals keyrt.opsForHash().values(“key”) //返回 List 对象
hkeys keyrt.opsForHash().keys(“key”) //返回 List 对象
hmget key field1 field2…rt.opsForHash().multiGet(“key”,keyList)
hsetnx key field valuert.opsForHash().putIfAbsent(“key”,“field”,“value”
hdel key field1 field2rt.opsForHash().delete(“key”,“field1”,“field2”)
hget key fieldrt.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 indexrt.opsForList().index(“list”, index)
llen keyrt.opsForList().size(“key”)
lpop keyrt.opsForList().leftPop(“key”)
rpop keyrt.opsForList().rightPop(“key”)
lpushx list nodert.opsForList().leftPushIfPresent(“list”,“node”)
rpushx list nodert.opsForList().rightPushIfPresent(“list”,“node”)
lrange list start endrt.opsForList().range(“list”,start,end)
lrem list count valuert.opsForList().remove(“list”,count,“value”)
lset key index valuert.opsForList().set(“list”,index,“value”)
Set 结构
sadd key member1 member2…rt.boundSetOps(“key”).add(“member1”,“member2”,…)
rt.opsForSet().add(“key”, set) //set 是一个集合对象
scard keyrt.opsForSet().size(“key”)
sidff key1 key2rt.opsForSet().difference(“key1”,“key2”) //返回一个集合对象
sinter key1 key2rt.opsForSet().intersect(“key1”,“key2”)//同上
sunion key1 key2rt.opsForSet().union(“key1”,“key2”)//同上
sdiffstore des key1 key2rt.opsForSet().differenceAndStore(“key1”,“key2”,“des”)
sinter des key1 key2rt.opsForSet().intersectAndStore(“key1”,“key2”,“des”)
sunionstore des key1 key2rt.opsForSet().unionAndStore(“key1”,“key2”,“des”)
sismember key memberrt.opsForSet().isMember(“key”,“member”)
smembers keyrt.opsForSet().members(“key”)
spop keyrt.opsForSet().pop(“key”)
srandmember key countrt.opsForSet().randomMember(“key”,count)
srem key member1 member2…rt.opsForSet().remove(“key”,“member1”,“member2”,…)

redis哨兵架构

每一个哨兵都可以去监控多个master-slaves的主从架构,因为公司可能为不通过的项目部署了多个master-slaves的redis主从集群,同一套哨兵集群,就可以去监视不同的多个redis主从集群。
  • 安装redis,参照,安装即可,不需要配置redis启动相关信息。

  • 添加哨兵配置sentinal.conf

mkdir -p /etc/sentinal sentinal配置文件目录mkdir -p /var/log/sentinal/5000 sentinal存放日志路径mkdir -p /var/sentinal/5000 sentinal路径,存放持久化数据cp /usr/local/redis-5.0.5/sentinel.conf /etc/sentinal/5000.conf 复制sentinal配置文件到指定目录并改名
修改配置文件cd /etc/sentinalvi 5000.confbind 192.168.56.103 绑定自己所在机器的ip,也可以绑定0.0.0.0port 5000logfile /var/log/sentinal/5000/sentinal_5000.logdir /var/sentinal/5000daemonize yessentinel monitor mymaster 192.168.16.101 6379 2 主机ip和port,quorumsentinel down-after-milliseconds mymaster 30000sentinel parallel-syncs mymaster 1sentinel failover-timeout mymaster 60000
开启防火墙5000端口firewall-cmd --zone=public --add-port=5000/tcp --permanentfirewall-cmd --reloadfirewall-cmd --list-port
启动哨兵redis-sentinel /etc/sentinal/5000.conf
  • 检查哨兵状态

在某台哨兵机器上登录哨兵redis-cli -h 192.168.56.101 -p 5000查看哨兵监控的某个master的信息sentinel master mymaster查看监控的master的slave的信息sentinel slaves mymaster查看监控的master的哨兵集群信息,不显示当前哨兵信息sentinel sentinels mymaster根据监控的集群名字查看master的地址信息sentinel get-master-addr-by-name mymaster

集群容灾演练

  • sentinel节点增加。哨兵集群会自动发现。

  • sentinel节点删除。

  • 停掉sentinel进程。

  • sentinel reset *,在所有sentinel上执行,清理所有的master状态。

  • sentinel master mastername,在所有sentinel上执行,查看所有sentinel对数量是否达成一致。

  • slave的永久下线。停掉slave->在所有sentinel上执行sentinel reset mastername

下一篇:《redis(六)cluster》

以上是关于Redis 的主从和哨兵以及集群架构的主要内容,如果未能解决你的问题,请参考以下文章

Redis 中主从哨兵和集群这三种模式有什么区别 ?

Redis 中主从哨兵和集群这三种模式有什么区别 ?

Redis服务集群架构(主从复制哨兵模式群集模式)看这一篇就够了

玩转Redis的高可用(主从、哨兵、集群)

Redis高可用集群方案(主从复制,哨兵模式,Redis集群)

Redis高级(持久化--redis主从架构--redis哨兵模式--redis分片集群)