前言

Codis 3.x 稳定版本已经很久没更新了,虽然有缺点也称不上完美但确实可以有效解决横向扩展问题。Redis 5.0 因为所谓的 政治正确 把 master-slave 名字修改为 master-replica 上了开源社区热议排行榜,客户端 SDK 的 API 完全兼容 redis-cluster 在 Redis 6.0 官方推出 redis-cluster-proxy 集群代理方案前成本很高,目前也需要等待社区验证新方案的稳定性,所以大家在选择 Redis 集群方案的时候除了自研和 Codis 或者 Pika 以外依然没有太多的选择余地。我们使用 Codis 的原因也很简单,Redis 主从模式内存从 128GB 一路增加到 1TB 后硬件终于受不鸟了,要么像数据库借鉴 “拆” 的奥义做到庖丁解牛一般,不然摆在眼前的路基本只剩下相对成熟可靠的 Codis。本文分享了 Redis 高可用技术解决方案选型的参考文章和 Codis 集群搭建的过程,希望对大家有帮助。

Redis(Codis)分布式集群部署实践

更新历史

2020 年 07 月 13 日 - 增加 Codis HA 搭建方案,codis-dashboard 迁移和 codis-server 扩容心得
2020 年 06 月 21 日 - 增加 Codis 手动编译安装
2019 年 07 月 23 日 - 初稿

阅读原文 - https://liaojiaxin158.github.io/post/codis/

扩展阅读

Redis
Codis
Pika


Codis 简介

Codis 是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生的 Redis Server 没有显著区别 (不支持的命令列表), 上层应用可以像使用单机的 Redis 一样使用, Codis 底层会处理请求的转发, 不停机的数据迁移等工作, 所有后边的一切事情, 对于前面的客户端来说是透明的, 可以简单的认为后边连接的是一个内存无限大的 Redis 服务。

Compared with Twemproxy and Redis Cluster

Codis Twemproxy Redis Cluster
resharding without restarting cluster Yes No Yes
pipeline Yes Yes No
hash tags for multi-key operations Yes Yes Yes
multi-key operations while resharding Yes - No(details)
Redis clients supporting Any clients Any clients Clients have to support cluster protocol

“Resharding” means migrating the data in one slot from one redis server to another, usually happens while increasing/decreasing the number of redis servers.

为什么要选择 Codis

Redis 获得动态扩容 / 缩容的能力,增减 redis 实例对 client 完全透明、不需要重启服务,不需要业务方担心 Redis 内存爆掉的问题. 也不用担心申请太大, 造成浪费. 业务方也不需要自己维护 Redis.

Codis 支持水平扩容 / 缩容,扩容可以直接界面的 “Auto Rebalance” 按钮,缩容只需要将要下线的实例拥有的 slot 迁移到其它实例,然后在界面上删除下线的 group 即可。

Codis 使用文档
Codis FAQ
Codis 不支持的命令列表
redis 修改部分(增加若干指令)
Performance (Benchmark)

Codis 架构

集群配置前需要了解架构,集群分片主要分三种:

  1. 客户端分片:这个需要自己开发,对客户端要求严格,集群很难扩容
  2. 代理端分片:如 codis,对客户端几乎无要求,集群容易扩容
  3. 服务端分片:如 redis 集群,需要智能客户端支持集群协议的,集群容易扩容

Codis 3.x 由以下组件组成:

  • Codis Server:基于 redis-3.2.8 分支开发。增加了额外的数据结构,以支持 slot 有关的操作以及数据迁移指令。具体的修改可以参考文档 redis 的修改

  • Codis Proxy:客户端连接的 Redis 代理服务, 实现了 Redis 协议。 除部分命令不支持以外(不支持的命令列表),表现的和原生的 Redis 没有区别(就像 Twemproxy)。

    • 对于同一个业务集群而言,可以同时部署多个 codis-proxy 实例;
    • 不同 codis-proxy 之间由 codis-dashboard 保证状态同步。
  • Codis Dashboard:集群管理工具,支持 codis-proxy、codis-server 的添加、删除,以及据迁移等操作。在集群状态发生改变时,codis-dashboard 维护集群下所有 codis-proxy 的状态的一致性。

    • 对于同一个业务集群而言,同一个时刻 codis-dashboard 只能有 0 个或者 1 个;
    • 所有对集群的修改都必须通过 codis-dashboard 完成。
  • Codis Admin:集群管理的命令行工具。

    • 可用于控制 codis-proxy、codis-dashboard 状态以及访问外部存储。
  • Codis FE:集群管理界面。

    • 多个集群实例共享可以共享同一个前端展示页面;
    • 通过配置文件管理后端 codis-dashboard 列表,配置文件可自动更新。
  • Storage:为集群状态提供外部存储。

    • 提供 Namespace 概念,不同集群的会按照不同 product name 进行组织;
    • 目前仅提供了 Zookeeper、Etcd、Fs 三种实现,但是提供了抽象的 interface 可自行扩展。

