1. 线上问题排查的黄金法则:从现象到本质的完整链路
当服务器监控大屏突然飘红,当用户投诉如雪花般涌来,每个技术人都会经历这样的惊魂时刻。线上问题排查不是简单的"看日志-改代码-重启服务"三板斧,而是一场需要严密逻辑和丰富经验的侦探游戏。我经历过凌晨三点被叫醒处理数据库连接池耗尽,也处理过诡异的偶发性接口超时,这些经历让我总结出一套完整的排查方法论。
线上问题的核心特征在于其突发性和不确定性。与开发环境不同,生产系统的变量呈指数级增长:用户行为不可预测、网络环境复杂多变、第三方依赖随时可能出问题。我曾遇到过一个案例:某电商App在促销期间突然出现支付失败,最终发现是CDN节点缓存了错误的静态资源版本。这种问题在测试环境永远无法复现,却能在线上造成巨大损失。
2. 日志系统的战略级部署
2.1 日志采集的三大维度
没有完善的日志系统,排查线上问题就像在黑暗中摸索。理想的日志体系应该包含:
- 请求链路日志:记录每个请求的完整生命周期,包括入参、关键处理节点、出参。对于Java项目,我习惯用MDC(Mapped Diagnostic Context)实现traceId透传。以下是Nginx的日志配置示例:
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" ' 'rt=$request_time uct="$upstream_connect_time" ' 'uht="$upstream_header_time" urt="$upstream_response_time"';系统资源日志:包括CPU、内存、磁盘IO、网络等指标。推荐使用Prometheus+Grafana组合,采集频率建议设置在10-15秒间隔。曾经有个服务频繁OOM,通过分析Prometheus的内存增长曲线,最终定位到是缓存组件没有设置过期时间。
业务埋点日志:记录核心业务动作,比如用户下单、支付回调等。这类日志需要特别注意敏感信息脱敏,我曾经见过把完整银行卡号打印到日志的案例。
2.2 日志分级策略
不同级别的日志要有明确的使用规范:
- DEBUG:开发环境专用,记录详细执行路径
- INFO:生产环境运行状态,如服务启动、定时任务触发
- WARN:异常但不影响主流程,比如重试操作
- ERROR:需要立即干预的问题,如数据库连接失败
关键经验:ERROR日志必须包含足够上下文,我曾见过最糟糕的错误日志是"系统出错:null",这种日志除了制造焦虑毫无价值。
3. 问题定位的六步诊断法
3.1 现象确认与影响评估
当报警响起时,首先要做的是确认问题现象的真实性。上周我们收到Redis集群报警,实际检查发现是监控系统自身网络抖动导致的误报。快速验证方法包括:
- 直接访问接口验证
- 查看用户行为埋点数据
- 检查基础设施状态(网络、中间件)
同时要评估影响范围:
- 受影响用户比例
- 核心业务功能是否可用
- 是否有资损风险
3.2 关键指标四象限分析
将监控指标分为四个维度交叉分析:
- 流量指标:QPS、并发数、带宽
- 错误指标:错误码分布、异常堆栈
- 耗时指标:平均响应时间、P99/P999
- 资源指标:CPU利用率、内存使用率、线程数
曾经处理过一个API响应变慢的问题,通过四象限分析发现:流量无明显变化,但P99耗时从200ms飙升到2s,同时CPU使用率下降。最终定位到是数据库连接池配置过小导致请求堆积。
3.3 调用链追踪实战
分布式系统没有调用链追踪就像没有地图的迷宫。以Spring Cloud Sleuth+Zipkin为例,要注意:
- 确保traceId在异步线程间传递
- 对MQ消息等异步场景要做特殊处理
- 采样率要根据流量动态调整
最近排查的一个诡异问题:用户偶尔收不到推送通知。通过调用链发现,当推送服务与用户服务部署在同一物理机时会出现跨进程调用超时,最终确认是主机防火墙策略冲突。
4. 性能优化的三重境界
4.1 基础优化:从低垂果实开始
SQL优化:通过EXPLAIN分析执行计划,重点关注:
- 全表扫描(type=ALL)
- 临时表(Using temporary)
- 文件排序(Using filesort)
最近优化的一个慢查询案例:
-- 优化前 SELECT * FROM orders WHERE status = 'PAID' ORDER BY create_time DESC; -- 优化后 SELECT * FROM orders FORCE INDEX(idx_status_create_time) WHERE status = 'PAID' ORDER BY create_time DESC LIMIT 100;缓存策略:
- 热点数据用本地缓存(Caffeine)
- 分布式缓存(Redis)要设置合理的过期时间
- 注意缓存击穿问题,用互斥锁或空值缓存应对
4.2 进阶优化:并发与资源控制
线程池调优:不要使用Executors的默认实现,建议自定义:
new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, new LinkedBlockingQueue(1000), // 根据业务特点设置 new CustomThreadFactory(), // 命名线程便于排查 new ThreadPoolExecutor.AbortPolicy());限流策略:
- 网关层限流(Nginx漏桶算法)
- 应用层限流(Guava RateLimiter)
- 分布式限流(Redis+Lua)
4.3 终极优化:架构改造
对于长期存在的性能瓶颈,可能需要:
- 数据分片(如用户ID哈希分库)
- 读写分离(CQRS模式)
- 异步化改造(事件驱动架构)
去年我们重构了一个报表系统,将实时统计改为预计算+增量更新,查询性能提升了40倍。
5. 防御性编程的艺术
5.1 熔断与降级策略
熔断器配置(以Hystrix为例):
@HystrixCommand( fallbackMethod = "defaultProducts", commandProperties = { @HystrixProperty(name="circuitBreaker.requestVolumeThreshold", value="20"), @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds", value="5000") } ) public List<Product> getProducts() {...}降级方案设计原则:
- 核心功能与非核心功能隔离
- 静态降级优于动态降级
- 降级结果要可监控
5.2 混沌工程实践
通过主动注入故障来验证系统韧性,常用手段包括:
- 网络延迟/丢包(使用tc命令)
- 服务进程随机kill
- 磁盘空间写满
我们每月会进行"故障演练日",最近一次发现当Redis响应时间超过500ms时,系统会雪崩,于是增加了后备本地缓存策略。
6. 排查工具箱精选
6.1 Linux系统级工具
快速定位高负载:
# 综合视图 htop # IO瓶颈定位 iostat -x 1 # 网络连接分析 ss -antp | grep ESTAB内存分析:
# 查看内存占用排序 ps aux --sort=-%mem | head # Java内存dump jmap -dump:format=b,file=heap.hprof <pid>
6.2 JVM专项工具
快速诊断命令:
# 查看线程栈 jstack <pid> > thread.txt # 分析GC情况 jstat -gcutil <pid> 1000 5Arthas实战技巧:
# 监控方法调用 watch com.example.service.*Service * '{params,returnObj}' -x 3 # 追踪耗时 trace com.example.Controller * '#cost>100'
6.3 网络分析利器
TCP抓包分析:
tcpdump -i eth0 -w packet.pcap port 8080HTTP请求追踪:
curl -v --trace-time http://example.com/api
7. 构建可持续的排障体系
7.1 事后复盘模板
每个线上问题都应该产出复盘报告,包含:
- 故障时间线(Timeline)
- 根因分析(5 Why分析法)
- 改进措施(短期/长期)
- 知识沉淀(内部Wiki归档)
7.2 监控体系升级
黄金指标(Google SRE标准):
- 请求量
- 错误率
- 延迟
- 饱和度
智能报警设置:
- 避免报警风暴(设置抑制规则)
- 动态基线报警(比上周同期增长50%)
- 关联分析报警(错误率上升且延迟增加)
7.3 演练常态化
建议每季度进行:
- 压测演练(逐步增加流量)
- 故障注入演练(随机kill服务)
- 容灾演练(机房级故障模拟)
最近一次演练暴露出我们的ZooKeeper集群没有配置跨机房容灾,这个隐患在真实故障前被及时修复。