Healthcheck
由於服務無法保證永遠不會下線,而且下線時不一定能有人員能及時發現,
所以api gateway 一般會引入一個監工 Healthcheck
, 像大家每年體檢一樣定時確認服務是否存活。
這樣就可以在上游節點發生故障或者遷移時,將請求代理到健康的節點上,最大程度避免服務不可用的問題。
一般其分為主動檢查和被動檢查。
主動檢查
其一般為使用單獨的執行緒、程式、甚至獨立的程式的探針,不斷輪休式主動檢查服務存活性。
一般支援 HTTP、HTTPS、TCP 三種探針型別, 也就是實際存不存活就是訪問大家服務,看能不能得到正常結果。
其判定存活邏輯一般為:當發向健康節點 A 的 N 個連續探針都失敗時(取決於如何配置),則該節點將被標記為不健康,不健康的節點將會被 api gateway忽略,無法收到請求;若某個不健康的節點,連續 M 個探針都成功,則該節點將被重新標記為健康,進而可以被代理。
(PS: 一般很多api gateway 為了方便大家使用,程式自帶主動檢查,所以api gateway 例項很多時,這樣主動檢查的請求就會過於大量,有些就會獨立搭建獨立的檢查服務,減少請求量級)
被動檢查
其一般為根據上游服務返回的情況,來判斷對應的上游節點是否健康。相對於主動健康檢查,被動健康檢查的方式無需發起額外的探針,但是也無法提前感知節點狀態,可能會有一定量的失敗請求。
同理一般也是發向健康節點 A 的 N 個連續請求都被判定為失敗(取決於如何配置),則該節點才被標記為不健康。
實踐
由於篇幅關係,這裡就只介紹被動檢查
的實現例子。
更具體實現可以參考 簡化的healthcheck 或者 完整的lua-resty-healthcheck (ps: lua-resty-healthcheck 被動檢查被標記為不健康之後無法恢復健康狀態)
worker_processes 1; #nginx worker 數量
error_log logs/error.log; #指定錯誤日誌檔案路徑
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr [$time_local] $status $request_time $upstream_status $upstream_addr $upstream_response_time';
access_log logs/access.log main buffer=16384 flush=3; #access_log 檔案配置
lua_package_path "$prefix/deps/share/lua/5.1/?.lua;$prefix/deps/share/lua/5.1/?/init.lua;$prefix/?.lua;$prefix/?/init.lua;;./?.lua;/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/openresty/luajit/share/lua/5.1/?.lua;/usr/local/openresty/luajit/share/lua/5.1/?/init.lua;";
lua_package_cpath "$prefix/deps/lib64/lua/5.1/?.so;$prefix/deps/lib/lua/5.1/?.so;;./?.so;/usr/local/lib/lua/5.1/?.so;/usr/local/openresty/luajit/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so;";
# 開啟 lua code 快取
lua_code_cache on;
lua_shared_dict http_healthcheck 20m;
upstream nature_upstream {
server 127.0.0.1:6699; #upstream 配置為 hello world 服務
# 一樣的balancer
balancer_by_lua_block {
local balancer = require "ngx.balancer"
local upstream = ngx.ctx.api_ctx.upstream
local ok, err = balancer.set_current_peer(upstream.host, upstream.port)
if not ok then
ngx.log(ngx.ERR, "failed to set the current peer: ", err)
return ngx.exit(ngx.ERROR)
end
}
}
init_by_lua_block {
-- 初始化 lb
local roundrobin = require("resty.roundrobin")
local nodes = {k1 = {host = '127.0.0.1', port = 6698}, k2 = {host = '127.0.0.1', port = 6699}}
local ns = {}
for k, v in pairs(nodes) do
-- 初始化 weight
ns[k] = 1
end
local picker = roundrobin:new(ns)
-- 被動檢查
local shm_healthcheck = ngx.shared["http_healthcheck"]
local check_healthcheck = function()
for _ in pairs(nodes) do
local k, err = picker:find()
if not k then
return nil, err
end
local node = nodes[k]
-- 檢查是否不健康
local status = shm_healthcheck:get(node.host..':'..tostring(node.port))
if not status then
return node
end
end
return nil, 'no health node'
end
-- 初始化路由
local radix = require("resty.radixtree")
local r = radix.new({
{paths = {'/aa/d'}, metadata = {
find = check_healthcheck
}},
})
-- 匹配路由
router_match = function()
local p, err = r:match(ngx.var.uri, {})
if err then
log.error(err)
end
-- 執行 roundrobin lb 選擇
local k, err = p:find()
if not k then
return nil, err
end
return k
end
}
server {
#監聽埠,若你的8699埠已經被佔用,則需要修改
listen 8699 reuseport;
location / {
# 在access階段匹配路由
access_by_lua_block {
local upstream = router_match()
if upstream then
ngx.ctx.api_ctx = { upstream = upstream }
else
ngx.exit(404)
end
}
proxy_http_version 1.1;
proxy_pass http://nature_upstream; #轉發到 upstream
# 上游節點 502 記錄到不健康列表,這裡為了理解簡單,失敗一次就寫入
log_by_lua_block {
local s = ngx.var.upstream_status
if s and s == '502' then
ngx.shared["http_healthcheck"]:incr(ngx.var.upstream_addr, 1, 5)
end
}
}
}
#為了大家方便理解和測試,我們引入一個hello world 服務
server {
#監聽埠,若你的6699埠已經被佔用,則需要修改
listen 6699;
location / {
default_type text/html;
content_by_lua_block {
ngx.say("HelloWorld")
}
}
}
}
啟動服務並測試
$ openresty -p ~/openresty-test -c openresty.conf #啟動
$ curl --request GET 'http://127.0.0.1:8699/aa/d' #第一次
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
</body>
</html>
$ curl --request GET 'http://127.0.0.1:8699/aa/d' #第二次
HelloWorld
$ curl --request GET 'http://127.0.0.1:8699/aa/d' #第三次
HelloWorld
$ curl --request GET 'http://127.0.0.1:8699/aa/d' #第四次
HelloWorld
可以看到不可訪問的服務節點只被訪問了一次,後續都到了健康的節點上