技术探索与实践:从“跑通”到“跑好”的真实旅程
开篇:为什么分享这个话题?

在我多年的技术生涯中,常常有人问我:“你们平时都忙啥?不就是写代码吗?”
这其实是个挺有意思的误解。技术团队的日常远不止写代码,尤其是在一个业务快速迭代、需求不断演进的环境中,技术探索与实践更是占据了相当大的比重。
这篇文章不是来教你某个具体的框架或算法的,而是想通过一次真实的项目经历,聊聊我们是如何在面对一个模糊的需求和不确定的技术路径时,一步步摸索、踩坑、试错,并最终构建出一个既稳定又能支撑未来发展的系统架构。如果你也在思考“如何在技术选型上做出更合理的决策”、“如何在资源有限的情况下推进关键项目”,那么本文或许能带来一些启发。
问题描述:我们要做一个高并发日志采集服务

项目背景
我们的核心业务是面向企业用户的 SaaS 平台,每天都会处理大量的用户行为日志,包括页面访问、点击事件、错误信息等等。最初这些日志直接打在应用服务器的日志文件里,然后通过 ELK 进行集中分析。
但随着业务规模扩大,日志量暴增(单日峰值超过 10 亿条),原来的方案开始显现出各种瓶颈:
- 应用服务器压力剧增,CPU 和磁盘 I/O 都快扛不住了
- 日志采集延迟变大,导致数据分析滞后
- 某些关键日志被截断甚至丢失
于是我们决定启动一项新项目:构建一个独立的高并发日志采集与转发服务(后文简称 LogAgent),目标是把日志采集从应用进程中解耦出来,降低对业务逻辑的影响,同时提升整体吞吐能力和稳定性。
解决方案:技术选型与架构设计

目标明确
首先我们要明确几个关键目标:
- 高性能:单实例至少支持几十万 TPS 的日志采集和转发能力。
- 低延迟:尽量保证日志到达与转发之间时间差在毫秒级别。
- 高可用性:即使部分节点宕机,也不能影响整体采集能力。
- 轻量级:部署简单、资源占用小,不影响业务进程。
- 可扩展:方便未来接入其他下游系统(如 Kafka、Prometheus、自定义 sink 等)。
技术调研与选型
一开始我们尝试了一些现成的解决方案,比如 Logstash、Fluentd、rsyslog,但它们要么太重,要么不适合这种高吞吐场景。
最后我们决定自己动手搭建一套基于 Go + gRPC + Kafka Producer 的采集服务,整个架构分为以下几个组件:
| 组件 | 功能 |
|---|---|
| LogAgent | 轻量级 Agent,负责收集本地日志文件、处理并发送至 Kafka |
| ConfigServer | 集中式配置中心,管理各个 Agent 的采集路径与策略 |
| MonitorAgent | 嵌入到业务进程,用于上报 Agent 状态、健康度、日志采集指标 |
| Kafka Cluster | 中转层,作为日志传输通道 |
这里有几个关键点需要说明:
为什么选 Go?
- 对于高并发场景来说,Go 天然适合做这类后台服务。
- 实际测试表明,在相同压测条件下,Go 编写的采集服务比 Python 实现性能高出 3~5 倍,资源消耗明显更低。
- 内置 runtime 协程机制,天然适合处理大量并发任务。
为什么用 gRPC?
- 后续我们计划将更多数据处理模块抽象为微服务,gRPC 是最合适的通信协议之一。
- 性能优秀,对比 JSON-RPC 有明显优势。
- 强类型接口,利于服务治理和后续扩展。
为什么引入 Kafka?
- 我们的下游系统(比如 Flink 实时计算、ELK 存储、BI 平台)基本都依赖 Kafka 作为中间件。
- Kafka 能够缓冲高峰期流量,防止单个下游服务过载。
- 分区机制也便于横向扩展消费端处理能力。
代码实践:LogAgent 核心实现片段

