通过 Consul+OpenResty 实现无reload动态负载均衡

【转载请注明出处】:https://www.jianshu.com/p/bee45550781e

动态Nginx负载均衡的配置,可以通过Consul+Consul-Template方式,但是这种方案有个缺点:每次发现配置变更都需要reload Nginx,而reload是有一定损耗的。而且,如果你需要长连接支持的话,那么当reload时Nginx长连接所在worker进程会进行优雅退出,并当该worker进程上的所有连接都释放时,进程才真正退出(表现为worker进程处于worker process is shutting down)。因此,如果能做到不reload就能动态更改upstream,那么就完美了。

目前的开源解决方法有3种:
1、Tengine的Dyups模块
2、微博的Upsync模块+Consul
3、使用OpenResty的balancer_by_lua。

这里使用的是Consul+OpenResty 来实现动态负载均衡。Consul的安装这里将不再介绍。

OpenResty简介

OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

Lua简介

Lua是一个简洁、轻量、可扩展的程序设计语言,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua由标准C编写而成,代码简洁优美,几乎在所有操作系统和平台上都可以编译,运行。

如何做到动态呢?

正常使用Nginx作为API网关, 需要如下配置, 当加后端实例时, reload一下Nginx, 使其生效

upstream backend {
    server 192.168.0.1;
    server 192.168.0.2;
}

那么只要让upstream变成动态可编程就OK了, 当新增后端实例时, 无需reload, 自动生效。
在OpenResty通过长轮训和版本号及时获取Consul的kv store变化。Consul提供了time_wait和修改版本号概念,如果Consul发现该kv没有变化就会hang住这个请求5分钟,在这5分钟内如果有任何变化都会及时返回结果。通过比较版本号我们就知道是超时了还是kv的确被修改了。
Consul的node和service也支持阻塞查询,相对来说用service更好一点,毕竟支持服务的健康检查。阻塞api和kv一样,加一个index就好了。

OpenResty安装

笔者使用的是MAC,下面重点介绍MAC系统上的操作。

Mac
brew tap openresty/brew
brew install openresty
Linux
sudo yum install yum-utils
sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo

sudo yum install openresty

#命令行工具 resty
sudo yum install openresty-resty

Mac OS系统上安装完之后Nginx的配置文件在目录/usr/local/etc/openresty,启动命令在目录/usr/local/opt/openresty/nginx/sbin

启动
openresty

在浏览器输入localhost

image.png

openresty -h
image.png

到这里OpenResty就已经安装好了。
重启

openresty -s reload

安装Lua的包管理器LuaRocks

LuaRocks是Lua模块的包管理器,安装LuaRocks之后可以通过LuaRocks来快速安装Lua的模块,官网地址是https://luarocks.org/
进入OpenResty的安装目录/usr/local/opt/openresty可以看到已经安装了luajit,也是OpenResty默认使用的lua环境。

image.png

下载LuaRocks安装包http://luarocks.github.io/luarocks/releases/luarocks-3.0.4.tar.gz,解压之后进入LuaRocks源码目录编译安装到OpenResty的安装目录。

./configure --prefix=/usr/local/opt/openresty/luajit --with-lua=/usr/local/opt/openresty/luajit --lua-suffix=luajit --with-lua-include=/usr/local/opt/openresty/luajit/include/luajit-2.1
make build
make install

安装完LuaRocks之后,可以看到LuaRocks的执行命令在目录/usr/local/opt/openresty/luajit

image.png

安装完LuaRocks之后就可以安装搭建环境需要的依赖包了。

./luarocks install luasocket

修改配置文件

