月底结算与营销活动高峰的稳定性设计复盘
对应简历段落
在保险销售系统中,参与月底佣金结算、营销活动高峰、商机流转和保单状态回写等核心链路建设。针对月底批处理任务与在线活动流量叠加导致的线程池打满、数据库连接耗尽、MQ 积压、接口超时等问题,设计线程池隔离、MQ 削峰、限流降级、批量任务分片、幂等补偿和监控告警方案,提升系统整体稳定性。
这段简历适合做一次完整复盘。资深面试官会希望听到的不只是“我用了哪些技术”,而是问题发生前的架构状态、故障表现、根因分析、改造方案、验证方式和最终收益。复盘型回答最能体现工程判断力。
业务背景
月底结算和营销活动高峰在保险业务里经常撞在一起。月底要计算代理人佣金、活动奖励、渠道费用、机构业绩,还要生成对账明细和报表。营销活动则会带来投保提交、客户咨询、商机分配、代理人跟进、短信通知等在线流量。一个偏批处理,一个偏在线交易,但它们可能共享同一套应用、数据库、线程池和 MQ 集群。
早期系统为了快速交付,很多异步任务共用一个默认线程池,数据库连接池也没有按业务隔离。结算任务在月底一次性扫描大量订单,按批次提交任务;活动高峰时,投保提交也会触发商机流转和通知。两个高峰叠加后,低优先级任务可能占住高优先级资源。
典型现象是:活动提交接口 RT 从几百毫秒涨到数秒;商机分配延迟增加;MQ 消费积压;数据库连接池等待明显;结算任务本身也变慢;运维看到 CPU 不一定满,但线程大量阻塞在数据库连接、外部 RPC 或队列等待上。这类问题的难点在于它不是单个 bug,而是容量、隔离和任务模型共同失衡。
核心原理
稳定性复盘要先区分在线链路和离线链路。在线链路关注低延迟和用户体验,典型请求包括活动页查询、投保提交、商机查询。离线链路关注吞吐和最终完成,典型任务包括佣金结算、对账报表、批量同步。两类链路对资源的使用方式不同,不能完全共用资源。
月底结算的风险来自批量放大。单条结算明细计算可能很快,但几十万条订单一起处理,会放大数据库扫描、锁冲突、批量写入、缓存访问和 MQ 投递。结算任务如果没有分片、限速和状态管理,就会把压力集中打到数据库。
营销活动的风险来自脉冲流量。活动开始后的几分钟内,入口请求可能远高于平时。在线链路如果同步执行后置动作,会把短信、CRM、保司等弱依赖的抖动传递给用户接口。
稳定性设计的核心是四个词:分层、隔离、削峰、补偿。分层是把核心请求和后置任务拆开;隔离是不同优先级任务不用同一份线程和队列;削峰是用 MQ 和限流把瞬时压力变平;补偿是允许部分动作延迟或失败,但最终可恢复。
项目落地
第一,入口层做限流和核心链路瘦身。活动提交接口只做资格校验、幂等校验、投保意向落库和事件写入,不在请求内同步做短信、CRM、报表刷新。超过承载能力时,入口快速返回繁忙提示,避免请求堆在线程里。
第二,异步层按业务拆 topic、消费组和线程池。商机流转、通知、CRM 同步、结算明细生成、运营埋点分别使用独立消费组和线程池。商机流转优先级最高,通知和埋点可以延迟,结算任务限制并发并尽量放在低峰窗口。
第三,结算任务分片和限速。按月份、机构、产品、代理人或订单 ID 范围拆分结算批次。每个批次都有独立状态:待处理、处理中、成功、失败、超时。调度器不一次性提交全部任务,而是按窗口和并发阈值派发。写入结算明细时使用批量提交,但批次大小受数据库能力约束。
第四,数据库访问优化。结算扫描使用覆盖索引和游标分页,避免深分页和大事务。结算结果写入采用幂等键,例如 settleMonth + policyNo + agentId + rewardType。在线活动表和结算明细表避免在同一事务中互相等待。必要时读写分离或结算库分离。
第五,外部依赖治理。保司、CRM、短信通道都设置独立超时、重试和熔断。在线链路中外部依赖失败时进入异步补偿,不让用户接口长时间等待。结算中外部依赖尽量提前拉取快照,减少结算过程中实时调用。
第六,监控告警从技术指标扩展到业务指标。除了 CPU、内存、线程池、连接池、MQ 积压,还要监控投保提交成功率、商机分配延迟、结算批次完成率、失败批次数、状态超时数量、补偿成功率。稳定性不是机器没挂,而是核心业务按预期完成。
关键伪代码或流程
结算批次创建:
public void createSettlementTask(String month) {
List<ShardKey> shards = shardService.splitByOrgAndAgent(month);
for (ShardKey shard : shards) {
settlementTaskRepository.insertIgnore(SettlementTask.builder()
.taskKey(month + ":" + shard.key())
.month(month)
.shard(shard)
.status("INIT")
.build());
}
}
任务抢占:
update settlement_task
set status = 'RUNNING',
owner = ?,
start_time = now(),
version = version + 1
where id = ?
and status in ('INIT', 'RETRY')
and version = ?;
分片处理:
public void processShard(SettlementTask task) {
try {
PageCursor cursor = PageCursor.start();
while (cursor.hasNext()) {
List<Policy> policies = policyRepository.scan(task.shard(), cursor, 500);
List<SettlementDetail> details = calculator.calculate(policies);
settlementRepository.batchUpsert(details);
cursor = cursor.next();
rateLimiter.acquire();
}
taskRepository.markSuccess(task.id());
} catch (TemporaryException ex) {
taskRepository.markRetry(task.id(), ex.getMessage());
} catch (Exception ex) {
taskRepository.markFailed(task.id(), ex.getMessage());
}
}
活动提交瘦身:
@Transactional
public SubmitResult submitActivity(SubmitCommand command) {
idempotentService.check(command.idempotentKey());
Opportunity opportunity = opportunityRepository.create(command);
eventRepository.save(DomainEvent.opportunityCreated(opportunity));
return SubmitResult.success(opportunity.id());
}
整体流程:活动入口限流;核心单据落库;事件异步流转;结算任务按分片进入独立 worker;在线和结算线程池隔离;数据库写入幂等;失败任务可重试;监控同时覆盖在线成功率和结算完成率;高峰结束后根据积压和失败数据复盘阈值。
常见坑与排查
第一个坑是结算任务一次性全量提交。大量任务瞬间进入线程池或 MQ,会造成排队和内存压力。应按窗口派发,并根据运行中任务数限速。
第二个坑是大事务。一个结算批次处理太多数据并在一个事务里提交,会导致锁持有时间长、回滚成本高、redo 压力大。应拆小批次,保证可重试。
第三个坑是在线和离线共用线程池。结算任务耗时长,会把活动提交后置任务堵住。排查时看线程名和队列堆积来源,确认是否低优先级任务占满资源。
第四个坑是只关注结算速度。结算跑得越快不一定越好,如果把数据库打满,会影响在线交易。结算吞吐要服从整体稳定性。
第五个坑是幂等不足。结算失败重跑时,如果没有唯一键和 upsert,容易重复生成佣金明细。补偿任务越多,幂等越重要。
第六个坑是高峰期临时调参没有记录。活动中手动扩容、修改限流、暂停消费者都要记录时间点,否则复盘时无法解释指标变化。
面试追问
追问一:月底结算和在线活动冲突时,你优先保什么?
追问二:结算任务如何分片?
追问三:如何避免重复结算?
追问四:MQ 积压时该扩容消费者吗?
追问五:如何证明改造有效?
追问六:高峰前你会做哪些准备?
推荐回答
我会回答:在线投保和商机创建优先级最高,月底结算可以延迟但不能错。设计上把在线链路和结算链路拆开,入口限流保护核心接口,后置动作 MQ 异步,结算任务独立线程池和消费组,按机构、月份、代理人维度分片,限制并发和批次大小。
避免重复结算靠三层保障。第一,任务表用状态机和版本号抢占,避免多 worker 同时处理同一批次。第二,结算明细表有业务唯一键,重跑时使用幂等写入。第三,失败批次保留错误原因和重试次数,补偿任务只重跑失败或超时分片。
如果 MQ 积压,我会先判断积压类型。商机核心消息可以临时扩容消费者,但要确认数据库和下游承载能力;通知、埋点、CRM 可以延迟;结算消息可能需要限速而不是扩容。证明改造有效则看活动提交 RT、成功率、线程池拒绝、数据库连接等待、商机分配延迟、结算完成时间和失败补偿率。
延伸学习路线
第一阶段,学习批处理稳定性:分片、游标分页、小事务、幂等写入、任务状态机、失败重跑。
第二阶段,学习在线高并发治理:限流、降级、熔断、隔离、MQ 削峰、线程池参数。
第三阶段,学习容量评估和压测。分别压在线接口、MQ 消费、结算批处理,再做混合压测,观察资源竞争。
第四阶段,学习可观测性建设。技术指标和业务指标要能按业务单号、活动 ID、结算月份、机构维度关联。
第五阶段,训练复盘表达。按背景、现象、根因、方案、验证、收益、遗留风险来讲,比单纯罗列技术点更能体现资深工程师能力。