我对go-redis-setlock进行了修改以适用于单服务器系统
改造go-redis-setlock以支持单台服务器构建系统的故事
大家好,即使只有一台服务器,我也想进行蓝绿部署。
系统结构
-
- サーバ1台
- docker-compose 環境
网页
如果只有一台服务器,则使用web
+app blue+app green
以类似的结构进行蓝绿部署。
负载均衡器(nginx)只是一个简单的切换开关。
部署的过程(网络版)
即使是单台服务器配置,也不能在部署时短暂中断服务
(硬件故障时没办法,因为有单点故障的问题。)
当蓝色应用处于活动状态,绿色应用处于待机状态时。
-
- 部署下一个版本的应用到app green
-
- 在app green上进行功能确认
-
- 通过负载均衡器(nginx)激活app green
-
- 通过负载均衡器(nginx)停用app blue(不要强制断开连接)
-
- 等待连接切换到app blue上(因为是网页界面,大约10秒左右)
- 通过负载均衡器(nginx)禁止对app blue的连接
顺着这个趋势,(优雅地)进行转变。
◎:活跃状态
○:从活跃状态切换到待机状态的过渡期
△:待机状态
×:停止
批次 (pī cì)
希望像网页一样,在没有中断的情况下,通过 cron 等批处理方式进行部署。
batch blue
batch green
部署的流程(批次)
由于批处理中存在同时运行多个脚本会导致问题,所以需要在多个容器之间实现互斥控制。
前提1:在批处理脚本中,存在一种只有先启动的才会运行的互斥控制机制。
前提2:对于每一个批处理作业,存在一种可以切换它是否运行的机制(例如supervisord)。
当批处理蓝色处于活动状态,批处理绿色处于待命状态时。
-
- 使用 docker-compose start [batch green] 命令启动批量绿色程序
-
- 使用新版本的批处理程序部署到批量绿色程序
-
- 同时运行批量绿色程序和批量蓝色程序
-
- 确保批量蓝色程序不会启动新的作业(不强制终止正在运行的作业)
-
- 在批量蓝色程序中,等待所有正在运行的作业完成后逐个停止作业(supervisorctl stop [job1])
- 停止批量蓝色程序(docker-compose stop [batch blue])
顺势而为,优雅地转变。
◎:活跃
○:从活跃到待机的过渡期
△:待机
×:停止
◎:活跃
○:从活跃切换到待机的时间段
△:待机
×:停止
◎:主动
○:从主动切换到待机的过渡时期
△:待机
×:停止
实现排他性控制的方法。
由于只有一个服务器配置,虽然使用flock也能实现,但当将来需要多台服务器配置时,就必须进行改造。
我们希望一开始就实现多服务器配置的方案。让我们使用redis进行排他控制吧。
如何在 Redis 中实现排他控制?
-
- 同時に実行しようとしたスクリプトは早いもの勝ち
- 遅れて実行したスクリプトは、前回処理日時を取得して、それが直近だった場合は他コンテナで実施済みと見なして終了する
如果能实现这两个,那就太好了。
redisで排他ロック取得
/job/job1/ p2.sh
前回実施日時を取得して、直近で実施されていない事を確認
/job/job1/ main.php本体処理
在 Redis 中,有以下的方法可以进行排他控制。
go-redis-setlock(fujiwara版本)
尝试使用go-redis-setlock(fujiwara版本)
由于服务器只有一台,因此Redis只监听/var/run/redis/redis.sock。
执行
REDIS_SOCK=/var/run/redis/redis.sock
go-redis-setlock -redis ${REDIS_SOCK} -n -x rkey_lock sleep 3
结果(错误)
Redis server seems down: dial tcp: address /var/run/redis/redis.sock: missing port in address
源代码中的 redis 连接部分
c, err = redis.DialTimeout("tcp", opt.Redis, time.Duration(timeout)*time.Second)
是的,这个连接只限制为TCP连接。
对 go-redis-setlock 进行了修改
为了保持Redis仅限于Unix域套接字连接(节省服务器资源),对go-redis-setlock进行了修改。
- c, err = redis.DialTimeout("tcp", opt.Redis, time.Duration(timeout)*time.Second)
+ prot := "tcp"
+ server := opt.Redis
+ if ("unix:" == server[:5]) {
+ prot = "unix"
+ server = server[5:]
+ }
+ c, err = redis.DialTimeout(prot, server, time.Duration(timeout)*time.Second)
因为它是一个只需要单台服务器的过于小众的功能,所以我们决定通过硬分叉来解决,而不是通过pull request的方式。
go-redis-setlock (modified version) – go-redis-setlock(改良版)
我尝试使用改造版的 go-redis-setlock。
执行
REDIS_SOCK=/var/run/redis/redis.sock
go-redis-setlock -redis unix:${REDIS_SOCK} -n -x rkey_lock sleep 3
结果(好)
第一层、第二层、第三层的每个脚本
环境变量
REDIS_SOCK=”/var/run/redis/redis.sock”
RKEY_JOB1=”批处理任务1的RKEY”
RKEY_JOB1_DT=”批处理任务1的日期时间RKEY”
第一层:/job/job1/p1.sh
#!/bin/bash
# ロックが取得できたら実行
# ロックが取得出来なかったら即終了
go-redis-setlock -redis unix:${REDIS_SOCK} \
-n -x ${RKEY_JOB1} /job/job1/p2.sh
第二层:/工作/工作1/p2.sh
#!/bin/bash
# 現在日時
DT=$(date +%Y%m%d%H%M%S)
# 前回実施日時
JOB1_DT=$(redis-cli -s ${REDIS_SOCK} GET ${RKEY_JOB1_DT})
# チェック日時=現在日時−1時間
HHMMSS=10000
DT_CHECK=$((${DT} - ${HHMMSS}))
# チェック日時 <= 前回実施日時 の時、他コンテナで実施済みと見なして終了
if [ ${DT_LOWER} -le ${DT_EXEC} ] ; then
echo "skip"
exit 0
fi
# 本体処理
/job/job1/main.php
第三层:/job/job1/main.php
本体処理