Codis 部署

Codis 官方的 GitHub 教程已经写的比较详细了,这里重点分享 Ansible 自动化部署方案

  1. 基于官方的简化版 ansible 一键部署 codis
  2. 基于组件的模块化 ansible 部署 codis

Redis Codis 部署安装

使用 codis-admin 搭建 codis 集群

Codis 3.x 集群搭建与使用

Codis 手动编译安装

安装 Go 运行环境

Getting Started

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# install package
yum install -y net-tools vim telnet wget git gcc autoconf automake m4

# download
wget https://dl.google.com/go/go1.13.12.linux-amd64.tar.gz
#tar -C /usr/local -xzf go$VERSION.$OS-$ARCH.tar.gz
tar -C /usr/local -xzf go1.13.12.linux-amd64.tar.gz

# config env
vim /etc/profile
export PATH=$PATH:/usr/local/go/bin

source /etc/profile

# test
go version
go version go1.13.12 linux/amd64

编译 Codis

以 Codis 为例,官方步骤可能有坑

Codis 使用文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 设置编译环境,在 / etc/profile 文件中输入下面三行
vim /etc/profile
export PATH=$PATH:/usr/local/go/bin
export GOPATH=/usr/local/codis/gopath
export PATH=$PATH:$GOPATH/bin

source /etc/profile

# 下载 Codis 源码
mkdir -p $GOPATH/src/github.com/CodisLabs
cd $_ && git clone https://github.com/CodisLabs/codis.git -b release3.2

# 编译 redis 依赖,以免报 jemalloc 版本等错误(Codis 官方遗漏的步骤)
cd $GOPATH/src/github.com/CodisLabs/codis/extern/redis-3.2.11/deps
make hiredis jemalloc linenoise lua geohash-int

# 编译 Codis 源代码,直接通过 make 编译,可以看到如下输出:
cd $GOPATH/src/github.com/CodisLabs/codis
make
===============================================================================
go build -i -o bin/codis-dashboard ./cmd/dashboard
go build -i -tags "cgo_jemalloc" -o bin/codis-proxy ./cmd/proxy
go build -i -o bin/codis-admin ./cmd/admin
go build -i -o bin/codis-ha ./cmd/ha
go build -i -o bin/codis-fe ./cmd/fe

# 查看编译后的版本
cat bin/version
version = 2018-11-04 16:22:35 +0800 @de1ad026e329561c22e2a3035fbfe89dc7fef764 @3.2.2-12-gde1ad02
compile = 2020-06-17 06:39:48 -0400 by go version go1.13.12 linux/amd64

# 拷贝 codis bin
mkdir -p /opt/codis/{config,bin}
cp -r $GOPATH/src/github.com/CodisLabs/codis/bin /opt/codis
cp -r $GOPATH/src/github.com/CodisLabs/codis/config /opt/codis

Codis 手动安装

dashboard.toml

  1. 调整为 zookeeper,强依赖
  2. 保持多个 instance 的 product_name 唯一性
1
2
3
4
5
6
7
8
cd /opt/codis/config
# dashboard.toml
vim dashboard.toml
#coordinator_name = "filesystem"
#coordinator_addr = "/tmp/codis"
coordinator_name = "zookeeper"
coordinator_addr = "127.0.0.1:2181"
product_name = "codis-demo"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

##################################################
# #
# Codis-Dashboard #
# #
##################################################

# Set Coordinator, only accept "zookeeper" & "etcd" & "filesystem".
# for zookeeper/etcd, coorinator_auth accept "user:password"
# Quick Start
#coordinator_name = "filesystem"
#coordinator_addr = "/tmp/codis"
coordinator_name = "zookeeper"
coordinator_addr = "127.0.0.1:2181"
#coordinator_auth = ""

# Set Codis Product Name/Auth.
product_name = "codis-demo"
product_auth = ""

# Set bind address for admin(rpc), tcp only.
admin_addr = "0.0.0.0:18080"

proxy.toml

  1. 调整为 zookeeper,强依赖
  2. 保持多个 instance 的 product_name 唯一性
1
product_name = "codis-demo"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
##################################################
# #
# Codis-Proxy #
# #
##################################################

# Set Codis Product Name/Auth.
product_name = "codis-demo"
product_auth = ""

# Set auth for client session
# 1. product_auth is used for auth validation among codis-dashboard,
# codis-proxy and codis-server.
# 2. session_auth is different from product_auth, it requires clients
# to issue AUTH <PASSWORD> before processing any other commands.
session_auth = ""

# Set bind address for admin(rpc), tcp only.
admin_addr = "0.0.0.0:11080"

