You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何gRPC-Java的UDS性能不如DNS?性能测试问题求助

问题:UDS性能未优于本地TCP(DNS解析到localhost)的原因排查

我们开展了UDS与DNS(解析到localhost的TCP)的gRPC性能对比测试,按网络资料所述,UDS因无需多层网络协议应该更快,但实际测试发现UDS最多与TCP性能持平,多数情况下表现更差,以下为测试用例及结果。

环境

  • jdk: jdk-17.0.10
  • grpc: 1.61.1
  • 测试机器: centos 7.9、64核CPU、251 GB内存

代码细节

UDS服务端核心代码

String UDS_FILE="/tmp/grpc_uds.socket";
server = NettyServerBuilder.forAddress(new DomainSocketAddress(UDS_FILE))
                .bossEventLoopGroup(new EpollEventLoopGroup())
                .workerEventLoopGroup(new EpollEventLoopGroup())
                .channelType(EpollServerDomainSocketChannel.class)
                .maxInboundMessageSize(4*1024*1024)
                .addService(BrSpringApplication.getBean(GreeterServiceImple.class))
                .build().start();
blockUtilShutDown();

UDS客户端核心代码

String socketPath="/tmp/grpc_uds.socket";
channel = NettyChannelBuilder.forAddress(new DomainSocketAddress(socketPath))
                .eventLoopGroup(new EpollEventLoopGroup())
                .channelType(EpollDomainSocketChannel.class)
                .maxInboundMessageSize(4 * 1024 * 1024)
                .usePlaintext().build();
GreeterGrpc.GreeterBlockingStub blockStub = GreeterGrpc.newBlockingStub(channel);
HelloRequest request = HelloRequest.newBuilder().setName(paramJson.toJSONString()).build();
HelloReply response = getBlockStub().sayHello(request);

GreeterServiceImpl.java

