Codis是一套用go语言编写的,为了应对高并环境下的redis集群软件,原理是对一个redis key操作前,先把这个key通过crc32算法,分配到不同redis的某一个slot,实现并发读写功能.而且能通过zookeeper调用redis-sentinel来实现故障切换功能.现在最新版本是3.2.1,依托于redis3.2.9开发出来.

优点:实现高并发读写,数据一致性高.

缺点:性能有较大损耗,故障切换无法保证不丢key,无法进行读写分离.

 

架构介绍

1.需要用到的软件有:

codis3.2.1

描述:codis集群套件,里面含有redis相关程序,和集群专用程序,主要功能程序解析:

codis-server:属于redis-server优化版,基于 redis-3.2.9 分支开发。增加了额外的数据结构,以支持 slot 有关的操作以及数据迁移指令。

codis-proxy:客户端连接的 Redis 代理服务, 实现了 Redis 协议。 除部分命令不支持以外(例如:keys *,flush ),表现的和原生的 Redis 没有区别(就像 Twemproxy)。

redis-sentinel:可以实现对Redis的监控、通知、自动故障转移。如果Master不能工作,则会自动启动故障转移进程,将其中的一个Slave提升为Master,其他的Slave重新设置新的Master服务。Sentinel的配置由 codis-dashboardzookeeper一起控制,不需要手工填写.

codis-dashboard:集群管理工具,支持 codis-proxycodis-server 的添加、删除,以及据迁移等操作。在集群状态发生改变时,codis-dashboard 维护集群下所有 codis-proxy 的状态的一致性。

codis-fe:集群web管理界面。

go1.9.1

描述:codis依赖语言包

jdk1.8

描述:zookeeper依赖语言包

zookeeper-3.4.11

描述:用于存放数据配置路由表。zookeeper简称zk。在生产环境中,zk部署越多,其可靠性越高。由于zk集群是以宕机个数过半才会让整个集群宕机,因此,奇数个zk更佳。

 

2. 逻辑架构如下:

访问层:访问方式可以是类似keepalived集群的vip方式,或者是通过java代码调用jodis控件再连接上zookeeper集群,然后查找到可用的proxy,进而连接调用不同的codis-proxy地址来实现高可用的LVSHA功能.

 

代理层:中间层由codis-proxyzookeeper处理数据走向和分配,通过crc32算法,key平均分配在不同redis的某一个slot.实现类似raid0的条带化,在旧版本的codis,slot需要手工分配,codis3.2之后,只要点一个按钮slot会自动分配,相当方便,但是也可以手动分配,需要另外调用codis-admin命令.

 

数据层:最后codis-proxy把数据存进真实的redis-server主服务器上,由于codis的作者黄东旭相当注重数据一致性,不允许有数据延时造成的数据不一致,所以架构从一开始就没考虑主从读写分离.从服务器仅仅是作为故障切换的冗余架构,codis-dashboard监控各服务的状态,然后通过改写zookeeper数据和调用redis-sentinel实现故障切换功能.



3.因为机器有限,部署的架构如下:

zookeeper集群:

10.0.2.5:2181

10.0.2.6:2181

10.0.2.7:2181

codis-configcodis-dashboard

10.0.2.6:18087

10.0.2.6:8090

codis-proxy

10.0.2.5:19000

10.0.2.7:19000

codis-server

10.0.2.5:6379(),10.0.2.5:6380()

10.0.2.6:6379(),10.0.2.6:6380()

10.0.2.7:6379(),10.0.2.7:6380()

 

安装部署

1. 下载程序代码

1)下载golang语言程序包,

按正常途径是要×××的,不过国内地址也有人放出来了,因为codis3.2要求至少是1.71.8以上版本的,那干脆下最新版吧.

https://studygolang.com/dl/golang/go1.9.1.linux-amd64.tar.gz

 

2)下载java语言程序包,

Java的下载地址一直在变,所以最好自己上去看着来下载

http://download.oracle.com/otn-pub/java/jdk/8u151-b12/e758a0de34e24606bca991d704f6dcbf/jdk-8u151-linux-x64.tar.gz?AuthParam=1513326216_bcf60226458d67751e1d8d1bbe6689b4

 

3)下载zookeeper程序

直接就是程序包,不用编译了,好方便

http://mirrors.hust.edu.cn/apache/zookeeper/zookeeper-3.4.11/zookeeper-3.4.11.tar.gz

 

4)下载codis3.2.1

直接就是程序包,不用编译了,好方便

