system.currenttimemillis() 是最稳、最轻量、最通用的打点时间源,适用于业务耗时统计;打点需贴近真实耗时边界,避免框架层统一拦截;应封装计时器、保留原始时间戳、注意时钟漂移与跨节点不可比性;监控需异步、采样、熔断兜底。

System.currentTimeMillis() 是 Java 中最基础的时间获取方式,它返回自 1970 年 1 月 1 日 00:00:00 UTC 起的毫秒数,精度为毫秒(实际取决于底层系统时钟,通常在 10–15ms 量级)。在系统监控打点中,它不是“最准”的,但却是“最稳”“最轻量”“最通用”的选择——尤其适合业务链路耗时统计、接口响应打点、异步任务生命周期记录等场景。
打点位置要贴近真实耗时边界
监控价值取决于打点是否真实反映用户感知或系统瓶颈。避免在包装方法、AOP 切面入口/出口统一打点,而应下沉到关键路径:
- HTTP 接口:在 Controller 方法真正开始处理请求时(如参数校验后)打起点,在 return 前打终点;不建议在 DispatcherServlet 拦截层打点,否则会混入 Spring MVC 自身开销
- DB 调用:在 Statement.execute() 或 PreparedStatement.executeUpdate() 调用前后打点,而非在 MyBatis 的 SqlSession#selectOne 外层——后者可能包含结果映射等非 DB 耗时
- RPC 调用:在 Netty ChannelHandlerContext.writeAndFlush() 发出前、以及收到 Response 后解析完成时打点,跳过序列化/反序列化环节可单独拆分统计
避免直接相减,封装成可复用的计时器
裸写 long start = System.currentTimeMillis(); ... long cost = System.currentTimeMillis() - start; 易出错(如忘记赋值、异常未覆盖终点、多线程共享变量)。推荐轻量封装:
- 使用 try-with-resources 风格的
StopWatch(如 Apache Commons Lang3 的StopWatch),或自定义简单类,构造时记录 start,close() 时计算并上报 - 关键字段保留原始时间戳(而非仅存耗时),便于后续对齐分布式 trace 时间轴(例如与 SkyWalking 的 span.timestamp 对齐)
- 打点日志建议包含:traceId、method、uri、costMs、status、startTs(毫秒时间戳)、error(如有)
注意时钟漂移与跨节点时间不可比
System.currentTimeMillis() 依赖本地系统时钟,受 NTP 校正、手动调时、虚拟机休眠等影响,可能出现回拨或跳跃。这意味着:
- 单机内耗时相减基本可靠(只要没发生严重回拨),但跨机器的“start A → end B”时间差不能直接作为链路总耗时
- 做告警时,避免用“某接口耗时 > 2s”这种绝对阈值触发,而应结合历史基线(如 P95 上浮 3 倍)+ 异常突增检测
- 若需高精度跨节点时间对齐,应引入 单调时钟(如
System.nanoTime())做本机相对计时,再通过 traceId 关联各节点的System.currentTimeMillis()做偏移估算
打点本身要有兜底,防止拖慢主流程
监控不应成为故障源。所有打点逻辑必须满足:无阻塞、无强依赖、失败静默、低开销:
- 上报异步化:打点数据写入内存 RingBuffer 或 BlockingQueue,由独立线程批量 flush 到日志文件 / Kafka / Prometheus Pushgateway
- 采样控制:高频接口(如心跳、埋点上报)默认 1% 采样,错误或超时自动升为 100%
- 资源熔断:当本地磁盘满、网络不通、上报队列积压超阈值时,自动降级为仅打印 error 日志或丢弃,不抛异常、不阻塞业务线程











