外观
异常的检测包括哪些内容
⭐ 题目日期:
阿里 - 2025/8/19
📝 题解:
面试回答:异常的检测包括哪些内容
异常的检测,它并非单指代码层面的 try-catch
,而是一个贯穿于应用、系统和业务全流程的、多层次的监控和告警体系。其核心目标是及时发现、定位并响应那些偏离了正常预期状态的事件。
原理
从原理上讲,异常检测主要分为三个层次,构建起一个纵深的防御体系:
代码层面的异常捕获 (Application Level):这是最基础也是最直接的异常检测。它通过编程语言提供的异常处理机制(如 Java 的
try-catch-finally
,Go 的panic-recover
)来捕获在代码执行过程中发生的错误。这些异常通常包括空指针、数据库连接失败、第三方服务调用超时等。捕获到的异常必须被详细记录,包含完整的堆栈信息、请求上下文(如 TraceID、用户ID、请求参数),以便于问题的快速定位和复现。系统/基础设施层面的指标监控 (System/Infrastructure Level):应用是运行在具体的基础设施之上的,其健康状况直接影响应用的稳定性。这部分检测主要关注基础设施的性能指标(Metrics),通过设定合理的阈值来预警潜在风险。主要监控内容包括:
- 主机指标:CPU 使用率、内存占用、磁盘 I/O、网络带宽等。例如,CPU 使用率突然飙升可能意味着某个进程出现死循环或进行了大量的计算。
- 运行时指标:针对特定技术栈的监控,例如 Java 应用的 JVM 堆内存使用情况、GC(垃圾回收)的频率和单次耗时、线程池活跃线程数等。频繁的 Full GC 或单次耗时过长,都可能预示着内存泄漏或性能瓶颈。
- 中间件与存储指标:数据库的连接池使用率、慢查询数量、QPS/TPS;消息队列的消息堆积量、生产/消费速率;缓存服务的命中率、内存占用等。这些指标的异常波动往往是服务出现问题的直接体现。
业务层面的状态监控 (Business Level):技术指标正常不完全代表业务是正常的。业务层面的异常检测是更高维度的监控,它关注核心业务流程的健康度,确保业务目标的达成。例如:
- 核心“黄金”指标:电商系统的下单成功率、支付成功率;社交应用的内容发布数量、评论数等。这些指标的异常下跌,即使没有收到任何技术报错,也可能意味着某个业务环节出现了严重的逻辑错误。
- 用户行为模式:单位时间内的用户注册量、登录失败次数、搜索量等。例如,登录失败次数在短时间内激增,可能意味着系统遭遇了恶意的撞库攻击。
这三个层次由内到外,从代码执行、系统资源到业务逻辑,构成了完整的异常检测体系。
应用场景
基于以上原理,异常检测在实际工作中有非常广泛的应用:
- 线上故障的实时告警:这是最核心的应用场景。例如,当错误日志的产生速率超过预设阈值时,或者接口的 99 分位响应时间突然增长 50% 时,监控系统会通过电话、短信或即时通讯工具(如钉钉、企业微信)向负责的工程师发送告警,使其能第一时间介入处理。
- 服务性能瓶颈分析:通过持续监控应用的各项性能指标(如接口耗时、数据库查询时间),我们可以发现并定位系统的性能瓶-。例如,借助 APM(应用性能管理)工具,我们可以清晰地看到一个请求在哪个微服务、哪个方法、甚至哪条 SQL 语句上耗时最长,从而进行针对性优化。
- 潜在风险的提前预警:系统层面的监控可以帮助我们防患于未然。比如,当服务器的磁盘使用率达到 85% 时,系统可以自动发出预警,提醒运维人员及时清理或扩容,避免因磁盘写满导致整个服务不可用。
- 业务状态的健康度巡检:对于核心业务,我们会设置“黄金指标”监控大盘。例如电商大促期间,会实时监控在线用户数、下单量、支付成功率等。如果发现下单量远低于预期,即使没有收到任何技术报错,也可能意味着上游的推荐服务或价格服务出现了逻辑异常。
示例
我们以一个电商系统的“创建订单”接口为例,来说明一个完整的异常检测流程是如何工作的:
代码层面:在
createOrder
服务中,核心逻辑被try-catch
块包裹。try
内部包含了参数校验、调用库存服务扣减库存、调用价格服务计算价格、生成订单并写入数据库等步骤。catch
会捕获不同类型的异常。例如,库存服务返回“库存不足”,这是一个业务异常,我们会记录警告(WARN)日志并给用户返回明确的提示。如果数据库写入时发生SQLException
,这是一个系统异常,需要记录严重错误(ERROR)日志,并立即触发高级别告警。
// 伪代码 public Order createOrder(CreateOrderRequest request) { // 附上全局唯一的 TraceID MDC.put("traceId", request.getTraceId()); try { // ... 核心业务逻辑 ... inventoryService.deductStock(...); priceService.calculatePrice(...); orderRepository.save(order); return order; } catch (InsufficientStockException e) { // 已知的业务异常,记录警告信息,无需告警 log.warn("创建订单失败:库存不足。 requestId={}", request.getRequestId(), e); throw new BusinessException(ErrorCode.INSUFFICIENT_STOCK); } catch (Exception e) { // 未知的系统异常,记录严重错误,并触发告警 log.error("创建订单时发生未知系统异常。 request={}", request.toString(), e); // 此处的日志通常会被日志系统收集并用于告警 throw new SystemException(ErrorCode.INTERNAL_SERVER_ERROR); } finally { MDC.clear(); } }
系统层面:我们会使用 Prometheus + Grafana 监控体系,并配置 Alertmanager 进行告警。
- 监控指标:我们会监控
createOrder
接口的 P99 响应时间、HTTP 状态码(5xx 错误率)、JVM 的 GC 次数和耗时,以及数据库连接池的活跃连接数。 - 告警规则:配置“如果
createOrder
接口在过去 5 分钟内 5xx 错误率超过 1%,则立即发送严重告警到订单服务负责人”。
- 监控指标:我们会监控
业务层面:
- 监控指标:通过日志或数据上报,实时计算“每分钟下单成功率”。计算公式为:
成功创建的订单数 / 调用创建订单接口的总次数
。 - 告警规则:配置“如果下单成功率在过去 10 分钟内,相比昨天同一时段下降超过 20%,则发送业务告警给产品经理和技术负责人”。这种告警能在技术指标完全正常的情况下,发现隐藏的业务逻辑问题。
- 监控指标:通过日志或数据上报,实时计算“每分钟下单成功率”。计算公式为:
通过这三层监控,我们可以确保无论是代码缺陷、资源瓶颈还是业务逻辑错误,都能被及时地检测到。
面试官可能的追问
追问1:你在日志记录时,有哪些最佳实践?
回答要点: 这是一个考察代码规范和工程化能力的问题。
- 结构化日志:采用 JSON 格式进行日志输出。这便于后续的日志采集、索引和查询分析(例如使用 ELK 或 Loki)。日志中应包含时间戳、日志级别、线程名、TraceID、以及具体的业务信息。
- 明确的日志级别:合理使用
INFO
,WARN
,ERROR
等级别。INFO
用于记录核心流程的关键信息;WARN
用于记录可预期的、但需要关注的业务异常(如用户输入参数错误);ERROR
用于记录非预期的、导致功能中断的系统异常。 - 包含上下文信息:日志中必须包含能够唯一标识一次请求的 TraceID。在排查问题时,可以通过一个 TraceID 串联起该请求在所有微服务中的完整日志,极大提高效率。同时,关键的业务参数(如订单号、用户ID)也应记录。
- 避免打印敏感信息:绝对不能在日志中记录用户的密码、身份证号、银行卡号等敏感数据,需要进行脱敏处理。
- 注意日志性能:避免在循环中大量打印日志;对于
DEBUG
级别的日志,要使用log.isDebugEnabled()
判断,避免不必要的字符串拼接开销。
追问2:当系统产生大量告警时,你们是如何处理“告警风暴”问题的?
回答要点: 这是一个考察SRE(网站可靠性工程)实践经验的问题。
- 告警收敛与聚合:在告警系统(如 Alertmanager)中配置分组规则。例如,将 1 分钟内由同一台主机、同一个服务实例产生的所有同类告警聚合成一条通知,并注明影响范围和触发次数。
- 告警依赖与抑制:配置告警依赖关系。例如,当数据库实例宕机的告警触发时,所有依赖该数据库的应用服务产生的“数据库连接失败”告警都应该被自动抑制,因为它们只是根本原因的“症状”。
- 告警分级与渠道分离:对告警进行优先级划分(如 P0-紧急,P1-重要,P2-次要)。P0 级别的告警通过电话或短信通知,要求立即响应;P1 级别的通过钉钉等即时通讯工具强提醒;P2 级别的可能只发送邮件或进入待办列表。
- 定期回顾与优化:定期复盘线上告警,识别并优化那些频繁触发但无实际意义的“噪音”告警,持续调整监控阈值和告警规则。
追问3:在微服务架构下,一个请求跨越了多个服务,你如何快速定位是哪个服务出了问题?
回答要点: 这是考察对分布式系统可观测性的理解。
- 分布式链路追踪 (Distributed Tracing):这是解决该问题的核心技术。通过引入一个全局唯一的 TraceID,当一个请求进入系统时,这个 TraceID 会在整个调用链中传递。每个服务在处理请求时,都会记录下自己的 SpanID(表示本次处理的单元)以及 TraceID。
- 工具与标准:我们可以使用像 OpenTelemetry 这样的开放标准,并结合 Jaeger、SkyWalking 或 Pinpoint 等 APM 工具来实现链路追踪。
- 定位过程:当一个请求失败或超时时,我们可以根据其 TraceID 在 APM 系统中查到完整的调用链拓扑图。图上会清晰地展示出每个服务的执行耗时、调用关系和状态。哪个服务的 Span 耗时最长,或者哪个服务的 Span 标记为 error 状态,就说明问题大概率出在该服务,从而实现快速定位。
追问4:你提到的业务指标监控,在技术上通常如何实现?
回答要点: 这是考察技术落地能力。 主要有三种方式:
- 基于日志:在代码的关键业务节点(如“下单成功”、“支付成功”)打印结构化的业务日志。然后通过日志采集系统(如 Logstash, Fluentd)将日志发送到大数据平台或日志分析系统(如 Elasticsearch),在其中进行实时的聚合、统计和告警。
- 基于指标系统 (Metrics):使用 Prometheus、InfluxDB 等时序数据库。在代码中引入 Micrometer 这样的客户端库,当业务事件发生时,直接调用
counter.increment()
或gauge.set()
等方法来更新指标。这种方式相比日志更轻量,查询性能也更好。 - 基于事件总线/消息队列:将业务事件(如
OrderCreatedEvent
)发送到消息队列(如 Kafka, RocketMQ)。下游可以有专门的数据处理服务(如 Flink, Spark Streaming)来消费这些事件,进行实时计算、聚合,并将结果写入数据库或直接用于监控告警。这种方式最为灵活和强大,适用于复杂的业务指标计算场景。