https://github.com/CodisLabs/codis/releases/download/3.2.1/codis3.2.1-go1.7.6-linux.tar.gz

 

2. 安装程序

1) 安装java

#解压程序包
tar xf jdk-8u144-linux-x64.tar.gz
#移动到指定目录
mv jdk1.8.0_144/ /usr/local/
#进入指定目录,并创建程序软连接
cd /usr/local/
ln -sf jdk1.8.0_144/ jdk
#创建环境变量文件
echo "export JAVA_HOME=/usr/local/jdk
export JRE_HOME=/usr/local/jdk/jre
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib
export PATH=$PATH:$JAVA_HOME/bin"> /etc/profile.d/java.sh
#重载环境变量
source /etc/profile
#测试检查是否安装完成
java -version
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)

安装完毕

 

2) 安装golang

#解压程序包
tar xf go1.9.1.linux-amd64.tar.gz
#移动到指定目录
mv go /usr/local/
#把程序包里的命令软连接到系统默认命令目录
ln -sf /usr/local/go/bin/* /usr/bin/
#测试检查是否安装完成
go version
go version go1.9.1 linux/amd64

安装完成

 

3) 安装zookeeper

#解压程序包
tar xf zookeeper-3.4.11.tar.gz
#移动到指定目录
mv zookeeper-3.4.11 /usr/local/
#进入指定目录,并创建程序软连接
cd /usr/local/
ln -sf zookeeper-3.4.11/ zookeeper

安装完成,等候配置.

 

4) 安装codis

#解压程序包
tar xf codis3.2.1-go1.7.6-linux.tar.gz
#移动到指定目录
mv codis3.2.1-go1.7.6-linux /usr/local/
#进入指定目录,并创建程序软连接
cd /usr/local/
ln -sf codis3.2.1-go1.7.6-linux/ codis

安装完成,等候配置,因为我们用的都是二进制程序包,只要依赖包有正常安装,就不会报错,直接就能用,所以安装就很简单.

 

3. 配置程序

1) 配置zookeeper,3台一起都是这么配置

#设置hosts跳转规则,好像不这么设置的话,不能顺利启动
echo “10.0.2.5        zookeeper-node1
10.0.2.6        zookeeper-node2
10.0.2.7        zookeeper-node3” >> /etc/hosts
#创建程序目录
mkdir -p /data/zookeeper
#创建配置文件,文件夹里有一个模板,有兴趣可以看看
vim /usr/local/zookeeper/conf/zoo.cfg
#最大连接数设置(单ip限制). 注:默认60,设成0即无限制. 
maxClientCnxns=500
#一个周期(tick)的时长(单位:毫秒). 
tickTime=28800
#初始化同步阶段最多耗费tick个数. 注:可用默认值
initLimit=10
#等待应答的最大间隔tick个数. 注:可用默认值
syncLimit=5
#数据存储目录,刚才创建那个. 注:勿放在/tmp目录
dataDir=/data/zookeeper/
#通信端口. 注:可用默认值
clientPort=2181
server.1=zookeeper-node1:2888:3888
server.2=zookeeper-node2:2888:3888
server.3=zookeeper-node3:2888:3888

生成ID,这里需要注意, myid对应的zoo.cfgserver.ID.比如zookeeper-node2对应的myid应该是2,不按规定设置,zookeeper集群将无法启动

echo "1"> /data/zookeeper/myid

==============================================

例如:在zookeeper-node3那台10.0.2.7的服务器,就应该是

echo "3"> /data/zookeeper/myid

====================================================

#zoo.cfg最后三行特别说明

说明:server.A=BCD:其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。

#最后启动,因为zookeeper的server是有顺序的,最好是按顺序启动,先启动server.1再启动server2,最后启动server.3这样
/usr/local/zookeeper/bin/zkServer.sh start
#查看状态,会有follower和leader的区别,他们自己会选谁是leader
/usr/local/zookeeper/bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Mode: follower

配置并启动完毕.

 

2) 配置codis-server,3台一起都是这么配置

注意:codis-server就是redis-server程序,属于codis优化版本,配合codis集群使用.

所以就是配置个redis的主从结构,实际生产环境不要搭在一起

#创建redis数据目录,配置文件目录,日志目录
mkdir -p /data/redis/data/config/
mkdir -p /data/redis/data/logs/
#创建主库的配置文件,暂时只配置这些,其他先默认
vim /data/redis/data/config/redis_6379.conf
#允许后台运行
daemonize yes
#设置端口,最好是非默认端口
port 6379
#绑定登录IP,安全考虑,最好是内网
bind *
#命名并指定当前redis的PID路径,用以区分多个redis
pidfile "/data/redis/data/config/redis_6379.pid"
#命名并指定当前redis日志文件路径
logfile "/data/redis/data/logs/redis_6379.log"
#指定RDB文件名,用以备份数据到硬盘并区分不同redis,当使用内存超过可用内存的45%时触发快照功能
dbfilename "dump_6379.rdb"
#指定当前redis的根目录,用来存放RDB/AOF文件
dir "/data/redis/data"
#当前redis的认证密钥,redis运行速度非常快,这个密码要足够强大,
#所有codis-proxy集群相关的redis-server认证密码必须全部一致
requirepass "123"
#当前redis的最大容量限制,建议设置为可用内存的45%内,最高能设置为系统可用内存的95%,
#可用config set maxmemory 去在线修改,但重启失效,需要使用config rewrite命令去刷新配置文件
#注意,使用codis集群,必须配置容量大小限制,不然无法启动
maxmemory 100000kb
#LRU的策略,有四种,看情况选择
maxmemory-policy allkeys-lru
#如果做故障切换,不论主从节点都要填写密码且要保持一致
masterauth "123"
 
#创建从库的配置文件,暂时只配置这些,其他先默认
vim /data/redis/data/config/redis_6380.conf
#允许后台运行
daemonize yes
#设置端口,最好是非默认端口
port 6380
#绑定登录IP,安全考虑,最好是内网
bind *
#命名并指定当前redis的PID路径,用以区分多个redis
pidfile "/data/redis/data/config/redis_6380.pid"
#命名并指定当前redis日志文件路径
logfile "/data/redis/data/logs/redis_6380.log"
#指定RDB文件名,用以备份数据到硬盘并区分不同redis,当使用内存超过可用内存的45%时触发快照功能
dbfilename "dump_6380.rdb"
#指定当前redis的根目录,用来存放RDB/AOF文件
dir "/data/redis/data"
#当前redis的认证密钥,redis运行速度非常快,这个密码要足够强大
#所有codis-proxy集群相关的redis-server认证密码必须全部一致
requirepass "123"
#当前redis的最大容量限制,建议设置为可用内存的45%内,最高能设置为系统可用内存的95%,
#可用config set maxmemory 去在线修改,但重启失效,需要使用config rewrite命令去刷新配置文件
#注意,使用codis集群,必须配置容量大小限制,不然无法启动
maxmemory 100000kb
#LRU的策略,有四种,看情况选择
maxmemory-policy allkeys-lru
#如果做故障切换,不论主从节点都要填写密码且要保持一致
masterauth "123"
#配置主节点信息
slaveof 10.0.2.5 6379

除了端口号不同带来的文件名不同.实际上从库配置只是多了最后一行,指定了主库地址

#然后就可以启动了,我一开始就说过codis-server就是redis-server
/usr/local/codis/codis-server /data/redis/data/config/redis_6379.conf
/usr/local/codis/codis-server /data/redis/data/config/redis_6380.conf
#验证一下
ss -ntplu |grep codis-server
tcp    LISTEN     0      128       *:6379                  *:*                   users:(("codis-server",pid=2192,fd=4))
tcp    LISTEN     0      128       *:6380                  *:*                   users:(("codis-server",pid=2197,fd=4))

启动方式和redis-server一样,指定配置文件就可以启动.这就配置并启动成功了.

 

3) 配置redis-sentinel,3台一起都是这么配置

正确来说,redis-sentinel是要配置主从架构才能生效,但是在codis集群中并不一样,因为他的配置由zookeeper来维护,所以,这里codis使用的redis-sentinel只需要配置一些基本配置就可以了.

#我们把配置放到redis数据目录的配置文件目录
vim /data/redis/data/config/sentinel.conf
bind 0.0.0.0
protected-mode no
port 26379
dir "/data/redis/data"
pidfile "/data/redis/data/config/sentinel_26379.pid"
logfile "/data/redis/data/logs/sentinel_26379.log"
daemonize yes
#然后就可以启动了
/usr/local/codis/redis-sentinel /data/redis/data/config/sentinel.conf
#验证一下
/usr/local/codis/redis-cli -p 26379 -c info Sentinel
# Sentinel
sentinel_masters:3
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=codis-test1-3,status=ok,address=10.0.2.7:6380,slaves=1,sentinels=3
master1:name=codis-test1-1,status=ok,address=10.0.2.5:6379,slaves=1,sentinels=3
master2:name=codis-test1-2,status=ok,address=10.0.2.6:6379,slaves=1,sentinels=3

配置并启动成功.

注意:没有配置好codis-dashboard会没最后那几行,因为还不受zookeeper控制,所以是正常的,配置好之后才会自动加载进来.

 

4) 配置codis-proxy,这次只有两台要配置,当然你也可以配三台

这个是codis集群的核心,实际上他也没配主从架构,配置也是从zookeeper拿来用的,所以,直接来看配置吧

#配置很多,我们先生成一下默认的配置文件
/usr/local/codis/codis-proxy --default-config | tee ./proxy.conf
#然后我们把配置放到redis数据目录的配置文件目录,再更改关键位置,其他默认即可
vim /data/redis/data/config/proxy.conf
#项目名称,会登记在zookeeper里,如果你想一套zookeeper管理多套codis,就必须区分好
product_name = "codis-test1"
# 设置登录dashboard的密码(与真实redis中requirepass一致)
product_auth = "123"
#客户端(redis-cli)的登录密码(与真实redis中requirepass不一致),是登录codis的密码
session_auth = "123456"
#管理的端口,0.0.0.0即对所有ip开放,基于安全考虑,可以限制内网
admin_addr = "0.0.0.0:11080"
#用那种方式通信,假如你的网络支持tcp6的话就可以设别的
proto_type = "tcp4"
#客户端(redis-cli)访问代理的端口,0.0.0.0即对所有ip开放
proxy_addr = "0.0.0.0:19000"
#外部配置存储类型,我们用的就是zookeeper,当然也是还有其他可以支持,这里不展开说
jodis_name = "zookeeper"
#配置zookeeper的连接地址,这里是三台就填三台
jodis_addr = "10.0.2.5:2181,10.0.2.6:2181,10.0.2.7:2181"
#zookeeper的密码,假如有的话
jodis_auth = ""
#codis代理的最大连接数,默认是1000,并发大要调大
proxy_max_clients = 100000
#假如并发太大,你可能需要调这个pipeline参数,大多数情况默认10000就够了
session_max_pipeline = 100000
#并发大也可以改以下参数
backend_max_pipeline = 204800
session_recv_bufsize = "256kb"
session_recv_timeout = "0s"
#然后就可以启动了,
/usr/local/codis/codis-proxy --ncpu=1 --config=/data/redis/data/config/proxy.conf --log=/data/redis/data/logs/proxy.log &
#验证一下
ss -ntplu |grep codis-proxy
tcp    LISTEN     0      128       *:19000                 *:*                   users:(("codis-proxy",pid=2075,fd=4))
tcp    LISTEN     0      128      :::11080                :::*                   users:(("codis-proxy",pid=2075,fd=6))

--ncpu    指定使用多少个cpu,我的是虚拟机,所以就1了,如果你是多核,那就填多个

--config    指定配置文件,就是刚才的配置文件

--log    指定输出日志文件

配置并启动成功,但是暂时还用不了,因为还需要配置codis-dashboard才能最终完成.

 

5) 配置codis-dashboard,只需要配置一台机上

这个属于管理配置codis集群信息的工具,配置完之后的配置信息会自动加载到zookeeper集群,即使这个服务挂了,配置都还在zookeeper,所以不用考虑高可用,单点就足够了,大不了重新启动一下也不是特别麻烦,配置界面由codis-fe来实现.因为限制于zookeeper的命名空间,通常是和proxy一套配置.

#我们也可以用程序来生成一下默认配置文件
/usr/local/codis/codis-dashboard --default-config | tee ./dashboard.conf
#然后我们把配置放到redis数据目录的配置文件目录,再更改关键位置,其他默认即可
vim /data/redis/data/config/dashboard.conf
#外部配置存储类型,我们用的就是zookeeper,当然也是还有其他可以支持,这里不展开说
coordinator_name = "zookeeper"
#配置zookeeper的连接地址,这里是三台就填三台
coordinator_addr = "10.0.2.5:2181,10.0.2.6:2181,10.0.2.7:2181"
#项目名称,会登记在zookeeper里,如果你想一套zookeeper管理多套codis,就必须区分好
product_name = "codis-test1"
#所有redis的登录密码(与真实redis中requirepass一致),因为要登录进去修改数据
product_auth = "123"
#codis-dashboard的通信端口,0.0.0.0表示对所有开放,最好使用内网地址
admin_addr = "0.0.0.0:18080"
#如果想要在codis集群在故障切换功能上执行一些脚本,可以配置以下两个配置
sentinel_notification_script = ""
sentinel_client_reconfig_script = ""
#然后就可以启动了,
/usr/local/codis/codis-dashboard --ncpu=1 --config=/data/redis/data/config/dashboard.conf --log=/data/redis/data/logs/codis_dashboard.log --log-level=WARN &
#验证一下
ss -ntplu |grep codis-dashboard
tcp    LISTEN     0      128      :::18080                :::*                   users:(("codis-dashboard",pid=2021,fd=5))

--ncpu    指定使用多少个cpu

--config    指定配置文件

--log    指定输出日志文件

--log-level    指定日志等级,有INFO,WARN,DEBUG,ERROR

安装完成,就差最后一步就可以开始配置.

由于codis-dashboard本身是不需要密码登录的,所以这将会非常危险,强烈建议使用内网地址,而作者表示将会在下个版本考虑增加codis-dashboard的认证密码.

 

6) 配置codis-fe,只需要配置一台机上

这个是属于web界面操作codis-dashboard配置的工具,web代码文件在codis安装文件夹的目录下,具体是:/usr/local/codis/assets/这个目录.可多台dashboard共用.

这个工具本身不需要配置文件就能启动,只需要指定codis-dashboardip和端口就可以了,但是我为了方便管理,还是生成一个配置文件的好.

#生成一下配置文件,其实也就是codis-dashboard的ip和端口
/usr/local/codis/codis-admin --dashboard-list --zookeeper=10.0.2.6:2181 >codis.json
#然后我们把配置放到redis数据目录的配置文件目录,看一下
cat /data/redis/data/config/codis.json 
[
    {
        "name": "codis-test1",
        "dashboard": "10.0.2.6:18080"
    }
]
#然后就可以启动了,
/usr/local/codis/codis-fe --ncpu=1 --log=/data/redis/data/logs/fe.log --log-level=WARN --dashboard-list=/data/redis/data/config/codis.json --listen=0.0.0.0:8090 &

--ncpu    指定使用多少个cpu

--log    指定输出日志文件

--log-level    指定日志等级,有INFO,WARN,DEBUG,ERROR

--dashboard-list    指定dashboard的地址和项目名称,这里因为生成了文件,所以就指定成文件了

--listen    指定codis-fe的web登录端口,也就是我们通过8090来访问这个管理端了,0.0.0.0即对来访IP无限制,其实最好是限制内网

验证一下

全套安装完成,成功启动.开始下一步.

 

使用举例

因为有了web界面,基本上就都是界面操作了,非常方便,配置会直接加载到zookeeper里面去.

首先,我们先添加codis-proxy地址和端口



按顺序:

第一步,先添加codis-proxy的地址和管理端口,上面设置的是11080.

第二步,点击左方的橙色按钮,然后就添加完毕.

第三步,看到下方出现该有的codis-proxy地址就算完成了,然后看到右方的SYNC字样的颜色是绿色,则代表配置正常.

如果要删除记录,点击最右方的红色按钮即可.

 

然后,我们添加真实redis-server(也是codis-server)地址和端口


按顺序:

第一步,先创建一个组,准备把相关的一组主从放进去

第二步,点击按钮生成这个分组

第三步,添加真实redis-server地址,并选定一个分组,例如刚才创建的分组1

第四步,点击按钮生成配置

第五步,可以看到配置已经登记好,注意sync状态.

第六步,点击重新平衡所有slots数据块(任何添加和删除新旧节点都需要点击这个)

在旧版本中slots需要手动配置,但是3.2版本之后就改成自动分配了,所以已经不需要配置,点击一下就可以了.当然你也可以手动去分配.

 

最后配置sentinel的地址和端口



按顺序:

第一步,添加真实的sentinel地址和端口

第二步,点击按钮添加

第三步,查看状态,这里有点不一样,他会自动添加当前主从组架构由多少台,控制切换

也正如我之前说的,他们自动去改配置文件,可以去看看sentinel的配置文件证实一下,这里不展开来说了

 

都配置好了,就可以使用了,连接其中一个codis-proxy测一下,注意区分好登录的地址和端口,还有密码

/usr/local/codis/redis-cli -h 10.0.2.5 -p 19000 -a 123456
10.0.2.5:19000> info
# Server
redis_version:3.2.9
redis_git_sha1:f8bc4e32
redis_git_dirty:0
redis_build_id:2bdb8aa56be3fbc2
redis_mode:standalone
os:Linux 4.10.0-19-generic x86_64
arch_bits:64
multiplexing_api:epoll
gcc_version:4.8.4
process_id:2032
run_id:98e2364d837990dfb47be050901ef9e36ea113fa
tcp_port:6379
uptime_in_seconds:16312
uptime_in_days:0
hz:10
lru_clock:3634855
executable:/usr/local/codis/codis-server
config_file:/data/redis/data/config/redis_6379.conf
 
# Clients
connected_clients:71
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0
 
# Memory
used_memory:61878808
used_memory_human:59.01M
used_memory_rss:76623872
used_memory_rss_human:73.07M
used_memory_peak:63148384
used_memory_peak_human:60.22M
total_system_memory:1529741312
total_system_memory_human:1.42G
used_memory_lua:37888
used_memory_lua_human:37.00K
maxmemory:102400000
maxmemory_human:97.66M
maxmemory_policy:allkeys-lru
mem_fragmentation_ratio:1.24
mem_allocator:jemalloc-4.0.3
.
.
.

可以使用了.

 

压力测试

1.性能测试

先用自带的redis-benchmark来压测性能,模拟500个并发和100万个请求.注意区分好登录的地址和端口,还有密码

先压测codis-proxy的性能

/usr/local/codis/redis-benchmark -h 10.0.2.5 -p 19000 -a 123456 -c 500 -n 1000000 -q

再压测单节点的性能

/usr/local/codis/redis-benchmark -h 10.0.2.5 -p 6379 -a 123 -c 500 -n 1000000 -q

redis-benchmark参数解析:

-h    ip地址

-p    redis端口

-a    认证密码

-c    设定多少个并发连接

-n    总共多少个请求

-q    显示模式:简要模式

然后看图



可以看到,有些操作相差不大,有些相差甚远,性能损耗明显,不过作为集群应用,主要应对的是高并发环境,性能损耗是可以接受的,何况对于redis这种内存型高速应用来说,性能损耗基本没什么太大感知.

 

2.数据分布测试

然后是读写分布测试:

我写了个脚本来测试:

cat t-redis.sh
#!/bin/bash
hos="10.0.2.7"
pot="19000"
pawd="123456"
cli="/usr/local/codis/redis-cli"
keyset="keytest2"
valueset="jlasdnfnsdfsdf;sdfhlkjahsdjlkfadfjkasdbbcjhdgasfyuefkbadjkhflk"
dbname=2
a=0
for i in `seq 1 300000`
do
        $cli -h $hos -p $pot -a $pawd -n $dbname 'set' ${keyset}${a} "${valueset}${a}" >/dev/null
        #echo $a
        let a++
done

脚本很简单,就是不断向codis集群写垃圾数据而已,执行脚本.

bash t-redis.sh

然后结果可以看web界面,因为你连接codis-proxyinfo来看,其实是不准确的,那个显示的只是单台的数据.



可以看到,每一个组都分布得比较均匀,把压力都分到三台redis-server主服务器去了.

 

3.故障切换测试

然后来看故障切换,继续执行那个脚本

bash t-redis.sh

进入其中一台codis-server,例如10.0.2.5,此时状态是正常的.


开始模拟操作

#查找主库进程
ss -ntplu |grep codis-server 
tcp    LISTEN     0      128       *:6379                  *:*                   users:(("codis-server",pid=2032,fd=4))
tcp    LISTEN     0      128       *:6380                  *:*                   users:(("codis-server",pid=2037,fd=4))
#杀掉主库进程
kill 2032

此时从库接管了主库的进程,sentinels有提示信息.



可能有人发现组1sync按钮变成了红色,也就是说主从失效,点一下就变回正常.

现在等待数据写完,我先把旧的主进程6379端口从新起来

/usr/local/codis/codis-server /data/redis/data/config/redis_6379.conf

然后看看:


看状态是恢复正常了,但是有计算机的同学可以算一下,我的脚本执行的总共是30万个key,但是现在少了几千个.


---1:

上面丢key的问题是由于redis-sentinel故障切换期间,整个codis集群并不会关闭对此故障redis-server的连接,所以codis-proxy依然会发送数据给当前故障的redis-server,而显然此时的redis-server是无法存储数据的,这就造成了丢key现象了.如果整个主从挂了,就会丢掉所有发送到此redis-serverkey,除非手工剔除故障节点.

虽然codis还自带有一种故障切换程序codis-ha,他属于一个守护进程,会连接codis-dashboard查看各节点状态,

#执行一下命令启动codis-ha,端口是codis-dashboard的端口
/usr/local/codis/codis-ha --dashboard=10.0.2.6:18080 --log=/data/redis/data/logs/ha.log --log-level=WARN &

--dashboard    指定dashboard的地址和端口

--log    指定日志文件

--log-level    指定日志等级,有INFO,WARN,DEBUG,ERROR

但是这个软件也是有缺陷,他会自动连接上dashboard检测各主从结构的健康信息,检测间隔很快(默认3秒,可修改参数--interval),检测到故障后,会将故障主库或者从库强制下线并删除在dashboard登记的信息.

虽然切换速度非常快,只会有很少的丢key现象(3秒还是会丢一些),但是后面会把故障旧主库强制下线,需要手动修改配置并重新启动redis-server(codis-server),还要再在codis-fe界面添加配置才行.

 

显然这是做不到全自动管理,有点麻烦了,而且也会让redis-sentinel变得没有意义了,所以只能两个方式选其一.

虽然看上去丢key现象是少了,但是依然还是有丢key的情况,只能说是50步笑100步,而且该组内其他 slave 实例是不会自动改变状态的,这些 slave 仍将试图从旧的 master 上同步数据,因而会导致组内新的 master 和其他 slave 之间的数据不一致。因此当出现主从切换时,需要管理员手动创建新的 sync action 来完成新 master 与 slave 之间的数据同步,这样反而增加了手动操作的工作量,各位对于codis-ha和redis-sentinel的集群的选择还是需要多考虑一些实际情况.

所以,说到底就是codis的故障切换没有做好,如果对丢key可以容忍的,就开redis-sentinel就足够了,对于数据一致性要求高的,就开codis-ha加脚本来实现比较好,各取所需.


实时添加删除节点
codis的另一个卖点就是可以在线添加/删除redis-server(codis-server)节点,做到实时扩容和更换问题节点,对于单点redis而言优势明显,不用重启服务就能有更大的空间,也可以在线切换掉有问题的节点.不过要注意,可以实时扩容和故障切换,并不代表没有性能损耗,真的要做也是要注意线上压力,避免性能压力导致的服务不可用.

实现的原理是因为codis集群把各个redis-server节点都用规则分成了多个slots数据块(总共1024个).需要扩容只要把新的节点添加完成后,在把这些slots信息从新分配就可以达到扩容效果,需要故障切换则把问题redis-server节点slots迁移走,然后就可以把这个节点下架了,对于线上环境可以说是几乎没感知,试验过程中也没发现有丢key现象,就是性能有所下降,但是我测试的环境下性能下降还能接受,大概只有10%-30%的性能损耗.

开始试验,首先我们假设添加两个新的redis-server节点,都直接是主库,没有从库环境(因为这次不需要测故障切换):

10.0.2.5:16379

10.0.2.6:16379


1.添加一个节点,等于是扩容,这还是比较简单

第一步,和之前差不多,先创建一个新组,点击按钮确认添加

第二步,把新地址添加到新的组,点击按钮确认添加

第三步,确认新的地址已成功添加进去

第四步,点击按钮,从新分配所有slots数据块

这里唯一问题就是最后一步,重新分配会耗费一定资源,codis会自动平衡数据块的分布,所以会有数据迁移过程,但是据我测试的结果来看,并不很严重,大概在20%左右.

根据它自带的监控来看的话,

可以看到之前正常情况的qps接近1500,刚点重新平衡下降比较严重一些,后面就大概有20%的性能损耗那样子,最后迁移完毕就恢复正常了.当然这是数据量少的情况,如果数据量多,这个迁移时间就恐怕不是那么简单了.


2.切换一个节点,并下架

正常下架只需要点击这个按钮

但是因为里面还有数据,是不允许直接下架的

所以我们要先迁移数据,如下所示:

第一步,确认一个需要迁移的组的数据块的编号,例如这里499-512的块是数据组4的,我现在要迁移组4,就选定这个

第二步,把刚才获取到的信息填进去,就是把500这个编号的数据块从组4迁移到组5,点击按钮执行

然后你就会看到,

显然组4的信息消失了,codis把组4的数据块都迁移到了组5去了,

这个时候,这个redis-server节点就可以删除了

至于还需不需要重新填补,这个问题则需要自身考虑.如果不需要填补,最好再点一下重新平衡slots比较好.

可以看到,又重新平衡了.


故障处理

1.codis-dashboard无法启动,并提示:

[ERROR] store: acquire lock of codis-test1 failed
[error]: zk: node
already exists

由于是测试环境,我经常强制关机,导致codis-dashboard没有正常关闭.直接造成zookeeper里面的状态没有更新,最终新启动的codis-dashboard不能注册进zookeeper,一直提示已存在而被强制关闭.

修复方法也不难,就是删除这个lock的状态键值就可以了

#输入项目名和zookeeper地址
/usr/local/codis/codis-admin --remove-lock --product=codis-test1 --zookeeper=10.0.2.6:2181

然后,codis-dashboard又可以正常启动了.

codis-admin是可以全权控制codis集群的工具,所有添加/删除/修改的工作都可以用他来实现,参数很多,这里只是举例了一个方法,详细可以参照codis-admin --help


2.codis-proxy异常退出导致无法删除

通常codis-proxy都是通过codis-dashboard进行移除,移除过程中codis-dashboard为了安全会向codis-proxy发送offline指令,成功后才会将proxy信息从外部存储(zookeeper)中移除。如果codis-proxy异常退出,该操作会失败。此时需要手动删除zookeeper信息。

#先登入zookeeper进行操作
/usr/local/zookeeper/bin/zkCli.sh -server
#确认有问题的codis-proxy地址对应在zookeeper上的信息
[zk: localhost:2181(CONNECTED) 6] get  /codis3/codis-zyyhj1/proxy/proxy-80722e128c6e8fc3d0da44983343a843
{
    "id": 1,
    "token": "80722e128c6e8fc3d0da44983343a843",
    "start_time": "2018-06-16 23:30:57.44436209 +0800 CST",
    "admin_addr": "10.21.1.140:11080",
    "proto_type": "tcp4",
    "proxy_addr": "10.21.1.140:19000",
    "jodis_path": "/jodis/codis-zyyhj1/proxy-80722e128c6e8fc3d0da44983343a843",
    "product_name": "codis-zyyhj1",
    "pid": 26493,
    "pwd": "/root",
    "sys": "Linux localhost.localdomain 3.10.0-514.el7.x86_64 #1 SMP Tue Nov 22 16:42:41 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux",
    "hostname": "localhost.localdomain",
    "datacenter": ""
}
cZxid = 0x100001c7b
ctime = Fri Jun 15 21:02:22 CST 2018
mZxid = 0x1000038ae
mtime = Sat Jun 16 23:30:57 CST 2018
pZxid = 0x100001c7b
cversion = 0
dataVersion = 2
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 576
numChildren = 0
#确认完毕,删除
[zk: localhost:2181(CONNECTED) 7] rmr /codis3/codis-zyyhj1/proxy/proxy-80722e128c6e8fc3d0da44983343a843
#删除完毕,还需要重载一下dashboard,不然信息也是不会更新的
/usr/local/codis/codis-admin  --dashboard=10.21.1.124:18081 --reload

然后,删不掉的proxy端就不见了,完成.


备注:

codis不支持命令列表:

Command Type Command Name
Keys KEYS

MIGRATE

MOVE

OBJECT

RANDOMKEY

RENAME

RENAMENX

SCAN


Strings BITOP

MSETNX


Lists BLPOP

BRPOP

BRPOPLPUSH


Pub/Sub PSUBSCRIBE

PUBLISH

PUNSUBSCRIBE

SUBSCRIBE

UNSUBSCRIBE


Transactions DISCARD

EXEC

MULTI

UNWATCH

WATCH


Scripting SCRIPT


Server BGREWRITEAOF

BGSAVE

CLIENT

CONFIG

DBSIZE

DEBUG

FLUSHALL

FLUSHDB

LASTSAVE

MONITOR

RESTORE

SAVE

SHUTDOWN

SLAVEOF

SLOWLOG

SYNC

TIME


Codis Slot SLOTSCHECK

SLOTSDEL

SLOTSINFO

SLOTSMGRTONE

SLOTSMGRTSLOT

SLOTSMGRTTAGONE

SLOTSMGRTTAGSLOT

 以下属于半支持命令, Codis不支持跨节点操作,因此您必须使用散列标签将可能显示在一个请求中的所有密钥放入同一个插槽中,然后您可以使用这些命令。 Codis不检查密钥是否有相同的标签,所以如果你不使用标签,你的程序会得到错误的回应。

Command Type Command Name
Lists RPOPLPUSH
Sets SDIFF

SINTER

SINTERSTORE

SMOVE

SUNION

SUNIONSTORE
Sorted Sets ZINTERSTORE

ZUNIONSTORE
HyperLogLog PFMERGE
Scripting EVAL

EVALSHA