被运营追着要报表那晚,我重构了整个数据服务
上周五晚上十点半,地铁末班车已经开走,我还在公司盯着屏幕上不断刷屏的 kubectl logs 输出。耳机里放的是 The Weeknd 的《Blinding Lights》——不是因为我有多喜欢,而是它节奏稳定,能让我在疯狂 debug 时不手抖。
我是老张,一个从 DBA 转型的后端开发,坐标北京中关村,每天通勤一小时,靠咖啡和 Postgres 的 WAL 日志续命。以前在数据库机房里调 buffer pool size,现在在 K8s 集群里调 HPA 阈值,说到底,还是在跟“延迟”和“一致性”死磕。
今天想聊聊这几年在技术探索与实践中的几个真实片段。没有高大上的架构图,只有被运营催报表催到凌晨三点的血泪史、被产品经理临时改需求的窒息感,以及一次又一次在“能跑就行”和“优雅可维护”之间艰难平衡的挣扎。
运营要实时数据?先问问数据库同不同意
去年双11前两周,运营团队突然提了个“小需求”:能不能在用户下单后 5秒内 在后台看到订单状态更新?他们要做实时转化漏斗分析。
我第一反应是:“你确定不是5分钟?”
但人家拿出了竞品截图,人家真做到了。行吧,卷就卷。
最初方案很简单:订单服务写库 → 运营后台直接查主库。结果上线第一天,主库 CPU 直接飙到 95%,慢查询日志里全是 SELECT * FROM orders WHERE status = 'paid' ORDER BY created_at DESC LIMIT 50 —— 没索引,全表扫描,还带 *。
我当时真的想砸键盘。DBA 时期的 PTSD 瞬间发作。
方案一:读写分离 + 缓存
我们立刻上了 Redis 缓存层,订单状态变更时发消息到 Kafka,下游消费写入缓存。运营查缓存,不碰主库。
# k8s deployment snippet for cache worker
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: order-cache-worker
env:
- name: KAFKA_TOPIC
value: "order.status.change"
- name: REDIS_ADDR
value: "redis-cluster.prod.svc:6379"
效果立竿见影,主库压力降了 70%。但新问题来了:缓存和数据库偶尔不一致。运营发现某笔订单“已支付”却没出现在列表里,急得差点报警。
方案二:物化视图 + CDC
作为 DBA 出身的人,我对“最终一致性”容忍度极低。于是祭出老本行:用 物化视图(Materialized View) 做预聚合,配合 Debezium 捕获数据库变更事件。
我们在 PostgreSQL 里建了一个轻量级物化视图:
CREATE MATERIALIZED VIEW mv_recent_paid_orders AS
SELECT id, user_id, amount, created_at
FROM orders
WHERE status = 'paid'
AND created_at > NOW() - INTERVAL '1 hour';
CREATE INDEX idx_mv_created ON mv_recent_paid_orders(created_at DESC);
然后通过 Debezium 监听 orders 表的 binlog(其实是 WAL),一旦有新支付订单,就触发物化视图刷新(增量刷新用 REFRESH CONCURRENTLY)。
这个方案把延迟压到了 2-3 秒,一致性也保证了。运维成本略高,但值得。
教训:别让运营直接查 OLTP 数据库!哪怕是“只读”。他们的“简单查询”往往是压垮系统的最后一根稻草。
教程写得再好,也抵不过一次线上事故
今年年初,我们团队决定把老旧的 Spring Boot 单体应用拆成微服务。领导说:“要拥抱云原生!” 我心想:行,反正 K8s 我熟。
我们选了 Spring Cloud Gateway + Nacos + Seata 的组合。拆分过程还算顺利,直到上线第三天。
那天早上九点,监控告警炸了:网关 5xx 错误率飙升至 40%。用户打不开商品详情页。
排查发现,是下游商品服务的一个接口响应变慢(GC 停顿),而网关的默认超时是 30 秒。大量请求堆积,线程池耗尽,雪崩了。
我当时坐在工位上,一边喝冰美式一边看 Grafana 曲线,冷汗直冒。产品在群里@我:“老张,用户投诉收不到货,怎么回事?”
关键修复:超时+熔断+重试策略
我们紧急调整了网关配置:
spring:
cloud:
gateway:
routes:
- id: product-service
uri: lb://product-service
predicates:
- Path=/api/product/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
httpclient:
connect-timeout: 1000 # 1秒连接超时
response-timeout: 2s # 2秒响应超时
resilience4j:
circuitbreaker:
instances:
productService:
failure-rate-threshold: 50
wait-duration-in-open-state: 30s
sliding-window-size: 10
同时,在商品服务内部加了 本地缓存兜底:即使 DB 挂了,也能返回最近 5 分钟的商品快照。
这次事故后,我写了份内部 《微服务容错实战教程》,重点不是讲理论,而是列出了 10 个真实踩过的坑,比如:
| 问题 | 表现 | 解决方案 |
|---|---|---|
| 网关无超时 | 线程池耗尽 | 显式设置 connect/response timeout |
| 无熔断机制 | 雪崩 | Resilience4j + Hystrix |
| 日志无 TraceID | 排查困难 | 强制透传 X-B3-TraceId |
| 服务注册慢 | 启动失败 | K8s readinessProbe 延迟调高 |
这份教程后来成了新人必读文档。最好的教程,往往诞生于深夜的故障复盘会。
从“能跑就行”到“可运维”的转变
刚转后端那会儿,我写的代码只关心功能是否实现。DBA 时期养成的“数据严谨性”习惯,一度被同事吐槽“太较真”。
但随着系统规模扩大,我发现:可运维性 = 可观测性 + 可恢复性 + 可配置性。
举个例子:我们有个定时任务,每天凌晨跑数据对账。最初是硬编码 cron 表达式:
@Scheduled(cron = "0 0 2 * * ?") // 凌晨2点
public void reconcile() {
// ...
}
结果有一次生产环境时间不对(Docker 容器时区没设),任务没跑,第二天财务对不上账,差点背锅。
后来我们改成 动态配置 + 手动触发接口:
@ConfigurationProperties(prefix = "job.reconcile")
@Data
public class ReconcileJobConfig {
private String cron;
private boolean enabled = true;
}
// 同时暴露 /actuator/reconcile/trigger 接口
@PostMapping("/trigger")
public ResponseEntity<String> triggerManually() {
reconcileService.runNow();
return ok("Triggered");
}
配合 K8s 的 ConfigMap + Prometheus 监控任务执行状态,终于睡得踏实了。
技术探索的本质,是解决人的焦虑
很多人觉得技术探索就是学新框架、玩新工具。但在我眼里,真正的技术探索,是为了缓解业务方的焦虑,减少团队的摩擦,让系统在 deadline 前稳如老狗。
运营要实时数据?背后是他们怕错过转化机会。
产品要快速迭代?背后是 KPI 压力。
测试要自动化?背后是不想半夜被叫起来验回归。
我们写代码,终究是在服务人,而不是服务机器。
所以,我的经验总结下来就三点:
- 永远假设上游会乱来,下游会挂掉 —— 做好防御式编程。
- 可观测性不是附加项,是核心功能 —— 没日志、没指标、没链路追踪的系统等于黑盒。
- 文档和教程要写给“明天的自己”看 —— 你总会忘记为什么当初要这么设计。
最后一点碎碎念
现在我写代码前,还是会下意识想:“这个表要不要加索引?”“这个事务会不会锁太久?”——DBA 的执念,深入骨髓。
但我也学会了妥协:有时候为了快速交付,先上个脚本 + crontab 也能活。关键是 留好重构入口,别让“临时方案”变成“祖传代码”。
技术没有银弹,只有权衡。而每一次权衡的背后,都是对业务、团队、时间的深刻理解。
哦对了,那晚被运营追着要报表之后,我们做了一套 自助式 BI 平台,让他们自己拖拽生成报表。现在他们很少找我了——除了问我“为什么这个图表加载这么慢?”
我微微一笑,打开 Explain Plan,又是一场新的战斗。
P.S. 如果你也经历过“被运营支配的恐惧”,欢迎留言交流。顺便求推荐好听的 coding BGM,The Weeknd 听腻了。

评论 0