4. 日志传输协议:TCP/UDP、Kafka、HTTP/HTTPS、gRPC 传输机制
日志从产线设备产生,到最终落入审计存储,中间这段路怎么走,非常关键。我见过不少项目,日志采集端写得挺好,结果传输环节出了幺蛾子,数据丢了、延迟爆炸、甚至把生产网络打挂了。今天咱们就把四种主流传输协议掰开揉碎聊清楚。
核心观点:没有最好的协议,只有最合适的场景。选型时重点看三点——可靠性要求、实时性要求、网络环境约束。
4.1 TCP:可靠但笨重,适合关键审计日志
TCP 是我个人最常用的协议之一。它保证数据不丢、不乱序,这对审计日志来说太重要了。你想想看,如果一条「设备超温报警」的日志在传输过程中丢了,后续追溯时根本查不到,那麻烦就大了。
我在一条汽车焊装线上遇到过一个问题:PLC 每 100ms 上报一次设备状态,日志量不大但每条都很关键。当时用了 TCP 直连,结果发现偶尔会有几秒的延迟抖动。排查下来,是 TCP 的 Nagle 算法在作祟——它把小包攒在一起发,攒包期间数据就卡在缓冲区里。
我的经验:产线日志场景,建议关闭 Nagle 算法(设置 TCP_NODELAY)。日志追求的是实时到达,不是省那几个包。另外,TCP 的粘包问题需要自己处理——我习惯在每个日志包前面加 4 字节的长度头,接收方先读长度再读内容,干净利落。
// TCP 发送端示例(C#)
using (var client = new TcpClient())
{
client.Connect("log-collector", 9000);
client.NoDelay = true; // 关闭 Nagle
byte[] body = Encoding.UTF8.GetBytes(logEntry);
byte[] header = BitConverter.GetBytes(body.Length);
// 先发长度头,再发内容
client.GetStream().Write(header, 0, 4);
client.GetStream().Write(body, 0, body.Length);
}
4.2 UDP:轻量但不可靠,适合高频状态日志
UDP 就一个字——快。没有三次握手,没有确认重传,发出去就不管了。这在产线场景里其实很有用。比如设备每 50ms 上报一次振动数据,这种高频状态日志,丢一两条根本不影响整体趋势分析。
但要注意,UDP 有 MTU 限制。标准以太网 MTU 是 1500 字节,减去 IP 和 UDP 头,实际 payload 建议控制在 1400 字节以内。我曾经见过一个项目,日志内容超过 2000 字节,UDP 直接分片了,结果接收端重组失败,整条日志就丢了。
避坑指南:UDP 日志一定要加序列号和时间戳。接收端发现序列号跳变时,可以主动补采或标记缺失。另外,组播场景下要小心交换机 IGMP Snooping 配置,否则组播流量可能泛洪到整个网络。
| 特性 | TCP | UDP |
|---|---|---|
| 可靠性 | 可靠,有序到达 | 不可靠,可能丢包乱序 |
| 延迟 | 较高(握手+重传) | 极低(无连接) |
| 适用场景 | 关键审计日志、告警 | 高频状态、传感器数据 |
| 网络开销 | 大(连接维护) | 小(无状态) |
| 典型端口 | 自定义(如 9000) | 自定义(如 9001) |
4.3 Kafka:海量日志的缓冲池与总线
当产线规模上来以后,TCP/UDP 直连就扛不住了。几百台设备同时发日志,接收端很容易成为瓶颈。这时候 Kafka 就派上用场了。
Kafka 的核心价值是解耦。生产者(设备端)只管往 Topic 里写,消费者(审计系统)按自己的节奏读。中间有分区做水平扩展,有副本做高可用。我参与过一个半导体工厂的项目,每天日志量超过 5TB,全靠 Kafka 集群撑着。
不过 Kafka 不是银弹。它的运维成本不低——需要 Zookeeper(或 KRaft),需要调优分区数、副本数、消息大小。而且 Kafka 的延迟通常在毫秒到秒级,不适合亚毫秒级的实时控制。
关键配置建议:
- acks=all:确保所有副本都写入才算成功,审计日志不能丢
- min.insync.replicas=2:至少两个副本同步,防止单点故障
- compression.type=snappy:产线日志重复度高,压缩比可观
- retention.ms:根据审计要求设置,一般 7-30 天
// Kafka 生产者示例(Java)
Properties props = new Properties();
props.put("bootstrap.servers", "kafka1:9092,kafka2:9092");
props.put("acks", "all");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
Producer producer = new KafkaProducer<>(props);
producer.send(new ProducerRecord<>("audit-log", deviceId, logJson));
4.4 HTTP/HTTPS:简单通用,但效率不是强项
HTTP 的好处是生态成熟,防火墙友好,调试方便。很多老旧设备只支持 HTTP 上报。但说实话,HTTP 在日志传输场景里效率不高——每次请求都要建连接(除非 keep-alive),请求头开销大,而且 HTTP 是单向的,服务端没法主动推。
HTTPS 加了 TLS 加密,安全性提升,但握手开销更大。我建议只在跨公网传输或合规要求严格的场景下用 HTTPS。内网环境用 HTTP + 网络隔离就够了。
我的习惯:如果非要用 HTTP,一定用 POST 方法,把日志放在请求体里。别用 GET,URL 长度有限制,而且日志内容会暴露在访问日志里。另外,批量上报比单条上报效率高得多——攒个几十条再发一次 POST。
4.5 gRPC:结构化日志的现代方案
gRPC 是我近几年越来越喜欢用的协议。它基于 HTTP/2,支持双向流、多路复用、头部压缩。最吸引我的是它的强类型接口——用 Protobuf 定义日志结构,客户端和服务端都生成代码,字段名、类型、嵌套关系都在编译期就确定了,不会出现「这个字段是 string 还是 int」的扯皮。
双向流是 gRPC 的杀手锏。设备端可以和服务端建立一条长连接,日志一条接一条地推过来,服务端也可以随时下发指令。我在一个锂电池产线项目中就用 gRPC 双向流做实时日志采集,同时服务端还能远程调整设备的日志级别,非常灵活。
// Protobuf 定义
syntax = "proto3";
service AuditLogService {
rpc StreamLogs(stream LogRequest) returns (stream LogResponse);
}
message LogRequest {
string device_id = 1;
int64 timestamp = 2;
string level = 3;
string message = 4;
map<string, string> tags = 5;
}
注意:gRPC 的流式传输依赖 HTTP/2 的长连接,如果网络中间有代理或防火墙不支持 HTTP/2,连接可能会被中断。我曾经在客户现场遇到过,IT 部门的防火墙把 HTTP/2 的 SETTINGS 帧当成了恶意流量给拦截了……最后加了 TLS 才解决。
4.6 选型决策矩阵
说了这么多,到底怎么选?我整理了一个决策矩阵,你对着自己的场景打勾就行。
| 需求维度 | TCP | UDP | Kafka | HTTP | gRPC |
|---|---|---|---|---|---|
| 数据不丢失 | ✅ | ❌ | ✅ | ✅ | ✅ |
| 低延迟(<10ms) | ⚠️ | ✅ | ❌ | ❌ | ✅ |
| 高吞吐(>100MB/s) | ❌ | ⚠️ | ✅ | ❌ | ⚠️ |
| 结构化日志 | ❌ | ❌ | ⚠️ | ⚠️ | ✅ |
| 运维简单 | ✅ | ✅ | ❌ | ✅ | ⚠️ |
| 跨公网安全 | ⚠️ | ❌ | ⚠️ | ✅ | ✅ |
嗯,这张表基本覆盖了产线日志传输的常见场景。我个人建议:小规模产线用 TCP + UDP 混合,关键日志走 TCP,状态日志走 UDP;中大规模产线直接上 Kafka,省心;如果团队有微服务基础,gRPC 是很好的长期选择。