1.codis简介
Codis 是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生的 Redis Server 没有显著区别 (不支持的命令列表), 上层应用可以像使用单机的 Redis 一样使用, Codis 底层会处理请求的转发, 不停机的数据迁移等工作, 所有后边的一切事情, 对于前面的客户端来说是透明的, 可以简单的认为后边连接的是一个内存无限大的 Redis 服务。
codis集群架构图如下:
1.1 codis3.2的改进与功能说明
- 最新 release 版本为 codis-3.2,codis-server 基于 redis-3.2.8
- 支持 slot 同步迁移、异步迁移和并发迁移,对 key 大小无任何限制,迁移性能大幅度提升
- 相比 2.0:重构了整个集群组件通信方式,codis-proxy 与 zookeeper 实现了解耦,废弃了codis-config 等
- 元数据存储支持 etcd/zookeeper/filesystem 等,可自行扩展支持新的存储,集群正常运行期间,即便元存储故障也不再影响 codis 集群,大大提升 codis-proxy 稳定性
- 对 codis-proxy 进行了大量性能优化,通过控制GC频率、减少对象创建、内存预分配、引入 cgo、jemalloc 等,使其吞吐还是延迟,都已达到 codis 项目中最佳
- proxy 实现 select 命令,支持多 DB
- proxy 支持读写分离、优先读同 IP/同 DC 下副本功能
- 基于 redis-sentinel 实现主备自动切换
- 实现动态 pipeline 缓存区(减少内存分配以及所引起的 GC 问题)
- proxy 支持通过 HTTP 请求实时获取 runtime metrics,便于监控、运维
- 支持通过 influxdb 和 statsd 采集 proxy metrics
- slot auto rebalance 算法从 2.0 的基于 max memory policy 变更成基于 group 下 slot 数量
- 提供了更加友好的 dashboard 和 fe 界面,新增了很多按钮、跳转链接、错误状态等,有利于快速发现、处理集群故障
- 新增 SLOTSSCAN 指令,便于获取集群各个 slot 下的所有 key
- codis-proxy 与 codis-dashbaord 支持 docker 部署
1.2 codis的组件及作用
- 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 可自行扩展。
3.zookeeper介绍
3.1 zookeeper简介
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,它包含一个简单的原语集,分布式应用程序可以基于它实现同步服务,配置维护和命名服务等。Zookeeper是hadoop的一个子项目,其发展历程无需赘述。在分布式应用中,由于工程师不能很好地使用锁机制,以及基于消息的协调机制不适合在某些应用中使用,因此需要有一种可靠的、可扩展的、分布式的、可配置的协调机制来统一系统的状态。
ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
ZooKeeper包含一个简单的原语集,提供Java和C的接口。
3.2 zookeeper在codis集群中的作用
由于之前没有接触过zookeeper,只知道这是个集群管理的工具,所以即使配置完之后对于zookeeper在集群中的作用不是很清晰,后来又咨询了QQ群里的一些朋友,才大概了解zookeeper在集群中的作用。下面说的有不对之处,敬请谅解。
zookeeper启动后开启了2181,2888,3888的端口,而zkCli.sh通过2181端口获取zookeeper集群中受控节点的状态,java程序可以通过2181端口确定后端proxy的状态,进而去访问存活的proxy。所以有了zookeeper之后,可以不再使用ha工具(haproxy,lvs,nginx)等,zookeeper会管理好后端的proxy集群。
4.实验之前
4.1 环境说明
系统环境和软件版本号:
项目 | 参数 |
系统版本 | CentOs 6.9 64位 |
JDK版本 | jdk 1.8.0_111 64位 |
zookeeper版本 | 3.4.8 |
go版本 | go 1.7.6 linux/amd64 |
codis版本 | codis 3.2 |
4.2 各主机配置
试验中使用3台主机,配置为1核1G虚拟机,下面是软件配置说明:
IP地址 | 主机名 | zookeeper端口 | dashboard端口 | proxy端口 | fe端口 | redis端口 | snetinel端口 |
192.168.0.146 | node011 | 2181,2888,3888 | 18080 | 19000 | 8080 | 6379,6380 | |
192.168.0.178 | node012 | 2181,2888,3888 | 无 | 19000 | 无 | 6379,6380 | |
192.168.0.102 | node013 | 2181,2888,3888 | 无 | 19000 | 无 | 6379,6380 |
4.3 主机环境配置
4.3.1 主机名解析
在各主机/etc/hosts中配置主机名解析,在原有的文件下面追加以下配置:
以146主机为例
#vim /etc/hosts
127.0.0.1 node011
192.168.0.146 node011
192.168.0.178 node012
192.168.0.102 node013
4.3.2 安全策略
在安装的过程中,由于部分安装需要使用浏览器访问相关端口,所以安装之前selinux、iptables的安全策略要做好,开放相应的端口,防止访问错误。
selinux状态与设置
getenforce:获取现在的selinux的状态,确认状态为permissive或disabled
setenforce 0|1:临时设置selinux的状态,0为permissive
设置selinux状态:修改/etc/selinux/config即可,重启后生效
iptables状态与设置:
service iptables status:查看状态
iptaqbles -vnL:查看现在的iptables规则
开放相应端口的iptables命令可以在网上搜索,相关文章比较多。
4.3.3 安装依赖包
yum -y install gcc glibc autoconf net-tools automake git
5.软件环境配置
5.1 配置jdk环境
下载jdk8相关版本的二进制包到本地:
tar -C /usr/local/ -xvf jdk-8u111-linux-x64.tar.gz
cd /usr/local/
mv jdk1.8.0_111 jdk8
配置jdk环境变量
#vim /etc/profile
追加如下内容:
export JAVA_HOME=/usr/local/jdk8
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/jre/lib/rt.jar
export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
使配置生效:
source /etc/profile
验证配置:
# java -version
java version "1.8.0_111"
Java(TM) SE Runtime Environment (build 1.8.0_111-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.111-b14, mixed mode)
# which java
/usr/local/jdk8/bin/java
5.2 配置go环境
配置go环境的时候,不要直接用yum install golang安装配置go的环境,要下载golang安装包进行配置。
wget https://storage.googleapis.com/golang/go1.7.6.linux-amd64.tar.gz
上面的地址亲测可用。
tar -C /usr/local/ -xvf go1.7.6.linux-amd64.tar.gz
vim /etc/profile
export GOPATH=/home/codis
export GOROOT=/usr/local/go
export GOBIN=$GOROOT/bin
export GOPKG=$GOROOT/pkg/tool/linux_amd64
export GOARCH=amd64
export GOOS=linux
export PATH=.:$PATH:$GOBIN:$GOPKG:$GOPATH/bin
注意:GOPATH和GOROOT是不一样的,千万不要配置成一样的。GOPATH是放置codis项目的主目录,GOROOT是go的主目录。
创建GOPATH目录:
mkdir -pv /home/codis/{bin,conf,logs,data,run,src}
source /etc/profile
#go version
go version go1.7.6 linux/amd64
由于编译codis需要godep,下面配置godep:
mkdir -p $GOPATH/src/github.com/tools #新建工程目录
cd $GOPATH/src/github.com/tools
go get -u github.com/tools/godep #下载godep
cd godep
go install ./ #安装godep
验证配置是否生效
#which godep
/usr/local/go/bin/godep
有的博客里面介绍,安装好godep之后,godep会在$GOPATH/bin下面,我这个在$GOROOT下面了,不清楚什么情况,不过并不会影响下面的codis编译安装。
6. zookeeper配置
6.1 下载zookeeper到本地
wget https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.4.6/zookeeper-3.4.6.tar.gz
tar -C /usr/local/ -xvf zookeeper-3.4.6.tar.gz
cd /usr/local
mv zookeeper-3.4.6 zookeeper
6.2 配置zookeeper
cd /usr/local/zookeeper/conf
cp cp zoo_sample.cfg zoo.cfg
编辑zoo.cfg,编辑好之后内容如下:
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/local/zookeeper/data
dataLogDir=/usr/local/zookeeper/logs
clientPort=2181
maxClientCnxns=60
server.1=192.168.0.146:2888:3888
server.2=192.168.0.178:2888:3888
server.3=192.168.0.102:2888:3888
创建datadir和datalogdir:
mkdir -pv /usr/local/zookeeper/{data,logs}
上面的zoo.cfg和创建文件夹操作,在三个主机上是完全相同的,在三个主机上都要操作。
参数说明:
- tickTime:这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 时间就会发送一个心跳。
- dataDir:顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。
- clientPort:这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。
- initLimit:这个配置项是用来配置 Zookeeper 接受客户端(这里所说的客户端不是用户连接 Zookeeper 服务器的客户端,而是 Zookeeper 服务器集群中连接到 Leader 的 Follower 服务器)初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过 5个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 5*2000=10 秒
- syncLimit:这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 2*2000=4 秒
- server.A=B:C:D:其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。
192.168.0.146主机:
echo "1" > /usr/local/zookeeper/data/myid
192.168.0.178主机:
echo "2" > /usr/local/zookeeper/data/myid
192.168.0.102主机:
echo "3" > /usr/local/zookeeper/data/myid
完成如上的配置之后,就可以启动zookeeper集群了。
6.3 启动zookeeper集群
在各个主机上启动zookeeper
cd /usr/local/zookeeper/bin
./zkServer.sh start
#启动无异常会显示如下内容
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
查看zookeeper集群各节点的状态:
./zkServer.sh status
显示如下:
./zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Mode: follower
Mode显示会有两种结果:follower(从节点)和leader(主节点)
使用netstat -tnulp|grep java查看各个主机的端口状态,可以看到,主节点比从节点多启动了一个端口2888,这个端口
表示的是这个服务器与集群中的 Leader 服务器交换信息的端口。
当主节点的zookeeper服务失效之后,其它节点会自动选出一个主节点,这个延迟时间非常小,zookeeper的机制保证并不会影响业务的正常运行。当主节点被重新选出来之后,新的主节点开启2888端口,并且原来的主机点恢复并不会发生主节点抢占。
7.安装codis
下面是配置codis集群之前的安装工作,需要在3台主机上进行相同的操作。
7.1 下载codis
Codis 源代码需要下载$GOPATH/src/github.com/CodisLabs/codis。
mkdir -pv $GOPATH/src/github.com/CodisLabs
cd $GOPATH/src/github.com/CodisLabs
git clone https://github.com/CodisLabs/codis.git -b release3.2
7.2 编译安装codis
直接通过 make 进行编译。
cd $GOPATH/src/github.com/CodisLabs/codis
make
通过make编译安装会看到如下内容:
make -j -C extern/redis-3.2.8/
... ...
go build -i -o bin/codis-dashboard ./cmd/dashboard
go build -i -o bin/codis-proxy ./cmd/proxy
go build -i -o bin/codis-admin ./cmd/admin
go build -i -o bin/codis-fe ./cmd/fe
编译安装中间没有报错信息,且编译安装完成后,可以看到bin目录下的文件如下:
# ls -l ./bin
总用量 81168
drwxr-xr-x. 4 root root 4096 7月 28 11:24 assets #codis-fe前端界面展示文件夹
-rwxr-xr-x. 1 root root 15710142 7月 28 11:24 codis-admin
-rwxr-xr-x. 1 root root 16978319 7月 28 11:24 codis-dashboard
-rwxr-xr-x. 1 root root 15805815 7月 28 11:24 codis-fe
-rwxr-xr-x. 1 root root 8730159 7月 28 11:24 codis-ha
-rwxr-xr-x. 1 root root 10271436 7月 28 11:24 codis-proxy
-rwxr-xr-x. 1 root root 6312957 7月 28 11:24 codis-server
-rwxr-xr-x. 1 root root 4588203 7月 28 11:24 redis-benchmark
-rwxr-xr-x. 1 root root 4690237 7月 28 11:24 redis-cli
-rw-r--r--. 1 root root 148 7月 28 11:24 version
查看版本信息:
# cat bin/version
version = 2017-07-27 11:11:41 +0800 @3e69191beb07821ad5f948986ac02ce4330d0624 @3.2.0-9-g3e69191
compile = 2017-07-26 11:35:02 +0800 by go version go1.7.6 linux/amd64
可以看到,编译时间是7.27号,codis版本是3.2.0。
7.3 配置之前的准备
配置之前需要先做一些准备工作,以方便接下来的配置。
先确认$GOPATH文件夹下面的子文件夹:
drwxr-xr-x 3 root root 4096 7月 29 17:33 bin #存放各种可执行文件
drwxr-xr-x 2 root root 4096 7月 29 17:05 conf #存放配置文件
drwxr-xr-x 4 root root 4096 7月 28 15:58 data #这个存放redis的缓存数据
drwxr-xr-x 2 root root 4096 7月 26 10:51 log
drwxr-xr-x 3 root root 4096 7月 28 17:43 logs #存放各组件的日志
drwxr-xr-x 3 root root 4096 7月 26 10:57 pkg #编译后自动生成的
drwxr-xr-x 2 root root 4096 7月 29 17:35 run #redis-pid文件路径
drwxr-xr-x 3 root root 4096 7月 26 10:57 src #安装包路径
如果没有就先创建:
mkdir -pv /home/codis/{bin,conf,data,logs,run}
mkdir -pv /home/codis/data/{6379,6380}
然后把$GOPATH/src/github.com/CodisLabs/codis/bin下面的文件全部递归复制到$GOPATH/bin下面,注意
assets文件夹一定要递归复制过来,否则codis-fe无法启动。
cp -r $GOPATH/src/github.com/CodisLabs/codis/bin/* $GOPATH/bin/
8.配置codis集群
下面开始配置codis集群,codis集群中的组件比较多,需要一个一个配置,千万不能乱了顺序,否则很有可能配置错误。
8.1 配置codis-serevr和sentinel
codis3.2版本中,codis-sever就是基于
redis-3.2.8做了一些改动,我们在每个主机上要配置两个redis(一主6379一从6380),3个sentinel(26379,36380,46380
)做主从切换。
8.1.1 配置redis.conf(主)
在/home/codis/conf文件夹下配置redis6379.conf文件。
配置详情如下:
daemonize yes
pidfile "/home/codis/run/redis_6379.pid"
port 6379
tcp-backlog 511
bind 192.168.0.125
timeout 0
tcp-keepalive 0
loglevel notice
logfile "/home/codis/logs/redis6379.log"
databases 16
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename "dump6379.rdb"
dir "/home/codis/data/6379"
masterauth "codispass"
slave-serve-stale-data yes
slave-read-only yes
repl-disable-tcp-nodelay no
slave-priority 100
requirepass "codispass"
appendonly no
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes
8.1.2 配置redis.conf(从)
配置redis从的配置文件,可以把redis6379.conf的复制一下,然后做一些修改即可。
主要修改,把6379改为6380,然后还有配置slaveof。
pidfile "/home/codis/run/redis_6380.pid"
port 6380
logfile "/home/codis/logs/redis6380.log"
dbfilename "dump6380.rdb"
dir "/home/codis/data/6380"
#
slaveof 192.168.0.125 6379
上面的参数主要还是沿用默认配置,后期要做优化的话,有一些参数肯定要做更改。
8.1.3 配置sentinel.conf
sentinel26379.conf的配置详情如下:
daemonize yes
port 26379
bind 192.168.0.125
dir "/tmp"
sentinel monitor mymaster 192.168.0.125 6379 2
sentinel notification-script mymaster /home/codis/conf/sentinel-notify.sh
sentinel client-reconfig-script mymaster /home/codis/conf/sentinel-failover.sh
sentinel auth-pass mymaster codispass
logfile "/home/codis/logs/redis-sentinel.log"
参数配置说明:
daemonize yes:
以后台进程模式运行。
port 26379:
Sentinel实例之间的通讯端口,该端口号默认为26379。
bind 192.168.0.125:
Sentinel默认会绑定到127.0.0.1,这里要在多台机器间通信,我们将它绑定到主机IP上。
sentinel monitor redis-master 192.168.0.125 6379 2
Sentinel去监视一个名为redis-master的主服务器,这个主服务器的IP地址为192.168.
0.125
,端口号为6379。将这个主服务器判断为失效至少需要2个Sentinel同意,一般设置为N/2+1(N为Sentinel总数)。只要同意Sentinel的数量不达标,自动故障迁移就不会执行。可以设置为1,但是有风险。
不过要注意,无论你设置要多少个Sentinel同意才能判断一个服务器失效, 一个Sentinel都需要获得系统中多数Sentinel的支持,才能发起一次自动故障迁移,并预留一个给定的配置纪元。(configuration Epoch ,一个配置纪元就是一个新主服务器配置的版本号)。
sentinel down-after-milliseconds redis-master 5000
down-after-milliseconds选项指定了Sentinel认为服务器已经断线所需的毫秒数。如果服务器在给定的毫秒数之内,没有返回Sentinel发送的PING命令的回复,或者返回一个错误,那么Sentinel将这个服务器标记为主观下线(subjectively down,简称SDOWN)。
不过只有一个Sentinel将服务器标记为主观下线并不一定会引起服务器的自动故障迁移,只有在足够数量的Sentinel都将一个服务器标记为主观下线之后,服务器才会被标记为客观下线(objectively down,简称ODOWN), 这时自动故障迁移才会执行。将服务器标记为客观下线所需的Sentinel数量由对主服务器的配置(sentinel monitor参数)决定。
sentinel failover-timeout redis-master 180000
如果在多少毫秒内没有把宕掉的那台Master恢复,那Sentinel认为这是一次真正的宕机。在下一次选取时排除该宕掉的Master作为可用的节点,然后等待一定的设定值的毫秒数后再来探测该节点是否恢复,如果恢复就把它作为一台Slave加入Sentinel监测节点群,并在下一次切换时为他分配一个”选取号”。
sentinel parallel-syncs redis-master 2
parallel-syncs选项指定了在执行故障转移时,最多可以有多少个从服务器同时对新的主服务器进行同步。这个数字越小,完成故障转移所需的时间就越长。
如果从服务器被设置为允许使用过期数据集(slave-serve-stale-data选项), 那么你可能不希望所有从服务器都在同一时间向新的主服务器发送同步请求。因为尽管复制过程的绝大部分步骤都不会阻塞从服务器,但从服务器在载入主服务器发来的RDB文件时,仍然会造成从服务器在一段时间内不能处理命令请求。
如果全部从服务器一起对新的主服务器进行同步,那么就可能会造成所有从服务器在短时间内全部不可用的情况出现。你可以通过将这个值设为1来保证每次只有一个从服务器处于不能处理命令请求的状态。
sentinel auth-pass redis-master 000000
当Master设置了密码时,Sentinel连接Master和Slave时需要通过设置参数auth-pass配置相应密码。
sentinel notification-script redis-master /etc/redis/notify.sh
指定Sentinel检测到该监控的Redis实例failover时调用的报警脚本。脚本被允许执行的最大时间为60秒,超过这个时间脚本会被kill。该配置项可选,但线上系统建议配置。这里的通知脚本简单的记录一下failover事件。
8.1.4 启动codis-server和sentinel
/home/codis/bin/codis-server /home/codis/conf/redis6379.conf
/home/codis/bin/codis-server /home/codis/conf/redis6380.conf
#启动sentinel
/home/codis/bin/redis-sentinel /home/codis/conf/sentinel26379.conf
/home/codis/bin/redis-sentinel /home/codis/conf/sentinel36380.conf
/home/codis/bin/redis-sentinel /home/codis/conf/sentinel46380.conf
确认是否启动:
# netstat -tnulp|grep redis
tcp 0 0 192.168.0.102:26379 0.0.0.0:* LISTEN 1583/redis-sentinel
tcp 0 0 192.168.0.102:46380 0.0.0.0:* LISTEN 1595/redis-sentinel
tcp 0 0 192.168.0.102:36380 0.0.0.0:* LISTEN 1589/redis-sentinel
#
netstat -tnulp|grep codis- tcp 0 0 192.168.0.102:6379 0.0.0.0:* LISTEN 1564/codis-server 1 tcp 0 0 192.168.0.102:6380 0.0.0.0:* LISTEN 1570/codis-server 1
8.1.5 一些问题
问题1:需要配置奇数个sentinel
需要配置奇数个sentinel,那么安全起见最少就是3个了,sentinel使用的投票机制要求必须
N/2+1(N为sentinel节点总数)
个节点以上确认master挂掉才能进行主从切换,所以建议最少3个。
问题2:主从切换的问题
当主节点挂掉之后,从节点会持续连接主节点,确认主节点的状态,防止由于网络波动引起的连接异常。日志内容如下:
[4677] 31 Jul 11:11:43.271 # Error condition on socket for SYNC: Connection refused
[4677] 31 Jul 11:11:44.276 * Connecting to MASTER 192.168.0.125:6379
当主节点一直拒接连接,sentinel会使原来的主节点降级,投票选择后使原来的从节点变为主节点。
此时的sentinel.log如下:
4237:X 31 Jul 11:38:36.187 # +sdown master mymaster 192.168.0.125 6379
4237:X 31 Jul 11:38:36.251 # +odown master mymaster 192.168.0.125 6379 #quorum 2/2
4237:X 31 Jul 11:38:36.251 # +new-epoch 5
4237:X 31 Jul 11:38:36.251 # +try-failover master mymaster 192.168.0.125 6379
4237:X 31 Jul 11:38:36.275 # +vote-for-leader 11cec1cc033d782229ffd4f86d17e195afa284ec 5
4237:X 31 Jul 11:38:36.285 # 192.168.0.125:26379 voted for 11cec1cc033d782229ffd4f86d17e195afa284ec 5
4237:X 31 Jul 11:38:36.355 # +elected-leader master mymaster 192.168.0.125 6379
4237:X 31 Jul 11:38:36.355 # +failover-state-select-slave master mymaster 192.168.0.125 6379
4237:X 31 Jul 11:38:36.419 # +selected-slave slave 192.168.0.125:6380 192.168.0.125 6380 @ mymaster 192.168.0.125 6379
4237:X 31 Jul 11:38:36.419 * +failover-state-send-slaveof-noone slave 192.168.0.125:6380 192.168.0.125 6380 @ mymaster 192.168.0.125 6379
4237:X 31 Jul 11:38:36.482 * +failover-state-wait-promotion slave 192.168.0.125:6380 192.168.0.125 6380 @ mymaster 192.168.0.125 6379
4237:X 31 Jul 11:38:37.295 # +promoted-slave slave 192.168.0.125:6380 192.168.0.125 6380 @ mymaster 192.168.0.125 6379
4237:X 31 Jul 11:38:37.295 # +failover-state-reconf-slaves master mymaster 192.168.0.125 6379
4237:X 31 Jul 11:38:37.350 # +failover-end master mymaster 192.168.0.125 6379
4237:X 31 Jul 11:38:37.350 # +switch-master mymaster 192.168.0.125 6379 192.168.0.125 6380
4237:X 31 Jul 11:38:37.351 * +slave slave 192.168.0.125:6379 192.168.0.125 6379 @ mymaster 192.168.0.125 6380
4237:X 31 Jul 11:39:07.379 # +sdown slave 192.168.0.125:6379 192.168.0.125 6379 @ mymaster 192.168.0.125 6380
此时的从节点日志如下:
[5081] 31 Jul 11:38:35.688 * MASTER <-> SLAVE sync started
[5081] 31 Jul 11:38:35.688 # Error condition on socket for SYNC: Connection refused
[5081] 31 Jul 11:38:36.482 * Discarding previously cached master state.
[5081] 31 Jul 11:38:36.482 * MASTER MODE enabled (user request)
[5081] 31 Jul 11:38:36.482 # CONFIG REWRITE executed with success.
可以从日志内容看到,此时从节点提升为了主节点,而且也可以读写了。但是在这个故障恢复的时间,从节点还是不可写的。
当原来的节点启动后会和新的主节点进行数据同步,日志如下:
[5081] 31 Jul 11:44:06.408 * Slave 192.168.0.125:6379 asks for synchronization
[5081] 31 Jul 11:44:06.408 * Full resync requested by slave 192.168.0.125:6379
[5081] 31 Jul 11:44:06.408 * Starting BGSAVE for SYNC with target: disk
[5081] 31 Jul 11:44:06.410 * Background saving started by pid 5359
[5359] 31 Jul 11:44:06.439 * DB saved on disk
[5359] 31 Jul 11:44:06.441 * RDB: 8 MB of memory used by copy-on-write
[5081] 31 Jul 11:44:06.507 * Background saving terminated with success
[5081] 31 Jul 11:44:06.507 * Synchronization with slave 192.168.0.125:6379 succeeded
问题3:主从切换中数据访问的问题
当主节点挂掉后,读从节点数据没有问题,但是写的时候就会报错,如下:
192.168.0.125:6379> set aaa xxx
(error) READONLY You can't write against a read only slave.
只有当从节点提升为主节点之后才能可读可写。
下面配置dashboard、proxy、fe,有些需要注意的,
dashboard在一个集群中只能有一个,只有当其他节点的dashboard挂掉之后,其他节点的才能启动,proxy可以配置多个,fe是依赖于dashboard的,所以只有在配置了dashboard之后,才能配置相应的fe。而一个fe可以配置多个dashboard集群的监控。下面的dashboard、proxy、fe暂时只在146主机上进行配置启动,其它两个节点可以不启动。
8.2 配置dashboard
dashboard的默认配置文件可以通过codis-dashboard命令生成。如下:
cd /home/codis/bin/
./codis-dashboard --default-config | tee ../conf/dashboard.toml
修改dashboard.toml配置文件,如下:
- # Set Coordinator, only accept "zookeeper" & "etcd" & "filesystem".
# Quick Start
coordinator_name = "zookeeper"
coordinator_addr = "192.168.0.146:2181,192.168.0.178:2181,192.168.0.102:2181"
#coordinator_name = "zookeeper"
#coordinator_addr = "127.0.0.1:2181"
# Set Codis Product Name/Auth.
product_name = "codis-test"
product_auth = "codispass"
# Set bind address for admin(rpc), tcp only.
admin_addr = "0.0.0.0:18080"
# Set arguments for data migration (only accept 'sync' & 'semi-async').
migration_method = "semi-async" #同步复制或半同步复制
migration_parallel_slots = 100
migration_async_maxbulks = 200
migration_async_maxbytes = "32mb"
migration_async_numkeys = 500
migration_timeout = "30s"
# Set configs for redis sentinel.
sentinel_quorum = 2
sentinel_parallel_syncs = 1
sentinel_down_after = "30s"
sentinel_failover_timeout = "5m"
sentinel_notification_script = ""
sentinel_client_reconfig_script = ""
启动dashboard,如下:
nohup /home/codis/bin/codis-dashboard --config=/home/codis/conf/dashboard.toml --log=/home/codis/logs/codis/dashboard.log --log-level=INFO &
编写启动脚本bashboard-start.sh:
#!/bin/bash
#bashboard-start.sh
/home/codis/bin/codis-dashboard --config=/home/codis/conf/dashboard.toml --log=/home/codis/logs/codis/dashboard.log --log-level=INFO
停止脚本:
#!/bin/bash
#dashboard-stop.sh
/home/codis/bin/codis-admin --dashboard=192.168.0.146:18080 --shutdown
添加执行权限:
cd /home/codis/bin
chmod +x dashboard*
查看dashboard.log日志,如下:
启动的时候发现如下的错误日志,启动报错了
2017/07/31 14:43:54 zkclient.go:23: [INFO] zookeeper - Connected to 192.168.0.102:2181
2017/07/31 14:43:54 zkclient.go:23: [INFO] zookeeper - Authenticated: id=242516392466579456, timeout=40000
2017/07/31 14:43:54 zkclient.go:23: [INFO] zookeeper - Re-submitting `0` credentials after reconnect
2017/07/31 14:43:54 topom.go:189: [ERROR] store: acquire lock of codis-test failed
[error]: zk: node already exists
6 /home/codis/src/github.com/CodisLabs/codis/pkg/models/zk/zkclient.go:218
github.com/CodisLabs/codis/pkg/models/zk.(*Client).create
5 /home/codis/src/github.com/CodisLabs/codis/pkg/models/zk/zkclient.go:173
github.com/CodisLabs/codis/pkg/models/zk.(*Client).Create.func1
4 /home/codis/src/github.com/CodisLabs/codis/pkg/models/zk/zkclient.go:112
github.com/CodisLabs/codis/pkg/models/zk.(*Client).shell
3 /home/codis/src/github.com/CodisLabs/codis/pkg/models/zk/zkclient.go:175
github.com/CodisLabs/codis/pkg/models/zk.(*Client).Create
2 /home/codis/src/github.com/CodisLabs/codis/pkg/models/store.go:119
github.com/CodisLabs/codis/pkg/models.(*Store).Acquire
1 /home/codis/src/github.com/CodisLabs/codis/pkg/topom/topom.go:188
github.com/CodisLabs/codis/pkg/topom.(*Topom).Start
0 /home/codis/src/github.com/CodisLabs/codis/cmd/dashboard/main.go:169
main.main
... ...
错误提示:topom.go:189: [ERROR] store: acquire lock of codis-test failed
解决如下:
# cd /home/codis/bin
# ./codis-admin --remove-lock --product=codis-test --zookeeper=127.0.0.1:2181
2017/07/31 14:47:33 zkclient.go:23: [INFO] zookeeper - zkclient setup new connection to 127.0.0.1:2181
2017/07/31 14:47:33 zkclient.go:23: [INFO] zookeeper - Connected to 127.0.0.1:2181
2017/07/31 14:47:33 zkclient.go:23: [INFO] zookeeper - Authenticated: id=98401204457570304, timeout=40000
2017/07/31 14:47:33 zkclient.go:23: [INFO] zookeeper - Re-submitting `0` credentials after reconnect
2017/07/31 14:47:33 zkclient.go:23: [INFO] zookeeper - Recv loop terminated: err=EOF
2017/07/31 14:47:33 zkclient.go:23: [INFO] zookeeper - Send loop terminated: err=<nil>
原因:是由于没有正常关闭dashboard(比如主机突然断电,dashboard进程被直接kill掉等)引起的,这种情况一定要注意,有可能会引起数据的丢失。操作一定要规范。
解决问题之后再次启动,可以看到日志刷新如下:
2017/07/31 14:48:59 main.go:78: [WARN] set ncpu = 1
2017/07/31 14:48:59 zkclient.go:23: [INFO] zookeeper - zkclient setup new connection to 192.168.0.146:2181,192.168.0.178:2181,192.168.0.102:2181
2017/07/31 14:48:59 topom.go:119: [WARN] create new topom:
{
"token": "f1b47f465fcc0a7378c363cd21412ba9",
"start_time": "2017-07-31 14:48:59.215449647 +0800 CST",
"admin_addr": "node011:18080",
"product_name": "codis-test",
"pid": 1884,
"pwd": "/home/codis/bin",
"sys": "Linux node011 2.6.32-696.3.1.el6.x86_64 #1 SMP Tue May 30 19:52:55 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux"
}
2017/07/31 14:48:59 main.go:142: [WARN] create topom with config
coordinator_name = "zookeeper"
coordinator_addr = "192.168.0.146:2181,192.168.0.178:2181,192.168.0.102:2181"
admin_addr = "0.0.0.0:18080"
product_name = "codis-test"
product_auth = "codispass"
migration_method = "semi-async"
migration_parallel_slots = 100
migration_async_maxbulks = 200
migration_async_maxbytes = "32mb"
migration_async_numkeys = 500
migration_timeout = "30s"
sentinel_quorum = 2
sentinel_parallel_syncs = 1
sentinel_down_after = "30s"
sentinel_failover_timeout = "5m"
sentinel_notification_script = ""
sentinel_client_reconfig_script = ""
2017/07/31 14:48:59 zkclient.go:23: [INFO] zookeeper - Connected to 192.168.0.146:2181
2017/07/31 14:48:59 zkclient.go:23: [INFO] zookeeper - Authenticated: id=98401204457570305, timeout=40000
2017/07/31 14:48:59 zkclient.go:23: [INFO] zookeeper - Re-submitting `0` credentials after reconnect
2017/07/31 14:48:59 topom_sentinel.go:169: [WARN] rewatch sentinels = []
2017/07/31 14:48:59 main.go:179: [WARN] [0xc420271320] dashboard is working ...
2017/07/31 14:48:59 topom.go:429: [WARN] admin start service on [::]:18080
看日志,这里面有一点不好的就是,日志里面会把你的密码刷新出来,还是明文的。接下来是启动proxy。
然后用netstat确认一下,146主机的18080端口启动了没有。
8.3 配置proxy
对于codis集群的访问,要通过zookeeper来访问proxy,然后到后端codis-serevr集群。所以可以配置多个proxy,可以每个主机上配置一个。这里测试只配置一个。
生成proxy配置文件:
cd /home/codis/bin
./codis-proxy --default-config | tee ../conf/proxy.toml
修改配置如下:
# Set Codis Product Name/Auth.
product_name = "codis-test"
product_auth = "codispass"
# 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
before processing any other commands. session_auth = "codispass"
# 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. 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 = "zookeeper"
jodis_addr = "192.168.0.146:2181,192.168.0.178:2181,192.168.0.102:2181"
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.
# 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 =
codis3.2比之前的版本感觉做了很大变动,功能也增多了,配置项也增多了。上面的先只修改必须的配置项,其他配置项另外说明。
修改好配置之后,启动proxy,如下:
nohup /home/codis/bin/codis-proxy --config=/home/codis/conf/proxy.toml --log=/home/codis/logs/codis/proxy.log --log-level=INFO &
启动脚本:
#!/bin/bash
nohup /home/codis/bin/codis-proxy --config=/home/codis/conf/proxy.toml --log=/home/codis/logs/codis/proxy.log --log-level=INFO &
有些时候还需要下面一个才能配置上,如下:
/home/codis/bin/codis-admin --dashboard=192.168.0.146:18080 --create-proxy -x 192.168.0.146:11080 &
这个命令的目的是把proxy绑定到指定的dashboard下面。测试后发现,在第一次启动的时候,由于加载了配置文件,配置文件中有相应的配置,所以可以不用配置,但是其他主机或者proxy由于意外停止之后再次加入相应的dashboard,还是要执行的,否则会报如下的错误:
2017/07/31 15:49:04 main.go:222: [WARN] [0xc420106210] proxy waiting online ...
这个时候,19000端口启动了,但是没有绑定dashboard,所以报错了,感觉这是个bug,要不然为什么都加载了配置文件,第一次可以,第二次就不行了。
停止脚本:
#!/bin/bash
/home/codis/bin/codis-admin --proxy=192.168.0.146:11080 --auth="codispass" --shutdown &
使用ps和netstat查看进程状态和端口监听:
# ps aux|grep codis-proxy|grep -v grep
root 1961 0.6 16.0 781496 163580 pts/1 Sl 15:02 0:01 /home/codis/bin/codis-proxy --config=/home/codis/conf/proxy.toml --log=/home/codis/logs/codis/proxy.log --log-level=INFO
# netstat -tnulp|grep codis-proxy
tcp 0 0 0.0.0.0:19000 0.0.0.0:* LISTEN 1961/codis-proxy
tcp 0 0 :::11080 :::* LISTEN 1961/codis-proxy
可以确定proxy已经启动,这个时候,我们可以通过192.168.0.146:19000这个地址访问proxy了,当然此时还不能读写数据,因为还没有配置proxy后端的codis-server主机组,下面我们通过codis-fe来配置主机组。当然我们也可以通过codis-admin来配置主机组,不过通过codis-fe更简单直接一些。
8.4 配置启动fe
codis-fe是一个管理和展示codis集群的工具,可以通过web进行访问,直接在web界面进行操作。
生成初始配置文件:
codis-admin --dashboard-list --zookeeper=127.0.0.1:2181 | tee /home/codis/conf/codis/codis.json
生成的配置文件codis.json如下:
[
{
"name": "codis-test",
"dashboard": "node011:18080"
}
]
修改/home/codis/conf/codis.json文件,如下:
[
{
"name": "codis-test",
"dashboard": "192.168.0.146:18080"
}
]
这里可以看出,我们可以配置多个dashboard。
启动codis-fe:
nohup /home/codis/bin/codis-fe --log=/home/codis/logs/codis/fe.log --log-level=INFO --dashboard-list=/home/codis/conf/codis.json --listen=0.0.0.0:8080 &
启动脚本fe-start.sh:
#!/bin/bash
nohup /home/codis/bin/codis-fe --log=/home/codis/logs/codis/fe.log --log-level=INFO --dashboard-list=/home/codis/conf/codis.json --listen=0.0.0.0:8080 &
停止codis-fe:
很遗憾,在codis生成的命令中,没有找到停止codis-fe的脚本,所以我们只能找到codis-fe的进程,然后使用kill把它杀死了。
停止脚本如下:
#!/bin/bash
fepid=$(ps aux|grep codis-fe|grep -v grep|gawk 'BEGIN{FS=" "} {print $2}')
kill -9 $fepid
9.使用fe管理界面
在浏览器中使用 http://192.168.0.146:8080地址访问,可以看到如下界面
其实感觉,
这个fe界面最好结合nginx来做反代,加一个用户认证更好一些,否则只要知道地址就可以访问,这个是一个安全问题了。
下面我们做一些配置:
9.1 新增proxy
我们在178主机上新增一个proxy。在178下新增proxy,
执行
如下
命令
:
nohup /home/codis/bin/codis-proxy --config=/home/codis/conf/proxy.toml --log=/home/codis/logs/codis/proxy.log --log-level=INFO &
proxy.toml的配置和146主机的一样。
启动之后可以看到日志刷新如下:
2017/07/31 16:29:42 main.go:222: [WARN] [0xc4200fc630] proxy waiting online ...
我们还需要另一条命令:
/home/codis/bin/codis-admin --dashboard=192.168.0.146:18080 --create-proxy -x 192.168.0.146:11080 &
命令运行完之后可以看到,在web界面出现新的proxy
当然我们在运行了第一条命令之后,也可以在界面添加:
新添加的还需要跟原有的proxy做一些同步,不过比较快。
9.2 新增主机组和codis-server主机
新增主机组:
添加成功:
添加主机:
node11:对新增的主机进行标记
192.168.0.146:6380:新增的主机的IP和端口
点击"Add Server"就可以添加了。
把3台主机上的codis-server主从都加进去,新增了3个主机组。
9.3 数据分片
codis中把数据分为0-1023共1024个片区,数据会分散到这1024个片区中保存。数据分片我们可以自定义分片,也可以使用自动分片。
未分片之前:
自定义分片如下:
设置好要分片的片区,片区要绑定的组,上面的就是把750-1023的片区分配到第三个组,点击“Migrate Range”就开始分片了。
数据迁移的时候,日志会有刷新,web界面也会显示迁移的进度,这个比较好,但是建议数据迁移操作还是在业务低峰期做,防止产生数据不一致。
采用自动分片:如果你是刚开始建立的组,可以尝试用自动分片。
创建了两个组,使用自动分片的情况:
开始分片了:
点击“RebalanceAllSlots”就可以自动分片了。
当我新加了一个组之后,再次选择自动分片:
可以看到,组3的分片是不连续的。所以最好使用手动调整
自动分片与手动分片的选择:如果你是刚开始集群的安装配置,建议使用自动分片,如果是要新增主机组,建议使用手动分片。因为新增主机组使用自动分片会让片区不连续。
9.4 添加sentinel使集群高可用
添加前确保你的sentinel启动了。添加方式很简单,如下:
“Add Sentinel"之后,就可以看到已经添加进去了,建议添加多个sentinel,添加完
sentinel之后,可以用“sync”同步一下各个sentinel的状态。
当然,使用完之后,你会发现这个有多少坑。这个后文再说了。
10.其它
codis集群的安装部署已经全部告一段落了,接下来就可以用redis-benchmark,redis-cli进行连接和测试了。
用redis-cli连接proxy的端口,如下:
# redis-cli -h 192.168.0.146 -a codispass -p 19000
192.168.0.146:19000> ping
PONG
192.168.0.146:19000> set hehe haha
OK
192.168.0.146:19000> set abc 123
OK
使用
redis-benchmark,如下:
redis-benchmark -h 192.168.0.146 -p 19000 -a codispass -t set,get -n 100000 -q
SET: 14304.10 requests per second
GET: 14287.75 requests per second
这个结果是由于虚拟机配置太低,不具有参考价值。
好了,安装配置codis的问题基本解决了。codis集群安装配置下来,总的感觉,组件太多,配置繁琐,不过后期运维应该会简单一下,可以做到不停机维护,数据迁移等,还可以通过web界面进行配置。