ngx_http_dyups_module是什么
-
ngx_http_dyups_module (dyups means dynamic upstreams)是github上一个nginx相关的开源插件,而且已经merge到了淘宝的tengine中。使用它可以在不reload nginx的情况下动态更新upstream.
rererence
: https://github.com/yzprofile/ngx_http_dyups_module
ngx_http_dyups_module能解决我们客服系统中哪些问题
自动化扩容和缩容还有智能化运维一直是我们努力实现的目标,我们面临的第一个问题就是每次给服务添加新实例或者升级重启服务的时候都需要人工更改upstream server list,
reload nginx
, dyups module可以帮助我们避免这种人工操作,实现自动化系统中使用了websocket,长连接,nginx reload操作会对影响长连接有影响,尤其是流量大的时候用户体验不好,dyups模块可以避免reload这个操作
和其他dynamic upstream update方案的对比
1. nginx + upsync 模块
å
这个方案在沙箱试了很久都没有,功能上没有任何问题。但是在线上系统并发超过50k的情况下, reload nginx后几分钟会出现页面无法访问,静态页面的请求都变成下载了,最后导致整个应用crash. 这个应该是upsync的bug导致http response content type类型改变了。没有upsync开发者的帮助这个问题很难解决。
2. 右拍云的slardar
* slardar 是基于nginx 1.9.5 然后集成了又拍云自己开发的一些lua module,还有春哥的balancer.lua 新模块 并且结合consul k/v 实现了upstream动态更新
* slardar可以通过动态更新consul的k/v存储来添加upstream,添加lua script, 非常灵活。 不过这个需要对nginx的开发流程非常了解,并且熟悉nginx lua代码
* github上 https://github.com/upyun/slardar 目前还没有人提issue和pr,估计只有又拍云自己在用了,目前来说通用性不好(虽然这个技术架构和我们的架构非常契合)
**3. tengine + ngx_http_dyups_module **
* github 上 https://github.com/yzprofile/ngx_http_dyups_module 这个模块已经开源很久el并且merge到了tengine里,说明这个模块已经相当成熟了, 这个模块都是C编写的效率肯定是超过用lua实现的
* ngx_http_dyups_module 提供了可以操作upstream的http restful api, 管理upstream变得非常容易
* 线上系统之前使用的nginx就是tengine, 所以使用这个方案会更容易一些,只需重新编译并加一个dyups模块,原来的配置无需改动
* 现在线上系统的nginx是通过健康监测模块主动check服务的端口号在不在,这个检测时有时间间隔的,线上的配置是
check interval=2000 rise=2 fall=3 timeout=1000;
这个健康监测连续失败3次nginx才会认为后端实例不可用,即6s后,nginx才会把一个有问题的实例屏蔽掉。通过consul service自己的健康监测不需要这么长的时间,几乎是近实时的,dyups可以很快的拿掉有问题的实例,影响小了很多。
** 4. consul nginx template **
*consul template定义nginx配置文件模版,从consul上读取server信息(ip + port),然后更新nginx,并reload nginx. 这些都是consul帮助实现的,不过还是有reload操作。
上面的方案中 tengine + ngx_http_dyups_module对于我们来说是难度最小而且可以很短时间内完成的,所以我们要先测试一下这个方案。
tengine 2.1.2 + dyups在客服系统中的使用
* java程序启动后把服务信息(server_name, ip, port,tag)注册到consul上
* 使用consul watch机制监控 consul上所有服务的状态变更 (包括重启,实例的添加,删除,服务状态从passing变为critical等)
* 捕捉到服务变更后,筛选出变化的服务
* 把最新的服务实例信息持久化到nginx upstream配置文件中以防止有nginx reload操作导致内存中的upstream信息丢失
* 持久化upstream配置成功后,在内存中直接更新有变化的upsgream server信息
tengine 2.1.2 安装配置
1. sudo yum install tengine-2.1.2
2. copy 线上的nginx配置文件到tengine 安装路径/data/apps/config/nginx
需要同步的配置文件有waf,nginx lua的第三方库 /etc/nginx/lua
3. 配置dyups
a. http block 中配置
dyups_upstream_conf conf.d/sandbox.upstream.conf;
#sandbox.upstream.conf 用来定义持久化从consul同步过来的upstream server list.
b. 定义dyups 管理端口
conf.d/dyups_management.conf
server {
listen 127.0.0.1:18882;
location / {
dyups_interface; # 这个指令表示这边是接口站点
}
}
4. 启动check_consul.py脚本
这个脚本使用长轮训watch consul里的services状态变化,如果有变更就会持久化conf.d/sandbox.upstream.conf文件,然后更新内存中的upstream server列表,这样就实现了不需要reload nginx更新nginx upsteam列表。
因为reload会把dyups更新到内存中的服务列表清除,所以每次更新到内存前要做持久化。
通过dyups api查看更新到内存的upstream
[easemob@sdb conf.d]$ curl 127.0.0.1:18882/list
WebappReadonly_a
TenantAppService
RobotChat
TicketsReadwrite
KFSystemmessage
IMgateway
AgentState
ServiceSessionAttributes
WebappReadonly_b
GatewayIMRoutewayPartner
KFRobot
WebappReadwrite_b
WebappReadwrite_a
ServiceQuality
查看WebappReadonly_b的server list
[easemob@sdb conf.d]$ curl 127.0.0.1:18882/upstream/WebappReadonly_b
server 10.117.17.21:8180
5. 配置使用dyups的服务
之前我们的upstream名字都是比较随意的,没有以服务名字名字,为了使用dyups的特性,upsteam的名字和consul中的服务名要有关联,我们把需要灰度的服务upstream name为 [serviceName]_[tag],只有一个版本的服务upsteram 定义为 [serviceName]
(以读写webapp为例,因为webapp需要灰度,所以配置文件有2个upstream kfapp_a,kfapp_b)
a. 全局更改kfapp_a为WebappReadwrite_a, kfapp_b 改为WebappReadwrite_b。
更改完配置,reload后 nginx会自动从内存中读取WebappReadwrite upstream的server信息。
b. 删除原有的upstream kfapp_a,kfapp_b 定义
配置可以参考: http://www.ttlsa.com/nginx/nginx-modules-ngx_http_dyups_module/
注意:在定义非灰度服务的时候,比如MessageServer,在proxy_pass的时候需要用变量替换它的upstream名字 eg: 我们不能使用proxy_pass http://MessageServer 而应该使用变量 set $message_service "MessageServer"; http://$message_service; 原因是如果直接用服务名字,nginx会一直使用原来内存中的地址,如果使用变量,nginx 会一直遍全局存储的upstream list,有变化的话都会及时更新
按照上述步骤配置后,扩容缩容就可以避免reload nginx了,运维操作步骤也会减少很多。
当然有好处也会有风险,使用dyups后nginx对consul就有依赖了,consul的异常可能会对nginx造成影响,目前能想到的几个异常比如超时,或者 consul服务挂掉,都会被python脚本catch住 然后退出,不会对nginx造成影响。
捕捉consul timeout,还用服务变更,consul本身服务的异常code:
while True:
try:
(index, consul_services) = getConsulServices(index_old)
except requests.exceptions.ConnectionError:
print "Can't not connect to local consul agent, please check it, will exit program."
sys.exit(1)
except requests.exceptions.RequestException as err:
print "There is http request error during calling consul agent, will exit program"
print err
sys.exit(1)
except Exception as err:
print err
sys.exit(1)
测试case
reload nginx
reload nginx会读取脚本更新的配置文件,重新把从consul获取的upstream更新到内存kill consul 进程
python 脚本中会catch到这个异常并退出,不会继续更新nginx的upstream
最后把代码和demo放到了github上,有兴趣的可以自己测试一下!
https://github.com/easemob/SwallowKeeper