近况

最近两天把redis复习了一下,确实学习到了很多新的东西,我对redis的了解还远远不够啊,慢慢学吧,
之前有搭过redis主从复制,哨兵模式,分片集群,现在使用docker重新搭建了一次,当然复习redis不光是搭建
这些东西,主要目标是想学习redis的分布式缓存,认识一下到底什么是分布式缓存,OK,回归正题,
现在暂且记录一下docker搭建三个基本的redis模型吧。

  • Docker搭建主从复制

    • 基本配置文件和目录

      redis-MasterAndSlave/		#主目录
      ├── docker-compose.yaml
      ├── master/ #目录       
      │   ├── data/			#目录
      │   ├── redis.conf
      ├── slave1/ 
      │   ├── data/      
      │   ├── redis.conf
      └── slave2/
          ├── data/      
          ├── redis.conf
    • redis.conf
      master中redis.conf

      port 6379
        protected-mode no
        
        repl-diskless-sync no
        repl-disable-tcp-nodelay no
        
        requirepass  master123765		#给主机设置密码

      slave中redis.conf

      port 6379
        protected-mode no
        
        slaveof 172.25.0.101 6379
        masterauth master123765		#主机密码
        slave-read-only yes
        slave-serve-stale-data yes
        
        requirepass 123765    #从机密码
    • docker-compose.yaml

      version: '3'
      
      services:
        #reids-master 
        redis-master:
          container_name: redis-master
          image: redis:latest
          ports:
            - '6379:6379'
          networks:
            mynetwork:    #配置固定IP
              ipv4_address: 172.19.0.10
          volumes:
            - "./master/redis.conf:/etc/redis.conf"
            - "./master/data:/data"			#redis的持久化配置文件将会保存在该目录
          command: ["redis-server", "/etc/redis.conf"]
          restart: always		#容器启动失败后会不断尝试重启
          
        #reids-slave1 
        redis-slave1:
          container_name: redis-slave1
          image: redis:latest
          depends_on:
            - redis-master
          ports:          
            - '6380:6379'
          networks:
            mynetwork:    #配置固定IP
              ipv4_address: 172.19.0.11
          volumes:
            - "./slave1/redis.conf:/etc/redis.conf"
            - "./slave1/data:/data"
          command: ["redis-server", "/etc/redis.conf"]
          restart: always
          
        #reids-slave2
        redis-slave2:
          container_name: redis-slave2
          image: redis:latest
          depends_on:
            - redis-master
          ports:         
            - '6381:6379'
          networks:    
            mynetwork:    #配置固定IP
              ipv4_address: 172.19.0.12
          volumes:
            - "./slave2/redis.conf:/etc/redis.conf"
            - "./slave2/data:/data"
          command: ["redis-server", "/etc/redis.conf"]
          restart: always
          
      networks:
        mynetwork:  #配置一个子网,前提是没有这个网络,如果有,docker network rm mynetwork
          driver: bridge		#这个子网会在配置文件启动时自动创建
          ipam:
            config:
              - subnet: 172.19.0.0/24 
    • 然后启动主从复制
      在主目录下执行,docker-compose up -d ,关闭直接:docker-compose down
      进入master容器中:docker exec -it redis-master bash
      进入redis客户端:redis-cli -a master123765,需填写密码
      或者你可以先进入客户端:redis-cli,然后再密码认证auth master123765
      注意:主从复制模式下,主机挂掉后,从机并不会上位,依然是从机状态,当主机重新上线后,
      仍然维持主机状态,若想当主机挂掉后,从机上位成主机,除了手动通过命令外,还可使用哨兵模式。

  • Docker搭建哨兵模式

    哨兵,用来监控master节点,当master节点挂掉后,通过选举将slave节点转换成master结点,
    哨兵节点务必大于三个才有效,哨兵节点需要获取所有节点的信息,但其只需监控master即可,通过master
    即可获取到slave节点,其他哨兵结点的信息,下面是搭建步骤

    • 基本配置文件和目录

      sentinel/
      ├── docker-compose.yml
      ├── master/
      │   ├── data/
      │   └── redis.conf
      ├── slave1/
      │   ├── data/
      │   └── redis.conf
      ├── slave2/
      |   ├── data/
      |   └── redis.conf
      ├── sentinel1
      │   └── sentinel.conf
      ├── sentinel2
      │   └── sentinel.conf
      └── sentinel3
          └── sentinel.conf
    • redis.conf
      master的redis.conf

      port 6379
      pidfile /var/run/redis_6379.pid
      protected-mode no
      timeout 0
      tcp-keepalive 300
      loglevel notice
      
      requirepass  123765
      
      ################################# REPLICATION #################################
      slave-serve-stale-data yes
      slave-read-only yes
      repl-diskless-sync no
      repl-diskless-sync-delay 5
      repl-disable-tcp-nodelay no
      
      ##################################### RDB #####################################
      dbfilename dump.rdb
      save 900 1
      save 300 10
      save 60 10000
      stop-writes-on-bgsave-error yes
      rdbcompression yes
      rdbchecksum yes
      dir ./
      
      ##################################### AOF #####################################
      appendonly yes
      appendfilename "appendonly.aof"
      appendfsync everysec
      no-appendfsync-on-rewrite no
      aof-load-truncated yes
      aof-use-rdb-preamble no

      slave的redis.conf

      # 在master的redis.conf的REPLICATION下添加,其他与master一致
      slaveof 172.19.0.10 6379		#主机地址
      masterauth 123765			#主机的密码
      requirepass 123765	#自己redis的密码
      
      注意:哨兵模式下,主机的密码和从机的密码必须一样,不然从机上位不了!!!
    • sentinel.conf

      # 所有哨兵端口都一致,因为使用 Docker 桥接网络映射 
      port 26379
      
      # 哨兵设置,所有哨兵皆一致,都指向 Master
      sentinel monitor mymaster 172.19.0.10 6379 2
      sentinel parallel-syncs mymaster 1
      sentinel auth-pass mymaster 123765	#配置主机密码,从机和主机密码务必一致
      sentinel down-after-milliseconds mymaster 30000
      sentinel failover-timeout mymaster 180000
      
      bind 0.0.0.0
      protected-mode no
      daemonize no
      pidfile /var/run/redis-sentinel.pid
      logfile ""
      dir /tmp
    • docker-compose.yaml

      version: "3"
      
      networks:
        redis-replication:
          driver: bridge
          ipam:
            config:
              - subnet: 172.19.0.0/24
      
      services:
        master:
          image: redis
          container_name: redis-master
          ports:
            - "6379:6379"
          volumes:
            - "./master/redis.conf:/etc/redis.conf"
            - "./master/data:/data"
          command: ["redis-server", "/etc/redis.conf"]
          restart: always
          networks:
            redis-replication:
              ipv4_address: 172.19.0.10
      
        slave1:
          image: redis
          container_name: redis-slave-1
          ports:
            - "6380:6379"
          volumes:
            - "./slave1/redis.conf:/etc/redis.conf"
            - "./slave1/data:/data"
          command: ["redis-server", "/etc/redis.conf"]
          restart: always
          networks:
            redis-replication:
              ipv4_address: 172.19.0.11
      
        slave2:
          image: redis
          container_name: redis-slave-2
          ports:
            - "6381:6379"
          volumes:
            - "./slave2/redis.conf:/etc/redis.conf"
            - "./slave2/data:/data"
          command: ["redis-server", "/etc/redis.conf"]
          restart: always
          networks:
            redis-replication:
              ipv4_address: 172.19.0.12
        
        sentinel1:
          image: redis
          container_name: redis-sentinel-1
          ports:
            - "26380:26379"
          volumes: 
            - "./sentinel1/sentinel.conf:/etc/sentinel.conf"
          command: ["/bin/bash", "-c", "cp /etc/sentinel.conf /sentinel.conf && redis-sentinel /sentinel.conf"]
          restart: always
          networks:
            redis-replication:
              ipv4_address: 172.19.0.13
      
        sentinel2:
          image: redis
          container_name: redis-sentinel-2
          ports:
            - "26381:26379"
          volumes: 
            - "./sentinel2/sentinel.conf:/etc/sentinel.conf"
          command: ["/bin/bash", "-c", "cp /etc/sentinel.conf /sentinel.conf && redis-sentinel /sentinel.conf"]
          restart: always
          networks:
            redis-replication:
              ipv4_address: 172.19.0.14
      
        sentinel3:
          image: redis
          container_name: redis-sentinel-3
          ports:
            - "26382:26379"
          volumes: 
            - "./sentinel3/sentinel.conf:/etc/sentinel.conf"
          command: ["/bin/bash", "-c", "cp /etc/sentinel.conf /sentinel.conf && redis-sentinel /sentinel.conf"]
          restart: always
          networks:
            redis-replication:
              ipv4_address: 172.19.0.15

      以上就是docker搭建哨兵模式的配置步骤

  • Docker搭建分片集群
    主从复制解决了 Redis 的性能问题,哨兵模式解决了 Redis 的可用性问题,但单个master仍然抗住高并发写
    因此分片集群产生,分片集群有多个master,每个master至少一台slave,分片指的是将数据分片存储。
    分片集群会根据数据计算分出16384个插槽(0-16383),插槽指的是存储单位,任何一个数据要想存储到集群
    都会将根据该数据做一个计算,看这个数据属于哪个插槽,而不同的master管理不同范围的插槽。
    如果一个客户端向集群中一个结点存储数据,redis便通过计算,判断这个数据属于哪个插槽,再将请求转给
    相应的master节点进行处理,所以不管你向集群中master结点存储数据,还是slave结点存储数据都行,
    redis会帮助你转发请求。

    这个分片就是划分不同的master管理不同范围的插槽,通过计算划分16384个插槽
    再根据数据计算该数据应该存放在哪个插槽中,这不就是散列存储,像散列表一样,大概如此吧。

    分片集群最少需要6个结点,3主3从,下面是分片集群的配置步骤

    • 基本的配置文件和目录

      redis-cluster/
      ├── docker-compose.yaml
      ├── node1
      │   ├── data
      │   └── redis.conf
      ├── node2
      │   ├── data
      │   └── redis.conf
      ├── node3
      │   ├── data
      │   └── redis.conf
      ├── node4
      │   ├── data
      │   └── redis.conf
      ├── node5
      │   ├── data
      │   └── redis.conf
      └── node6
          ├── data
          └── redis.conf
    • redis.conf

      port 6371		#6371~6376
      protected-mode no
      daemonize no
      ################################ REDIS CLUSTER  ###############################
      cluster-enabled yes
      cluster-config-file nodes.conf
      cluster-node-timeout 15000
      cluster-announce-bus-port 16371		#16371~16376

      6个redis.conf,除了port和cluster-announce-bus-port不同,其他都一样。

    • docker-compose.yaml

      version: "3"
      
      services:
        node1:
          image: redis
          container_name: redis-cluster-node-1
          network_mode: "host"
          volumes:
            - "./node1/redis.conf:/etc/redis.conf"
            - "./node1/data:/data"
          command: ["redis-server", "/etc/redis.conf"]
          restart: always
      
        node2:
          image: redis
          container_name: redis-cluster-node-2
          network_mode: "host"
          volumes:
            - "./node2/redis.conf:/etc/redis.conf"
            - "./node2/data:/data"
          command: ["redis-server", "/etc/redis.conf"]
          restart: always
      
        node3:
          image: redis
          container_name: redis-cluster-node-3
          network_mode: "host"
          volumes:
            - "./node3/redis.conf:/etc/redis.conf"
            - "./node3/data:/data"
          command: ["redis-server", "/etc/redis.conf"]
          restart: always
      
        node4:
          image: redis
          container_name: redis-cluster-node-4
          network_mode: "host"
          volumes:
            - "./node4/redis.conf:/etc/redis.conf"
            - "./node4/data:/data"
          command: ["redis-server", "/etc/redis.conf"]
          restart: always
      
        node5:
          image: redis
          container_name: redis-cluster-node-5
          network_mode: "host"
          volumes:
            - "./node5/redis.conf:/etc/redis.conf"
            - "./node5/data:/data"
          command: ["redis-server", "/etc/redis.conf"]
          restart: always
      
        node6:
          image: redis
          container_name: redis-cluster-node-6
          network_mode: "host"
          volumes:
            - "./node6/redis.conf:/etc/redis.conf"
            - "./node6/data:/data"
          command: ["redis-server", "/etc/redis.conf"]
          restart: always

      这6个节点都是部署在宿主机上的,所以他们的端口号不同,之前sentinel,主从复制都是配置在子网中
      redis集群的结点如果配置在子网中,那么当宿主机使用客户端存储数据时,redis可能将请求转发到其他
      结点做存储,但是这些结点都是子网ip,宿主机根本访问不到,会造成阻塞,所以分片集群最好部署在宿主机上。

  • Redis的主从复制原理

    • 从机会发消息给主机,要求增量同步,此时主机会检查从机的replication id和offset数据偏移量
      每个redis服务都会有一个id,称为replication id,如果两个redis进程replId一致,说明是主从关系
      offset则是该redis进程从其他redis进程拷贝的数据的偏移量。
    • 主机接收到从机发过来的消息,它需要判断,如果从机是第一次发送请求,则需要拒绝该增量同步请求
      需要全量同步,而如果不是第一次发送请求,则同意它的增量请求,判断从机是否是第一次请求,
      需要根据replId来判断,redis中规定,每个redis结点最初replId都不一致,如果称为了主从关系,
      则从机的replId需要与主机的replId保持一致,比较两者的replId就能知道是不是第一次发送请求了。
    • 如果不是第一次发送请求,则说明之前已经发送过数据给从机了,此时没必要再全量同步,而是同意增量
      请求,如果是第一次发送请求,则拒绝增量请求,需要全量请求,现在假设该从机是第一次发送消息
      此时主机会有两个操作
      1,主机会生成一个线程去拷贝内存中的数据进行持久化成rdb文件,拷贝好rdb文件后,将该文件发给从机
      2,在线程持久化内存数据的时候,主机会开辟一个内存缓存区,叫repl_baklog的区域,用来存储在此
      期间接收的客户端请求的数据。
    • 当从机接收到rdb文件后,会清除本地自己的rdb文件,加载接收到的rdb文件,从而获得主机的数据
      从机拿到主机的数据,并不是完整的,因为可能在主机持久化数据的时候,有新的数据,被存到内存缓存区中
      所以从机再次发送增量请求
    • 主机仍然判断从机是否为第一次发送消息,发现replId一致,则同意增量请求,将repl_baklog中的数据
      发送给从机,但并不是将内存缓冲区数据全部发送给从机,而是根据从机的offset来确定到底发多少数据
      内存缓冲区相当于一个双向链表,或者说是一个环,从机的offset和主机的offset像两个指针一样,
      它们之间的间隔就是主机需要发送的数据,所以我们可以知道,从机的offset一定是小于主机的offset的
      如果它们的offset则说明它们的数据一致,完成同步。
  • 文章与资料
    掘金有非常多好文章,我也是看着别人的文章搭建的,下面放上大佬的文章和一些网课资料
    资料中有上述的案例文件,中文解析的redis.conf,redis数据同步原理的ppt