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 · 109 天前 · 4517 次点击
    这是一个创建于 109 天前的主题,其中的信息可能已经有所发展或是发生改变。

    背景:

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

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

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

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

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

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

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

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

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

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

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


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