从一次“小”需求说起:技术探索与实践的那些事儿
大家好,我是某互联网公司的后端团队负责人。从业十来年,写过不少代码、踩过不少坑,也见证了一个个小功能如何一步步演变成支撑业务的核心模块。
今天想和大家聊聊技术探索与实践这件事儿。这个题目听起来有点抽象,但别担心,我会结合我们最近刚上线的一个项目,从一个看似简单的需求出发,谈谈我们在技术选型、方案落地、开发实施过程中遇到的各种挑战,以及我们是如何通过不断尝试和迭代,最终把事情做好的。
希望这篇文章不仅有技术细节,更有我在实际工作中的思考和心得,能对你有所启发。
背景介绍:一个小功能引发的连锁反应

事情要从几个月前的一次产品会上说起。
当时产品经理提了一个看起来非常“轻量”的需求:在我们的后台管理系统中,新增一个操作记录模块,能够实时展示用户对系统关键配置项的修改记录,并支持按时间、用户名等维度进行筛选查询。
听起来是不是很普通?不就是个操作日志展示嘛?
但是,等我们真正开始拆解需求的时候才发现,这背后隐藏着一堆问题:
- 日志数据量大:每个配置项的操作都会记录下来,预估每天的日志量超过百万条。
- 实时性要求高:用户点击进入页面后,需要立刻看到最新的操作记录。
- 历史数据检索复杂:不仅支持当前查询,还要提供过去一段时间内的操作回放。
- 服务不能挂:作为后台系统的一部分,任何不可用都可能影响运维人员的决策效率。
- 性能要快:用户期望秒级响应,尤其是分页查询和筛选功能。
于是,这个最初被评估为“半人天即可搞定”的需求,变成了我们接下来两周讨论最多的项目之一。
技术选型:怎么存储这些操作日志?

首先摆在我们面前的问题是:操作日志该存哪?
最简单的做法当然是——直接写入 MySQL 表中。但我们很快否定了这个思路。
为什么?因为:
- 日志数据是只增不改的,MySQL 不适合这种高频插入的场景;
- 后续要做检索、聚合分析时,性能可能会成为瓶颈;
- 数据量达到百万级以上时,传统数据库的查询效率会明显下降。
那换用 Elasticsearch(ES)呢?这是我们日常使用较多的一款搜索中间件,天生适合做全文检索、聚合查询,也支持分页、排序等功能。
但 ES 是不是最优解呢?我们又对比了几种方案:
| 存储方式 | 写入性能 | 查询能力 | 实时性 | 扩展性 | 适用场景 |
|---|---|---|---|---|---|
| MySQL | 一般 | 弱 | 强 | 差 | 小数据量,事务性 |
| MongoDB | 中 | 较强 | 一般 | 中 | 非结构化数据 |
| Elasticsearch | 强 | 极强 | 强 | 强 | 搜索、日志分析 |
| Kafka + 自研 | 极强 | 取决于处理引擎 | 极强 | 极强 | 大数据流式场景 |
最终,我们决定采用 Elasticsearch,理由如下:
- 写入性能强,符合日志类数据写多读少的特点;
- 原生支持分页、过滤、排序、聚合等高级查询;
- 支持水平扩展,便于后续容量规划;
- 我们已有 ES 的维护经验,上手成本低。
实现思路:怎么采集和推送操作日志?


