system.currenttimemillis() 虽非最准但最稳轻易落地,打点需贴近真实业务边界,http、db、rpc 各有推荐位置,应封装避免裸写,日志需含 traceid 等关键字段,警惕时钟回拨,告警用动态基线,打点须异步采样熔断。

用 System.currentTimeMillis() 记录接口耗时,不是最准的,但确实最稳、最轻、最易落地——尤其适合快速上线监控、排查偶发慢请求、验证优化效果。
打点位置要贴近真实业务边界
耗时统计失真的常见原因,不是代码写错了,而是打点位置漂了。比如在 DispatcherServlet 入口打起点,实际把 Spring MVC 解析参数、匹配 Handler 的开销也算了进去;在 MyBatis 的 mapper 方法外层打终点,又混入了结果映射、类型转换等非 DB 时间。
- HTTP 接口:起点放在 Controller 方法内、参数校验通过后;终点放在 return 前(或 finally 块中)
- DB 操作:起点在 PreparedStatement.execute() 调用前,终点在 execute() 返回后
- RPC 调用:起点在序列化完成、准备 writeAndFlush() 前;终点在反序列化完成、业务对象构建好之后
别裸写 start/end,封装成可复用结构
直接写两行 currentTimeMillis() 看似省事,但容易漏写、异常不覆盖、多线程误共享。推荐用轻量封装:
- 用 Apache Commons Lang3 的 StopWatch,支持 start/stop/reset,还能分段计时
- 自定义简单 Timer 类:构造时记 startTs,close() 时算 costMs 并返回原始时间戳
- 日志里至少保留 traceId、uri、status、costMs 和 startTs(毫秒级时间戳),方便后续和分布式链路对齐
警惕时钟问题,别跨节点硬比时间
System.currentTimeMillis() 依赖本地系统时钟,受 NTP 校正、虚拟机休眠、手动调时影响,可能出现回拨或跳跃。
- 单机内耗时相减基本可靠(只要没严重回拨),但 A 机器的 start 和 B 机器的 end 不能直接相减算链路总耗时
- 告警逻辑避免“耗时 > 2s”这种静态阈值,改用动态基线(如近 1 小时 P95 上浮 2.5 倍)+ 突增检测
- 高频打点时,可同步记录 System.nanoTime() 作辅助校验(仅用于同一线程内差值比对,不替代 currentTimeMillis)
日志与上报要异步、采样、带熔断
打点本身不该成为性能瓶颈。尤其在高并发或压测场景下,同步写日志可能拖慢真实耗时,甚至引发雪崩。
- 使用异步日志框架(如 Logback 的 AsyncAppender),或缓冲批量写入
- 对低优先级接口做采样(例如只记录 P99 以上或错误请求),降低存储与解析压力
- 加熔断保护:当打点模块自身延迟超阈值(如 > 50ms)或失败率过高时,自动降级为只记内存指标或暂停上报











