一、簡介
Codis 是一個分佈式 Redis 解決方案, 對於上層的應用來說, 連接到 Codis Proxy 和連接原生的 Redis Server 沒有明顯的區別 , 上層應用可以像使用單機的 Redis 一樣使用, Codis 底層會處理請求的轉發, 不停機的數據遷移等工作, 所有後邊的一切事情, 對於前面的客戶端來說是透明的, 可以簡單的認爲後邊連接的是一個內存無限大的 Redis 服務.
組成部分
Codis Proxy (codis-proxy)
Codis Manager (codis-config)
Codis Redis (codis-server)
codis-proxy 是客戶端連接的 Redis 代理服務, codis-proxy 本身實現了 Redis 協議, 表現得和一個原生的 Redis 沒什麼區別 (就像 Twemproxy), 對於一個業務來說, 可以部署多個 codis-proxy, codis-proxy 本身是無狀態的.
codis-config 是 Codis 的管理工具, 支持包括, 添加/刪除 Redis 節點, 添加/刪除 Proxy 節點, 發起數據遷移等操作. codis-config 本身還自帶了一個 http server, 會啓動一個 dashboard, 用戶可以直接在瀏覽器上觀察 Codis 集羣的運行狀態.
codis-server 是 Codis 項目維護的一個 Redis 分支, 基於 2.8.13 開發, 加入了 slot 的支持和原子的數據遷移指令. Codis 上層的 codis-proxy 和 codis-config 只能和這個版本的 Redis 交互才能正常運行.
Codis 依賴 ZooKeeper 來存放數據路由表和 codis-proxy 節點的元信息, codis-config 發起的命令都會通過 ZooKeeper 同步到各個存活的 codis-proxy.
Codis 支持按照 Namespace 區分不同的產品, 擁有不同的 product name 的產品, 各項配置都不會衝突
特性
自動平衡
使用非常簡單
圖形化的面板和管理工具
支持絕大多數 Redis 命令,完全兼容twemproxy
安全而且透明的數據移植,可根據需要輕鬆添加和刪除節點
提供命令行接口
優點:實現高併發讀寫,數據一致性高.
缺點:性能有較大損耗,故障切換無法保證不丟key,無法進行讀寫分離.
邏輯架構如下:
訪問層:訪問方式可以是vip或者是通過java代碼調用jodis,然後連接調用不同的codis-proxy地址來實現高可用的LVS和HA功能.
代理層:然後中間層由codis-proxy和zookeeper處理數據走向和分配,通過crc32算法,把key平均分配在不同redis的某一個slot中.實現類似raid0的條帶化,在舊版本的codis中,slot需要手工分配,在codis3.2之後,slot會自動分配,相當方便.
數據層:最後codis-proxy把數據存進真實的redis-server主服務器上,由於codis的作者黃東旭相當注重數據一致性,不允許有數據延時造成的數據不一致,所以架構從一開始就沒考慮主從讀寫分離.從服務器僅僅是作爲故障切換的冗餘架構,由zookeeper調用redis-sentinel實現故障切換功能.
因爲機器有限,部署的架構如下:
序號 | IP | 主機名 | 部署程序 |
---|---|---|---|
01 | 172.16.1.150 | codis-porxy | codis-proxy:19000 codis-dashborad:18080、codis-fe:18090 |
02 | 172.16.1.151 | codis-redis1 | codis-server:(6379&6380) redis-sentinel:26379 |
03 | 172.16.1.152 | codis-redis2 | codis-server:(6379&6380) redis-sentinel:26379 |
04 | 172.16.1.153 | codis-redis3 | codis-server:(6379&6380) redis-sentinel:26379 |
1.安裝go
cd /application/tools/ wget https://dl.google.com/go/go1.10.2.linux-amd64.tar.gz tar -zxf go1.10.2.linux-amd64.tar.gz -C /usr/local/ cat << EOF >>/etc/profile export PATH=$PATH:/usr/local/go/bin export GOROOT=/usr/local/go export GOPATH=/usr/local/go/work path=$PATH:$HOME/bin:$GOROOT/bin:$GOPATH/bin EOF source /etc/profile
2.獲取codis
wget https://github.com/CodisLabs/codis/archive/release3.2.zip mkdir -p $GOPATH/src/github.com/CodisLabs cd /usr/local/go/work/src/github.com/CodisLabs unzip /application/tools/release3.2 mv codis-release3.2/ codis cd codis/ make
#執行全部指令後,會在 bin 文件夾內生成 codis-proxy、codis-server三個可執行文件。另外, bin/assets 文件夾是 dashboard http 服務需要的前端資源) [root@codis-redis1 application]# ll /usr/local/go/work/src/github.com/CodisLabs/codis/bin total 107560 drwxr-xr-x 4 root root 4096 Jun 2 03:57 assets -rwxr-xr-x 1 root root 16237048 Jun 2 03:57 codis-admin -rwxr-xr-x 1 root root 17222240 Jun 2 03:56 codis-dashboard -rwxr-xr-x 1 root root 15512405 Jun 2 03:57 codis-fe -rwxr-xr-x 1 root root 14457471 Jun 2 03:57 codis-ha -rwxr-xr-x 1 root root 19425814 Jun 2 03:57 codis-proxy -rwxr-xr-x 1 root root 7983449 Jun 2 03:56 codis-server -rwxr-xr-x 1 root root 5578703 Jun 2 03:56 redis-benchmark -rwxr-xr-x 1 root root 5710507 Jun 2 03:56 redis-cli -rwxr-xr-x 1 root root 7983449 Jun 2 03:56 redis-sentinel -rw-r--r-- 1 root root 97 Jun 2 03:59 version [root@codis-porxy application]#
3.codis-server配置
作用:基於 redis-3.2.8 分支開發。增加了額外的數據結構,以支持 slot 有關的操作以及數據遷移指令。具體的修改可以參考文檔 redis 的修改。 #拷貝codis程序 mkdir -p /application/codis/bin mkdir /application/codis/conf mkdir /application/codis/log mkdir /application/codis/proc mkdir /application/codis/data/redis_data_6380 -p mkdir /application/codis/data/redis_data_6379 -p cp -fr /usr/local/go/work/src/github.com/CodisLabs/codis/bin/* /application/codis/bin/ cp -fr /usr/local/go/work/src/github.com/CodisLabs/codis/config/* /application/codis/conf/ #修改主配置 cd /application/codis/conf/ #主配置 cp redis.conf redis-6379.conf vim redis-6379.conf bind 172.16.1.150 daemonize yes #進程ID文件路徑 pidfile /application/codis/proc/redis-6379.pid #綁定端口 port 6379 timeout 86400 tcp-keepalive 60 loglevel notice #日誌文件路徑 logfile "/application/codis/log/redis-6379.log" databases 16 save “” save 900 1 save 300 10 save 60 10000 stop-writes-on-bgsave-error no rdbcompression yes #dump文件 dbfilename dump-6379.rdb #dump路徑 dir /application/codis/data/redis_data_6379 #Master密碼(從主同步密碼) masterauth "123456" slave-serve-stale-data yes repl-disable-tcp-nodelay no slave-priority 100 #鑑權密碼(客戶端連接密碼) requirepass "123456" maxmemory 10gb maxmemory-policy allkeys-lru appendonly no appendfsync everysec no-appendfsync-on-rewrite yes auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb lua-time-limit 5000 slowlog-log-slower-than 10000 slowlog-max-len 128 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 client-output-buffer-limit normal 0 0 0 client-output-buffer-limit slave 0 0 0 client-output-buffer-limit pubsub 0 0 0 hz 10 aof-rewrite-incremental-fsync yes repl-backlog-size 33554432 #修改從配置 cp redis-6379.conf redis-example.conf egrep -v "#|^$" redis-example.conf > redis-6380.conf sed -i "s@6379@6380@g" redis-6380.conf vim redis-6380.conf bind 172.16.1.150 protected-mode yes port 6380 tcp-backlog 511 timeout 86400 tcp-keepalive 60 daemonize yes supervised no pidfile /application/codis/proc/redis-6380.pid loglevel notice logfile "/application/codis/log/redis-6380.log" databases 16 #save "" #save 900 1 #save 300 10 #save 60 10000 stop-writes-on-bgsave-error no rdbcompression yes rdbchecksum yes dbfilename dump-6380.rdb dir /application/codis/data/redis_data_6380 masterauth 123456 slave-serve-stale-data yes slave-read-only yes repl-diskless-sync no repl-diskless-sync-delay 5 repl-disable-tcp-nodelay no repl-backlog-size 33554432 slave-priority 100 requirepass 123456 maxmemory 10gb maxmemory-policy allkeys-lru appendonly no appendfilename "appendonly.aof" appendfsync everysec no-appendfsync-on-rewrite yes auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb aof-load-truncated yes lua-time-limit 5000 slowlog-log-slower-than 10000 slowlog-max-len 128 latency-monitor-threshold 0 notify-keyspace-events "" hash-max-ziplist-entries 512 hash-max-ziplist-value 64 list-max-ziplist-size -2 list-compress-depth 0 set-max-intset-entries 512 zset-max-ziplist-entries 128 zset-max-ziplist-value 64 hll-sparse-max-bytes 3000 activerehashing yes client-output-buffer-limit normal 0 0 0 client-output-buffer-limit slave 0 0 0 client-output-buffer-limit pubsub 0 0 0 hz 10 aof-rewrite-incremental-fsync yes #拷貝codis-server程序到其他節點 scp -r codis [email protected]:/application/ scp -r codis [email protected]:/application/ #啓動腳本 vim /server/scripts/codis-server.sh if [ $# -eq 0 ] then echo "useage:sh $0 {start|stop}" exit 0 fi case $1 in start) /application/codis/bin/codis-server /application/codis/conf/redis-6379.conf /application/codis/bin/codis-server /application/codis/conf/redis-6380.conf ;; stop) ps -ef|grep codis-server|grep -v grep|awk '{print $2}'|xargs kill ;; esac #拷貝腳本到其他節點 scp /server/scripts/codis-server.sh [email protected]:/server/scripts/ scp /server/scripts/codis-server.sh [email protected]:/server/scripts/ #所有節點啓動 sh /server/scripts/codis-server.sh start
4.Codis-dashboard
作用:集羣管理工具,支持 codis-proxy、codis-server 的添加、刪除,以及據遷移等操作。在集羣狀態發生改變時,codis-dashboard 維護集羣下所有 codis-proxy 的狀態的一致性。 1)對於同一個業務集羣而言,同一個時刻 codis-dashboard 只能有 0個或者1個; 2)所有對集羣的修改都必須通過 codis-dashboard 完成。 cd /application/codis/bin/ #生成配置 ./codis-dashboard --default-config | tee ../conf/dashboard.conf vim ../conf/dashboard.conf #外部存儲類型 coordinator_name = "zookeeper" #外部存儲IP列表 coordinator_addr = "172.16.1.111:2181" #項目名稱 product_name = "my-codis" #集羣密碼(注意:需要與redis配置中的requirepass保持一致) product_auth = "123456" #RESTful API 端口 admin_addr = "0.0.0.0:18080" #爲了防止出現dashboard監控頁面中OPS始終爲0的現象,需要將proxy的IP和主機名寫到hosts文件中. 172.16.1.150 codis-porxy #啓動程序 nohup ./codis-dashboard --ncpu=24 --config=/application/codis/conf/dashboard.conf --log=/application/codis/log/dashboard.log --log-level=WARN & #關閉 ./codis-admin --dashboard=172.16.1.150:18080 –auth=123456 --shutdown
5.Codis-proxy
作用:客戶端連接的 Redis 代理服務, 實現了 Redis 協議。 除部分命令不支持以外(不支持的命令列表),表現的和原生的 Redis 沒有區別(就像 Twemproxy)。 1)對於同一個業務集羣而言,可以同時部署多個 codis-proxy 實例; 2)不同 codis-proxy 之間由 codis-dashboard 保證狀態同步 #修改配置 cd /application/codis/bin/ #生成配置 ./codis-proxy --default-config | tee ../conf/proxy.conf #修改配置 vim ../conf/proxy.conf #設置項目名 product_name = "my-codis" #設置登錄dashboard的密碼(注意:與redis中requirepass一致) product_auth = "123456" #Redis客戶端的登錄密碼(注意:與redis中requirepass不一致) session_auth = "56789" # Set bind address for admin(rpc), tcp only. admin_addr = "0.0.0.0:11080" proto_type = "tcp4" #綁定端口(Redis客戶端連接此端口) proxy_addr = "0.0.0.0:19000" #外部存儲類型 jodis_name = "zookeeper" #外部存儲列表 jodis_addr = "172.16.1.111:2181" jodis_timeout = "20s" #會話設置 如果不爲0可能導致應用程序出現”write: broken pipe”的問題 session_recv_timeout = "0s" #啓動程序 nohup ./codis-proxy --ncpu=24 --config=/application/codis/conf/proxy.conf --log=/application/codis/log/proxy.log &
6.Redis-sentinel
作用:Redis官方推薦的高可用性(HA)解決方案。它可以實現對Redis的監控、通知、自動故障轉移。如果Master不能工作,則會自動啓動故障轉移進程,將其中的一個Slave提升爲Master,其他的Slave重新設置新的Master服務 #修改配置 其餘節點配置相同 vim /application/codis/conf/sentinel.conf bind 0.0.0.0 protected-mode no port 26379 dir /application/codis/data/ scp /application/codis/conf/sentinel.conf [email protected]:/application/codis/conf/ scp /application/codis/conf/sentinel.conf [email protected]:/application/codis/conf/ #啓動程序 nohup /application/codis/bin/redis-sentinel /application/codis/conf/sentinel.conf &
7.Codis-fe
作用:集羣管理界面。 1)多個集羣實例共享可以共享同一個前端展示頁面; 2)通過配置文件管理後端codis-dashboard列表,配置文件可自動更新 #生成配置 cd /application/codis/bin/ ./codis-admin --dashboard-list --zookeeper=172.16.1.111:2181 | tee ../conf/codis.json [ { "name": "my-codis", "dashboard": "codis-porxy:18080" }, { "name": "codis-chinasoft", "dashboard": "172.16.1.150:18080" } ] #啓動程序 nohup ./codis-fe --ncpu=1 --log=/application/codis/log/fe.log --log-level=WARN --zookeeper=172.16.1.111:2181 --listen=172.16.1.150:8090 &
#訪問172.16.1.150:8090設置
添加proxy
添加codis-server
添加redis-sentinels
配置slots
8.開機自啓相關設置
#codis-server+Redis-sentinel vim /server/scripts/sentinel.sh if [ $# -eq 0 ] then echo "useage:sh $0 {start|stop}" exit 0 fi case $1 in start) nohup /application/codis/bin/redis-sentinel /application/codis/conf/sentinel.conf & ;; stop) ps -ef|grep redis-sentinel|grep -v grep|awk '{print $2}'|xargs kill ;; esac vim /server/scripts/auto_start_codis.sh sh /server/scripts/codis-server.sh start sh /server/scripts/sentinel.sh start chmod +x /server/scripts/auto_start_codis.sh vim /etc/rc.local /server/scripts/auto_start_codis.sh #Codis-fe + Codis-proxy + Codis-dashboard vim /server/scripts/codis.sh export GO_HOME=/usr/local/go/bin/ if [ $# -eq 0 ] then echo "useage:sh $0 {start|stop}" exit 0 fi case $1 in start) nohup /application/codis/bin/codis-dashboard --ncpu=24 --config=/application/codis/conf/dashboard.conf --log=/application/codis/log/dashboard.log --log-level=WARN & nohup /application/codis/bin/codis-proxy --ncpu=24 --config=/application/codis/conf/proxy.conf --log=/application/codis/log/proxy.log & nohup /application/codis/bin/codis-fe --ncpu=1 --log=/application/codis/log/fe.log --log-level=WARN --zookeeper=172.16.1.111:2181 --listen=172.16.1.150:8090 & ;; stop) ps -ef|grep codis-proxy|grep -v grep|awk '{print $2}'|xargs kill ps -ef|grep codis-fe|grep -v grep|awk '{print $2}'|xargs kill ps -ef|grep codis-dashboar|grep -v grep|awk '{print $2}'|xargs kill chmod +x /server/scripts/codis.sh vim /etc/rc.local /server/scripts/codis.sh start #由於是測試環境,我經常強制關機,導致codis-dashboard沒有正常關閉.直接造成zookeeper裏面的狀態沒有更新,最終新啓動的codis-dashboard不能註冊進zookeeper,一直提示已存在而被強制關閉. 解決方法如下 刪除product /application/codis/bin/codis-admin --remove-lock --product=my-codis --zookeeper=172.16.1.111:2181 #再次啓動 nohup /application/codis/bin/codis-dashboard --ncpu=24 --config=/application/codis/conf/dashboard.conf --log=/application/codis/log/dashboard.log --log-level=WARN &
9.性能測試
redis-benchmark參數解析:
-h ip地址
-p redis端口
-a 認證密碼
-c 設定多少個併發連接
-n 總共多少個請求
-q 顯示模式:簡要模式
#集羣測試 /application/codis/bin/redis-benchmark -h 172.16.1.150 -p 19000 -a 56789 -c 500 -n 1000000 -q #單節點測試 /application/codis/bin/redis-benchmark -h 172.16.1.151 -p 6739 -a 56789 -c 500 -n 1000000 -q
10.讀寫分佈測試
vim /server/scripts/test_codis.sh #!/bin/bash hos="172.16.1.150" pot="19000" pawd="56789" cli="/application/codis/bin/redis-cli" keyset="keytest2" valueset="jlasdnfnsdfsdf;sdfhlkjahsdjlkfadfjkasdbbcjhdgasfyuefkbadjkhflk" dbname=2 a=0 for i in `seq 1 5000` do $cli -h $hos -p $pot -a $pawd -n $dbname 'set' ${keyset}${a} "${valueset}${a}" >/dev/null #echo $a let a++ done sh /server/scripts/test_codis.sh
key基本平均分佈在3個組內
11.故障切換測試
#修改一下腳本 vim #!/bin/bash hos="172.16.1.150" pot="19000" pawd="56789" cli="/application/codis/bin/redis-cli" keyset="test2" valueset="asdklalksjdklajsdlkajs" dbname=2 a=0 for i in `seq 1 6000` do $cli -h $hos -p $pot -a $pawd -n $dbname 'set' ${keyset}${a} "${valueset}${a}" >/dev/null #echo $a let a++ done sh /server/scripts/test_codis.sh
#查找主庫進程ss -ntplu | grep codis-server(172.16.1.152) tcp LISTEN 0 128 172.16.1.152:6379 *:* users:(("codis-server",1068,4)) tcp LISTEN 0 128 172.16.1.152:6380 *:* users:(("codis-server",1073,4)) #殺掉主庫進程 kill -9 1068
共計5100個KEY
丟KEY的問題是由於redis-sentinel故障切換期間,整個codis集羣並不會關閉對此故障redis-server的連接,所以codis-proxy依然會發送數據給當前故障的redis-server,而顯然此時的redis-server是無法存儲數據的,這就造成了丟key現象了.如果整個主從掛了,就會丟掉所有發送到此redis-server的key了,除非手工剔除故障節點.
雖然codis還自帶有一種故障切換程序codis-ha,他屬於一個守護進程,會連接codis-dashboard查看各節點狀態,
1 2 | #執行一下命令啓動codis-ha,端口是codis-dashboard的端口 /usr/local/codis/codis-ha --dashboard=172.16.1.150:18080 --log= /application/codis/log/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加腳本來實現比較好,各取所需.