Redis 是一个高性能的key-value数据库。 为了达到最佳性能, Redis 使用 内存中的数据集。通过定期将数据集转储到磁盘 或将每个命令附加到基于磁盘的日志来持久化您的数据。

redis-cluster设计

Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。

img

redis cluster 为了保证数据的高可用性,加入了主从模式,一个主节点对应一个或多个从节点,主节点提供数据存取,从节点则是从主节点拉取数据备份,当这个主节点挂掉后,就会有这个从节点选取一个来充当主节点,从而保证集群不会挂掉

部署redis集群

我们遵循一主两从的模式进行部署,三台物理节点,一个节点上部署六个redis节点。

一、构建镜像(节点同步)

1.编写Dockerfile,6.0需要注意的地方需要自己对配置文件赋权。

#基础镜像
FROM redis:6.2.6
#修复时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone
#环境变量
ENV REDIS_PORT 8000
#ENV REDIS_PORT_NODE 18000
#暴露变量
EXPOSE $REDIS_PORT
#EXPOSE $REDIS_PORT_NODE
#复制
COPY entrypoint.sh /usr/local/bin/
COPY redis.conf /usr/local/etc/
#for config rewrite
RUN chmod -R 777 /usr/local/etc/redis.conf
RUN chmod +x /usr/local/bin/entrypoint.sh
#入口
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
#命令
CMD ["redis-server", "/usr/local/etc/redis.conf"]

2.编写shell文件 entrypoint.sh,脚本主要作用是通过**$REDIS_PORT**变量对对应的容器端口进行开放,具体的变量赋值会在docker-compose文件中

#!/bin/sh
#只作用于当前进程,不作用于其创建的子进程
set -e
#$0--Shell本身的文件名 $1--第一个参数 $@--所有参数列表
# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
    sed -i 's/REDIS_PORT/'$REDIS_PORT'/g' /usr/local/etc/redis.conf
    chown -R redis .  #改变当前文件所有者
    exec gosu redis "$0" "$@"  #gosu是sudo轻量级”替代品”
fi

exec "$@"

3.编写redis.conf

port REDIS_PORT
cluster-enabled no

cluster-config-file nodes.conf
cluster-node-timeout 15000

appendonly no
save ""
maxmemory 10737418240
maxmemory-policy allkeys-lru
activedefrag yes
tcp-backlog 511
logfile "redis.log"

# masterauth
# requirepass

requirepass和masterauth不能启用,否则redis-trib创建集群失败。
protected-mode 保护模式是禁止公网访问,但是不能设置密码和bind ip。

4.构建镜像

[root@tv0-business-65]# docker build -t ycloud/redis:6.2.5 .   #构建
[root@tv0-business-65]# docker save 04d818646945 -o krc6.tar ycloud/redis:6.2.5   #打包并附名

docker-compose部署redis集群

1.编写docker-compose.yaml文件

version: '3'

services:
 redis1:
  image: ycloud/redis:6.2.5
  network_mode: host
  restart: always
  volumes:
   - /data/redis/8001/:/data
  environment:
   - REDIS_PORT=8001

 redis2:
  image: ycloud/redis:6.2.5
  network_mode: host
  restart: always
  volumes:
   - /data/redis/8002/:/data
  environment:
   - REDIS_PORT=8002

 redis3:
  image: ycloud/redis:6.2.5
  network_mode: host
  restart: always
  volumes:
   - /data/redis/8003/:/data
  environment:
   - REDIS_PORT=8003
   
 redis4:
  image: ycloud/redis:6.2.5
  network_mode: host
  restart: always
  volumes:
   - /data/redis/8004/:/data
  environment:
   - REDIS_PORT=8004

 redis6.2.5:
  image: ycloud/redis:6.2.5
  network_mode: host
  restart: always
  volumes:
   - /data/redis/8006.2.5/:/data
  environment:
   - REDIS_PORT=8005

 redis6:
  image: ycloud/redis:6.2.5
  network_mode: host
  restart: always
  volumes:
   - /data/redis/8006/:/data
  environment:
   - REDIS_PORT=8006

network使用的是host模式,容器直接使用宿主机的IP和端口。基础薄弱的同学可以了解容器网络的相关知识,这里不做过多讲解

2.启动容器

$docker-compose up -d
$docker-compose ps 
[root@wjfh-189-6-188 redis]# docker-compose -f dc6.yml ps 
     Name                    Command               State   Ports
----------------------------------------------------------------
redis_redis1_1   /usr/local/bin/entrypoint. ...   Up           
redis_redis2_1   /usr/local/bin/entrypoint. ...   Up           
redis_redis3_1   /usr/local/bin/entrypoint. ...   Up           
redis_redis4_1   /usr/local/bin/entrypoint. ...   Up           
redis_redis5_1   /usr/local/bin/entrypoint. ...   Up           
redis_redis6_1   /usr/local/bin/entrypoint. ...   Up 

三个节点都启动成功之后,接下来就要组件redis集群

3.组建集群