为了保持轻量级,我们并没有使用太多复杂的库,只保留最核心的功能。下面是一些关键代码片段(简化版)供参考。
初始化采集器
type Collector struct {
paths []string
client *kafka.Producer
}
func NewCollector(logPaths []string) (*Collector, error) {
producer, err := kafka.NewProducer(&kafka.ConfigMap{
"bootstrap.servers": "kafka-broker1:9092",
"acks": "all",
})
if err != nil {
return nil, err
}
return &Collector{
paths: logPaths,
client: producer,
}, nil
}
文件监听与消息发送
func (c *Collector) Start() {
for _, path := range c.paths {
go func(p string) {
file, err := os.Open(p)
if err != nil {
log.Printf("open file %s failed: %v", p, err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
msg := scanner.Text()
c.client.ProduceChannel() <- &kafka.Message{
TopicPartition: kafka.TopicPartition{Topic: StringPtr("logs"), Partition: kafka.PartitionAny},
Value: []byte(msg),
}
}
}(path)
}
}
当然这只是最基础的实现,实际生产版本还加入了:
- 文件轮换检测(logrotate)
- Offset 回溯机制
- 错误重试策略
- 上报 Metrics 到 Prometheus
踩坑经验:那些深夜调试的日子
尽管做了充分准备,但在实践中还是遇到了不少棘手的问题。
1. 日志采集延迟严重
刚开始上线时,发现 Kafka 中的日志会有明显延迟(甚至长达几分钟)。排查发现是因为某些日志文件在生成过程中,LogAgent 提前打开了句柄,而日志还没完全写入就被读走了 —— 导致内容为空或残缺。
解决方法:加入了一个简单的等待机制,在每次扫描完一行后检查是否还有新的内容。如果是,则继续往下读;否则间隔一小段时间后再检查。
for scanner.Scan() {
line := scanner.Text()
if strings.TrimSpace(line) == "" {
time.Sleep(100 * time.Millisecond)
continue
}
// send to Kafka...
}
2. Kafka 生产者频繁超时
一开始我们没有设置合适的超时参数,导致在网络波动时整个采集线程阻塞,甚至引发内存泄漏。
解决方式:调整了 Kafka 客户端的参数:
"message.timeout.ms": "10000",
"enable.idempotence": "true",
"max.in.flight.requests.per.connection": "5",
同时对失败的消息进行重试(最多三次),避免丢数据。
3. 多线程竞争与锁冲突
由于每个采集路径都是单独协程运行,初期没有加任何互斥锁,导致某些共享资源出现竞态条件。
后来我们在关键部分加上了 sync.Mutex,并在测试环境复现了并发问题后修复了多个 race condition。
效果总结:技术投入的回报看得见

经过近两个月的开发、测试与灰度上线,新系统取得了显著效果:
| 指标 | 改造前 | 改造后 | 提升/下降 |
|---|---|---|---|
| 日志采集延迟 | 平均 2 分钟 | <100ms | ✅下降 |
| CPU 使用率 | 60%+ | 15%~25% | ✅显著优化 |
| 日志丢失率 | ~5% | <0.01% | ✅几乎无损 |
| 系统负载 | 高峰期不稳定 | 稳定运行 | ✅改善明显 |
最重要的是,我们成功将采集逻辑从业务主流程中剥离,使得业务应用更加专注核心逻辑,不再为日志采集“背锅”。
现在我们可以灵活地对接不同的下游系统,也为后续的数据治理打下了良好基础。
经验分享:给开发者的一些建议
1. 技术探索不能闭门造车
很多时候我们以为某个技术很牛,结果一上生产就翻车。技术选型必须结合实际场景来做判断,而不是一味追求“新”或者“热门”。
比如 Go 在这里表现得很好,但如果你的团队熟悉 Java 或 Rust,也不是非要用 Go 不可。关键是选择最适合团队、业务、成本结构的技术栈。
2. 要敢于重构和舍弃
我们原本也有尝试在旧系统的基础上修修补补,但后来发现代价更高。果断决定重写反而效率更高。有时候“推倒重来”不是坏事,尤其在工程复杂度已经严重影响进度的时候。
3. 工程细节决定了成败
很多人觉得只要架构漂亮就行,但我越来越意识到:真正决定成败的往往是最基础的部分 —— 日志埋点是否正确、异常处理是否完善、监控指标是否全面。
建议大家重视以下几点:
- 建立完善的 Metrics 上报体系(Prometheus + Grafana 很推荐)
- 统一日志格式,便于后续自动化分析
- 做好告警机制(哪怕是简单的邮件通知)
4. 小步迭代,持续交付
不要幻想一次性把所有事情做完,那样只会陷入“永不完工”的怪圈。我们采用的方式是先上线最核心的采集功能,然后再逐步添加重试、缓存、上报、动态配置等功能。每一步都有产出,也更容易评估风险。
结语:技术的价值在于服务业务
这篇文章讲的只是一个小小的日志采集服务的故事,但它背后折射出了很多我们日常开发中面临的核心问题:技术怎么选、系统怎么搭、故障怎么排、效率怎么提。
我想说的是:技术本身从来都不是目的,它只是手段。真正重要的是——如何通过技术的力量,推动业务走得更稳、更快、更远。
如果你也在技术探索的路上,希望这篇文章能给你带来一点点灵感和鼓励。毕竟,每一个伟大的系统,都是从一个小小的想法和一次次实践开始的。
作者简介:我是某大型互联网公司技术负责人,十年后端工程师,专注于分布式系统、云原生与性能优化领域。欢迎关注我的博客,一起探讨技术背后的真知灼见。

评论 0