前言

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://wsgzao.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
18
# 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
36
# 设置编译环境,在/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
23

##################################################
# #
# 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
7
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
35
# 创建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
109
110
# 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
7
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 存在,再酌情迁移。

3.关于 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. 参考文章