引言

在前面的文章中,我们深入学习了TCP和UDP两种传输层协议的工作原理、优缺点和适用场景。今天是传输层协议篇的最后一篇,我们来系统总结:如何在实际项目中选择TCP还是UDP?

核心问题

  • ✅ 什么时候必须用TCP?什么时候必须用UDP?
  • ✅ 微服务场景如何选择协议?
  • ✅ 主流RPC框架(Dubbo、gRPC)为什么选择TCP?
  • ✅ 如何权衡可靠性与性能?

今天我们来理解:

  • ✅ TCP vs UDP的决策树
  • ✅ 微服务通信的协议选择
  • ✅ RPC框架的协议策略
  • ✅ 性能调优的权衡之道

TCP vs UDP决策树

决策流程图

开始选择协议
    |
    ↓
需要可靠传输?
    |
    ├─ 是 ────────────────────────┐
    |                              |
    ↓                              ↓
需要顺序保证?              需要建立连接?
    |                              |
    ├─ 是 ─────┐                   ├─ 是 ─────┐
    |          |                   |           |
    ↓          ↓                   ↓           ↓
需要流量控制?  → TCP         需要拥塞控制? → TCP
    |                              |
    ├─ 否 ─────┘                   ├─ 否 ─────┘
    |                              |
    ↓                              ↓
实时性优先?               支持广播/多播?
    |                              |
    ├─ 是 ─────┐                   ├─ 是 ─────┐
    |          |                   |           |
    ↓          ↓                   ↓           ↓
能容忍丢包?  → UDP         简单请求响应? → UDP
    |                              |
    ├─ 否 ─────┘                   └─ 否 ─────┘
    |                              |
    └──────────────────────────────┘
                |
                ↓
            使用TCP

快速决策表

需求协议典型应用
数据完整性最重要TCP文件传输、数据库同步、支付交易
实时性最重要UDP视频直播、在线游戏、VoIP
需要顺序保证TCPHTTP/HTTPS、邮件传输
广播/多播UDP设备发现、IPTV组播
简单请求-响应UDPDNS查询、SNMP监控
长连接TCPWebSocket、数据库连接池
低延迟UDP高频交易、实时监控

微服务场景的协议选择

场景1:RESTful API(HTTP/HTTPS)

协议:TCP

原因

HTTP基于TCP:
- ✅ 需要可靠传输(状态码、响应体不能丢失)
- ✅ 需要顺序保证(请求头、请求体必须按序到达)
- ✅ 支持长连接(HTTP/1.1 Keep-Alive)

示例:Spring Cloud微服务

@RestController
public class UserController {
    @Autowired
    private RestTemplate restTemplate;  // 基于TCP的HTTP客户端

    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        // 调用订单服务(HTTP over TCP)
        Order order = restTemplate.getForObject(
            "http://order-service/api/orders?userId=" + id,
            Order.class
        );
        // ...
    }
}

性能优化

# Spring Boot配置
server:
  tomcat:
    max-connections: 10000  # TCP最大连接数
    accept-count: 200       # TCP全连接队列大小

# Feign配置
feign:
  httpclient:
    enabled: true  # 启用连接池
    max-connections: 200
    max-connections-per-route: 50

场景2:gRPC

协议:TCP(基于HTTP/2)

原因

gRPC为什么选TCP?
- ✅ RPC调用必须可靠(调用结果不能丢失)
- ✅ 支持双向流(需要TCP的全双工通信)
- ✅ HTTP/2多路复用(一个TCP连接,多个请求并行)
- ✅ 流量控制(避免客户端或服务器过载)

示例:gRPC服务

// gRPC服务定义
service UserService {
  rpc GetUser(GetUserRequest) returns (User);          // 一元RPC
  rpc ListUsers(Empty) returns (stream User);          // 服务器流
  rpc CreateUsers(stream User) returns (Summary);      // 客户端流
  rpc ChatUsers(stream Message) returns (stream Message);  // 双向流
}

// gRPC底层使用TCP + HTTP/2
// 一个TCP连接,支持多个并发请求(多路复用)

性能优化

