微服务拆分后接口超时、数据不一致、链路追不到?我踩过的 6 个坑和排查参数

🔑 关键词:微服务拆分,分布式事务,服务治理,链路追踪,Kubernetes

📖 摘要:不是微服务八股。我把单体拆成 14 个服务后遇到的超时、数据一致性、链路追踪、K8s 参数问题写了一遍,附可抄的排查步骤和具体参数。

先别急着拆,我一开始也以为微服务就是按模块建工程

图片

2021 年我把一个电商单体拆成 14 个 Spring Boot 服务,Spring Boot 2.7 + Spring Cloud 2021.0.3 + K8s 1.23。 发布次数从每周 2 次变成每天 30 次,听起来很爽,但事故也多了 4 倍。 最惨一次大促,库存服务 P99 从 80ms 涨到 2.3s,网关 504,订单掉了 12%。 后来发现不是微服务本身,是 Hikari maximumPoolSize=10、慢 SQL 没索引、Feign readTimeout 默认 60s 一起叠加。

我的独立观点:微服务不是架构升级,是把组织边界投影到技术上。 拆分顺序应该反过来,先拆可观测性和发布流水线,再拆数据库,最后才拆服务。 团队不到 20 人、服务不到 30 个,别上 Istio,Spring Cloud Gateway + Resilience4j + OpenTelemetry 够用。 这个结论得罪人,但我交过学费。

坑 1:接口 504,不要只盯网关

图片

排查顺序我固定成 6 步。 第 1 步看网关 P99 和 5xx,Prometheus 里查 histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket[5m])) by (le, uri))。 第 2 步看下游 trace,Jaeger 或 Tempo 里按 trace_id 找最慢 span。 第 3 步看连接池,Hikari active_connections 接近 maximumPoolSize 就是排队。 第 4 步看线程池,Tomcat max-threads 默认 200,别乱调,先看队列。 第 5 步看 K8s 事件,kubectl describe pod 看 OOMKilled 和 readiness 失败。 第 6 步看数据库慢查询,MySQL slow_query_log=ON,long_query_time=0.5。

我最后改的参数:Spring Cloud Gateway connect-timeout=1000,response-timeout=3s。 Feign connectTimeout=1000,readTimeout=3000。 Resilience4j TimeLimiter 2s,CircuitBreaker slidingWindowSize=20,failureRateThreshold=50,waitDurationInOpenState=10s。 Hikari maximumPoolSize 从 10 调到 20,connectionTimeout=3000,maxLifetime=1800000。 P99 从 2.3s 回到 220ms,但这不是银弹,数据库慢 SQL 才是根。

坑 2:数据一致性,先别上 Seata

图片

订单创建要扣库存,很多人第一反应是 Seata AT。 我试过,在 8 个库、峰值 1500 TPS 下,全局锁和 undo_log 把 RT 拉高了 37%。 后来改成本地消息表 + 幂等消费,覆盖了 80% 场景。 order 服务本地事务写 orders 和 outbox_event 两张表,Debezium 读 binlog 发 Kafka,库存服务消费。 幂等表唯一键是 order_id + event_type,重复消费直接 insert ignore。

Kafka 参数:acks=all,enable.idempotence=true,retries=5,delivery.timeout.ms=120000。 min.insync.replicas=2,分区 12,副本 3。 消费者 max.poll.records=200,max.poll.interval.ms=300000。 补偿用 Saga 状态机,超时 5s 重试 3 次,退避 1s、2s、4s。 每天凌晨 2 点跑对账,差异写 reconciliation_diff 表,人工只处理金额大于 1 元的。

坑 3:链路追踪断在异步线程

图片

OpenTelemetry Java agent 挂上 -javaagent:opentelemetry-javaagent.jar。 环境变量 OTEL_SERVICE_NAME=order-service,OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317。 OTEL_TRACES_SAMPLER=parentbased_traceidratio,OTEL_TRACES_SAMPLER_ARG=0.1。 生产采样 10%,排障临时 100%。 坑是 CompletableFuture 和线程池不会自动传 context,要 Context.current().wrap(runnable),MDC 也要手动塞 trace_id。

日志里我要求至少打印 trace_id 和 span_id,ELK 里用 trace_id 串。 别只存 7 天,排障经常翻 14 天前。 Grafana 面板看 P50、P95、P99,不要只看平均值,平均 80ms 可能藏着一批 3s 的请求。 这个面板我改了三次才顺眼。

图片

坑 4:K8s 参数乱抄,服务一直重启

我见过有人把 liveness initialDelaySeconds 设成 5,Spring Boot 启动要 25 秒,结果 pod 循环重启。 我的基线:readiness initialDelaySeconds=20,periodSeconds=10,failureThreshold=3。 liveness initialDelaySeconds=60,periodSeconds=10,failureThreshold=3。 resources requests cpu=250m memory=512Mi,limits cpu=1 memory=1Gi。 HPA minReplicas=3,maxReplicas=30,targetCPUUtilizationPercentage=65。

JVM 在容器里用 -XX:MaxRAMPercentage=75,别写死 -Xmx。 GC 日志开 -Xlog:gc*:file=/var/log/gc.log:time,uptime,level,tags,文件 50MB 滚动 5 个。 我们 4C8G 的 node 上,单 pod 压到 1200 QPS 时 CPU 到 70%,再高就抖。 这个数不是标准,得看你的序列化和 SQL。

图片

我的对比结论:小团队别学大厂上网格

Istio 我上过 1.20,sidecar 让 P99 多了 1.5ms 左右。 mTLS 开启后 CPU 多 10% 到 15%。 它解决多语言治理,但把复杂度沉到 YAML,出问题要同时懂 Envoy、istiod、K8s。 服务少、语言统一,Spring Cloud Gateway + Resilience4j + OpenTelemetry 更划算。 服务超过 80 个、团队超过 50 人、多语言,再考虑 mesh。

最后给你一个拆分检查清单。 能不能独立部署,能不能独立回滚,数据库是不是私有 schema。 有没有 trace_id,有没有幂等,超时和重试有没有上限。 告警能不能定位到人和服务。 七个里缺三个以上,先别拆下一个服务。 微服务不是目的,让变更更安全才是。

🏷️ 标签: