V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
bthulu
V2EX  ›  程序员

nginx 是怎么实现 tps>2000 的, 理论上这不可能呀

  •  
  •   bthulu · 20 天前 · 2260 次点击

    短链接有 time_wait 时间, 最少 30 秒.

    可用端口最多 65535.

    那理论上短链接最大 TPS 只有 65535/30=2184, nginx 是用了啥魔法突破了 TCP 协议限制吗?

    25 条回复    2024-05-28 17:40:49 +08:00
    codehz
        1
    codehz  
       20 天前
    首先这是服务端,端口是固定的,除非你把 benchmark 也跑在同一个网卡上(如 localhost )
    其次可以 http keep-alive
    再三 benchmark 程序可以使用端口复用
    phrack
        2
    phrack  
       20 天前 via iPhone
    什么菜鸡
    InkStone
        3
    InkStone  
       20 天前
    TCP/UDP 连接是否重复是四元组(dstPort,dstIp, srcPort,srcIp)决定的,而不是你理解的单个 port
    ipwx
        4
    ipwx  
       20 天前
    同一个 (server, serverPort) 可以被很多不同的 (client, clientPort) 同时连接。
    deplives
        5
    deplives  
       20 天前
    你这计网全还给老师了还是根本没学
    flyqie
        6
    flyqie  
       20 天前
    四元组。。。

    你这知识都忘了?
    bthulu
        7
    bthulu  
    OP
       20 天前
    @InkStone 这跟四元组有什么关系? nginx 压测, 后端起一个服务器, nginx 代理请求到这个服务器, 测试端全部是短链接请求, dstPort,dstIp, srcIp 都是固定的, 只有 srcPort 是变化的.
    mxT52CRuqR6o5
        8
    mxT52CRuqR6o5  
       20 天前
    nginx 不止有反向代理功能,也能直接 serve 本地静态文件,不作反向代理服务器时 nginx 就不需要作为客户端去访问某个服务器了
    chenyu0x00
        9
    chenyu0x00  
       20 天前
    @bthulu 比如 nginx 监听 443 端口,IP 是 1.1.1.1 ,一个 client 的 IP 是 2.2.2.2 ,那么这个 client 理论上可以向 nginx 建立 65535 个连接,每个连接的四元组在 client 上看是(src ip = 2.2.2.2, src port=1 到 65535 ,dst ip=1.1.1.1, dst port = 443),在 nginx 上看的话 src 和 dst 会交换。但是如果有另一个 client 的 IP 是 3.3.3.3 ,那么又可以和 nginx 建立 65535 个连接,这样 nginx 就可以同时服务 65535*2 个连接,这些连接的四元组是不同的,所以不会出问题。
    bthulu
        10
    bthulu  
    OP
       20 天前
    @codehz nginx 不是服务端呀, nginx 是做反向代理, 是客户端. benchmark 程序可以使用端口复用, nginx 应该是不会端口复用的吧?
    也就是说, 就是 keep-alive 确保了可以突破 2000?
    bthulu
        11
    bthulu  
    OP
       20 天前
    @chenyu0x00 但是 nginx 反代呢, 他跟后端服务器之间, nginx 的 IP 是固定的, 后端服务器的 IP 和端口也是固定的, 这个时候只有 nginx 的端口是动态的了. 可不可以认为这种情况下, nginx 的 TPC 不可能>2000?
    pengjay
        12
    pengjay  
       20 天前
    一台客户端对一台服务器理论是这样的。端口用完没释放的话就建立不了链接了
    chenyu0x00
        13
    chenyu0x00  
       20 天前
    @bthulu 另外同一个 TCP 连接在完成一个请求之后可以不断开继续完成下一个请求(也叫长连接),如果单个请求很简单(比如发送 index.html)的话,是可以在短时间内完成多次请求的。一般 http 压力测试的话都会启用长连接,因为长连接更考验 nginx 的性能,如果每次都新建 TCP 连接的话考验的是操作系统和 TCP 协议的性能
    chenyu0x00
        14
    chenyu0x00  
       20 天前
    @bthulu #11 nginx 反向代理也会启用长连接
    bthulu
        15
    bthulu  
    OP
       20 天前
    @chenyu0x00 我就说为啥我这突破不了 2000 了, 我还以为哪里配置有问题. 我这里是代理了 Modbus 主服务器, 走的是短链接 tcp 协议, 不是 http. Modbus 主服务器是施耐德的 PLC, 限制只能短链接. 这种情况下, 还有什么别的办法突破 2000 这个限制吗?
    ccnoobs
        16
    ccnoobs  
       20 天前
    同一台 client ( ip 固定 多线程跑) 和同一台 service 在同一时刻(一瞬间)下确实不会超过这个限制
    不过具体请求时无法保证同一时刻
    chenyu0x00
        17
    chenyu0x00  
       20 天前
    @bthulu #15 纯 tcp 协议我接触得不多,你可以试试在 server 上多监听几个端口,或者问问 ChatGPT 看能不能调整一些系统参数
    bthulu
        18
    bthulu  
    OP
       20 天前
    @InkStone
    @flyqie
    @chenyu0x00
    @pengjay
    @ccnoobs
    刚才实测, 同一个客户端, 先向服务端 1 发送 7 万次请求, 再向服务端 2 发送 7 万次请求.

    结果是向服务端 1 发送到 6 万多次时就报 SocketException: 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。

    再向服务端 2 发请求时, 一次请求都发不出去.

    实践证明在 windows 上, 端口是共用的, 若通过端口 9000 请求 dstIp1 并关闭后, 9000 端口进入 time_wait 状态, 此时往其他 dstIp 发送请求时也无法使用这个端口.

    服务端代码
    ```C#
    using System.Net;
    using System.Net.Sockets;

    int port = 9000;
    TcpListener server = new TcpListener(IPAddress.Any, port);
    server.Start();
    Console.WriteLine($"Server listening on :{port}");

    try
    {
    var writer = File.AppendText("c:/temp/a.txt");
    List<int> ports = [];
    while (true)
    {
    TcpClient client = server.AcceptTcpClient();
    IPEndPoint endPoint = (IPEndPoint)client.Client.RemoteEndPoint!;
    ports.Add(endPoint.Port);
    if (ports.Count > 10)
    {
    writer.WriteLine(string.Join("\r\n", ports));
    writer.Flush();
    ports.Clear();
    }
    }
    }
    catch (SocketException e)
    {
    Console.WriteLine($"SocketException: {e}");
    }
    finally
    {
    server.Stop();
    }
    ```

    客户端代码
    ```C#
    public class UnitTest1
    {
    [TestMethod]
    public async Task TestMethod1()
    {
    int k = 0;
    for (int j = 0; j < 70; j++)
    {
    List<Task> list = [];
    for (int i = 0; i < 1000; i++)
    {
    k++;
    Task task = OpenAndCloseTcp("10.98.20.129");
    list.Add(task);
    }

    await Task.WhenAll(list);
    Console.WriteLine(k);
    }
    }

    [TestMethod]
    public async Task TestMethod2()
    {
    int k = 0;
    for (int j = 0; j < 70; j++)
    {
    List<Task> list = [];
    for (int i = 0; i < 1000; i++)
    {
    k++;
    Task task = OpenAndCloseTcp("10.98.20.130");
    list.Add(task);
    }

    await Task.WhenAll(list);
    Console.WriteLine(k);
    }
    }

    private async Task OpenAndCloseTcp(string ip)
    {
    using var client = new TcpClient();
    await client.ConnectAsync(ip, 9000);
    await using NetworkStream stream = client.GetStream();
    byte[] bytes = "\r\n"u8.ToArray();
    await stream.WriteAsync(bytes);
    }
    }
    ```
    dhb233
        19
    dhb233  
       20 天前
    nginx 到后端不能连接复用?有大量连接的情况,只能扩充 IP 地址了,4 层 LB 是这么干的
    zhuisui
        20
    zhuisui  
       20 天前
    上面的人都没理解你的问题
    你的问题关键是连接进入了 time_wait 状态,此时连接还没有完全关闭,妨碍了新的连接建立。
    正常关闭的连接不会进入 time_wait 状态,而是直接 closed 然后消失。
    另外你也可以缩短连接在 time_wait 状态停留的时间,Windows 上怎么弄我不知道
    seedhk
        21
    seedhk  
       20 天前   ❤️ 1
    班门弄斧一下,有问题请指正:

    "可用端口最多 65535."

    1. 就像 1 楼 说的:
    站在服务器接收数据的角度,一般都是指定某个端口,比如 80 端口用于接收请求。

    可用端口数是站在客户端的角度来说的,因为客户端发起一个请求,除非绑定了端口,否则都是随机选择一个高位端口。


    "那理论上短链接最大 TPS 只有 65535/30=2184"

    2.TCP 通过四元组确定一个链接是否重用

    源 IP 地址 (srcIp)
    源端口号 (srcPort)
    目的 IP 地址 (dstIp)
    目的端口号 (dstPort)
    对于服务器来说,dstIp 和 dstPort 是固定的,而 srcIp 和 srcPort 是可变的。

    所以,在 IPV4 中,绝对理想的情况下(不考虑操作系统,内存大小,请求耗时,请求大小,time_wait 等)
    一个服务端能接受的最大请求数应该是:

    客户端 ip 数×客户端 port 数


    #10
    "nginx 不是服务端呀, nginx 是做反向代理, 是客户端"

    nginx 作为反向代理接收数据时,这时候他就已经是“服务端”了,绑定了某个端口(比如 80)
    seedhk
        22
    seedhk  
       20 天前
    接 #21

    如果 你是按照 #18 的写法的话,那么讨论的问题其实是,客户端最多能同时向外发送多少个请求,从这个角度来说,你说的确实没错。

    但是这和 nginx 有啥关系呢
    ysc3839
        23
    ysc3839  
       19 天前 via Android
    反向代理换成 Unix socket 就解决问题了
    ysc3839
        24
    ysc3839  
       19 天前 via Android
    @ysc3839 实在不能换,127.0.0.0/8 整个网段都能作为回环地址使用,换着用也能解决问题。
    paceewang1
        25
    paceewang1  
       19 天前
    你这个提问的方式有点问题,大部分兄弟都是没仔细看你的问题的😂,nginx 确实在某些反代的场景需要和后端建立 1 到 1 的多个单独长连接,比如说 ws ,tcp 等,这种情况下确实会出现端口耗尽的情况,解决的话可以在 nginx 上绑定虚拟 ip+端口。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3480 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 04:26 · PVG 12:26 · LAX 21:26 · JFK 00:26
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.