参考:https://juejin.im/post/5c132b076fb9a04a08218eef
https://blog.csdn.net/antony9118/article/category/7045910
http://www.cnblogs.com/EasonJim/p/7818004.html
https://github.com/CodisLabs/codis/blob/release3.2/doc/FAQ_zh.md
目录
Codis 是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生的 Redis Server 没有显著区别 (除了一些命令不支持外), 上层应用可以像使用单机的 Redis 一样使用, Codis 底层会处理请求的转发, 不停机的数据迁移等工作, 所有后边的一切事情, 对于前面的客户端来说是透明的, 可以简单的认为后边连接的是一个内存无限大的 Redis 服务。
1.框架介绍
了解框架中的部分概念:
codis-fe:用户的最直观操作,用户可以通过可视化页面直接操作集群;
codis-dashboard: codis的集群管理工具,支持proxy和server的添加、删除、数据迁移,所有对集群的操作必须通过dashboard;
codis-proxy:客户端连接的Redis代理服务,本身实现了Redis协议,表现很像原生的Redis (就像 Twemproxy)。一个业务可以部署多个 codis-proxy,其本身是无状态的;
codis-group:组中可包含slot的分片情况及主从实例,且一个组中只能有一个master;
codis-server:Codis 项目维护的一个Redis分支,加入了slot的支持和原子的数据迁移指令。等同于redis-server;
redis-sentinel:sentinel是redis集群高可用的保障。当一个master挂掉后,可以将slave升级为master。
ZooKeeper:Codis依赖ZooKeeper来存放数据路由表和codis-proxy节点的元信息,发起的命令会通过 ZooKeeper同步到各个存活的codis-proxy。
框架理解:
用户可通过jodis-client、redis-client操作codis-proxy,也可通过可视化codis-fe,结合集群管理工具codis-dashboard实现proxy,server,sentinel的管理。用户通过codis-proxy发起一个操作请求,codis首先根据key确定slotId,slotId = crc32(key) % 1024,计算出slotId,根据group的slot范围,确定该slotId在哪一个group中,从而确定数据存储的机器.再根据codis-server(等同于redis-server)实现redis的操作。如果codis-group中的master挂掉或者需要将slave升级为master,就需要借助codis-sentinel了。整个过程中zookeeper会存储一些基本的元数据信息,比如proxy,sentinel,group等。
2.组件启动
启动:
nohup /usr/local/software/codis3.2.2-go1.9.2-linux/codis-server /redis/data/config/redis_6379.conf &
nohup /usr/local/software/codis3.2.2-go1.9.2-linux/codis-server /redis/data/config/redis_6380.conf &
nohup /usr/local/software/codis3.2.2-go1.9.2-linux/redis-sentinel /redis/data/config/sentinel.conf &
nohup /usr/local/software/codis3.2.2-go1.9.2-linux/codis-proxy --ncpu=4 --config=/redis/data/config/proxy.conf --log=/redis/data/logs/proxy.log &
nohup /usr/local/software/codis/codis-dashboard --ncpu=4 --config=/redis/data/config/dashboard.conf --log=/redis/data/logs/codis_dashboard.log --log-level=WARN &
验证
ss -ntplu |grep codis-server
ss -ntplu |grep codis-proxy
ss -ntplu |grep codis-dashboard
proxy启动过程:
读取配置文件,获取Config对象。根据Config新建Proxy,填充Proxy的各个属性,这里面比较重要的是填充models.Proxy(详细信息可以在zk中查看),并且与zk连接、注册相关路径。启动goroutine监听11080端口的codis集群发过来的请求并进行转发,以及监听发到19000端口(客户端redis-cli访问代理的端口)的redis请求并进行相关处理
在启动proxy的时候,会有一个goroutine专门负责监听发送到19000端口的redis请求。每次有redis请求过来,会新建一个session,并启动两个goroutine,loopReader和loopWriter。loopReader的作用是,Router根据请求指派到slot,并找到slot后面真正处理请求的BackendConn,再将请求放入BackendConn的RequestChan中,等待后续取出并进行处理,然后将请求写入session的RequestChan。loopWriter则将请求的结果从session的RequestChan(Request的*redis.Resp属性)中取出并返回,如果是MSET这种批处理的请求,要注意合并结果再返回
dashboard的启动:
dashboard是codis的集群管理工具,支持proxy和server的添加、删除、数据迁移,所有对集群的操作必须通过dashboard。dashboard的启动过程和proxy类似。dashboard的启动只是初始化一些必要的数据结构。
启动的时候,首先读取配置文件,填充config信息。根据coordinator的信息,如果是zookeeper而不是etcd的话,就创建一个zk客户端。然后根据client和config创建一个topom。首先来看看topom中有哪些信息。这个类在/pkg/topom/topom.go中。Topom非常重要,这个结构里面存储了集群中某一时刻所有的节点信息,在深入codis的过程中我们会逐步看到。
启动dashboard的过程中,需要连接zk,创建Topom这个struct,通过18080这个端口与集群进项交互,并将该端口收到的信息进行转发。最重要的是启动了四个goroutine,刷新集群中的redis和proxy的状态,以及处理slot和同步操作。
fe的启动:
虽然dashboard负责对集群的实际操作,但是用户的最直观操作,都是来自于fe的。首先,启动的时候指定fe的监听端口,这个就是我们最后通过浏览器打开fe的端口。然后根据系统参数找到assets文件夹目录,再在这个目录下寻找index.html,没错,就是浏览器中看到的html。如果在本地启动的时候这一步报错,assets目录下找不到index.html,就自己手动写死assets的地址即可。接下来,找启动的时候配置的codis.json。通过codis.json连接到dashboard,fe就算是和集群关联起来了。使用martini框架,新建路由和handler。最终我们得到了一个拥有完整规则的请求处理器handler,就是m。下一步,监听启动端口,新建路由表并将路由表指向刚才的martini框架的Router。最后一步,hs.Serve(l)这里,服务器接收指定监听器收到的请求,并为每个请求新建goroutine。goroutine负责读取并调用srv.Handler对请求进行相应。
3.配置文件中的关键参数
配置文件 | 关键参数 |
redis_6379.conf | port 6379 logfile /redis/data/logs/redis_6379.log #日志路径 requirepass "codisqwer" #所有codis-proxy集群相关的redis-server认证密码必须全部一致 masterauth "codisqwer"#如果做故障切换,不论主从节点都要填写密码且要保持一致 databases 64#修改库的个数 |
redis_6380.conf | port 6380 logfile /redis/data/logs/redis_6380.log #日志路径 requirepass "codisqwer" masterauth "codisqwer" slaveof 10.2.10.109 6379 databases 64 |
sentinel.conf | logfile /redis/data/logs/sentinel_26379.log #日志路径 port 26379 |
proxy.conf | product_auth = "codisqwer" #设置登录dashboard的密码(与真实redis中requirepass一致) session_auth = "qwer!23” #客户端(redis-cli)的登录密码(与真实redis中requirepass不一致),是登录codis的密码 admin_addr = "0.0.0.0:11080"#管理的端口,0.0.0.0即对所有ip开放,基于安全考虑,可以限制内网 jodis_name = "zookeeper" proxy_addr = "0.0.0.0:19000"#客户端(redis-cli)访问代理的端口,0.0.0.0即对所有ip开放 backend_number_databases = 64#修改库的个数 |
dashboard.conf | product_auth = "codisqwer" |
4.客户端测试
通过proxy:/usr/local/software/codis/redis-cli -h 10.2.10.109 -p 19000 -a qwer!23
通过redis-server:/usr/local/software/codis/redis-cli -h 10.2.10.109 -p 6379 -a codisqwer
5.数据存储特点
数据写入测试:通过proxy写入key,会先按照key进行计算,SlotId = crc32(key) % 1024,计算出slotId,根据group的slot范围,确定该slotId在哪一个group中,从而确定数据存储的机器,将数据存储在该机器对应的slotId中。如果刚好该机器挂掉,则返回错误(error) ERR handle response, backend conn reset/failure;
通过codis-server写入key,会先按照key进行计算,SlotId = crc32(key) % 1024,计算出slotId,则直接将该数据存储在该codis-server对应的机器上的slotId中。如果刚好该机器挂掉,则返回错误Could not connect to Redis at %host%:%port%: Connection refused
注意:通过codis-proxy写set a1 800,会根据a1计算其所在的机器,比如在机器1。在机器3的codis-server上执行set a1 100,则a1会写在机器3上。在机器3上的codis-proxy执行get a1,会获得800;在机器3上的codis-server执行get a1,会获得100。
在写入数据报错的情况下,可以通知管理员。管理员可以进行以下操作:
操作一:
借助codis-fe面板,观察GROUP情况,比如出现以下情况
如上图所示,当10.2.10.108:6379挂掉,10.2.10.108:6380正常的话,可以手动点击PROMOTE和如下图所示,则可以继续写入数据。
操作二:
借助codis-fe面板,观察GROUP情况,比如出现以下情况
重新启动10.2.10.108:6379的codis-server
/usr/local/software/codis3.2.2-go1.9.2-linux/codis-server /redis/data/config/redis_6379.conf
数据迁移测试:不管是通过proxy还是codis-server写入的数据,在数据迁移时都会受影响。
***补充:判断key是处于哪一个slot上?***
Codis 采用 Pre-sharding 的技术来实现数据的分片, 默认分成 1024 个 slots (0-1023), 对于每个key来说, 通过以下公式确定所属的 Slot Id : SlotId = crc32(key) % 1024
验证:
crc32(key):https://www.lammertbies.nl/comm/info/crc-calculation.html
十六进制转十进制:https://www.sojson.com/hexconvert/16to10.html
在线余数:http://www.ab126.com/shuxue/2892.html
测试值:
key=a1 slot=35
key=s1 slot=240
key=a2 slot=409
key=c1 slot=673
key=-f1 slot=740
key=z1 slot= 953
key=p1 slot=819
***判断key是处于哪一个slot上***
***补充:存储在slot的具体信息***
slotsinfo 查看数据存储在哪个slot上,存储了多少数据,codis-server上操作
slotsscan slotId cursor [count] 查看指定slotId上的key列表,codis-server和codis-proxy上均可执行。不过有区别,codis-server上执行该命令,是获得该台机器上该slotId上的相应数量的keys列表,而codis-proxy上执行该命令,是获得该集群中通过codis-proxy写入数据中符合要求的keys列表。
更多新指令参考https://github.com/CodisLabs/codis/blob/release3.2/doc/redis_change_zh.md
***存储在slot的具体信息***
6.支持的命令
不支持的命令参见https://github.com/CodisLabs/codis/blob/release3.2/doc/unsupported_cmds.md
***补充:实现类似于keys效果***
方案一:https://github.com/CodisLabs/codis/issues/1152,借助proxy的slotsscan命令遍历1024个slot实现
方案二:附上前缀,将前缀用花括号括起来,计算出前缀应该所在的slotId,借助proxy的slotsscan命令遍历
比如:set {a}0 0
set {a}1 0
set {a}2 0
set {a}3 0
set {a}4 0
a按照SlotId = crc32(key) % 1024,计算出slotId=579,再执行命令slotsscan 579 0 count 10
***实现类似于keys效果***
7.主从区别
slave仅可以读数据,不能写数据;
当master挂掉的时候,可以将slave升级为master,继续写入数据。
8.zookeeper的存储信息
9.多数据库问题
首先Redis不支持自定义数据库的名字,每个数据库都以编号命名,开发者必须自己记录哪些数据库存储了哪些数据。另外Redis也不支持为每个数据库设置不同的访问密码,所以一个客户端要么可以访问全部数据库,要么连一个数据库也没有权限访问。最重要的一点是多个数据库之间并不是完全隔离的,比如FLUSHALL命令可以清空一个Redis实例中所有数据库中的数据。综上所述,这些数据库更像是一种命名空间,而不适宜存储不同应用程序的数据。比如可以使用0号数据库存储某个应用生产环境中的数据,使用1号数据库存储测试环境中的数据,但不适宜使用0号数据库存储A应用的数据而使用1号数据库B应用的数据,不同的应用应该使用不同的Redis实例存储数据。