让nginx支持TCP长连接代理

问题

1
2
3
4
5
-----> LCS
/
Client(PHP) -------> LCS
\
------> LCS

如上,我们客户端需要和LCS交互,正常情况下,由于PHP比较难做长链接(当然RAL后续可以做长连接的支持),所以现在每次和LCS交互,都需要建立一次TCP连接,然后做数据传输;现在问题是,如果LCS在其他的IDC,这样client和LCS每次都得做一次跨机房的三次握手, 这样对服务稳定性等都有一定的影响

方案

1
2
3
4
5
-----> LCS
/
Client(PHP)---> Proxy -------> LCS
\
------> LCS

一个方案是如上,加上一个proxy,这个Proxy的作用是和LCS维护一个长连接,和client保持短连接,同时让Client和Proxy部署在一个IDC, Proxy和LCS可以跨IDC;此时Proxy和LCS的由于是长连接交互,不必要每次都有个跨IDC的三次握手, 但也有如下的问题:

  • 增加了一层,意味着多一层网络交互,
  • 长连接的跨IDC交互有可能也避免不了跨IDC的网络异常问题

实现

如上,上面的proxy,其实就是我们说的nginx proxy, 现在的问题是nginx虽然支持简单的tcp proxy,但是并不支持downstream是短链接,然后upstream是长连接(upstream和downstream的长短链接必须一致), 但是我们知道nginx http模块里面是有一个keepalive模块的,所以技术上面应该问题不大的,我们要做的就是把那个东西迁移过来, 具体如下:

关于nginx HTTP 长连接

  • 内部实现 : http://blog.csdn.net/gzh0222/article/details/8523635
  • 大概思路 :
    • 对于一个具体的upstream,可以设置其connection pool的size
    • 每次需要访问upstream的时候,先去connection pool里面查找, 如果存在,就用改连接;否则新建一个连接
    • 当访问结束的时候, 不主动断开连接,而且放回到连接池; 如果连接池是满的,需要释放最老的无用的连接(LRU)

配置demo

依照http upstream keepalive module的工作原理,我们编写了一个tcp upstream keepalie模块,具体代码请见ngx_stream_keepalive_module

如下是简单的配置demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
stream {
upstream stream_test {
# 设置连接池的大小, 用法同http upstream keepalive module
keepalive 10;
server yf-forum-rdcjs1.yf01.baidu.com:12388;
}
server {
listen 12345;
proxy_pass stream_test;
proxy_protocol on;
}
}

问题&思路

一旦加入一个proxy,我们其实又面临一个问题,以上图为例,就是LCS如何获取真正的client ip?

像http协议的代理,一般会在应用层做这个事情,比如说通过ngx_http_realip_module 把相关的信息放到header里面,这样是能满足业务需求的; 那tcp proxy怎么办?

像TCP层一下的代理,比如说LVS一般会需要内核的支持,以便我们正确的获取客户端的IP,感觉解决方案都是挺重的

一个简单的方案是The PROXY protocol, 这个协议原理&思路非常简单,就是在协议代理,新建连接的时候,会传输具体的代理信息。比如按照上图,当ProxyLCS建立连接的时候,他做的第一个事情是发送一个文本信息,告诉LCS相关的client信息,大概如下:

1
2
PROXY TCP4 192.168.0.1 192.168.0.11 56324 443

所以此时客户端需要修改其读取逻辑,在首次连接之后,需要尝试去读取proxy的信息,如果存在,就可以做一些具体的解析

同时,也要求proxy做一些修改,在发送请求给后端的时候,需要额外带上这些信息,具体可以通过proxy_protocol这个指令控制;

其他