使用openresty实现简单的灰度切换

发布 : 2023-09-05 分类 : 笔记

底层使用了 openresty 的动态网关应用有 apisix Kong 等知名网关服务。
本文就是在 openresty 的基础上实现简版的动态流量切换,达到灰度流量切换的目的。

运行原理

  • openresty 执行过程,基础学习参照文章尾部 OpenResty 最佳实践

当HTTP请求进入时,判断请求信息(请求头或者请求URL参数),如果符合灰度条件,则转发流量到灰度服务。

准备环境

两个不同端口的后端服务,本文使用 nodejs 环境运行后端服务,代码如下:

1
2
3
4
5
6
7
const http = require('http');
const port = Number(process.argv[2]) || 0;
console.log(port);
http.createServer((req, res) => {
res.write(String(port));
res.end();
}).listen(port);

openresty conf 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
http {
...
upstream test.switch {
server localhost:12345;
server localhost:12346;
}

upstream grey.test.switch {
server localhost:12347;
}
...

server {
listen 1280;
location / {
proxy_pass http://test.switch;
}
}
}

此时是使用反向代理的常规配置,命中的均为上游测试服务。

实践

基于本地文件的灰度识别

lua 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
local cjosn = require 'cjson'
-- 获取请求头
local headers = ngx.req.get_headers()
local key = ""
if headers ~= nil and headers["user-id"] ~= nil then
-- 整理请求key
key = "grey_key_prefix:"..headers["user-id"];
end

-- 灰度控制
ngx.header["x-response-switch"] = "origin"
local v, ok = ngx.shared["grey_host_dict"]:get(key)
if not v then
-- base_upstream 是外部配置的变量,正常服务
ngx.var.switch_upstream = ngx.var.base_upstream
else
-- grey_upstream 是外部配置的变量,灰度服务
ngx.header["x-response-switch"] = "grey"
ngx.var.switch_upstream = ngx.var.grey_upstream
end

-- 重载灰度配置文件
if ngx.var.document_uri == "/reload_grey_file" then
local fd = io.open(ngx.var.grey_file, "r")
local s = fd:read("*a")
fd:close()

-- 输出load file 的内容
ngx.print("load json: ")
ngx.say(s)
local json = cjosn:new()
local tb = json.decode(s)

for k, v in pairs(tb) do
-- 输出实际加入的灰度信息
ngx.say("kv: "..k.." => "..v)
ngx.shared["grey_host_dict"]:safe_set(k, v)
end
ngx.exit(200)
end

openresty 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
lua_shared_dict grey_mem 1m; # 配置灰度信息跨 worker 共享内存块

server {
# 配置基础变量
set $base_upstream "grey.test.switch";
set $grey_upstream "test.switch";
set $grey_host_dict "grey_mem2";
set $grey_file "/usr/local/openresty/custom/grey/config.json"; # {"grey_key_prefix:1": "1"}

listen 1280;
location / {
# 配置切换上下游变量便于切换流量
set $switch_upstream $switch_upstream;
proxy_pass http://$switch_upstream;
}
}
...

这样就达到了如开篇图的效果。

基于 redis 的灰度识别

但是,基于文件的灰度配置不便,因此可以利用 openresty 提供的各种后端存储库实现方便的配置,如:ngx_postgres ngx_redis2 ngx_redis 等模块, lua-resty-memcached lua-resty-mysql lua-resty-redis 等库。

模块的简单使用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
upstream redis_backend {
server 127.0.0.1:6379;

keepalive 16; # redis持久化连接
}
...
server {
...
location = /redis {
internal; # 标记为内部服务,外部无法请求到该路由
set_unescape_uri $query $arg_query; # 处理请求参数

redis2_raw_query $query; # 传递给redis后端处理原始 redis 请求
redis2_pass redis_backend;
}
...
}
...

修改lua代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
-- 获取请求头
if ngx.req.is_internal then
return
end

local parser = require "redis.parser" -- 引入基础库,解析redis响应
local headers = ngx.req.get_headers()
local key = ""
if headers ~= nil and headers["user-id"] ~= nil then
-- 整理请求key
key = "grey_key_prefix:"..headers["user-id"];
end

function hmget(dict, key)
-- 构造内部请求
local res = ngx.location.capture("/redis?query=hmget " .. dict .. " ".. key .."\r\n")
local replies = parser.parse_replies(res.body, 1)
-- 解析响应结果
for i, reply in ipairs(replies) do
return reply[1]
end
end

-- 灰度控制
ngx.header["x-response-switch"] = "origin"
local v, ok = hmget("grey_host_dict", key)
if not v then
-- base_upstream 是外部配置的变量,正常服务
ngx.var.switch_upstream = ngx.var.base_upstream
else
-- grey_upstream 是外部配置的变量,灰度服务
ngx.header["x-response-switch"] = "grey"
ngx.var.switch_upstream = ngx.var.grey_upstream
end

如此就完成了轻量级的灰度流量自动切换。
Openresty还可以实现更加复杂的流量控制,可以直接使用成熟的网关框架;如果服务较小也可以直接基于 openresty 做一些简单的任务。

参考文章

  1. OpenResty 官网 https://openresty.org/cn/
  2. OpenResty 最佳实践 https://moonbingbing.gitbooks.io/openresty-best-practices/content/
  3. OpenResty 开源组件 https://github.com/search?q=openresty&type=repositories
本文作者 : 萧逸雨
原文链接 : http://qiubo.ink/2023/09/05/使用openresty实现简单的灰度切换/
版权声明 : 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!