Codis简介

Codis是豌豆荚使用Go和C语言开发、以代理的方式实现的一个Redis分布式集群解决方案,且完全兼容Twemproxy。Twemproxy对于上一层的应用来说, 连接Codis Proxy(Redis代理服务)和连接原生的Redis服务器没有明显的区别,上一层应用能够像使用单机的Redis一样对待。Codis底层会处理请求的转发、不停机的数据迁移等工作, 所有底层的一切处理, 对于客户端来说是透明的。总之,可以简单的认为后台连接的是一个内存无限大的Redis服务。Codis遵循MIT开源协议发布,更多关于Codis的信息请登录其在GitHub的主页查看。

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

Codis 由四部分组成:

Codis Proxy     (codis-proxy)
Codis Manager     (codis-config)
Codis Redis     (codis-server)
ZooKeeper

codis-proxy是客户端连接的Redis代理服务, codis-proxy本身实现了Redis协议, 表现得和一个原生的Redis没什么区别(就像Twemproxy), 对于一个业务来说, 可以部署多个codis-proxy, codis-proxy本身是无状态的. 可以执行多个Codis Dashboard(codis-config)是Codis的管理工具, 支持包括, 添加/删除 Redis 节点, 添加/删除Proxy节点, 发起数据迁移等操作. codis-config本身还自带了一个 http server, 会启动一个dashboard, 用户可以直接在浏览器上观察Codis集群的运行状态。

Codis Redis(codis-server)是Codis项目维护的一个Redis分支, 基于2.8.21开发, 加入了slot的支持和原子的数据迁移指令. Codis上层的codis-proxy和codis-config只能和这个版本的Redis交互才能正常运行。

Codis依赖ZooKeeper来存放数据路由表和codis-proxy节点的元信息, codis-config 发起的命令都会通过ZooKeeper同步到各个存活的codis-proxy。

Codis支持按照Namespace区分不同的产品, 拥有不同的product name 的产品, 各项配置都不会冲突。

Codis架构

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=

安装并测试Codis

本文使用Codis分支的2.0版本,也是很多公司正在用的版本,Codis3.0版本已经有分支了,但是有线上使用的用户不确定。Codis3.0可以不依赖zookeeper,dashboard和proxy直接通过HTTP方式通讯。出于稳定性考虑,我们还是用目前的版本,避免在新版本上踩坑。

Codis新增一个group的概念,每个group包含一个Redis Master和至少一个Redis Slave。Codis可以支持数据热迁移.Codis采用预先分片机制,分成1024个slots,也就是最多可以支持1024个Codis server,这些信息保存在zookeeper中。

安装GO语言和编译安装codis

Codis由Go语言写的,所以需要安装Go语言包

安装go
wget  https://storage.googleapis.com/golang/go1.6.linux-amd64.tar.gz
wget  http://golangtc.com/static/go/1.6/go1.6.linux-amd64.tar.gz   #国内镜像地址
tar -C /usr/local -xzf go1.6.linux-amd64.tar.gz
配置环境变量
vi /etc/profile
在最后添加:
export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin
保存,执行:
source /etc/profile
判断go是否安装成功,运行go version
[root@centos6 local]# go version
go version go1.6 linux/amd64

也可以yum安装:
#yum -y install gcc gcc-c++ make git wget go 
#export GOROOT=/usr/local/go
#export PATH=$GOROOT/bin:$JAVA_HOME/bin:$PATH
安装codis
#安装编译工具
yum groupinstall "Development Tools"
#下载并编译codis
mkdir $HOME/goproj
export GOPATH=$HOME/goproj
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
go get -u -d github.com/CodisLabs/codis
cd $GOPATH/src/github.com/CodisLabs/codis
make
make gotest

这里需要注意的是,最好按照Codis的文档使用go get载codis,我尝试过自己下载需要的依赖包然后编译codis,但是总是报错说是GOPATH设置不正确。这里对于初次接触GO项目编译的人来说有点诡异。

编译完成后会在bin目录下生成3个二进制文件

codis-config    Codis的管理工具,支持添加/删除Redis节点,添加/删除Proxy节点,执行Auto Rebalance等操作

codis-server    Codis 项目维护的一个Redis分支, 基于2.8.21开发, 加入了slot的支持和原子的数据迁移指令. Codis上层的codis-proxy和codis-config只能和这个版本的Redis交互才能正常运行.