@Override
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
    long startTime = System.currentTimeMillis();
    logger.warn("request:{}", request.getName());
    try {
        if (Context.current().isCancelled()) {
            logger.warn("deadline cancalled,deadline:{}", Context.current().getDeadline());
            responseObserver.onError(new Exception("Cancelled by client"));
            return;
        }
        JSONObject msgRequest= JSONObject.parseObject(request.getName());
        int num=msgRequest.getIntValue("factor");
        String msg=msgRequest.getString("message");
        StringBuilder messageBack = new StringBuilder("rep-");
        for (int i = 0; i < num; i++) {
            messageBack.append(msg).append("|");
        }
//        logger.warn("response:{}", messageBack);
        HelloReply response = HelloReply.newBuilder().setMessage(messageBack + ",ms:" + (System.currentTimeMillis() - startTime)).build();
        responseObserver.onNext(response);
        responseObserver.onCompleted();

    } catch (BrException e) {
        logger.error("BrException,code:{},message:{}", e.getCode(), e.getMessage());
        HelloReply response = HelloReply.newBuilder().setMessage(e.getMessage()).build();
        responseObserver.onNext(response);
        responseObserver.onCompleted();
    } catch (Exception ex) {
        logger.error("sys error", ex);
        HelloReply response=HelloReply.newBuilder().setMessage(ex.getMessage()).build();
        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

DNS(TCP)服务端核心代码

String port="80";
server = NettyServerBuilder.forPort(port)
                .maxInboundMessageSize(4*1024*1024)
                .addService(BrSpringApplication.getBean(GreeterServiceImple.class))
                .build().start();
blockUtilShutDown();

DNS(TCP)客户端核心代码

String ip="localhost:80"
channel = ManagedChannelBuilder.forTarget(ip).usePlaintext().build();
GreeterGrpc.GreeterBlockingStub blockStub = GreeterGrpc.newBlockingStub(channel);
HelloRequest request = HelloRequest.newBuilder().setName(paramJson.toJSONString()).build();
HelloReply response=getBlockStub().sayHello(request);

测试结果

客户端消息大小响应消息大小并发数总请求数类型测试次数总耗时(ms)QPS
12B60B505000000dns1127359ms5535.79
12B60B505000000dns2126051ms5593.23
12B60B505000000dns3128828ms5472.67
12B60B505000000uds1133212ms5292.56
12B60B505000000uds2128315ms5494.55
12B60B505000000uds3127177ms5543.71
12B120B505000000dns1125919ms5599.10
12B120B505000000dns2122034ms5777.35
12B120B505000000dns3120439ms5853.86
12B120B505000000uds1123538ms5707.01
12B120B505000000uds2126988ms5551.96
12B120B505000000uds3126450ms5575.58
12B6kb505000000dns1123176ms5723.78
12B6kb505000000dns2127242ms5540.88
12B6kb505000000dns3121556ms5800.07
12B6kb505000000uds1131939ms5343.63
12B6kb505000000uds2133399ms5285.14
12B6kb505000000uds3130091ms5419.53
5kb5kb505000000dns1133404ms5284.94
5kb5kb505000000dns2130532ms5401.23
5kb5kb505000000dns3130145ms5417.29
5kb5kb505000000uds1140809ms5007.01
5kb5kb505000000uds2140685ms5011.43
5kb5kb505000000uds3140992ms5000.52

注:原表格中"thead"列应为并发数,已修正。

原因分析

  1. 本地TCP的内核级优化
    当TCP连接指向localhost时,Linux内核会通过loopback接口处理,完全绕过物理网卡和网络协议栈的下层(如ARP、以太网帧),仅保留TCP/IP的核心逻辑,性能损耗极低。而UDS虽然省去了IP层封装,但内核对TCP的优化(如TCP小数据包合并、缓冲区调优、连接复用机制)经过数十年打磨,成熟度更高,在高并发场景下可能反超UDS。

  2. gRPC配置的不对称性
    你的UDS服务端和客户端显式创建了EpollEventLoopGroup,但未指定线程数,默认线程数为CPU核心数。而TCP版本使用的是gRPC默认的EventLoopGroup,其线程模型可能更适配本地TCP场景。此外,UDS的Netty通道实现(EpollDomainSocketChannel)在gRPC中的优化程度可能不如TCP通道,存在额外的上下文切换或数据拷贝开销。

  3. 业务逻辑开销掩盖传输层差异
    测试中的服务端包含JSON解析、字符串循环拼接等CPU密集型操作,这些开销占总请求耗时的比例较高,会稀释UDS和TCP在传输层的性能差异。如果将业务逻辑替换为无计算的echo服务,传输层的性能差距会更明显。

  4. UDS的文件系统无影响
    你将UDS路径设置到SSD后性能无变化,是因为/tmp目录默认挂载为tmpfs(内存文件系统),UDS socket的元数据和数据都在内存中,与磁盘介质无关,因此调整磁盘类型不会带来性能提升。

  5. 连接复用与初始化开销
    测试中使用的是阻塞Stub,若未启用连接复用(gRPC默认启用,但需确认配置),UDS的连接初始化开销可能略高于TCP。不过你的测试总请求数达500万,连接复用应该已经生效,这部分影响较小。

优化建议

  • 统一EventLoopGroup配置:为TCP版本也显式配置EpollEventLoopGroup,确保两者线程模型一致,排除配置差异影响。
  • 简化业务逻辑:使用纯echo服务重新测试,聚焦传输层性能差异。
  • 调整UDS缓冲区大小:通过Netty的option(ChannelOption.SO_SNDBUF, size)和option(ChannelOption.SO_RCVBUF, size)调整UDS的收发缓冲区,匹配TCP的默认配置。
  • 升级gRPC版本:新版本可能对UDS实现有性能优化,尝试升级到最新稳定版后再测试。

内容的提问来源于stack exchange,提问作者Chao Zhang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 09:49:51