在Consul注册好服务之后通过Consul的http://127.0.0.1:8500/v1/catalog/service/api_tomcat2接口就可以获取到服务的列表。这里不再讲述Consul的服务注册。
在默认配置文件目录/usr/local/etc/openresty创建一个servers文件夹来放新的配置文件,创建lualib文件夹来放lua脚本,修改配置文件nginx.conf,添加include servers/*.conf;
lualib文件夹下创建脚本upstreams.lua

local http = require "socket.http"
local ltn12 = require "ltn12"
local cjson = require "cjson"

local _M = {}

_M._VERSION="0.1"

function _M:update_upstreams()
    local resp = {}

    http.request{
        url = "http://127.0.0.1:8500/v1/catalog/service/api_tomcat2", sink = ltn12.sink.table(resp)
    }
       
        local resp = table.concat(resp);
    local resp = cjson.decode(resp);

    local upstreams = {}
    for i, v in ipairs(resp) do
        upstreams[i] = {ip=v.Address, port=v.ServicePort}
    end
        
    ngx.shared.upstream_list:set("api_tomcat2", cjson.encode(upstreams))
end

function _M:get_upstreams()
   local upstreams_str = ngx.shared.upstream_list:get("api_tomcat2");
   local tmp_upstreams = cjson.decode(upstreams_str);
   return tmp_upstreams;
end

return _M

过luasockets查询Consul来发现服务,update_upstreams用于更新upstream列表,get_upstreams用于返回upstream列表,此处可以考虑worker进程级别的缓存,减少因为json的反序列化造成的性能开销。
还要注意使用的luasocket是阻塞API,这可能会阻塞我们的服务,使用时要慎重。
创建文件test_openresty.conf

lua_package_path "/usr/local/etc/openresty/lualib/?.lua;;";
lua_package_cpath "/usr/local/etc/openresty/lualib/?.so;;";

lua_shared_dict upstream_list 10m;

# 第一次初始化
init_by_lua_block {
    local upstreams = require "upstreams";
    upstreams.update_upstreams();
}

# 定时拉取配置
init_worker_by_lua_block {
    local upstreams = require "upstreams";
    local handle = nil;

    handle = function ()
        --TODO:控制每次只有一个worker执行
        upstreams.update_upstreams();
        ngx.timer.at(5, handle);
    end
    ngx.timer.at(5, handle);
}

upstream api_server {
    server 0.0.0.1 down; #占位server

    balancer_by_lua_block {
        local balancer = require "ngx.balancer";
        local upstreams = require "upstreams";    
        local tmp_upstreams = upstreams.get_upstreams();
        local ip_port = tmp_upstreams[math.random(1, table.getn(tmp_upstreams))];
        balancer.set_current_peer(ip_port.ip, ip_port.port);
    }
}

server {
    listen       8000;
    server_name  localhost;
    charset utf-8;
    location / {
         proxy_pass http://api_server;
         access_log  /usr/local/etc/openresty/logs/api.log  main;
    }
}

init_worker_by_lua是每个Nginx Worker进程都会执行的代码,所以实际实现时可考虑使用锁机制,保证一次只有一个人处理配置拉取。另外ngx.timer.at是定时轮询,不是走的长轮询,有一定的时延。有个解决方案,是在Nginx上暴露HTTP API,通过主动推送的方式解决。


image.png

Agent可以长轮询拉取,然后调用HTTPAPI推送到Nginx上,Agent可以部署在Nginx本机或者远程。
对于拉取的配置,除了放在内存里,请考虑在本地文件系统中存储一份,在网络出问题时作为托底。

获取upstream列表,实现自己的负载均衡算法,通过ngx.balancer API进行动态设置本次upstream server。通过balancer_by_lua除可以实现动态负载均衡外,还可以实现个性化负载均衡算法。

可以使用lua-resty-upstream-healthcheck模块进行健康检查,后面会单独介绍。
到这里,其实还有一个问题没有解决掉,虽然upstream中的占位server是下线的,但是nginx在检测upstream列表中server的健康状态的时候是会去检测这个占位server的,最好的方式还是在启动之后把它彻底从upstream列表中给移除掉。

【转载请注明出处】:https://www.jianshu.com/p/bee45550781e

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355