codis-proxy     是客户端连接的 Redis 代理服务, codis-proxy本身实现了Redis协议, 表现得和一个原生的Redis没什么区别(就像 Twemproxy), 对于一个业务来说, 可以部署多个codis-proxy, codis-proxy本身是无状态的.

Codis 支持按照 Namespace 区分不同的产品, 拥有不同的product name的产品, 各项配置都不会冲突

另外值得注意的是

bin/assets文件夹是codis-config的dashboard http服务需要的前端资源, 需要和codis-config放置在同一文件夹下。

把Codis编译完成后直接复制bin目录下的codis-proxy,codis-config,codis-server三个二进制文件和assets资源目录到其他机器上也是可以直接运行的。目标主机不一定安装有go语言。

常用命令维护说明
server:
主要用来添加,删除,提权,查找 server group(实际操作zookeeper)
./codis-config server --help
codis-config server list
codis-config server add <group_id> <redis_addr> <role>
codis-config server remove <group_id> <redis_addr>
codis-config server promote <group_id> <redis_addr>
codis-config server add-group <group_id>
codis-config server remove-group <group_id>

slot:
主要用来初始化,迁移,设置range-set,查询slot

./codis-config slot --help
usage:
codis-config slot init [-f]
codis-config slot info <slot_id>
codis-config slot set <slot_id> <group_id> <status>
codis-config slot range-set <slot_from> <slot_to> <group_id> <status>
codis-config slot migrate <slot_from> <slot_to> <group_id>
[--delay=<delay_time_in_ms>]
codis-config slot rebalance [--delay=<delay_time_in_ms>]

dashboard:
主要用来启动dashboard
./codis-config dashboard --help
usage: codis-config dashboard [--addr=<address>] [--http-log=<log_file>]
options:
--addr listen ip:port, e.g. localhost:12345, :8086, [default: :8086]
--http-log http request log [default: request.log ]

action:
主要用来操作codis保存的事件记录,并解除zk锁(迁移异常会出现锁)

./codis-config action --help
usage: codis-config action (gc [-n <num> | -s <seconds>] | remove-lock)
options:
gc:
gc -n N
gc -s Sec
keep last N actions;
keep last Sec seconds actions;
remove-lock force remove zookeeper lock;

proxy:
主要用来实现proxy上线,下线,查询
./codis-config proxy
usage:
codis-config proxy list
codis-config proxy offline <proxy_name>
codis-config proxy online <proxy_name>

codis-proxy
主要用来启动proxy进程
./codis-proxy --help
usage: proxy [-c <config_file>] [-L <log_file>] [--log-level=<loglevel>] [--cpu=<cpu_num>]
[--addr=<proxy_listen_addr>] [--http-addr=<debug_http_server_addr>]

options:
-cset config file
-Lset output log file, default is stdout
--log-level=<loglevel> set log level: info, warn, error, debug [default: info]
--cpu=<cpu_num>
num of cpu cores that proxy can use
--addr=<proxy_listen_addr>
proxy listen address, example: 0.0.0.0:9000
--http-addr=<debug_http_server_addr>
debug vars http server

codis-server
主要用来启动 codis(redis 实例)
./codis-server --help
Usage: ./redis-server [/path/to/redis.conf] [options]
./redis-server - (read config from stdin)
./redis-server -v or --version
./redis-server -h or --help
./redis-server --test-memory <megabytes>

Examples:
./redis-server (run the server with default conf)
./redis-server /etc/redis/6379.conf
./redis-server --port 7777
./redis-server --port 7777 --slaveof 127.0.0.1 8888
./redis-server /etc/myredis.conf --loglevel verbose

Sentinel mode:
./redis-server /etc/sentinel.conf –sentinel