docker run --rm -it inem0o/redis-trib  create --replicas 2 \
10.189.6.188:8001  10.189.6.189:8001  10.189.6.190:8001   \
10.189.6.188:8002  10.189.6.189:8002  10.189.6.190:8002   \
10.189.6.188:8003  10.189.6.189:8003  10.189.6.190:8003   \
10.189.6.188:8004  10.189.6.189:8004  10.189.6.190:8004   \
10.189.6.188:8005  10.189.6.189:8005  10.189.6.190:8005   \
10.189.6.188:8006  10.189.6.189:8006  10.189.6.190:8006 

redis-trib是redis官方提供的一个组件集群的工具

–replicas 表示一个主节点创建两个从节点,其他参数值得是地址的集合

>>> Performing hash slots allocation on 18 nodes...
Master[0] -> Slots 0 - 2730
Master[1] -> Slots 2731 - 5460
Master[2] -> Slots 5461 - 8191
Master[3] -> Slots 8192 - 10922
Master[4] -> Slots 10923 - 13652
Master[5] -> Slots 13653 - 16383
·····
·····
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

显示提示集群组件成功

4.自定义主从匹配节点

  • 原cluster create方式无法实现三机一组,一主两从的设想。
  • 只能确定8001~8002为主。需要将8003 ~ 8006 调整为该组的从。

在这里插入图片描述

这是原cluster的主从分配,无法确定master分配的slave,我们这里书写脚本,让每个节点的80018002为master,从80038004,8005~8006中,各取一个为slave。

#!/usr/bin/env python3
# coding:utf-8


import time
import sys
import os


class host_info:
    def __init__(self):
        self.ip = ''
        self.port = 0
        self.id = ''


master_list = []
slave_list = []


def slave_exec(do):
    for node in slave_list:
        ip = node.ip
        port = node.port

        # do something
        cmd = '/usr/local/bin/redis-cli -h ' + ip + \
            ' -p ' + str(port) + ' ' + do

        print(cmd)
        las = os.popen(cmd).read()
        print(las)
        time.sleep(5)


def master_exec(do):
    for node in master_list:
        ip = node.ip
        port = node.port

        # do something
        cmd = '/usr/local/bin/redis-cli -h ' + ip + \
            ' -p ' + str(port) + ' ' + do

        print(cmd)
        las = os.popen(cmd).read()
        print(las)
        time.sleep(5)


def get_redis_nodes(ip, port):

    cmd = '/usr/local/bin/redis-cli -h ' + ip + \
        ' -p ' + str(port) + ' cluster nodes'

    las = os.popen(cmd).read()
    nodes = las.split("\n")
    for node in nodes:
        if len(node) < 2:
            continue
        nt = node.split(" ")
        host_ip = nt[1]
        at = host_ip.find("@")
        colon = host_ip.find(":")

        if at == -1 or colon == -1:
            print('at or colon not found', host_ip)
            continue

        host_tmp = host_info()

        host_tmp.ip = host_ip[:colon]
        host_tmp.port = host_ip[colon + 1:at]
        host_tmp.id = nt[0]

        if nt[2].find('master') != -1:
            print("master---:", nt[2])
            master_list.append(host_tmp)

        if nt[2].find('slave') != -1:
            print("slave---:", nt[2])
            # master id
            host_tmp.id = nt[3]
            slave_list.append(host_tmp)

    return


def set_replicate(ip, port, master_ip, master_port):
    s = ip + ":" + str(port) + ' is replicate of ' + \
        master_ip + ":" + str(master_port)
    print(s)
    for ins in master_list:
        if ins.ip == master_ip and ins.port == str(master_port) and len(ins.id) > 0:

            cmd = '/usr/local/bin/redis-cli -h ' + ip + ' -p ' + \
                str(port) + ' cluster replicate ' + ins.id

            print(cmd)
            tmp = os.popen(cmd).readlines()
            print(tmp)
            time.sleep(2)
            return True

    print("ip:%s,port:%d not found", ip, port)


def sub_group():

    ip_d_l = ['10.189.11.222','10.189.11.223','10.189.11.224']

    for i in range(len(ip_d_l)):
        # print(ip_d_l[i])
        if (i + 1) % 3 == 1:
            for port in range(8003, 8004):
                set_replicate(ip_d_l[i], port, ip_d_l[i + 1], port - 8)
            for port in range(8005, 8006):
                set_replicate(ip_d_l[i], port, ip_d_l[i + 2], port - 16)
        if (i + 1) % 3 == 2:
            for port in range(8003, 8004):
                set_replicate(ip_d_l[i], port, ip_d_l[i - 1], port - 8)
            for port in range(8005, 8006):
                set_replicate(ip_d_l[i], port, ip_d_l[i + 1], port - 16)
        if (i + 1) % 3 == 0:
            for port in range(8003, 8004):
                set_replicate(ip_d_l[i], port, ip_d_l[i - 2], port - 8)
            for port in range(8005, 8006):
                set_replicate(ip_d_l[i], port, ip_d_l[i - 1], port - 16)


