Nginx实现Socket代理功能,根据Socket报文内容动态代理

2 篇文章 0 订阅

近期有一需求:原有一Socket服务端(以下称为A),可以处理一些固定类型的报文,在不能修改A的情况下,需要增加额外的报文类型处理支持。

考虑到A服务不能够被修改,所以必须新增B服务来处理增量报文类型,但这样客户端就需要配置两个Socket地址,并且根据报文类型来判断应该发往哪个地址,这样一来对于客户端的修改非常大,并且不符合开闭原则,因此最终决定将B服务与A服务并列部署,并且在其上游增加反向代理服务,由反向代理服务来决定报文应该发往A还是B。

形成设计如下图:

B服务的功能与A服务类似,就不展开多说了,下面主要对于反向代理的选型与设计进行介绍。

反向代理最初有两种方案:1、代码实现,2、Nginx转发。

代码实现初步设计如下:

代码实现其实就是在一个Socket连接执行的过程中,内嵌一个Socket连接,去完成反向代理操作,需求看上去是可以满足的,但是由于开发、测试工作量大,并且报文内容多的话,有可能出现客户端Socket连接超时的情况,所以没有被选做最优方案。

于是又开始进行了Nginx代理Socket请求的研究。

Nginx一般被用于代理Http、Https请求,1.9.0版本增加了对于Socket请求的处理——stream模块。

但是只是代理Socket请求是不够的,还需要根据Socket报文中的报文类型去动态分发到不同的服务端才行,于是又引入了Lua的概念——Lua是一种轻量小巧的脚本语言,可以很方便的嵌入别的程序里。

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

思路如下:

(1)在Nginx配置中引入stream模块;

(2)在stream模块中建立多个路由upstream,指向服务A与服务B;

(3)在stream模块中使用Lua获取Socket报文,并截取报文类型字段;

(4)根据报文类型字段确定最终使用的upstream;

(5)代理到相应路由

最终学习 + 开发使用了不到2天的时间,中间采坑记录如下:

(1)Nginx的stream模块中无法使用set $var方式建立变量

Nginx的stream模块暂时不支持像Http模块一样,使用set $var的方式建立变量,否则会报错。

在仔细研究了Openresty文档后,发现Openresty针对这一情况提供了建立变量的方式——StreamLuaNginxModule模块的lua_add_variable表达式。

lua_add_variable的作用域为Nginx的stream模块中,使用方法如下:

stream {
    upstream serviceA {
      server 127.0.0.1:5432;
    }
    upstream serviceB {
      server 127.0.0.1:5433;
    }

    lua_add_variable $proxy;

    #...
}

使用lua_add_variable定义了变量后,即可在Nginx中直接调用,或者在Lua中使用ngx.var.proxy调用并修改变量值。

(2)Lua代码在转发前不执行

不执行代码如下:

stream {
    #...

    server {		
        listen 5430;
        content_by_lua_block{
	    local sock = ngx.req.socket(true)
	    local reader = sock:receiveuntil("\r\n")
            local data, err, partial = reader()
            -- ...
	}
	proxy_pass $proxy;		
    }
}

经查资料,发现“content_by_lua_block是内容处理器,接受请求并输出响应”,于是怀疑content_by_lua_block域的代码是返回的时候执行的,将content_by_lua_block更换为preread_by_lua_block即可,preread_by_lua_block为预读处理器,执行顺序在转发之前。

修改后代码:

stream {
    #...

    server {		
        listen 5430;
        preread_by_lua_block{
	    local sock = ngx.req.socket(true)
	    local reader = sock:receiveuntil("\r\n")
            local data, err, partial = reader()
            -- ...
	}
	proxy_pass $proxy;		
    }
}

(3)Lua获取Socket报文后,Socket连接中的报文会被消耗,判断并转发完成后,下游服务A / B无法再次获取该Socket连接中的报文

Lua获取Socket报文代码如下:

stream {
    #...

    server {		
        listen 5430;
        preread_by_lua_block{
	    local sock = ngx.req.socket(true)
	    local reader = sock:receiveuntil("\r\n")
            local data, err, partial = reader()
            -- ...
	}
	proxy_pass $proxy;		
    }
}

经查看Openresty文档发现:

If any request body data has been pre-read into the Nginx core request header buffer, the resulting cosocket object will take care of this to avoid potential data loss resulting from such pre-reading. Chunked request bodies are not yet supported in this API.

如果cosocket对象(ngx.req.socket(true)将返回cosocket对象)已经将请求报文数据读取到Nginx core request header buffer中(已经使用receive / receiveunitl方法获取报文),会出现此类因为预读取导致的报文数据丢失。

所以cosocket对象在这个业务中不能使用receive相关方法获取报文数据,经过研究发现了cosocket的另一个获取报文方法:peek。

官方对于peek方法的说明如下:

Peeks into the preread buffer that contains downstream data sent by the client without consuming them. That is, data returned by this API will still be forwarded upstream in later phases.

瞥一眼预读取缓冲区,该缓冲区包含客户端发送的报文数据,而不消耗这些数据。也就是说,这个API返回的数据在以后的阶段仍然会被转发到下游。

所以将代码修改为:

stream {

    #...

    server {
        listen 5430;

        preread_by_lua_block {
	    local sock = ngx.req.socket()
            local data = sock:peek(4) -- 瞥一眼报文的前4个字符
            
            --...
	}

        proxy_pass $proxy;
    }
}

最终代码生效。

完整代码如下:

stream {

    upstream serverA{
        server 127.0.0.1:5432;
    }

    upstream serverB{
        server 127.0.0.1:5431;
    }

    lua_add_variable $proxy;

    server {
        listen 5430;
        preread_by_lua_block {
	    local sock = ngx.req.socket()
            local data = sock:peek(4)
            --根据data判断下游服务,将$proxy的值修改为serviceA或serviceB           
           if (data == "0000") then
		ngx.var.proxy= "serverA";
	    else
		ngx.var.proxy= "serverB";
            end
	}

        proxy_pass $proxy;
    }
}    
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值