旁路的目的是为了把请求复制一份发到另外的服务上去。这样就可以不影响主流程的情况下 处理额外的逻辑了。最简单的方式就是让nginx把请求发出去,这样我们只要改配置就行了, 而不用改代码。相对于开发,测试回归的成本往往要高很多。

技术方案

  1. nginx收到请求后,把请求发到主流程的服务A上去
  2. 同步把请求旁路发到服务B上去
  3. 等待A和B回包
  4. 响应客户端

标准的nginx是不支持旁路的,我们用lua-nginx-module模块,通过lua来控制多发多收。

正常的流程应该是这样的

  1. nginx收到请求
  2. nginx把请求发到server A
  3. nginx收到server A的回包
  4. nginx回包给客户端

加上旁路后,流程变成了这样

  1. nginx收到请求
  2. nginx把请求发给server A
  3. nginx把请求发给server B
  4. nginx收到server A的回包
  5. nginx收到server B的回包
  6. nginx回包给客户端 对于A和B的回包,我们并不能决定顺序,因为server B的处理和网络比server A的快,所以 B可能会先回包。这是一种比较理想的情况。所谓旁路,它就不应该影响我们的正常业务流 程。所以,最理想的情况应该是对于server B,只发不收,收到A的回包后,我们就直接回 包给客户端。但是nginx并不支持这样的处理。

测试

以下是配置

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
41
42
43
44
45
46
47
48
49
50
51
52
53
events {
worker_connections 1024;
}

http {
include mime.types;
default_type application/octet-stream;

upstream route0 {
server route0:8080;
}
upstream route1 {
server route1:8080;
}
server {
listen 8080;
server_name localhost;

location /route0{
rewrite ^/route0(.*)$ $1 break;
proxy_pass http://route0;
}
location /route1{
proxy_connect_timeout 100ms;
proxy_read_timeout 100ms;
proxy_send_timeout 100ms;
rewrite ^/route1(.*)$ $1 break;
proxy_pass http://route1;
}
location / {
content_by_lua '
local route0, route1, action
action = ngx.var.request_method

if action == "POST" then
ngx.req.read_body()
local data = ngx.req.get_body_data()
arry = {method = ngx.HTTP_POST, body = data}
else
arry = {method = ngx.HTTP_GET}
end

route0, route1 = ngx.location.capture_multi {
{ "/route0" .. ngx.var.request_uri, arry},
{ "/route1" .. ngx.var.request_uri, arry},
}

ngx.status = route0.status
ngx.say(route0.body)
';
}
}
}

我们监听8080的端口,然后代理转发到route0:8080和route1:8080上去,然后route0和 route1的回包。

对于route1的旁路,我们限制了proxy_connect_timeout, proxy_read_timeout, proxy_send_timeout的超时都为100ms,这样即使旁路的处理非常慢,也不会对我们主流程 产生较大的影响。

另外用go写了一个hello world的http server,配置了旁路,链接:https://github.com/tenfyzhong/nginx-capture-multi