4. 网络通信模型:TCP vs UDP、Reactor模型、Netty实战、序列化协议(Protobuf)
交易系统里,网络通信是命脉。你想想看,一笔订单从客户端发出,经过网关、交易引擎、清算服务,最后落地到数据库,这中间要经过多少次网络跳转?我见过不少系统,业务逻辑写得挺漂亮,结果一压测,网络先崩了。
今天咱们就聊聊网络通信模型。这部分内容,说白了就是解决两个问题:怎么传和传什么。
TCP vs UDP:选谁?
先看个最基础的选择题。TCP 和 UDP,到底用哪个?
我个人习惯,交易系统里首选 TCP。为什么?因为交易对可靠性要求极高。你一笔转账发出去,丢了怎么办?TCP 有确认重传机制,能保证数据不丢、不乱序。
但 UDP 也不是一无是处。我记得有个做行情推送的项目,数据量巨大,每秒几万笔行情。如果用 TCP,重传和拥塞控制反而成了瓶颈。这时候 UDP 就派上用场了——丢几个行情包无所谓,反正下一秒又有新的。
| 特性 | TCP | UDP |
|---|---|---|
| 可靠性 | 有确认重传 | 无确认,可能丢包 |
| 有序性 | 保证顺序 | 不保证顺序 |
| 性能 | 相对较低(有开销) | 高,延迟低 |
| 适用场景 | 交易指令、订单状态 | 行情推送、心跳检测 |
Reactor 模型:高并发的基石
选好了 TCP,下一个问题来了:怎么处理成千上万的并发连接?
传统的做法是「一个连接一个线程」。嗯,听起来简单,但连接数一上去,线程数暴涨,上下文切换能把 CPU 拖死。我见过一个系统,连接数到 5000 的时候,CPU 全花在线程切换上了,业务逻辑反而跑不动。
Reactor 模型就是来解决这个问题的。它的核心思想是:用少量线程处理大量连接。
说白了,就是用一个线程(或者几个线程)专门监听事件——谁有数据来了,谁可以写了,谁断开了。然后把这些事件分发给对应的处理器。
Reactor 模型有三种常见变体:
- 单 Reactor 单线程:适合 CPU 密集型场景,比如计算行情指标。但一个线程阻塞,整个系统就挂了。
- 单 Reactor 多线程:Reactor 线程只负责分发,业务逻辑交给线程池。这是最常用的模式。
- 主从 Reactor 多线程:一个主 Reactor 负责接受连接,多个从 Reactor 负责读写。Netty 就是用的这个。
关键点:Reactor 模型的核心是「事件驱动」。你不需要主动轮询,而是等事件来了再处理。这就像你去餐厅吃饭——不用一直盯着厨房,菜好了服务员会叫你。
Netty 实战:从入门到落地
理论说完了,咱们来点实际的。Netty 是 Java 领域最流行的 NIO 框架,交易系统里几乎人手一个。
我刚开始用 Netty 的时候,也踩过不少坑。比如,内存泄漏。Netty 用了直接内存(Direct Memory),如果你不手动释放,GC 管不了。我曾经有一次线上事故,就是忘了释放 ByteBuf,结果内存飙到 8G,服务直接 OOM。
下面是一个简单的 Netty 服务端示例:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535, 0, 4));
ch.pipeline().addLast(new ProtobufDecoder(OrderProto.Order.getDefaultInstance()));
ch.pipeline().addLast(new OrderServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(8080).sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
序列化协议:Protobuf 为什么是首选?
数据传过去了,但怎么表示?这就涉及到序列化协议了。
交易系统里,序列化协议的选择直接影响性能。JSON 虽然可读性好,但体积大、解析慢。Java 原生序列化就更别提了——又慢又不安全。
我个人强烈推荐 Protobuf。为什么?
- 体积小:二进制格式,比 JSON 小 3-10 倍。带宽就是钱啊。
- 解析快:不需要反射,直接生成代码,解析速度是 JSON 的 10 倍以上。
- 向前/向后兼容:字段可以增删,不影响老版本。这在微服务架构里太重要了。
来看一个 Protobuf 的定义示例:
syntax = "proto3";
message Order {
string order_id = 1;
string symbol = 2;
double price = 3;
int64 quantity = 4;
OrderType type = 5;
}
enum OrderType {
BUY = 0;
SELL = 1;
}
编译之后,你会得到一个 Java 类。序列化和反序列化只需要一行代码:
// 序列化
byte[] data = order.toByteArray();
// 反序列化
Order order = Order.parseFrom(data);
知识体系总览
最后,我用一张图来总结本章的核心内容。这张图展示了从传输层到应用层的完整链路:
这张图从下往上,正好对应了网络通信的四个层次。每一层都有对应的技术和选型考量。你在实际项目中,可以按照这个层次来设计和排查问题。
好了,网络通信模型就聊到这里。记住一句话:选对协议、用对模型、配好参数,你的交易系统在网络层面就不会出大问题。
公众号:蓝海资料掘金营,微信deep3321