主从模式

哨兵模式

Cluster模式

主从模式和哨兵模式暂不介绍,主要介绍 cluster 模式

前置准备工作

拉取镜像

docker pull redis:7

创建网络

docker network create -d bridge my-net

查看创建得网络列表 docker network ls

查看网络详细信息 docker network inspect my-net

准备 redis.conf 文件

一个容器准备一个配置文件,这里用脚本创建,直接运行如下命令即可

for port in $(seq 6380 6385);
do
mkdir -p /home/shuxiaoyuan/docker/redis/node-${port}/conf
touch /home/shuxiaoyuan/docker/redis/node-${port}/conf/redis.conf
cat  << EOF > /home/shuxiaoyuan/docker/redis/node-${port}/conf/redis.conf
# 节点端口
port ${port}

# 密码
requirepass 123456

# 保护模式,默认值 yes,即开启。开启保护模式以后,需配置 bind ip 或者设置访问密码;关闭保护模式,外部网络可以直接访问;
protected-mode no

# 是否以守护线程的方式启动(后台启动),默认 no;
daemonize no

# 是否开启 AOF 持久化模式,默认 no;
appendonly yes

# 是否开启集群模式,默认 no;
cluster-enabled yes

# 集群节点信息文件,自动在/data目录中生成
cluster-config-file nodes.conf

# 集群节点连接超时时间;
cluster-node-timeout 5000
EOF
done

批量重置脚本

当发生错误的时候,批量停止、删除容器并清理相关文件,方便重来

停止容器,删除容器,删除容器产生的数据文件

for port in $(seq 6380 6385);
do 
docker stop redis-${port}
docker rm redis-${port}
sudo rm -rf /home/shuxiaoyuan/docker/redis/node-${port}/data
done

采用 docker compose

