4. OpenTelemetry在医疗微服务中的落地

OpenTelemetry(简称 OTel)在医疗微服务中的落地,核心不是“装一个 Agent 就完事”,而是把 Trace、Metric、Log 三类信号与医疗业务语义绑定起来。医疗系统对可观测性的要求比一般互联网系统更严格:一次挂号请求可能跨越网关、患者服务、号源服务、支付服务、消息服务,任何一段延迟或异常都可能影响患者就诊。因此,OTel 的落地要解决四个问题:SDK 如何集成、自动埋点覆盖哪些框架、手动埋点补充哪些业务语义、医疗属性如何扩展并遵守采集规范。

一、SDK 集成:从依赖到资源属性

以 Java 微服务为例,推荐使用 OpenTelemetry Java Agent 做无侵入接入,同时通过 SDK 手动补充业务属性。Agent 启动参数如下:

java -javaagent:/opt/otel/opentelemetry-javaagent.jar \
  -Dotel.service.name=patient-service \
  -Dotel.resource.attributes=deployment.environment=prod,hospital.zone=outpatient \
  -Dotel.exporter.otlp.endpoint=http://otel-collector:4317 \
  -Dotel.traces.exporter=otlp \
  -Dotel.metrics.exporter=otlp \
  -Dotel.logs.exporter=otlp \
  -jar patient-service.jar

其中 service.name 是服务唯一标识,deployment.environment 区分生产与测试,hospital.zone 是医疗场景自定义资源属性,用于区分门诊、住院、急诊等区域。资源属性会附加到该服务产生的所有 Span 上,是后续按院区、科室聚合分析的基础。若使用 Spring Boot,也可通过 opentelemetry-spring-boot-starter 以依赖方式集成,但 Agent 方式对遗留系统更友好。

二、自动埋点:覆盖通用调用链

OTel Java Agent 默认对常见框架自动埋点,医疗微服务中需要重点确认以下覆盖:HTTP 服务端与客户端(Spring MVC、WebFlux、OkHttp)、RPC(Dubbo、gRPC)、数据库(JDBC、MyBatis)、消息队列(Kafka、RocketMQ)、缓存(Redis)。自动埋点会生成 Span 并建立父子关系,形成完整调用链。例如一次“挂号”请求,自动埋点可得到:网关 Span → 患者服务 Span → 号源服务 Span → MySQL Span → Redis Span。但自动埋点只知道“调用了什么”,不知道“这次调用属于哪个患者、哪个科室、哪类业务”,因此必须手动补充业务属性。

三、手动埋点:补充医疗业务语义

手动埋点使用 OTel API 在关键业务节点创建 Span 并设置属性。以下示例在挂号服务中创建业务 Span:

Tracer tracer = GlobalOpenTelemetry.getTracer("patient-service");
Span span = tracer.spanBuilder("register.appointment")
    .setAttribute("medical.patient_id", patientId)
    .setAttribute("medical.department", department)
    .setAttribute("medical.doctor_id", doctorId)
    .setAttribute("medical.appointment_type", "online")
    .setAttribute("medical.insurance_type", insuranceType)
    .startSpan();
try (Scope scope = span.makeCurrent()) {
    // 业务逻辑:校验号源、锁定号源、生成订单
    appointmentService.register(patientId, department, doctorId);
    span.setAttribute("medical.appointment_result", "success");
} catch (Exception e) {
    span.recordException(e);
    span.setStatus(StatusCode.ERROR, "register failed");
    span.setAttribute("medical.appointment_result", "failed");
    throw e;
} finally {
    span.end();
}

手动埋点的原则是:只在业务关键路径上创建 Span,避免为每个方法都建 Span 导致链路膨胀;属性命名统一使用 medical. 前缀,便于与通用属性区分;异常必须记录 recordException 并设置错误状态,否则链路中无法识别失败节点。

四、医疗业务属性扩展与数据采集规范

医疗属性扩展要兼顾可观测性与隐私合规。建议定义统一的属性字典,例如:medical.patient_id(患者标识,需脱敏或哈希)、medical.visit_id(就诊流水号)、medical.department(科室)、medical.doctor_id(医生标识)、medical.appointment_type(预约类型)、medical.insurance_type(医保类型)、medical.triage_level(分诊级别)。其中患者标识、医生标识等敏感字段不得明文上报,应使用哈希或内部编码替代。数据采集规范包括:Trace 采样率生产环境建议 10%~20%,急诊、支付等关键链路可提升至 100%;Metric 采集间隔 15s~60s;Log 与 Trace 通过 trace_id 关联,便于从日志跳转到链路。

OpenTelemetry 医疗微服务落地:SDK → 自动埋点 → 手动埋点 → 属性扩展 ① SDK 集成层 Java Agent 启动参数 service.name / 环境 hospital.zone 资源属性 OTLP → Collector ② 自动埋点层(框架覆盖) HTTP / Spring MVC Dubbo / gRPC JDBC / MyBatis Kafka / RocketMQ Redis 缓存 ③ 手动埋点层(业务 Span) register.appointment setAttribute 业务属性 recordException 异常 setStatus 错误状态 ④ 医疗属性扩展与采集规范 patient_id(脱敏) department 科室 triage_level 分诊 采样率 10%~100% trace_id 关联日志

落地时建议按“先自动、后手动、再规范”的顺序推进:先通过 Agent 获得基础调用链,确认服务间拓扑;再在挂号、缴费、报告查询等核心业务路径补充手动 Span;最后统一属性字典和采样策略,并接入 Collector 做属性过滤与脱敏。这样既能快速看到链路,又能逐步把医疗业务语义沉淀为可查询、可告警、可复盘的观测数据。

医疗属性中涉及患者身份、诊断、处方等敏感信息时,应遵循最小必要原则:只采集排障与统计必需的字段,敏感值使用哈希或内部编码,禁止将明文身份证号、手机号、诊断文本写入 Span 属性或日志。