确定了存储方式之后,下一步就是解决日志的采集和推送机制。
我们目前的架构是基于 Spring Boot 开发的微服务系统,核心服务之间通过 RESTful API 进行通信。操作日志主要集中在几个关键的配置修改接口上。
所以,我们采取了以下设计:
1. 使用 AOP 统一拦截操作事件
为了统一日志采集入口,我们使用了 Spring AOP 对关键方法进行切面监听。例如:
@Aspect
@Component
public class OperationLogAspect {
@Autowired
private OperationLogService operationLogService;
// 拦截所有带有@Loggable注解的方法
@Pointcut("@annotation(com.example.Loggable)")
public void loggableMethod() {}
@AfterReturning("loggableMethod()")
public void afterOperation(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Loggable annotation = method.getAnnotation(Loggable.class);
String description = annotation.description();
// 获取当前登录用户
String operator = getCurrentUser();
// 记录操作日志
OperationLog log = new OperationLog();
log.setOperator(operator);
log.setAction(description);
log.setTimestamp(new Date());
log.setDetails(getActionDetails(joinPoint));
operationLogService.asyncSave(log); // 异步保存,避免阻塞主流程
}
// 省略获取用户和参数详情的方法...
}
这样做的好处是,我们不需要在每个接口里手动加日志埋点,只要打上 @Loggable 注解就可以自动记录。
2. 异步写入队列
考虑到日志写入的高并发场景,为了避免 ES 插入操作拖慢主线程,我们引入了一个异步队列机制。
这里我们用了 RabbitMQ 作为消息队列中介:
@Service
public class OperationLogService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void asyncSave(OperationLog log) {
rabbitTemplate.convertAndSend("operation_log_exchange", "log.key", JSON.toJSONString(log));
}
}
然后我们再开一个消费端去消费这些日志消息并写入 ES:
@Component
public class OperationLogConsumer {
@Autowired
private ElasticSearchService esService;
@RabbitListener(queues = "operation_log_queue")
public void processMessage(String message) {
OperationLog log = JSON.parseObject(message, OperationLog.class);
esService.indexOperationLog(log);
}
}
这样一来,我们就实现了日志采集和持久化的分离,提高了整体系统的稳定性。
踩过的坑:那些你以为不会有问题的事儿
虽然整体设计方案看起来没问题,但在实际开发过程中还是遇到了不少“小插曲”。
坑点一:Spring AOP 无法拦截非Controller层方法
我们在某个 service 层封装了一些操作行为,原本以为 AOP 切面可以拦截到这些方法调用。
结果发现根本没生效!
查了一圈文档才明白,Spring 的代理机制默认是针对 Bean 的,如果你在同一个类内通过 this 调用目标方法,AOP 是不起作用的。
最后的解决办法也很简单:将需要记录操作的方法提取到独立的 Service 类中,并确保是注入调用,而不是 this 调用。
坑点二:写入 ES 性能波动大
刚开始测试的时候,数据量还不大的情况下,一切都很顺利。
但压力测试中我们发现,当并发写入量突然升高时,ES 插入延迟严重,甚至出现堆积现象。
排查后发现两个主要原因:
- 索引刷新间隔设置太短,默认是1秒,导致频繁触发 refresh;
- 单条数据写入性能较好,但我们可以批量写入提升效率。
优化手段:
- 修改索引刷新间隔为 30 秒,降低 refresh 次数;
- 使用 Bulk API 批量插入日志数据,每次攒够 50 条再写入;
- 使用 Elasticsearch 客户端的异步写入能力,避免线程阻塞;
优化后写入性能提升了约 3 倍,吞吐稳定了不少。
效果总结:上线后的变化和收益
这个功能上线两个月以来,我们观察到了以下几个方面的显著变化:
- 用户体验明显提升:操作日志查询几乎无感延迟,运维反馈查找操作更方便了;
- 系统稳定性更高:由于异步和队列机制的加持,即使高峰期也没有出现系统抖动;
- 后续扩展性强:ES 的接入为我们后续做日志分析、安全审计提供了良好基础;
- 团队协作更顺畅:有了统一的操作记录,排查问题时沟通更高效,责任划分也更清晰。
心得体会:关于技术探索与实践的几点建议
作为一名一线开发者出身的技术负责人,我想分享一下自己在这一过程中的几点感悟:
1. 不要低估“小需求”的复杂度
很多时候,产品眼中看起来“简单”的功能,往往涉及多个系统模块、需要考虑数据结构、权限控制、性能边界等多个维度。我们一定要保持技术敏感性,在需求评审阶段就要深入思考实现路径。
2. 技术选型没有银弹,适合的才是最好的
比如 ES 很强大,但如果只是几千条数据的场景,直接用数据库更合适。一定要根据业务背景、数据规模、团队熟悉程度来做判断,而不是为了炫技而选择新技术。
3. 异步化、队列化是提升系统稳定性的有效手段
尤其是在写入场景中,适当引入缓冲和异步处理,可以让整个系统更加健壮,也能缓解高峰期的压力。
4. 代码设计要有可扩展性和前瞻性
比如我们在这个项目中采用了注解驱动的方式记录操作,未来如果需要添加新的行为标签、增加字段或调整结构,都可以低成本扩展。
结语:技术永远服务于业务,但也要敢于创新
说实话,这次的项目并不是特别“高大上”,但它让我又一次深刻体会到:
技术的价值在于解决问题,而不是堆砌名词。
从一开始的一个“不起眼”的操作日志需求,到最后一个完整的技术方案落地,我们经历了设计讨论、性能压测、线上调优等多个阶段,每一个环节都在考验着团队的技术功底和协作能力。
如果你问我:“技术探索与实践”到底是什么?
我会说:它是我们面对未知挑战时的那份从容,是在实践中不断试错、调整、优化的坚持,也是在一个个真实业务场景下,让技术真正为业务赋能的过程。
希望你也在自己的项目中,找到属于你的“探索之路”。
如你对本文内容有任何疑问或建议,欢迎留言交流~

评论 0