# Set bind address for proxy, proto_type can be "tcp", "tcp4", "tcp6", "unix" or "unixpacket".
proto_type = "tcp4"
proxy_addr = "0.0.0.0:19000"

# Set jodis address & session timeout
# 1. jodis_name is short for jodis_coordinator_name, only accept "zookeeper" & "etcd".
# 2. jodis_addr is short for jodis_coordinator_addr
# 3. jodis_auth is short for jodis_coordinator_auth, for zookeeper/etcd, "user:password" is accepted.
# 4. proxy will be registered as node:
# if jodis_compatible = true (not suggested):
# /zk/codis/db_{PRODUCT_NAME}/proxy-{HASHID} (compatible with Codis2.0)
# or else
# /jodis/{PRODUCT_NAME}/proxy-{HASHID}
jodis_name = ""
jodis_addr = ""
jodis_auth = ""
jodis_timeout = "20s"
jodis_compatible = false

# Set datacenter of proxy.
proxy_datacenter = ""

# Set max number of alive sessions.
proxy_max_clients = 1000

# Set max offheap memory size. (0 to disable)
proxy_max_offheap_size = "1024mb"

# Set heap placeholder to reduce GC frequency.
proxy_heap_placeholder = "256mb"

# Proxy will ping backend redis (and clear 'MASTERDOWN' state) in a predefined interval. (0 to disable)
backend_ping_period = "5s"

# Set backend recv buffer size & timeout.
backend_recv_bufsize = "128kb"
backend_recv_timeout = "30s"

# Set backend send buffer & timeout.
backend_send_bufsize = "128kb"
backend_send_timeout = "30s"

# Set backend pipeline buffer size.
backend_max_pipeline = 20480

# Set backend never read replica groups, default is false
backend_primary_only = false

# Set backend parallel connections per server
backend_primary_parallel = 1
backend_replica_parallel = 1

# Set backend tcp keepalive period. (0 to disable)
backend_keepalive_period = "75s"

# Set number of databases of backend.
backend_number_databases = 16

# If there is no request from client for a long time, the connection will be closed. (0 to disable)
# Set session recv buffer size & timeout.
session_recv_bufsize = "128kb"
session_recv_timeout = "30m"

# Set session send buffer size & timeout.
session_send_bufsize = "64kb"
session_send_timeout = "30s"

# Make sure this is higher than the max number of requests for each pipeline request, or your client may be blocked.
# Set session pipeline buffer size.
session_max_pipeline = 10000

# Set session tcp keepalive period. (0 to disable)
session_keepalive_period = "75s"

# Set session to be sensitive to failures. Default is false, instead of closing socket, proxy will send an error response to client.
session_break_on_failure = false

# Set metrics server (such as http://localhost:28000), proxy will report json formatted metrics to specified server in a predefined period.
metrics_report_server = ""
metrics_report_period = "1s"

# Set influxdb server (such as http://localhost:8086), proxy will report metrics to influxdb.
metrics_report_influxdb_server = ""
metrics_report_influxdb_period = "1s"
metrics_report_influxdb_username = ""
metrics_report_influxdb_password = ""
metrics_report_influxdb_database = ""

# Set statsd server (such as localhost:8125), proxy will report metrics to statsd.
metrics_report_statsd_server = ""
metrics_report_statsd_period = "1s"
metrics_report_statsd_prefix = ""

redis.conf

  1. 每台机器创建两个 redis 实例,对应端口 6379 和 6380
  2. 按需求修改 redis.conf 配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 创建 codis data 目录
mkdir -p /opt/codis/data

# 创建 6379 的配置文件:
cp redis.conf redis-6379.conf
vim redis-6379.conf
# 修改如下内容
protected-mode no
port 6379
pidfile "/opt/codis/data/redis_6379.pid"
logfile "/opt/codis/data/redis_6379.log"
dbfilename "dump_6379.rdb"
dir "/opt/codis/data"
# 创建 6380 的配置文件
cp redis-6379.conf redis-6380.conf
sed -i 's/6379/6380/g' redis-6380.conf

sentinel.conf

  1. 如果只是 test 可以不配置
  2. 线上环境做 HA 建议分布在 3 台不同节点
1
2
3
4
5
6
vim sentinel.conf

dir "/opt/codis/data"
daemonize yes
loglevel notice
logfile "/opt/codis/data/setinel.log"

Codis 启停脚本

  1. 测试环境可以使用 nohup 在后台运行
  2. 线上环境建议使用 Supervisord 或者 Monit 来管理
  3. 注意这里最后一个 fe 参数是你要访问的前端地址,但是因为 zookeeper 已经占用了 8080 端口,所以你可以改成别的端口。而且为了你在任何地址都可以访问,你可以设置监听 ip 为 0.0.0.0,因为 FE 是不用密码的,所以端口最好设置一个不常见的,避免被不怀好意的人看到前端页面之后对你的 codis 集群做出不好的事情,或者通过防火墙和 SSO 限制访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 创建 codis log 目录
mkdir -p /opt/codis/data
cd /opt/codis

# start_dashboard.sh
nohup ./bin/codis-dashboard --ncpu=1 --config=config/dashboard.toml --log=log/dashboard.log --log-level=WARN &

# start_proxy.sh
nohup ./bin/codis-proxy --ncpu=1 --config=config/proxy.toml --log=log/proxy.log --log-level=WARN &

# start_server.sh
./bin/codis-server config/redis-6379.conf
./bin/codis-server config/redis-6380.conf

# start_fe.sh (可选,如果 8080 被占用需要修改端口)
nohup ./bin/codis-fe --ncpu=1 --log=log/fe.log --log-level=WARN --zookeeper=127.0.0.1:2181 --listen=0.0.0.0:8081 &

# start_sentinel.sh (可选)
./bin/codis-server config/sentinel.conf --sentinel

# 登录 codis-fe
http://192.168.184.131:8081/#codis-demo
# 添加 codis-proxy
New Proxy: 192.168.184.131:11080
# 添加 codis-server,注意先添加 group,再添加 server 分配到相应 group。Data Center 可以留空
New Group: 1
Add Server:
192.168.184.131:6379 to 1
192.168.184.131:6380 to 1
# 点击扳手图标会自动设置 SLAVEOF,当然线上环境的配置就会复杂些
Auto-Rebalance: Rebalance All Slots


# 好的,基本上到这,一个 Codis Demo 搭建过程基本就算完成了。如果你还需要主从,也可以通过增加三个节点然后通过 FE 操作,它自动就可以帮助你做好主从同步。线上环境需要考虑 HA 是否采用 Sentinel 以及还是主从切换。

codis 命令行

具体用法在上面的步骤中已经列出,使用命令行可以方便自动化运维管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# check codis/bin
cd /data/codis/bin
assets
codis-admin
codis-dashboard
codis-fe
codis-ha
codis-proxy
codis-server
redis-benchmark
redis-cli
redis-sentinel
version

# codis-dashboard
./codis-dashboard -h
Usage:
codis-dashboard [--ncpu=N] [--config=CONF] [--log=FILE] [--log-level=LEVEL] [--host-admin=ADDR] [--pidfile=FILE] [--zookeeper=ADDR|--etcd=ADDR|--filesystem=ROOT] [--product_name=NAME] [--product_auth=AUTH] [--remove-lock]
codis-dashboard --default-config
codis-dashboard --version

Options:
--ncpu=N set runtime.GOMAXPROCS to N, default is runtime.NumCPU().
-c CONF, --config=CONF run with the specific configuration.
-l FILE, --log=FILE set path/name of daliy rotated log file.
--log-level=LEVEL set the log-level, should be INFO,WARN,DEBUG or ERROR, default is INFO.

# codis-fe
./codis-fe -h
Usage:
codis-fe [--ncpu=N] [--log=FILE] [--log-level=LEVEL] [--assets-dir=PATH] [--pidfile=FILE] (--dashboard-list=FILE|--zookeeper=ADDR [--zookeeper-auth=USR:PWD]|--etcd=ADDR [--etcd-auth=USR:PWD]|--filesystem=ROOT) --listen=ADDR
codis-fe --version

Options:
--ncpu=N set runtime.GOMAXPROCS to N, default is runtime.NumCPU().
-d FILE, --dashboard-list=FILE set list of dashboard, can be generated by codis-admin.
-l FILE, --log=FILE set path/name of daliy rotated log file.
--log-level=LEVEL set the log-level, should be INFO,WARN,DEBUG or ERROR, default is INFO.
--listen=ADDR set the listen address.

# codis-proxy
./codis-proxy -h
Usage:
codis-proxy [--ncpu=N [--max-ncpu=MAX]] [--config=CONF] [--log=FILE] [--log-level=LEVEL] [--host-admin=ADDR] [--host-proxy=ADDR] [--dashboard=ADDR|--zookeeper=ADDR [--zookeeper-auth=USR:PWD]|--etcd=ADDR [--etcd-auth=USR:PWD]|--filesystem=ROOT|--fillslots=FILE] [--ulimit=NLIMIT] [--pidfile=FILE] [--product_name=NAME] [--product_auth=AUTH] [--session_auth=AUTH]
codis-proxy --default-config
codis-proxy --version

Options:
--ncpu=N set runtime.GOMAXPROCS to N, default is runtime.NumCPU().
-c CONF, --config=CONF run with the specific configuration.
-l FILE, --log=FILE set path/name of daliy rotated log file.
--log-level=LEVEL set the log-level, should be INFO,WARN,DEBUG or ERROR, default is INFO.
--ulimit=NLIMIT run 'ulimit -n' to check the maximum number of open file descriptors.