codis-ha
主要来实现 server_group 中的主从 ha
./codis-ha --help
Usage of ./codis-ha:
-codis-config="localhost:18087": api server address
-productName="test": product name, can be found in codis-proxy's config
Codis不支持的命令
KEYS, MOVE, OBJECT, RENAME, RENAMENX, SORT, SCAN, BITOP,MSETNX, BLPOP, BRPOP, BRPOPLPUSH, PSUBSCRIBE,PUBLISH, PUNSUBSCRIBE, SUBSCRIBE, UNSUBSCRIBE, DISCARD, EXEC, MULTI, UNWATCH, WATCH, SCRIPT EXISTS, SCRIPT FLUSH, SCRIPT KILL, SCRIPT LOAD, AUTH, ECHO, SELECT, BGREWRITEAOF, BGSAVE, CLIENT KILL, CLIENT LIST, CONFIG GET, CONFIG SET, CONFIG RESETSTAT, DBSIZE, DEBUG OBJECT, DEBUG SEGFAULT, FLUSHALL, FLUSHDB, INFO, LASTSAVE, MONITOR, SAVE, SHUTDOWN, SLAVEOF, SLOWLOG, SYNC, TIME、MIGRATE、RANDOMKEY、PSUBSCRIBE、PUBLISH、PUNSUBSCRIBE、BGREWRITEAOF、RESTORE、SLOTSCHECK、SLOTSDEL、SLOTSINFO、SLOTSMGRTONE、SLOTSMGRTSLOT、SLOTSMGRTTAGONE、SLOTSMGRTTAGSLOT
Codis半支持的命令

需要将以下key放入同一slot才能支持,方式采用{},如key为”bar{zap}”,则只会对zap进行hash

RPOPLPUSH、SDIFF、SINTER、SINTERSTORE、SMOVE、SUNION、SUNIONSTORE、ZINTERSTORE、ZUNIONSTORE、PFMERGE、EVAL、EVALSHA
安装JDK

zookeeper依赖java环境,所以要先安装JDK。

wget --no-cookies --no-check-certificate --header "Cookie: oraclelicense=accept-securebackup-cookie" rpm" -O jdk-7u65-linux-x64.rpm
rpm -ivh jdk-7u65-linux-x64.rpm
安装和配置zookeeper
wget http://www.us.apache.org/dist/zookeeper/zookeeper-3.4.6/zookeeper-3.4.6.tar.gz
tar zxvf zookeeper-3.4.6.tar.gz
cd zookeeper-3.4.6/conf/
cp zoo_sample.cfg zoo.cfg
部署zookeeper集群
  • 复制三份zookeeper实例

    cp -rf  zookeeper-3.4.6 zookeeper-1
    cp -rf  zookeeper-3.4.6 zookeeper-2
    cp -rf  zookeeper-3.4.6 zookeeper-3
  • 创建三个数据和日志目录

    mkdir -p /var/local/zookeeper/data{1..3}
    mkdir -p /var/local/zookeeper/logs{1..3}
  • 编辑配置文件

    修改数据目录和日志目录.并且添加server(由于是一台机器,端口则不能相同),如下

vim /opt/zookeeper-1/conf/zoo.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/var/local/zookeeper/data1
dataLogDir=/var/local/zookeeper/logs1
clientPort=2181
server.1=server01:2887:3887
server.2=server01:2888:3888
server.3=server01:2889:3889

vim /opt/zookeeper-2/conf/zoo.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/var/local/zookeeper/data2
clientPort=2182
server.1=192.168.119.100:2887:3887
server.2=192.168.119.100:2888:3888
server.3=192.168.119.100:2889:3889

vim /opt/zookeeper-3/conf/zoo.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/var/local/zookeeper/data3
clientPort=2183
server.1=192.168.119.100:2887:3887
server.2=192.168.119.100:2888:3888
server.3=192.168.119.100:2889:3889
  • 创建myid文件

    要在每台机器的dataDir下,新建一个myid文件,里面存放一个数字,用来标识当前主机。

echo "1">/var/local/zookeeper/data1/myid
echo "2">/var/local/zookeeper/data2/myid
echo "3">/var/local/zookeeper/data3/myid
  • 启动三个zookeeper

/opt/zookeeper-1/bin/zkServer.sh start
/opt/zookeeper-2/bin/zkServer.sh start
/opt/zookeeper-3/bin/zkServer.sh start
  • 查看是否已经自动选举

/opt/zookeeper-1/bin/zkServer.sh status
JMX enabled by default
Using config: /opt/zookeeper-1/bin/../conf/zoo.cfg
Mode: follower

/opt/zookeeper-2/bin/zkServer.sh status
JMX enabled by default
Using config: /opt/zookeeper-2/bin/../conf/zoo.cfg
Mode: leader

/opt/zookeeper-3/bin/zkServer.sh status
JMX enabled by default
Using config: /opt/zookeeper-3/bin/../conf/zoo.cfg
Mode: follower
  • 备注