// gRPC连接池配置
ManagedChannel channel = ManagedChannelBuilder
    .forAddress("user-service", 9090)
    .usePlaintext()
    .maxInboundMessageSize(10 * 1024 * 1024)  // 最大接收10MB
    .keepAliveTime(30, TimeUnit.SECONDS)       // Keep-Alive 30秒
    .build();

场景3:Dubbo RPC

协议:TCP(默认),也支持HTTP、gRPC

原因

Dubbo为什么默认选TCP?
- ✅ RPC调用需要可靠传输
- ✅ 自定义协议(dubbo://),效率高于HTTP
- ✅ 长连接复用(避免频繁握手)
- ✅ 序列化灵活(Hessian、Protobuf、JSON等)

示例:Dubbo服务

// Dubbo服务提供者
@Service(protocol = "dubbo")  // 使用TCP协议
public class UserServiceImpl implements UserService {
    @Override
    public User getUser(Long id) {
        // ...
    }
}

// Dubbo消费者
@Reference  // 远程调用,底层使用TCP长连接
private UserService userService;

public void test() {
    User user = userService.getUser(123L);
}

协议配置

<!-- Dubbo协议配置 -->
<dubbo:protocol name="dubbo" port="20880"
                threads="200"           # 线程池大小
                connections="10"        # 每个服务的TCP连接数
                payload="8388608" />    # 最大消息8MB

协议对比

Dubbo协议特点适用场景
dubboTCP长连接,二进制序列化,高性能内网微服务(推荐)
rmiJava标准RMI协议,短连接Java互操作
hessianHTTP短连接,跨语言跨语言调用
httpHTTP短连接,JSON序列化前后端分离
grpcHTTP/2,Protobuf序列化云原生微服务

场景4:服务注册与心跳

协议:UDP(心跳) + TCP(注册)

原因

为什么心跳用UDP?
- ✅ 心跳数据频繁发送(每5-10秒一次)
- ✅ 心跳丢失可以容忍(下次心跳会更新)
- ✅ UDP开销小,降低服务器压力

为什么注册用TCP?
- ✅ 注册信息必须可靠传输
- ✅ 注册是一次性操作,不频繁

示例:Eureka服务注册

// Eureka客户端配置
eureka:
  client:
    register-with-eureka: true      # TCP注册
    fetch-registry: true            # TCP获取注册表
  instance:
    lease-renewal-interval-in-seconds: 30  # UDP心跳间隔30秒

Consul服务注册

# Consul支持TCP和UDP心跳
consul:
  host: localhost
  port: 8500
  discovery:
    heartbeat:
      enabled: true
      use-scheduler-health-indicator: false

场景5:日志采集

协议:UDP(推荐) + TCP(可选)

原因

为什么日志采集用UDP?
- ✅ 日志量大,UDP开销小
- ✅ 丢失少量日志可以容忍
- ✅ 不影响业务性能

什么时候用TCP?
- 审计日志(必须可靠)
- 金融交易日志(必须完整)

示例:Fluentd日志采集

# Fluentd配置(支持UDP和TCP)
<source>
  @type forward
  port 24224
  bind 0.0.0.0
  transport tcp    # 可靠传输:tcp,高性能:udp
</source>

# 应用程序发送日志
logger.info("User {} logged in", userId);
# 通过UDP发送到Fluentd

场景6:监控指标上报

协议:UDP(推荐)

原因

为什么监控用UDP?
- ✅ 指标数据频繁上报(每秒几百次)
- ✅ 丢失少量数据点不影响趋势分析
- ✅ 不能因为监控拖慢业务

示例:Prometheus Pushgateway

// Micrometer上报指标(UDP)
@Autowired
private MeterRegistry meterRegistry;

public void recordMetric() {
    Counter counter = meterRegistry.counter("api.requests", "endpoint", "/users");
    counter.increment();

    // 定期通过UDP发送到监控系统
}

RPC框架的协议策略

Dubbo协议选择

Dubbo支持的协议

协议传输层序列化连接性能适用场景
dubboTCPHessian2长连接⭐⭐⭐⭐⭐内网微服务(推荐)
rmiTCPJava序列化短连接⭐⭐Java互操作
hessianHTTP/TCPHessian短连接⭐⭐⭐跨语言
httpHTTP/TCPJSON短连接⭐⭐前后端分离
webserviceHTTP/TCPXML短连接跨平台
grpcHTTP/2Protobuf长连接⭐⭐⭐⭐⭐云原生

配置示例

<!-- 高性能内网调用 -->
<dubbo:protocol name="dubbo" port="20880" />

<!-- 跨语言调用 -->
<dubbo:protocol name="grpc" port="50051" />

<!-- 兼容老系统 -->
<dubbo:protocol name="hessian" port="8080" server="jetty" />

gRPC协议策略

gRPC为什么选TCP + HTTP/2?

HTTP/2的优势:
1. ✅ 多路复用:一个TCP连接,多个RPC请求并行
2. ✅ 二进制分帧:比HTTP/1.1的文本协议效率高
3. ✅ 头部压缩(HPACK):减少开销
4. ✅ 服务器推送:服务器可以主动推送数据
5. ✅ 流量控制:避免接收方过载

HTTP/1.1 vs HTTP/2性能对比

场景:1个TCP连接,发送100个RPC请求

HTTP/1.1:
- 串行处理:请求1 → 响应1 → 请求2 → 响应2 → ...
- 总时间:100 × RTT = 100 × 50ms = 5秒

HTTP/2(gRPC):
- 并行处理:100个请求同时发送
- 总时间:1 × RTT = 50ms

性能提升100倍!

gRPC连接管理

// gRPC客户端连接池
List<ManagedChannel> channels = new ArrayList<>();
for (int i = 0; i < 10; i++) {  // 创建10个连接
    ManagedChannel channel = ManagedChannelBuilder
        .forAddress("user-service", 9090)
        .usePlaintext()
        .build();
    channels.add(channel);
}

// 轮询使用连接(简单负载均衡)
int index = requestCount++ % channels.size();
UserServiceGrpc.UserServiceBlockingStub stub = UserServiceGrpc
    .newBlockingStub(channels.get(index));
User user = stub.getUser(request);

Thrift协议策略

Thrift支持多种传输协议

传输协议描述性能适用场景
TSocket阻塞式TCP⭐⭐⭐简单RPC
TFramedTransport非阻塞式TCP⭐⭐⭐⭐高并发
THttpTransportHTTP⭐⭐跨防火墙
TMemoryTransport内存⭐⭐⭐⭐⭐测试

配置示例

// Thrift服务器(使用TCP)
TServerSocket serverTransport = new TServerSocket(9090);
UserService.Processor<UserServiceImpl> processor =
    new UserService.Processor<>(new UserServiceImpl());

TThreadPoolServer server = new TThreadPoolServer(
    new TThreadPoolServer.Args(serverTransport)
        .processor(processor)
);

server.serve();

性能调优的权衡之道

权衡1:可靠性 vs 性能

场景:订单支付

需求

  • 必须可靠(不能丢失支付结果)
  • 性能要求高(100ms内响应)

方案对比

方案1:TCP + 短连接

优点:简单,可靠
缺点:每次都要三次握手(+75ms延迟)
性能:中等

方案2:TCP + 长连接池

优点:可靠,复用连接,延迟低
缺点:需要维护连接池
性能:高

方案3:UDP + 应用层重传

优点:延迟最低
缺点:需要自己实现可靠性,复杂度高
性能:极高

推荐方案TCP + 长连接池

// HttpClient连接池配置
PoolingHttpClientConnectionManager connectionManager =
    new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(200);  // 最大连接数
connectionManager.setDefaultMaxPerRoute(20);  // 每个路由最大连接数

HttpClient httpClient = HttpClients.custom()
    .setConnectionManager(connectionManager)
    .setKeepAliveStrategy((response, context) -> 30000)  // 30秒
    .build();

权衡2:带宽 vs 延迟

场景:视频直播

需求

  • 延迟优先(实时性)
  • 可以容忍少量丢包

方案对比

方案1:TCP

优点:可靠,画质完美
缺点:丢包重传导致延迟累积(5-10秒延迟)
用户体验:差(卡顿)

方案2:UDP

优点:实时性好(< 1秒延迟)
缺点:偶尔花屏
用户体验:好(流畅)

推荐方案UDP + FEC(前向纠错)

FEC原理:
- 发送10个数据包 + 2个冗余包
- 丢失任意2个包,可以通过冗余包恢复
- 不需要重传,保持低延迟

实现:
- WebRTC(内置FEC)
- SRT协议(Secure Reliable Transport)

权衡3:连接数 vs 延迟

场景:微服务网格(100个服务)

需求

  • 服务间频繁调用
  • 每个服务都要调用其他服务

方案对比

方案1:短连接(每次调用都建立连接)

连接数:0(连接用完即关闭)
延迟:高(每次+75ms三次握手)
服务器压力:中等(频繁握手)

方案2:长连接(每对服务之间保持连接)

连接数:100 × 100 = 10,000个连接(过多!)
延迟:低(复用连接)
服务器压力:高(维护大量连接)

方案3:连接池 + 多路复用(HTTP/2 / gRPC)

连接数:100 × 10 = 1,000个连接(每对服务10个连接)
延迟:低(复用连接)
服务器压力:中等(连接数适中)
多路复用:一个连接,多个请求并行

推荐方案gRPC + 连接池

# gRPC客户端配置
grpc:
  client:
    user-service:
      address: user-service:9090
      pool-size: 10  # 每个服务10个连接
      max-inbound-message-size: 10MB

决策清单:选择TCP还是UDP?

必须用TCP的场景

场景原因
文件传输不能有任何损坏或丢失
数据库操作SQL命令必须完整执行
支付交易交易结果必须可靠传递
邮件传输邮件内容不能丢失
RESTful APIHTTP基于TCP
RPC调用调用结果必须可靠
WebSocket需要持久连接和可靠传输
需要顺序保证如消息队列、数据同步

可以用UDP的场景

场景原因
视频直播实时性优先,可容忍丢帧
在线游戏低延迟优先,可容忍位置跳跃
VoIP通话实时性优先,可容忍噪音
DNS查询简单请求-响应,丢失可重查
SNMP监控数据频繁更新,丢失可容忍
广播/多播如设备发现、IPTV
传感器数据数据频繁上报,丢失可容忍
日志采集大量数据,丢失少量可容忍

需要权衡的场景

场景TCP方案UDP方案推荐
服务心跳可靠但开销大高效但可能丢失UDP
监控上报完整但影响性能高效但可能丢失UDP
日志采集完整但吞吐量低高吞吐但可能丢失UDP
实时推送可靠但延迟高低延迟但可能丢失看需求
文件分发(CDN)可靠但慢快速但需要应用层重传UDP+QUIC

总结

核心要点

  1. TCP的使用场景

    • 数据完整性最重要
    • 需要顺序保证
    • RPC调用、HTTP、数据库、文件传输
  2. UDP的使用场景

    • 实时性最重要
    • 可以容忍丢包
    • 广播/多播
    • 视频直播、在线游戏、DNS、监控
  3. 微服务协议选择

    • RESTful API:TCP(HTTP/1.1)
    • gRPC:TCP(HTTP/2)
    • Dubbo:TCP(dubbo协议)
    • 服务心跳:UDP
    • 日志采集:UDP
  4. 性能调优权衡

    • 可靠性 vs 性能:TCP + 连接池
    • 带宽 vs 延迟:UDP + FEC
    • 连接数 vs 延迟:HTTP/2多路复用

第二阶段完成!

恭喜!至此我们已经完成了计算机网络从入门到精通专题的第二阶段《传输层协议篇》全部8篇文章:

  1. ✅ 07-传输层的核心使命
  2. ✅ 08-TCP三次握手详解
  3. ✅ 09-TCP四次挥手详解
  4. ✅ 10-TCP流量控制机制
  5. ✅ 11-TCP拥塞控制机制
  6. ✅ 12-TCP重传机制
  7. ✅ 13-UDP协议原理
  8. ✅ 14-TCP与UDP的选择策略

下一阶段预告

第三阶段《应用层协议篇》将学习:

  • HTTP协议基础与进阶
  • HTTPS与TLS/SSL
  • HTTP/2与HTTP/3
  • DNS域名解析
  • WebSocket与gRPC

思考题

  1. 为什么大部分RPC框架选择TCP而不是UDP?
  2. 在什么场景下,UDP的性能优势最明显?
  3. 如果要在UDP之上实现类似TCP的可靠性,需要实现哪些机制?
  4. 微服务场景下,如何平衡TCP长连接的数量和性能?

感谢学习!期待第三阶段见!