# codis-admin
./codis-admin -h
Usage:
codis-admin [-v] --proxy=ADDR [--auth=AUTH] [config|model|stats|slots]
codis-admin [-v] --proxy=ADDR [--auth=AUTH] --start
codis-admin [-v] --proxy=ADDR [--auth=AUTH] --shutdown
codis-admin [-v] --proxy=ADDR [--auth=AUTH] --log-level=LEVEL
codis-admin [-v] --proxy=ADDR [--auth=AUTH] --fillslots=FILE [--locked]
codis-admin [-v] --proxy=ADDR [--auth=AUTH] --reset-stats
codis-admin [-v] --proxy=ADDR [--auth=AUTH] --forcegc
codis-admin [-v] --dashboard=ADDR [config|model|stats|slots|group|proxy]
codis-admin [-v] --dashboard=ADDR --shutdown
codis-admin [-v] --dashboard=ADDR --reload
codis-admin [-v] --dashboard=ADDR --log-level=LEVEL
codis-admin [-v] --dashboard=ADDR --slots-assign --beg=ID --end=ID (--gid=ID|--offline) [--confirm]
codis-admin [-v] --dashboard=ADDR --slots-status
codis-admin [-v] --dashboard=ADDR --list-proxy
codis-admin [-v] --dashboard=ADDR --create-proxy --addr=ADDR
codis-admin [-v] --dashboard=ADDR --online-proxy --addr=ADDR
codis-admin [-v] --dashboard=ADDR --remove-proxy (--addr=ADDR|--token=TOKEN|--pid=ID) [--force]
codis-admin [-v] --dashboard=ADDR --reinit-proxy (--addr=ADDR|--token=TOKEN|--pid=ID|--all) [--force]
codis-admin [-v] --dashboard=ADDR --proxy-status
codis-admin [-v] --dashboard=ADDR --list-group
codis-admin [-v] --dashboard=ADDR --create-group --gid=ID
codis-admin [-v] --dashboard=ADDR --remove-group --gid=ID
codis-admin [-v] --dashboard=ADDR --resync-group [--gid=ID | --all]
codis-admin [-v] --dashboard=ADDR --group-add --gid=ID --addr=ADDR [--datacenter=DATACENTER]
codis-admin [-v] --dashboard=ADDR --group-del --gid=ID --addr=ADDR
codis-admin [-v] --dashboard=ADDR --group-status
codis-admin [-v] --dashboard=ADDR --replica-groups --gid=ID --addr=ADDR (--enable|--disable)
codis-admin [-v] --dashboard=ADDR --promote-server --gid=ID --addr=ADDR
codis-admin [-v] --dashboard=ADDR --sync-action --create --addr=ADDR
codis-admin [-v] --dashboard=ADDR --sync-action --remove --addr=ADDR
codis-admin [-v] --dashboard=ADDR --slot-action --create --sid=ID --gid=ID
codis-admin [-v] --dashboard=ADDR --slot-action --remove --sid=ID
codis-admin [-v] --dashboard=ADDR --slot-action --create-some --gid-from=ID --gid-to=ID --num-slots=N
codis-admin [-v] --dashboard=ADDR --slot-action --create-range --beg=ID --end=ID --gid=ID
codis-admin [-v] --dashboard=ADDR --slot-action --interval=VALUE
codis-admin [-v] --dashboard=ADDR --slot-action --disabled=VALUE
codis-admin [-v] --dashboard=ADDR --rebalance [--confirm]
codis-admin [-v] --dashboard=ADDR --sentinel-add --addr=ADDR
codis-admin [-v] --dashboard=ADDR --sentinel-del --addr=ADDR [--force]
codis-admin [-v] --dashboard=ADDR --sentinel-resync
codis-admin [-v] --remove-lock --product=NAME (--zookeeper=ADDR [--zookeeper-auth=USR:PWD]|--etcd=ADDR [--etcd-auth=USR:PWD]|--filesystem=ROOT)
codis-admin [-v] --config-dump --product=NAME (--zookeeper=ADDR [--zookeeper-auth=USR:PWD]|--etcd=ADDR [--etcd-auth=USR:PWD]|--filesystem=ROOT) [-1]
codis-admin [-v] --config-convert=FILE
codis-admin [-v] --config-restore=FILE --product=NAME (--zookeeper=ADDR [--zookeeper-auth=USR:PWD]|--etcd=ADDR [--etcd-auth=USR:PWD]|--filesystem=ROOT) [--confirm]
codis-admin [-v] --dashboard-list (--zookeeper=ADDR [--zookeeper-auth=USR:PWD]|--etcd=ADDR [--etcd-auth=USR:PWD]|--filesystem=ROOT)

Options:
-a AUTH, --auth=AUTH
-x ADDR, --addr=ADDR
-t TOKEN, --token=TOKEN
-g ID, --gid=ID

Codis 常见问题

在官方文档中列举了 2 个案例,Codis Admin(命令行工具)

注意:使用 codis-admin 是十分危险的。

codis-dashboard 异常退出的修复

1
2
3
# codis-dashboard 无法启动,并提示:
[ERROR] store: acquire lock of codis-demo failed
[error]: zk: node already exists

当 codis-dashboard 启动时,会在外部存储上存放一条数据,用于存储 dashboard 信息,同时作为 LOCK 存在。当 codis-dashboard 安全退出时,会主动删除该数据。当 codis-dashboard 异常退出时,由于之前 LOCK 未安全删除,重启往往会失败。因此 codis-admin 提供了强制删除工具:

  1. 确认 codis-dashboard 进程已经退出( 很重要 );
  2. 运行 codis-admin 删除 LOCK:
1
$ ./bin/codis-admin --remove-lock --product=codis-demo --zookeeper=127.0.0.1:2181

codis-proxy 异常退出的修复

通常 codis-proxy 都是通过 codis-dashboard 进行移除,移除过程中 codis-dashboard 为了安全会向 codis-proxy 发送 offline 指令,成功后才会将 proxy 信息从外部存储中移除。如果 codis-proxy 异常退出,该操作会失败。此时可以使用 codis-admin 工具进行移除:

  1. 确认 codis-proxy 进程已经退出( 很重要 );
  2. 运行 codis-admin 删除 proxy:
1
$ ./bin/codis-admin --dashboard=127.0.0.1:18080 --remove-proxy --addr=127.0.0.1:11080 --force

选项 --force 表示,无论 offline 操作是否成功,都从外部存储中将该节点删除。所以操作前,一定要确认该 codis-proxy 进程已经退出。

codis-dashboard 和 codis-proxy 迁移小技巧

负责过 codis-fe/dashboard 迁移,也做过正常的 codis-server scale-out 横向扩容,踩过不少坑其中有 2 点需要提醒下大家

如果 codis-dashboard 使用 Supervisord 或者 Monit 来管理,需要注意添加 --remove-lock 参数,原因上面已经写过了

1
2
# codis-dashboard --remove-lock
/data/codis/bin/codis-dashboard --remove-lock --config=/data/codis/config/dashboard.toml --log=/data/codis/log/codis-dashboard.log --log-level=DEBUG

需要注意搭建 codis-proxy 时是否有添加 --dashboard 参数,因为添加后启动 proxy 就会主动加入对应 IP 的 dashboard,如果不添加这个参数则需要通过 dashboard UI 或者 codis-admin 命令行添加

1
2
# codis-proxy --dashboard
/data/codis/bin/codis-proxy --config=/data/codis/config/proxy-auth.toml --log=/data/codis/log/codis-proxy-auth.log --log-level=DEBUG --dashboard=10.71.14.112:18092 --ncpu=8

Codis Zookeeper 数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
cd /opt/zookeeper/bin
./zkCli.sh -server 127.0.0.1:2181

ZooKeeper -server host:port cmd args
stat path [watch]
set path data [version]
ls path [watch]
delquota [-n|-b] path
ls2 path [watch]
setAcl path acl
setquota -n|-b val path
history
redo cmdno
printwatches on|off
delete path [version]
sync path
listquota path
rmr path
get path [watch]
create [-s] [-e] path data acl
addauth scheme auth
quit
getAcl path
close
connect host:port

[zk: 127.0.0.1:2181(CONNECTED) 4] ls /codis3
[gop-codis-auth, gop-codis-pay]

[zk: 127.0.0.1:2181(CONNECTED) 8] ls /codis3/gop-codis-auth
[proxy, slots, topom, group]

[zk: 127.0.0.1:2181(CONNECTED) 9] ls /codis3/gop-codis-auth/proxy
[proxy-62fba8c56577980aca48b869d6c1059d, proxy-bf965c6421da0ae004d4f142bec2dc45]

[zk: 127.0.0.1:2181(CONNECTED) 12] get /codis3/gop-codis-auth/proxy/proxy-bf965c6421da0ae004d4f142bec2dc45
{
"id": 3,
"token": "bf965c6421da0ae004d4f142bec2dc45",
"start_time": "2020-06-25 17:20:49.129561877 +0800 +08 m=+0.023604373",
"admin_addr": "10.71.49.88:11082",
"proto_type": "tcp4",
"proxy_addr": "10.71.49.88:19082",
"product_name": "gop-codis-auth",
"pid": 65948,
"pwd": "/data/codis",
"sys": "Linux sg-gop-10-71-49-88 3.10.0-957.27.2.el7.x86_64 #1 SMP Mon Jul 29 17:46:05 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux",
"hostname": "sg-gop-10-71-49-88",
"datacenter": ""
}
cZxid = 0x3869
ctime = Thu Jun 25 17:25:09 SGT 2020
mZxid = 0x386a
mtime = Thu Jun 25 17:25:09 SGT 2020
pZxid = 0x3869
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 519
numChildren = 0