# 定义服务,可以多个
services:
  
  # 服务名称
  redis-6380:
    # 采用 image 指定镜像
    image: "redis:7"
    
    # 指定容器名称。默认将会使用 项目名称_服务名称_序号 这样的格式。
    container_name: redis-6380
    
    # 指定容器退出后的重启策略为始终重启。
    # 该命令对保持服务始终运行十分有效,在生产环境中推荐配置为 always 或者 unless-stopped
    restart: always

    # 配置日志
    logging:
      # 日志驱动类型:json-file、syslog、none
      driver: "json-file"
      # 其他选项
      options:
        max-size: "1g"
    
    # 配置容器连接的网络
    network_mode: "my-net"

    # 暴露端口信息
    ports:
      - "6380:6380"
    
    # 数据卷所挂载路径设置
    volumes:
      - /home/shuxiaoyuan/docker/redis/node-6380/conf/redis.conf:/usr/local/etc/redis/redis.conf
      - /home/shuxiaoyuan/docker/redis/node-6380/data:/data

    # 覆盖容器启动后默认执行的命令。
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]

  # 服务名称
  redis-6381:
    # 采用 image 指定镜像
    image: "redis:7"
    
    # 指定容器名称。默认将会使用 项目名称_服务名称_序号 这样的格式。
    container_name: redis-6381
    
    # 指定容器退出后的重启策略为始终重启。
    # 该命令对保持服务始终运行十分有效,在生产环境中推荐配置为 always 或者 unless-stopped
    restart: always

    # 配置日志
    logging:
      # 日志驱动类型:json-file、syslog、none
      driver: "json-file"
      # 其他选项
      options:
        max-size: "1g"
    
    # 配置容器连接的网络
    network_mode: "my-net"

    # 暴露端口信息,使用 host 模式后,这个不需要
    ports:
      - "6381:6381"
    
    # 数据卷所挂载路径设置
    volumes:
      - /home/shuxiaoyuan/docker/redis/node-6381/conf/redis.conf:/usr/local/etc/redis/redis.conf
      - /home/shuxiaoyuan/docker/redis/node-6381/data:/data

    # 覆盖容器启动后默认执行的命令。
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]

  # 服务名称
  redis-6382:
    # 采用 image 指定镜像
    image: "redis:7"
    
    # 指定容器名称。默认将会使用 项目名称_服务名称_序号 这样的格式。
    container_name: redis-6382
    
    # 指定容器退出后的重启策略为始终重启。
    # 该命令对保持服务始终运行十分有效,在生产环境中推荐配置为 always 或者 unless-stopped
    restart: always

    # 配置日志
    logging:
      # 日志驱动类型:json-file、syslog、none
      driver: "json-file"
      # 其他选项
      options:
        max-size: "1g"
    
    # 配置容器连接的网络
    network_mode: "my-net"

    # 暴露端口信息
    ports:
      - "6382:6382"
    
    # 数据卷所挂载路径设置
    volumes:
      - /home/shuxiaoyuan/docker/redis/node-6382/conf/redis.conf:/usr/local/etc/redis/redis.conf
      - /home/shuxiaoyuan/docker/redis/node-6382/data:/data

    # 覆盖容器启动后默认执行的命令。
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]

  # 服务名称
  redis-6383:
    # 采用 image 指定镜像
    image: "redis:7"
    
    # 指定容器名称。默认将会使用 项目名称_服务名称_序号 这样的格式。
    container_name: redis-6383
    
    # 指定容器退出后的重启策略为始终重启。
    # 该命令对保持服务始终运行十分有效,在生产环境中推荐配置为 always 或者 unless-stopped
    restart: always

    # 配置日志
    logging:
      # 日志驱动类型:json-file、syslog、none
      driver: "json-file"
      # 其他选项
      options:
        max-size: "1g"
    
    # 配置容器连接的网络
    network_mode: "my-net"

    # 暴露端口信息
    ports:
      - "6383:6383"
    
    # 数据卷所挂载路径设置
    volumes:
      - /home/shuxiaoyuan/docker/redis/node-6383/conf/redis.conf:/usr/local/etc/redis/redis.conf
      - /home/shuxiaoyuan/docker/redis/node-6383/data:/data

    # 覆盖容器启动后默认执行的命令。
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]

  # 服务名称
  redis-6384:
    # 采用 image 指定镜像
    image: "redis:7"
    
    # 指定容器名称。默认将会使用 项目名称_服务名称_序号 这样的格式。
    container_name: redis-6384
    
    # 指定容器退出后的重启策略为始终重启。
    # 该命令对保持服务始终运行十分有效,在生产环境中推荐配置为 always 或者 unless-stopped
    restart: always

    # 配置日志
    logging:
      # 日志驱动类型:json-file、syslog、none
      driver: "json-file"
      # 其他选项
      options:
        max-size: "1g"
    
    # 配置容器连接的网络
    network_mode: "my-net"

    # 暴露端口信息
    ports:
      - "6384:6384"
    
    # 数据卷所挂载路径设置
    volumes:
      - /home/shuxiaoyuan/docker/redis/node-6384/conf/redis.conf:/usr/local/etc/redis/redis.conf
      - /home/shuxiaoyuan/docker/redis/node-6384/data:/data

    # 覆盖容器启动后默认执行的命令。
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]

  # 服务名称
  redis-6385:
    # 采用 image 指定镜像
    image: "redis:7"
    
    # 指定容器名称。默认将会使用 项目名称_服务名称_序号 这样的格式。
    container_name: redis-6385
    
    # 指定容器退出后的重启策略为始终重启。
    # 该命令对保持服务始终运行十分有效,在生产环境中推荐配置为 always 或者 unless-stopped
    restart: always

    # 配置日志
    logging:
      # 日志驱动类型:json-file、syslog、none
      driver: "json-file"
      # 其他选项
      options:
        max-size: "1g"
    
    # 配置容器连接的网络
    network_mode: "my-net"

    # 暴露端口信息
    ports:
      - "6385:6385"
    
    # 数据卷所挂载路径设置
    volumes:
      - /home/shuxiaoyuan/docker/redis/node-6385/conf/redis.conf:/usr/local/etc/redis/redis.conf
      - /home/shuxiaoyuan/docker/redis/node-6385/data:/data

    # 覆盖容器启动后默认执行的命令。
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]

运行 docker compose

# 后台运行
docker compose up -d

# 停止但不删除
docker compose stop

# 删除
docker compose down

进入容器配置集群

docker exec -it redis-6380 bash

# cluster-replicas 1 意思是主从配置 1:1
redis-cli -a 123456 --cluster create redis-6380:6380 redis-6381:6381 redis-6382:6382 redis-6383:6383 redis-6384:6384 redis-6385:6385 --cluster-replicas 1

采用 Docker 方式

在 Windows 上采用docker方式,启动多个容器来模拟集群

脚本批量启动 Redis 容器

for port in $(seq 6380 6385); \
do \
  docker run -it -d -p ${port}:${port} -p 1${port}:1${port} \
  --restart always \
  --mount type=bind,source=/home/shuxiaoyuan/docker/redis/node-${port}/conf/redis.conf,target=/usr/local/etc/redis/redis.conf \
  --mount type=bind,source=/home/shuxiaoyuan/docker/redis/node-${port}/data,target=/data \
  --name redis-${port} --net my-net \
  redis:7 redis-server /usr/local/etc/redis/redis.conf
done

进入容器配置集群

docker exec -it redis-6380 bash

# cluster-replicas 1 意思是主从配置 1:1
redis-cli -a 123456 --cluster create redis-6380:6380 redis-6381:6381 redis-6382:6382 redis-6383:6383 redis-6384:6384 redis-6385:6385 --cluster-replicas 1

