记录:db服务器资源使用率过高
2025-12-19

从图中可以看出有一条insert sql每秒执行252.55次,并且是记录用户操作的,不是业务数据。
等待类型分析(Wait Events)
可以看出,其中IO:XactSync 占主导,表明数据库正在频繁地将事务日志刷盘(write-ahead logging)。
存在风险
DB 延迟升高
应用超时
连接池耗尽
最终服务不可用
问题分析
目前记录操作日志是通过event-listener异步逐条插入db,所以应用服务器目前没有太大压力,但是db有问题。并且没有设置开关/分级能动态启动关闭这个功能,所以导致活动期间,用户一多,操作飙升时造成db很大压力。
解决方案
短期
在配置文件中添加userActionLogEnable:true,并且在listener中进行判断,如果为false则不落库。这种方式可以瞬间减轻db压力,缺点是操作记录丢失。
中期
对操作进行分级(类似info,warn,error),这样可以在高并发时选择关闭info的操作记录又能记录重要的操作。
对listener进行改造,使用批量插入,而不是来一条插入一条,这样可以减少
IO:XactSync的时间。
@Component
@Slf4j
public class BatchAuditLogListener {
private final List<UserActionLog> buffer = new ArrayList<>();
private final Object lock = new Object();
@Autowired
private UserActionLogRepository logRepository;
// 使用独立线程池(避免占用 Spring 的事件线程)
private final ExecutorService batchExecutor =
Executors.newSingleThreadExecutor(r -> new Thread(r, "audit-batch-writer"));
// 监听事件:只入队,不写 DB
@EventListener
public void onUserAction(UserActionEvent event) {
synchronized (lock) {
buffer.add(convertToLog(event));
// 可选:达到批次大小立即 flush
if (buffer.size() >= 100) {
flush();
}
}
}
// 每 2 秒强制刷一次(防止尾部数据积压)
@Scheduled(fixedDelay = 2000)
public void scheduledFlush() {
synchronized (lock) {
if (!buffer.isEmpty()) {
flush();
}
}
}
private void flush() {
if (buffer.isEmpty()) return;
List<UserActionLog> toSave = new ArrayList<>(buffer);
buffer.clear(); // 清空缓冲区
batchExecutor.submit(() -> {
try {
logRepository.saveAll(toSave); // 批量插入(JPA/Hibernate)
log.debug("Batch saved {} audit logs", toSave.size());
} catch (Exception e) {
log.error("Failed to batch save audit logs", e);
// TODO: 考虑重试 or 写死信文件
}
});
}
@PreDestroy
public void destroy() {
scheduledFlush(); // 应用关闭前清空 buffer
batchExecutor.shutdown();
}
}效果对比
长期
可以增加消息队列(Kafka),由于本应用在AWS上部署,可以优先考虑Kinesis Data Streams + Fargate 消费者。
最终效果
在问题发生当天采用了短期方案,然后活动过后对代码进行修改,使用了中期方案,通过后面半年的观察,再决定用不用使用长期方案。