[zk: 127.0.0.1:2181(CONNECTED) 13] get /codis3/gop-codis-auth/proxy/proxy-62fba8c56577980aca48b869d6c1059d
{
"id": 2,
"token": "62fba8c56577980aca48b869d6c1059d",
"start_time": "2020-06-23 11:10:54.526348352 +0800 +08 m=+0.024387970",
"admin_addr": "10.71.49.89:11082",
"proto_type": "tcp4",
"proxy_addr": "10.71.49.89:19082",
"product_name": "gop-codis-auth",
"pid": 24684,
"pwd": "/data/codis",
"sys": "Linux sg-gop-10-71-49-89 3.10.0-957.27.2.el7.x86_64 #1 SMP Mon Jul 29 17:46:05 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux",
"hostname": "sg-gop-10-71-49-89",
"datacenter": ""
}
cZxid = 0x4d
ctime = Tue Jun 23 11:12:31 SGT 2020
mZxid = 0x4e
mtime = Tue Jun 23 11:12:31 SGT 2020
pZxid = 0x4d
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 519
numChildren = 0

[zk: 127.0.0.1:2181(CONNECTED) 21] get /codis3/gop-codis-auth/group/group-0001
{
"id": 1,
"servers": [
{
"server": "10.71.49.88:6379",
"datacenter": "",
"action": {},
"replica_group": false
},
{
"server": "10.71.49.88:6380",
"datacenter": "",
"action": {
"state": "synced"
},
"replica_group": false
}
],
"promoting": {},
"out_of_sync": false
}
cZxid = 0x1f
ctime = Tue Jun 23 10:34:10 SGT 2020
mZxid = 0x25
mtime = Tue Jun 23 10:34:25 SGT 2020
pZxid = 0x1f
cversion = 0
dataVersion = 6
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 440
numChildren = 0

Codis HA

Codis 的架构本身分成 Proxy 集群 + Redis 集群,Proxy 集群的高可用,可以基于 Zookeeper 来做故障转移,而 Redis 集群的高可用是借助于 Redis Sentinel 开源的哨兵集群来实现,那边 Codis 作为非 Redis 组件,需要解决的一个问题就是如何集成 Redis 哨兵集群。

我分享 2 种实践方案,都是经历过线上 12000000 qps 考验的架构,重点看下 HA 方案

前置任务:内部流量均使用 CoreDNS 转发请求至 LVS VIP 此时 codis-proxy 作为 realserver 客户端,或者直接分配给 codis-proxy 但 DNS 无故障检测机制

  1. codis-server 使用 keepalived HA 架构类似 Redis 主从同步 HA 方案,codis-dashboard group server 填写 VIP 地址,依赖少但需要使用额外计算资源
  2. codis-server 使用 sentinel 保障 HA 高可用切换,这是 codis 官方的推荐方案,但由于 codis 目前已经处于停滞状态,如果出现组件 bug 影响也会非常大

Codis in Kubernetes

好处是上线很快,坏处是出了问题你就直接嗝屁了,debug 困难推荐先上测试后上生产

https://github.com/CodisLabs/codis/tree/release3.2/kubernetes

Codis 监控

Codis 的监控主要分为 3 部分,衍生部分还包括 Zookeeper,这里就不展开了

  1. Codis Proxy
  2. Codis Redis Cluster
  3. Codis Sentinels

Redis 迁移至 Codis

分两种情况:

  1. 原来使用 twemproxy 的用户: 可以, 使用 codis 项目内的 redis-port 工具, 可以实时的同步 twemproxy 底下的 redis 数据到你的 codis 集群上. 搞定了以后, 只需要你修改一下你的配置, 将 twemproxy 的地址改成 codis 的地址就好了. 除此之外, 你什么事情都不用做.
  2. 原来使用 Redis 的用户: 如果你使用了 doc/unsupported_cmds中提到的命令,是无法直接迁移到 Codis 上的. 你需要修改你的代码, 用其他的方式实现.

先搭建好 codis 集群并让 codis-proxy 正确运行起来。对线上每一个 redis 实例运行一个 redis-port 来向 codis 导入数据,例如:

1
2
3
4
5
6
for port in {6379,6380,6479,6480}; do
nohup redis-port sync --ncpu=4 --from=redis-server:${port} \
--target=codis-proxy:19000 > ${port}.log 2>&1 &
sleep 5
done
tail -f *.log
  • 每个 redis-port 负责将对应的 redis 数据导入到 codis
  • 多个 redis-port 之间不互相干扰,除非多个 redis 上的 key 本身出现冲突
  • 单个 redis-port 可以将负责的数据并行迁移以提高速度,通过 –ncpu 来指定并行数
  • 导入速度受带宽以及 codis-proxy 处理速度限制(本质是大量的 slotsrestore 操作)
  • 完成数据迁移,在适当的时候将服务指向 Codis,并将原 redis 下线,旧 redis 下线时,会导致 reids-port 链接断开,于是自动退出