连接 Redis 查看信息

redis-cli -c -p 6380 -a 123456
cluster info
cluster nodes

测试数据

可以看到,每次 set 操作的时候,会将相应的键分配到对应的卡槽,然后自动转到相应的机器上,get 的时候,同理

Windows下安装多个 Redis 实例

GitHub 上下载 Windows 版的 Redis(3.2.100 的 zip 版),点击此处

创建多个 Redis 实例

根据你需要启动的实例个数,创建相应的 conflog 文件,文件名可以随便取,不过为了更加直观,这里统一采用带端口的文件名形式取名

conf 文件内容,根据你的端口,将配置文件中的端口号都替换掉

port 6380
loglevel notice
logfile "E:/Redis-x64-3.2.100/logs/redis6380_log.txt"
appendonly yes
appendfilename "appendonly.6380.aof"
cluster-enabled yes
cluster-config-file nodes.6380.conf
cluster-node-timeout 15000
cluster-slave-validity-factor 10
cluster-migration-barrier 1
cluster-require-full-coverage yes

启动多个 Redis 服务

安装 Redis 系统服务 redis-server.exe --service-install redis.6380.conf --service-name redis6380

查看 Redis 服务,可以在此配置手动启动或者自动启动等

连接 Redis

删除 Redis 服务

运行 regedit 打开注册表编辑器

找到:计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\

找到对应的 Redis 服务,右键删除即可

下载并安装Ruby

下载 Ruby :http://dl.bintray.com/oneclick/rubyinstaller/rubyinstaller-2.2.4-x64.exe

其他版本:http://dl.bintray.com/oneclick/rubyinstaller/

安装:略

安装 gem 驱动

下载 Ruby 环境下 Redis 的驱动:确保 gem 命令可用

redis-3.2.2.gem 下载地址(https://rubygems.org/downloads/redis-3.2.2.gem)

安装,下图的 --local 后面跟下载好的 gem 文件路径,自行替换即可

搭建集群

下载 Redis 官方提供的创建 Redis集群 的 ruby 脚本文件 redis-trib.rb

下载地址:https://raw.githubusercontent.com/MSOpenTech/redis/3.0/src/redis-trib.rb

如果无法下载,本文最后有附录,如果打开后是一个页面,就将页面上的内容全部不保存到本地

搭建 redis-trib.rb create --replicas 0 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384

测试

127.0.0.1:6380 中添加一个 key,刷新其他实例,可以发现,该 key 已同步过来

使用一个实例的不同数据库模拟

<?php

/**
 * 请提供简单说明
 *
 * User: 舒孝元
 * Date: 2020/8/19 15:01
 * Mail: sxy@shuxiaoyuan.com
 * Website: https://www.shuxiaoyuan.com
 */


namespace App\Common;


class RedisHa {
    protected static $name = 'redis_ha';
    protected static $connections = [];

    protected static function init() {
        $hosts = explode(',', env('REDIS_HA_HOSTS'));
        $databases = explode(',', env('REDIS_HA_DATABASES'));

        foreach ($hosts as $index => $host) {
            $redisManage = new \Illuminate\Redis\RedisManager('redis-cluster',config('database.redis.client'), [
                'default' => [
                    'host'     => $host,
                    'password' => env('REDIS_PASSWORD', null),
                    'port'     => env('REDIS_PORT', 6379),
                    'database' => $databases[$index],
                ]
            ]);

            self::$connections[$index] = $redisManage->connection();
        }
    }

    public static function count() {
        if (empty(self::$connections)) {
            self::init();
        }

        return count(self::$connections);
    }

    public static function connection($index) {
        if (empty(self::$connections)) {
            self::init();
        }

        if (!isset(self::$connections[$index]))
            throw new Exception('未找到redis连接' . $index);

        return self::$connections[$index];
    }

    public static function __callStatic($method, $parameters) {
        if (empty(self::$connections)) {
            self::init();
        }

        $result = [];
        foreach (self::$connections as $connection) {
            $result[] = $connection->{$method}(...$parameters);
        }

        return $result;
    }
}

简单分配逻辑

    /**
     * 根据 openID 分配 Redis
     */
    private function type4() {
        for ($i = 0; $i < 10000; $i++) {
            $openid = str_random(28);

            $openid_crc32 = crc32($openid);
            $redis_id = (int)$openid_crc32 % count(explode(',', env('REDIS_HA_HOSTS')));

            $redis = RedisHa::connection($redis_id);
            $redis->set('test:' . $openid, json_encode(['openid' => $openid, 'redis_id' => $redis_id]));
            $redis->incr('number');
        }

        return 'success';
    }