三种类型的节点
 Leader   : 处理写请求,最终更新状态;
 Follower : 处理客户端请求,参与投票;
 Observer : 不参加投票,只处理客户端请求,主要是为提升zookeeper的性能;

当leader重启或宕机后,通过paxos算法,重新选出Leader,并以Leader为准,进行数据同步;
 关于为Server ID,是标识host机器在集群中的机器序号,在每个ZK机器上,需要在dataDir目录下创建一个myid文件,myid中就是这个Server ID的数字。

  • 连接测试

/opt/zookeeper-1/bin/zkCli.sh -server 192.168.119.100:2181
Connecting to 192.168.119.100:2181
2016-03-25 13:35:12,972 [myid:] - INFO  [main:Environment@100] - Client environment:zookeeper.version=3.4.6-1569965, built on 02/20/2014 09:09 GMT
2016-03-25 13:35:12,974 [myid:] - INFO  [main:Environment@100] - Client environment:host.name=localhost
2016-03-25 13:35:12,974 [myid:] - INFO  [main:Environment@100] - Client environment:java.version=1.8.0_65
2016-03-25 13:35:12,987 [myid:] - INFO  [main:Environment@100] - Client environment:java.vendor=Oracle Corporation
2016-03-25 13:35:12,987 [myid:] - INFO  [main:Environment@100] - Client environment:java.home=/usr/java/jdk1.8.0_65/jre
2016-03-25 13:35:12,987 [myid:] - INFO  [main:Environment@100] - Client environment:java.class.path=/opt/zookeeper-1/bin/../build/classes:/opt/zookeeper-1/bin/../build/lib/*.jar:/opt/zookeeper-1/bin/../lib/slf4j-log4j12-1.6.1.jar:/opt/zookeeper-1/bin/../lib/slf4j-api-1.6.1.jar:/opt/zookeeper-1/bin/../lib/netty-3.7.0.Final.jar:/opt/zookeeper-1/bin/../lib/log4j-1.2.16.jar:/opt/zookeeper-1/bin/../lib/jline-0.9.94.jar:/opt/zookeeper-1/bin/../zookeeper-3.4.6.jar:/opt/zookeeper-1/bin/../src/java/lib/*.jar:/opt/zookeeper-1/bin/../conf:.:/usr/java/jdk1.8.0_65//lib/dt.jar:/usr/java/jdk1.8.0_65//lib/tools.jar
2016-03-25 13:35:12,987 [myid:] - INFO  [main:Environment@100] - Client environment:java.library.path=/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
2016-03-25 13:35:12,987 [myid:] - INFO  [main:Environment@100] - Client environment:java.io.tmpdir=/tmp
2016-03-25 13:35:12,987 [myid:] - INFO  [main:Environment@100] - Client environment:java.compiler=<NA>
2016-03-25 13:35:12,987 [myid:] - INFO  [main:Environment@100] - Client environment:os.name=Linux
2016-03-25 13:35:12,987 [myid:] - INFO  [main:Environment@100] - Client environment:os.arch=amd64
2016-03-25 13:35:12,988 [myid:] - INFO  [main:Environment@100] - Client environment:os.version=2.6.32-573.12.1.el6.x86_64
2016-03-25 13:35:12,988 [myid:] - INFO  [main:Environment@100] - Client environment:user.name=root
2016-03-25 13:35:12,988 [myid:] - INFO  [main:Environment@100] - Client environment:user.home=/root
2016-03-25 13:35:12,988 [myid:] - INFO  [main:Environment@100] - Client environment:user.dir=/root
2016-03-25 13:35:12,989 [myid:] - INFO  [main:ZooKeeper@438] - Initiating client connection, connectString=192.168.119.100:2181 sessionTimeout=30000 watcher=org.apache.zookeeper.ZooKeeperMain$MyWatcher@67424e82
Welcome to ZooKeeper!
JLine support is enabled
2016-03-25 13:35:13,125 [myid:] - INFO  [main-SendThread(192.168.119.100:2181):ClientCnxn$SendThread@975] - Opening socket connection to server 192.168.119.100/192.168.119.100:2181. Will not attempt to authenticate using SASL (unknown error)
2016-03-25 13:35:13,236 [myid:] - INFO  [main-SendThread(192.168.119.100:2181):ClientCnxn$SendThread@852] - Socket connection established to 192.168.119.100/192.168.119.100:2181, initiating session
[zk: 192.168.119.100:2181(CONNECTING) 0] 2016-03-25 13:35:13,345 [myid:] - INFO  [main-SendThread(192.168.119.100:2181):ClientCnxn$SendThread@1235] - Session establishment complete on server 192.168.119.100/192.168.119.100:2181, sessionid = 0x153ab70dc7c0001, negotiated timeout = 30000

WATCHER::
WatchedEvent state:SyncConnected type:None path:null
[zk: 192.168.119.100:2181(CONNECTED) 0] ls /
[zk, zookeeper]
[zk: 192.168.119.100:2181(CONNECTED) 1] ls /zookeeper
[quota]
[zk: 192.168.119.100:2181(CONNECTED) 2]
启动Codis服务
启动dashboard

codis-config和codis-proxy使用config.ini这个配置文件,编辑配置文件

grep -v '#'  config.ini|grep  -v '^$'
#根据实际情况,修改以下项
coordinator=zookeeper
zk=192.168.119.100:2181,192.168.119.100:2182,192.168.119.100:2183
product=test
dashboard_addr=192.168.119.100:18087
password=
backend_ping_period=5
session_max_timeout=1800
session_max_bufsize=131072
session_max_pipeline=1024
zk_session_timeout=30000
proxy_id=proxy_1   #如果有多个proxy,proxy_id 需要唯一。
  • 启动dashboard

bin/codis-config dashboard
  • 关闭dashboard

我们在启动了dashboard后,他打印了一堆内容,如下

[code]2015/09/26 11:18:41 dashboard.go:160: [INFO] dashboard listening on addr: :18087
2015/09/26 11:18:41 dashboard.go:143: [INFO] dashboard node created: /zk/codis/db_test/dashboard, {"addr": "localhost:18087", "pid": 1701}
2015/09/26 11:18:41 dashboard.go:144: [WARN] ********** Attention **********
2015/09/26 11:18:41 dashboard.go:145: [WARN] You should use `kill {pid}` rather than `kill -9 {pid}` to stop me,
2015/09/26 11:18:41 dashboard.go:146: [WARN] or the node resisted on zk will not be cleaned when I'm quiting and you must remove it manually
2015/09/26 11:18:41 dashboard.go:147: [WARN] *******************************

是的,如内容说说的,如果要关闭,记得要kill -{pid}

要不然突然电脑没电之类的bug,导致异常退出的时候,就得手动关闭,要不然在下次启动的时候,就会遇到下面的内容:

bin/codis-config dashboard
2015/09/26 11:14:10 dashboard.go:160: [INFO] dashboard listening on addr: :18087
2015/09/26 11:14:10 dashboard.go:234: [PANIC] create zk node failed
[error]: dashboard already exists: {"addr": "192.168.0.123:18087", "pid": 30155}
[stack]: 
    3   /usr/local/codis/src/github.com/wandoulabs/codis/cmd/cconfig/dashboard.go:234
            main.runDashboard
    2   /usr/local/codis/src/github.com/wandoulabs/codis/cmd/cconfig/dashboard.go:54
            main.cmdDashboard
    1   /usr/local/codis/src/github.com/wandoulabs/codis/cmd/cconfig/main.go:84
            main.runCommand
    0   /usr/local/codis/src/github.com/wandoulabs/codis/cmd/cconfig/main.go:151
            main.main
        ... ...

备注:必须先启动zookeeper

  • 通过NGINX代理认证访问dashboard

    配置nginx

yum install -y nginx httpd
vim /etc/nginx/conf.d/codis.conf
修改如下参数:
server {
  listen       80;
  server_name  192.168.119.100;

  location / {
    auth_basic "codis auth";
    auth_basic_user_file /etc/nginx/passwd.db;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_pass http://192.168.119.100:18087;
  }
}

启动nginx

htpasswd -c /etc/nginx/passwd codis
service nginx start
chkconfig nginx on
chkconfig httpd off

备注:因为codis的dashboard不支持认证访问,可以通过nginx代理做认证访问(htpasswd的apache的一个命令工具,用于生成http 基本认证的密码文件),可以用防火墙关闭18087端口。通过URL浏览器访问codis的dashboard。

初始化slots

初始化slots(codis-config上操作)

bin/codis-config slot init

该命令会在zookeepr上创建slot相关信息

重新初始化slot

bin/codis-config slot init -f
{
  "msg": "OK",
  "ret": 0
}