V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
NGINX
NGINX Trac
3rd Party Modules
Security Advisories
CHANGES
OpenResty
ngx_lua
Tengine
在线学习资源
NGINX 开发从入门到精通
NGINX Modules
ngx_echo
liuguangxuan
V2EX  ›  NGINX

有对 nginx 熟悉的老哥吗?请教一下 nginx 代理 tcp 的一个问题?

  •  
  •   liuguangxuan · 2022-12-01 11:05:23 +08:00 · 5369 次点击
    这是一个创建于 484 天前的主题,其中的信息可能已经有所发展或是发生改变。

    背景:

    使用 nginx 代理 tcp 服务,假如后端有一个 tcp 的业务服务,中间用 nginx 做代理,前面有 1000 个客户端连接,那么每当一个客户端连接 nginx 的时候,nginx 就会发起一个 tcp 连接至后端的业务服务。

    问题:

    假设我的那个 tcp 后端服务是负责提供下载地图数据的,对所有的 1000 个客户端来说,从地图服务器下载的数据都是一样的。那么当一个客户端连接上 nginx 的时候,nginx 发起了一个 tcp 连接至地图服务器,地图服务器把地图数据推给了 nginx ,nginx 把数据转给了客户端,这样相当于把一份地图数据在网络中走了两遍(地图服务器到 nginx ,nginx 到客户端),当地图数据比较大的时候,带来的开销就很大了。

    想要达到的效果:

    能否对 nginx 做改造,在 nginx 中缓存一下地图数据,当第一个客户端连接 nginx 的时候,nginx 从后端的地图服务器获取了地图数据,一直缓存在内存里,后面 999 个客户端连接的时候,nginx 就直接把地图数据推给客户端,而不再朝后端的地图服务器下载地图数据。

    如果想实现上述的效果,需要对 stream 模块做改造吗?还是有其他更好的解决办法?麻烦各位老哥赐教。

    第 1 条附言  ·  2022-12-01 14:39:58 +08:00
    1 、各位老哥不要再质疑为啥不用 http 了,有些业务场景不适合 http 。就像 19#老哥说的游戏服。😂
    2 、看到有老哥推荐 openresty ,等会儿我去了解一下,现在对 openresty 的理解就是 nginx+lua ,然后 lua 实现我所要的业务逻辑。
    3 、如果有老哥知道更好的解决方案,也欢迎提出来哈。非常感谢。
    75 条回复    2022-12-04 12:25:03 +08:00
    killva4624
        1
    killva4624  
       2022-12-01 11:10:51 +08:00
    一个问题,nginx 如何识别这 999 个客户端里,哪些需要缓存,哪些是需要请求新的数据呢?
    liuguangxuan
        2
    liuguangxuan  
    OP
       2022-12-01 11:13:40 +08:00
    @killva4624 老哥精准的理解了我的困惑,我需要解决的就是这部分。
    如果想要精准的识别哪些需要缓存,哪些是需要请求新的数据,我需要怎么做?对 nginx 的 stream 模块改造,加自定义协议吗?是否还有更好的办法?
    cnoder
        3
    cnoder  
       2022-12-01 11:14:54 +08:00
    有反向代理缓存,proxy_cache
    ng1nx
        4
    ng1nx  
       2022-12-01 11:16:51 +08:00
    能否把地图上数据推到 Redis ,设置数据有效期(time to live); nginx 从 Redis 取数据,如果取不到就从后端再取一次。
    cnoder
        5
    cnoder  
       2022-12-01 11:18:13 +08:00
    想复杂一点可以用 openresty 这样的,自己实现一下
    victorc
        6
    victorc  
       2022-12-01 11:19:19 +08:00
    nginx+lua 就是干这个的,不建议直接修改 nginx 的 c 代码,可维护性太差了

    去翻 openrestry 的文档吧,能解决
    jiangzm
        7
    jiangzm  
       2022-12-01 11:21:44 +08:00
    地图服务应该下发一个更新包链接(文件服务或者 OSS 存储),给客户端下载下来。
    wzy44944
        8
    wzy44944  
       2022-12-01 11:23:16 +08:00
    文件小的话 nginx 自己就有个内存缓存,一般十几 KB 的数据可以用,再多会有些性能和功能上的问题,文件比较多或者比较大就需要 nginx+缓存软件比如 ATS ,squid 。是否需要 cache 这个在 http 交互里有标准的,比如请求头里的 no-cache,响应头里的过期时间
    seers
        9
    seers  
       2022-12-01 11:24:25 +08:00 via Android
    起一个 mem 数据库,ng 从里面拿数据
    killva4624
        10
    killva4624  
       2022-12-01 11:25:52 +08:00
    @liuguangxuan 如果是 HTTP 这种高层协议是有缓存的,自己包 TCP 方法的话可能就像楼上老哥推荐的,走 openrestry 自己实现缓存协议,或者把重的下载数据逻辑放到另外的存储层去下载。
    leafre
        11
    leafre  
       2022-12-01 11:25:59 +08:00
    在 nginx 中缓存不如在服务端缓存,nginx 和服务端同个内网,哪来的大开销
    lambdaq
        12
    lambdaq  
       2022-12-01 11:31:05 +08:00   ❤️ 7
    为什么人们不基于 tcp 直接做浏览器,而是要搞个 http 。。就是为了解决 LZ 这问题的。。http 特点之一就是为了方便中间盒子来缓存。。。。
    iphoneXr
        13
    iphoneXr  
       2022-12-01 11:31:07 +08:00
    我的做法一般都是 负载均衡做 4 层代理转发 Nginx 做 7 层代理转发和静态文件
    其他下载类的尽量用 OSS 便宜且稳定
    iphoneXr
        14
    iphoneXr  
       2022-12-01 11:32:41 +08:00
    还没有写完 提前提交了
    缓存类走 CDN 主要是流量便宜的多
    ttvast
        15
    ttvast  
       2022-12-01 11:33:37 +08:00
    @lambdaq 真是让人觉得奇怪,下载数据不用 http 还要用自己的 tcp
    gstqc
        16
    gstqc  
       2022-12-01 11:36:10 +08:00
    nginx 的 stream 模块不识别 TCP 协议的上一层协议
    所以无法对数据进行缓存
    如果要实现缓存,你得在 nginx 里写个模块,识别你的协议,然后才能解包出原数据并对数据进行缓存

    搞这么麻烦不如换 HTTP 算了
    rrfeng
        17
    rrfeng  
       2022-12-01 11:36:54 +08:00   ❤️ 8
    1. TCP 是数据流,不存在『缓存』这种东西,你没法缓存一个『数据流』
    2. 所以要做的是把『数据流』变为『数据块』,有边界的数据就能够缓存了
    3. 那么在 TCP 里自己切分一下数据,做一个『自定义协议』来实现『数据块』
    4. 所以为什么不用 HTTP ?
    pjntt
        18
    pjntt  
       2022-12-01 11:37:08 +08:00
    这样做让 nginx 完成的事情就很多了,比如用户鉴权。或者你可以想想在应用服务端上做这个缓存处理。代理就只做代理工作就好。
    lambdaq
        19
    lambdaq  
       2022-12-01 12:01:08 +08:00
    @lambdaq 估计有可能是 minecraft 一类的游戏服。。。
    xyjincan
        20
    xyjincan  
       2022-12-01 12:05:49 +08:00
    这是服务器下载地图,发送地图都使用公网带宽是吗,开发一个地图客户端代理服务,地图数据缓存在硬盘上吧
    villivateur
        21
    villivateur  
       2022-12-01 13:01:07 +08:00
    TCP 本身不能缓存,你得要一个更高层次的应用层服务。
    然后,既然用到应用层服务了,为什么不直接用 HTTP ?
    julyclyde
        22
    julyclyde  
       2022-12-01 13:26:52 +08:00
    @ttvast 自己山寨个协议,是很多公司内卷的选择
    要不然咋评职称啊
    DefoliationM
        23
    DefoliationM  
       2022-12-01 13:50:19 +08:00
    自己写个 tcp 转发吧,不复杂
    xuanbg
        24
    xuanbg  
       2022-12-01 14:17:35 +08:00
    TCP 要什么代理?哦哦,你是想在代理服务器上缓存数据来加速访问。可 TCP 是流,这可怎么缓存?再说,没有应用层协议分包,你这边怎么知道数据读完了?这不就那个啥,“粘包”了吗?

    你这必须要自己造个协议来传数据才行。既然是自己造的协议,那就要在 nginx 上加载一个识别这个协议的模块。既然模块都加载了,你想干点什么还不是你自己说了算?
    liuguangxuan
        25
    liuguangxuan  
    OP
       2022-12-01 14:23:30 +08:00
    @cnoder
    @victorc
    @killva4624 看到了 3 位老哥都推荐了 openrestry ,刚查了一下资料,openrestry 是 nginx+lua ,我用 lua 控制我想要实现的逻辑,是这样的吗?
    liuguangxuan
        26
    liuguangxuan  
    OP
       2022-12-01 14:28:14 +08:00
    @xuanbg 老哥,原有的地图服务是自定义协议和客户端进行传输的,传输 tcp 数据流,所以造协议这块儿已经有了,缺的是您说的这个模块,如何加在 nginx 上?
    liuguangxuan
        27
    liuguangxuan  
    OP
       2022-12-01 14:34:25 +08:00
    @DefoliationM tcp 转发之前写过一套,现在想把它和 nginx 统一到一起,这样就能统一 http 和 tcp 的入口。所以才有了我上面的提问。
    seakingii
        28
    seakingii  
       2022-12-01 14:41:25 +08:00
    NGINX+LUA,NGINX 是 C 代码,不好改,LUA 是脚本,容易改一点,
    不过, TCP 缓存不好做啊,像上面说的,TCP 是流,不好定边界

    另外你们自己实现的协议 ,那你至少要在 LUA 这边解析你们的请求流,根据请求流返回原始数据或者返回缓存数据?

    最简单的就是 NGINX+LUA 来缓存 HTTP,很好解析请求也很好缓存
    liuguangxuan
        29
    liuguangxuan  
    OP
       2022-12-01 14:47:45 +08:00
    @seakingii 是的,我目前了解到的也是这种,其中 lua 控制业务逻辑,来实现是从内存中取缓存数据推送给客户端,还是重新建立 socket 连接至地图服务器,进行数据的拉取。
    seakingii
        30
    seakingii  
       2022-12-01 15:54:16 +08:00
    @liuguangxuan 如果只是这样的转发,是否自己用代码做个端口转发,用 C++或者 GO 这样的编译语言来实现,性能更高?而不是利用 NGINX+LUA 这样的组合
    liuguangxuan
        31
    liuguangxuan  
    OP
       2022-12-01 16:10:29 +08:00
    @seakingii 可能是我没有表达清楚哈,老哥。我们之前写了一个转发 tcp 的服务( C++的),中间缓存了地图的数据,所以对下面 1000 个客户端的话,它只向地图服务器请求一次数据即可,然后对下转发给 1000 个客户端。
    现在我们又新增了一些 web 的服务,所以会有 http 请求。
    我想把 web 的 http 请求和地图的 tcp 请求统一入口,都走 nginx ,或者都走我们之前写的 tcp 转发服务。所以就有了我今天问的问题,我想在 nginx 里面集成我们之前的 tcp 转发服务的功能(并且能对地图服务只保持 1 个 socket 连接,请求 1 次地图数据,节省带宽),但是不知道该怎么集成进去。😂
    liuguangxuan
        32
    liuguangxuan  
    OP
       2022-12-01 16:11:47 +08:00
    @seakingii 看看各位老哥,有没有什么更好的方案。
    Twan
        33
    Twan  
       2022-12-01 17:02:18 +08:00
    怎么感觉是个开发游戏的
    ysc3839
        34
    ysc3839  
       2022-12-01 17:05:19 +08:00
    游戏资源下载用 http 没什么问题吧?
    xhinliang
        35
    xhinliang  
       2022-12-01 17:09:17 +08:00
    TCP 是传输层协议,是一个流协议,没听说过在传输层协议做缓存的....
    再者,这个「把一份地图数据在网络中走了两遍」,如果是内网的话,是不是开销可以忽略不计?
    JingKeWu
        36
    JingKeWu  
       2022-12-01 17:21:55 +08:00
    tcp 是流,没办法缓存。建议还是采用 http ,用 p2p 分发
    JingKeWu
        37
    JingKeWu  
       2022-12-01 17:22:30 +08:00
    或者自定义协议 加入缓存
    seakingii
        38
    seakingii  
       2022-12-01 17:36:42 +08:00
    @liuguangxuan 我的建议是

    一 要么不要整合,继续分成两个服务

    二 要么地图请求改成 HTTP ,和其它 WEB 服务统一,然后 NGINX 上 HTTP/2
    rrfeng
        39
    rrfeng  
       2022-12-01 17:51:06 +08:00
    不用 HTTP ,那就在 Nginx 里把你 C++ 实现的『私有协议』搞一遍就行了。

    Nginx Stream 就是纯 TCP 代理,做不到你想要的。
    wangritian
        40
    wangritian  
       2022-12-01 18:16:51 +08:00
    也可以考虑用 go 自己写一个网关代替 nginx ,想搞啥都方便
    liuguangxuan
        41
    liuguangxuan  
    OP
       2022-12-01 19:25:25 +08:00
    @Twan #33 是的,老哥,类似于游戏。
    @ysc3839 #34 老哥,业务场景对实时性要求较高,可以想象成打 CS 。
    liuguangxuan
        42
    liuguangxuan  
    OP
       2022-12-01 19:26:00 +08:00
    @seakingii #38 多谢老哥耐心解答。
    liuguangxuan
        43
    liuguangxuan  
    OP
       2022-12-01 19:32:54 +08:00
    @rrfeng #39 老哥,你提到的“那就在 Nginx 里把你 C++ 实现的『私有协议』搞一遍就行了”,具体怎么实现呢?
    我目前了解到的信息有:
    方式 1:直接改造 nginx 代码,但是感觉这种难度比较大,最起码需要看懂 stream 模块的代码及变量。
    方式 2:nginx 好像支持扩展模块,用 C 语言,新增自定义的 nginx 模块,但是目前还不清楚自己增加的 nginx 模块如何截获 stream 模块的流量数据,然后加上自己的私有协议。
    方式 3:使用 OpenResty ,用 lua 模块重写,但是问题和方式 2 一样。

    烦请老哥有空的时候帮忙解答一下疑惑。
    liuguangxuan
        44
    liuguangxuan  
    OP
       2022-12-01 19:38:28 +08:00
    @wangritian #40 go 语言不熟啊老哥。
    @xhinliang #35 不能忽略不计,老哥。业务场景类似于游戏,而且地图数据特别大,如果按 1G 来计算的话,1000 个客户端同时开一局,那么这种局域网的流量也就是 1000G 。
    ysc3839
        45
    ysc3839  
       2022-12-01 19:53:29 +08:00 via Android
    @liuguangxuan 实时性要求高和使用 http 传输资源冲突吗? CS 都支持 http 下载地图呀。
    rrfeng
        46
    rrfeng  
       2022-12-01 20:03:57 +08:00
    @liuguangxuan
    这得靠你自己学了
    garyox64
        47
    garyox64  
       2022-12-01 20:10:32 +08:00
    我理解用啥改,你都得学 nginx 的模块和架构设计了
    liuguangxuan
        48
    liuguangxuan  
    OP
       2022-12-01 20:27:51 +08:00
    @rrfeng #46
    @garyox64 #47 感谢老哥们。
    wangritian
        49
    wangritian  
       2022-12-01 21:29:15 +08:00
    @liuguangxuan go 语法非常精简,容易学习,其中的精髓 goroutine 协程开发容易,并发性能也强,非常适合做网络中间件,推荐你趁机实践一下
    liuguangxuan
        50
    liuguangxuan  
    OP
       2022-12-01 21:31:14 +08:00
    @wangritian #49 多谢老哥。
    maichaide
        51
    maichaide  
       2022-12-01 21:37:09 +08:00
    不能用 squid 缓存么?
    ouyangjun
        52
    ouyangjun  
       2022-12-01 21:49:22 +08:00
    Openresty 本身的 stream 模块支持上下游连接,然后你可以在 lua 里面保存数据,对于你的需求来说应该是可以完成的,有兴趣的话我们可以聊聊细节的。
    des
        53
    des  
       2022-12-01 22:10:00 +08:00 via iPhone
    想想办法能不能改造客户端?
    地图用 http 也能瓦片加载啊,你这种自定义协议就不建议在 nginx 上改了
    gstqc
        54
    gstqc  
       2022-12-01 22:27:42 +08:00 via Android
    HTTP 协议把地图通过 CDN 分发不更快?
    HTTP2 就是个二进制协议,有啥效率问题啊
    liuguangxuan
        55
    liuguangxuan  
    OP
       2022-12-01 22:41:17 +08:00
    @maichaide #51 老哥,这个 squid 技术是针对 tcp 的吗?
    liuguangxuan
        56
    liuguangxuan  
    OP
       2022-12-01 22:41:32 +08:00
    @ouyangjun #52 老哥,怎么联系?
    liuguangxuan
        57
    liuguangxuan  
    OP
       2022-12-01 22:44:12 +08:00
    @des #53 感谢老哥,改造客户端是个办法。但是目前所有的后台服务还是以 tcp 为主,不仅仅是这一个地图服务器,目前后台服务加进来几个 web 服务,所以想统一一下所有后台服务的入口。
    liuguangxuan
        58
    liuguangxuan  
    OP
       2022-12-01 22:45:39 +08:00
    @gstqc #54 感谢老哥,目前因为历史原因,后台已有的服务还是以 tcp 为主,不仅仅是这一个地图服务器,如果改造成 http 或者 http2 的话,代价可能比改造 nginx 还大。
    des
        59
    des  
       2022-12-01 22:50:49 +08:00 via iPhone
    @liuguangxuan 如果说地图不是经常变动的话,长期来讲还是建议改客户端走 cdn
    des
        60
    des  
       2022-12-01 22:51:59 +08:00 via iPhone
    客户端一旦多了之后,你这系统就没法部署
    Weixiao0725
        61
    Weixiao0725  
       2022-12-02 00:42:32 +08:00
    我觉得,这问题真的是匪夷所思。你应该缓存的是所有参数相同的请求,跟客户没有关系。如果有第 1001 个客户请求用相同的参数请求数据,就不走缓存了? 第二点是,专业的人干专业的事,nginx 被设计出来就不是干缓存的,要不然要 memcached, redis 是干啥的?
    PolarBears
        62
    PolarBears  
       2022-12-02 07:47:11 +08:00
    没必要执着在 nginx 里做 tcp 的协议识别和缓存吧,不如直接用别的高级语言实现一个 tcp 转发+缓存功能来的更快,
    反正都是在一台服务器上跑的。
    liuguangxuan
        63
    liuguangxuan  
    OP
       2022-12-02 08:38:21 +08:00
    @des 老哥,非公网,内部网络,没有 cdn 。😂

    @Weixiao0725 已经有了一个类似 redis 的缓存服务了,我的疑问点是 nginx 到缓存服务( redis ),这一段,地图数据的流量太大了,如果每连上一个客户端,就要 nginx 向 redis 请求一次的话,网络压力很大。又因为地图数据对每个客户端来说都是一样的,怎么样能把 nginx 到 redis 这一段给只请求一次,然后 nginx 就可以转发给下面所有的客户端。


    @PolarBears 老哥,您提到的这个“不如直接用别的高级语言实现一个 tcp 转发+缓存功能来的更快”,我们已经自己写了这样的一个后台服务,类似于网关,现在呢,又多了一些 B/S 的后台服务,所以有了 http 的流量,我们想把 http 的流量和 tcp 的流量统一入口。所以才有了我的提问。
    julyclyde
        64
    julyclyde  
       2022-12-02 09:16:41 +08:00
    把控制流放在自定义协议里
    资源用 http 另一个通道来传输就行了呗
    要善用现有的基础设施
    不要重复发明问题
    gstqc
        65
    gstqc  
       2022-12-02 09:25:31 +08:00 via Android
    散了吧,我总结出 OP 的问题其实是
    我的历史包袱很重,我的架构不能动,你们看有什么东西能帮我实现,明天上线!
    ysjdx
        66
    ysjdx  
       2022-12-02 09:33:45 +08:00
    @liuguangxuan nginx+lua 用共享内存做一次缓存不就行了?
    Ansen
        67
    Ansen  
       2022-12-02 09:42:54 +08:00
    统一入口后,是否可以区分一下流量?比如:tcp==>缓存服务,http ==>缓存服务转发==>后台服务
    fengfisher3
        68
    fengfisher3  
       2022-12-02 09:46:38 +08:00
    还没这方面的经验,希望楼主有合理的方案后,帮不上忙。整理分享一下,谢谢。
    lostsquirrelX
        69
    lostsquirrelX  
       2022-12-02 09:57:19 +08:00
    有没有可能这个 X-Y Problem
    duckyrain
        70
    duckyrain  
       2022-12-02 10:16:30 +08:00
    你们是不是该考虑使用 UDP 协议了
    angryfish
        71
    angryfish  
       2022-12-02 10:38:35 +08:00
    你纠结的 nginx 到地图服务器之间的流量。
    你原来就有一个 tcp 缓存代理。
    但你又想用 nginx 统一 http 和这个 tcp 。
    那你不如把原来的 tcp 缓存代理和 nginx 部署在同一个服务器。nginx 在代理去旧的 tcp 缓存代理。可解决内部网络流量大,http 和 tcp 统一整合。
    kiddingU
        72
    kiddingU  
       2022-12-02 15:26:51 +08:00
    丢共享内存可以,丢外部 cache 也行,比如 redis, lua redis 连接池,开销也不大
    PolarBears
        73
    PolarBears  
       2022-12-02 16:55:59 +08:00
    @liuguangxuan #63 统一一个入口的意思是想要端口复用吗?如果是想端口复用的话 haproxy 能做到。
    ouyangjun
        74
    ouyangjun  
       2022-12-04 01:31:43 +08:00
    @liuguangxuan 给我你的微信或者 tg 帐号?
    liuguangxuan
        75
    liuguangxuan  
    OP
       2022-12-04 12:25:03 +08:00
    @ouyangjun #74 绿色:Z3Vhbmd4dWFubGl1
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5526 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 06:05 · PVG 14:05 · LAX 23:05 · JFK 02:05
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.