redis-port
RedisShake
redis-rdb-tools

Codis 扩容

Codis FE, Codis-Dashboard, Codis-Proxy 如果没有添加特殊的参数可以算是无状态组件,扩容或迁移相对比较容易

Codis 可以实现在线不停服务进行扩容,具体的步骤如下:

  1. 安装配置 codis-server 主从
  2. 打开 codis 管理界面,新建 server group 并添加刚刚安装的 redis 实例(注意:codis 默认第一个添加的是 master)
  3. 规划 slot 分布,把部分 slot 迁移到新的 server group 中

备注说明

  1. slot 迁移的过程中,Codis 服务可以正常访问,codis 的迁移机制可以保证数据的一致性
  2. 迁移时,key 都是单个进行迁移,并且不能同时运行多个迁移任务,所以 codis 的迁移时间会比较长。一定要在扩容前留有足够的时间和空间。

Codis 其他经验分享

  1. 关于 HOT KEY, HOT KEY 很影响 Codis/Redis 的性能,这点如果你监控不到位,你就得花一些力气去找到底是哪组出了问题,再 monitor 看看找出是哪个应用干的,比较费时费力,所以在交付 rd 上线时, 我们就严肃声明坚决不允许存在 HOT KEY,宁可使用笨方法多消耗一些内存,也要降低线上故障的风险。

  2. 关于 BIG KEY, 这点风险更为巨大:

由于 Codis 支持 “resharding without restarting cluster”,如果迁移失败,所导致的后果也是不可简单衡量的。Redis 是串行提供服务的,所以当迁移该 BIG KEY 时,其他的请求就会被 BLOCK 住,这点是十分危险的,访问该组的请求皆会失败。

由于 Codis-ha 也会依赖该节点的返回来判断 Codis-server 是否挂掉,如果无响应超过设置时间,便会强制提升 SLAVE 至 MASTER,导致整个迁移任务失败。这时如果 Proxy 的信息没有更新的话,并且迁移故障的 KEY 所在 SLOT 可能会存在 KEY 的信息不完整,虽然服务恢复,但是仍有大量 key 失效。

所以一般不推荐使用 Codis 存大的 HASH 表,LIST 等等,并且在迁移之前,至少要对该 Group 做一次检查 BIG KEY 即:redis-cli –bigkeys 查看是否有 BIG KEY 存在,再酌情迁移。

  1. 关于 Codis-server

一般 Codis-proxy 或者 Codis-dashboard 我们使用 supervisor 管理,在进程退出的情况下立即拉起来重新服务,而 Codis-Server 则不推荐使用该方式,原因是这样的:一般作为 Codis-server,是关闭 rdb dump 的,如果 Codis-server 挂掉,当重新启动时,是没有 rdb 文件的,或者 rdb 文件是上一次切换之前的。如果挂掉立即重新启动,则该 Codis 有可能是空的,或者数据不是最新,而同时,SLAVE 同步,也会清空数据库,或者同步旧数据。

参考文章

Codis 作者黄东旭细说分布式 Redis 架构设计和踩过的那些坑们
史上最全 Redis 高可用技术解决方案大全
深入浅出百亿请求高可用 Redis (codis) 分布式集群揭秘
大规模 codis 集群的治理与实践
避免 Redis (Codis) 的 Timeout 及监控
10 分钟彻底理解 Redis 的持久化和主从复制
Codis AutoRebalance 流程学习
使用 codis-admin 搭建 codis 集群
Codis 运维 D4 - Codis3 详解
4000 余字为你讲透 Codis 内部工作原理
Codis 源码分析

文章目录
  1. 1. 前言
  2. 2. 更新历史
  3. 3. Codis 简介
  4. 4. Codis 架构
  5. 5. Codis 部署
  6. 6. Codis 手动编译安装
    1. 6.1. 安装 Go 运行环境
    2. 6.2. 编译 Codis
    3. 6.3. Codis 手动安装
    4. 6.4. codis 命令行
  7. 7. Codis 常见问题
    1. 7.1. codis-dashboard 异常退出的修复
    2. 7.2. codis-proxy 异常退出的修复
    3. 7.3. codis-dashboard 和 codis-proxy 迁移小技巧
  8. 8. Codis Zookeeper 数据结构
  9. 9. Codis HA
  10. 10. Codis in Kubernetes
  11. 11. Codis 监控
  12. 12. Redis 迁移至 Codis
  13. 13. Codis 扩容
  14. 14. Codis 其他经验分享
  15. 15. 参考文章