def is_contains(ip, port):
    for ins in master_list:
        if ins.ip == ip and ins.port == str(port):
            print("ip:%s,port:%d,id:%s is found", ip, port, ins.id)
            return True
    print("ip:%s,port:%d not found", ip, port)


# 检查各个集群是否是每台服务器8001~8008为主
def check_masters():

    ip_d_l = ['10.189.11.222','10.189.11.223','10.189.11.224']

    port_d_l = [8001, 8002]

    ins_num = len(ip_d_l) * len(port_d_l)
    if ins_num != len(master_list):
        print("ins_num:%d, master_list_num:%d", ins_num, len(master_list))

    for ip in ip_d_l:
        for port in port_d_l:
            is_contains(ip, port)


def check_group_slave(ip, port):
    mid = ''
    mip = ''
    mport = ''
    for mins in master_list:
        if mins.ip == ip and mins.port == str(port):
            mid = mins.id
            mip = mins.ip
            break

    if len(mid) < 1:
        print("ip:%s, port:%d not found!!!", ip, port)
        return

    print("----------------master ins %s:%d", ip, port)
    for sins in slave_list:
        if sins.id == mid:
            print("slave ip:%s,port:%s", sins.ip, sins.port)
            if sins.ip == mins.ip:
                print("+++++++++++++error!!!+++++++++++++")
    print("----------------")


# 检查集群的最终配置是否为三服务器一组,互为主备
def check_group():

    ip_d_l = ['10.189.11.222','10.189.11.223','10.189.11.224']

    port_d_l = [8001, 8002]

    ins_num = len(ip_d_l) * len(port_d_l)
    if ins_num != len(master_list):
        print("ins_num:%d, master_list_num:%d", ins_num, len(master_list))

    for ip in ip_d_l:
        for port in port_d_l:
            check_group_slave(ip, port)


def main():
    ip = '10.189.11.223'
    port = 8001
    get_redis_nodes(ip, port)

    check_group()
    return


if __name__ == "__main__":
    sys.exit(main())

数据迁移

redis-migrate-tool是唯品会的一个方便实用redis间数据迁移工具。

特点

  • 快速。
  • 多线程。
  • 基于redis复制。
  • 实时迁移。
  • 迁移过程中,源集群不影响对外提供服务。
  • 异构迁移。
  • 支持Twemproxy集群,redis cluster集群,rdb文件 和 aof文件。
  • 过滤功能。
  • 当目标集群是Twemproxy,数据会跳过Twemproxy直接导入到后端的redis。
  • 迁移状态显示。
  • 完善的数据抽样校验。

依赖

先安装 automake、libtool、autoconf 和 bzip2。

构建

  • 构建 redis-migrate-tool:
$ cd redis-migrate-tool
$ autoreconf -fvi
$ ./configure
$ make
$ src/redis-migrate-tool -h
  • 配置文件:从redis cluster迁移到另一个redis cluster
[source]
type: redis cluster
servers:
- 10.189.6.188:8001
 
[target]
type: redis cluster
servers:
- 10.189.6.199:8001
 
[common]
listen: 0.0.0.0:8888
  • 运行迁移
src/redis-migrate-tool -c rmt.conf -o log -d
  • 数据校验
$src/redis-migrate-tool -c rmt.conf -o log -C redis_check
Check job is running...
 
Checked keys: 1000
Inconsistent value keys: 0
Inconsistent expire keys : 0
Other check error keys: 0
Checked OK keys: 1000
 
All keys checked OK!
Check job finished, used 1.041s

配置密钥

redis-cli --cluster call 10.189.11.222:8001 CONFIG SET masterauth "zKxNbyHxFRjokTc7mzWzHEp7m"
redis-cli --cluster call 10.189.11.222:8001 CONFIG SET requirepass "zKxNbyHxFRjokTc7mzWzHEp7m"
redis-cli --cluster call 10.177.43.93:8001  -a zKxNbyHxFRjokTc7mzWzHEp7m CONFIG REWRITE

config rewrite可以将临时配置的密钥写入配置文件,方式重启后密钥丢失

四、Redis Manager

Redis Manager 是 Redis 一站式管理平台,支持集群的监控、安装、管理、告警以及基本的数据操作功能。 集群监控:支持监控 Memory、Clients 等 Redis 重要指标 。

安装

docker run -d --net=host --name redis-manager  -e DATASOURCE_DATABASE='redis_manager' -e DATASOURCE_URL='jdbc:mysql://10.177.42.49:3306/redis_manager?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2b8' -e DATASOURCE_USERNAME='root' -e DATASOURCE_PASSWORD='BowtpqLZ5jUqvfg' reasonduan/redis-manager 

需提前准备好db,部署好之后访问web,默认用户密码admin:admin

–>import cluster 添加集群,可在dashboard中查看相关信息
在这里插入图片描述

参考文献

https://github.com/vipshop/redis-migrate-tool

https://www.jianshu.com/p/813a79ddf932

更多推荐