Posted in

万声音乐Go可观测性体系构建实录:OpenTelemetry + Prometheus + Grafana黄金三角配置模板(含告警阈值清单)

第一章:万声音乐Go可观测性体系构建实录:OpenTelemetry + Prometheus + Grafana黄金三角配置模板(含告警阈值清单)

在万声音乐微服务架构中,Go语言服务的可观测性需统一采集指标、日志与追踪数据。我们采用OpenTelemetry SDK作为唯一数据采集入口,通过otelhttp中间件和runtime/process仪器化模块实现零侵入式埋点。

OpenTelemetry Go SDK 集成要点

在服务启动时初始化全局TracerProvider与MeterProvider,并配置OTLP exporter指向本地Collector:

import (
  "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
  "go.opentelemetry.io/otel/sdk/trace"
  "go.opentelemetry.io/otel/sdk/metric"
)

func initTracing() {
  client := otlptracegrpc.NewClient(otlptracegrpc.WithEndpoint("localhost:4317"), otlptracegrpc.WithInsecure())
  tp := trace.NewTracerProvider(trace.WithBatcher(exporter))
  otel.SetTracerProvider(tp)
}

该配置确保HTTP请求延迟、goroutine数、GC暂停时间等关键运行时指标自动上报。

Prometheus 采集配置模板

prometheus.yml中定义静态job,启用OpenTelemetry Collector暴露的/metrics端点:

- job_name: 'otel-collector'
  static_configs:
  - targets: ['otel-collector:8888']  # 默认metrics端口

Grafana 仪表盘核心视图

导入ID为13906的Grafana官方OpenTelemetry仪表盘,并补充以下自定义面板:

  • Go Runtime Metrics:go_goroutines, go_memstats_alloc_bytes, go_gc_duration_seconds_quantile
  • HTTP SLI:http_server_duration_seconds_bucket{le="0.2"}(P95

关键告警阈值清单

指标名称 阈值 告警级别 触发条件
go_goroutines > 5000 Critical 持续5分钟超限,提示协程泄漏
http_server_duration_seconds_bucket{le="0.2"} Warning P95延迟突破200ms
otelcol_exporter_queue_capacity Critical Exporter队列积压超阈值

所有告警通过Prometheus Alertmanager路由至企业微信机器人,响应延迟低于15秒。

第二章:可观测性三大支柱的Go语言原生落地实践

2.1 OpenTelemetry Go SDK接入规范与Instrumentation最佳实践

初始化 SDK 的最小可行配置

需显式设置 TracerProviderMeterProvider,并注册全局实例:

import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/sdk/trace"
    "go.opentelemetry.io/otel/sdk/metric"
)

func initTracing() {
    tp := trace.NewTracerProvider(
        trace.WithSampler(trace.AlwaysSample()), // 强制采样便于调试
        trace.WithResource(resource.MustNewSchemaVersion("1.0.0")),
    )
    otel.SetTracerProvider(tp) // 全局生效

    mp := metric.NewMeterProvider()
    otel.SetMeterProvider(mp)
}

逻辑分析:trace.WithSampler 控制链路采样策略;resource.MustNewSchemaVersion 声明语义约定版本,确保 span 属性兼容性;otel.Set*Provider 是全局单例绑定关键步骤。

Instrumentation 关键原则

  • ✅ 在业务入口(HTTP handler、gRPC server)创建 span
  • ❌ 避免在循环内高频 Start() span
  • 🔄 使用 context.WithValue(ctx, key, value) 传递 span context

常见 Span 属性对照表

场景 推荐属性键 示例值
HTTP 方法 http.method "GET"
数据库操作类型 db.operation "SELECT"
错误分类 error.type(非标准,建议用) "io_timeout"

自动化注入流程

graph TD
    A[HTTP Handler] --> B{otelhttp.Middleware}
    B --> C[Start span with request info]
    C --> D[Inject context into handler]
    D --> E[End span on response write]

2.2 自动化与手动埋点协同策略:HTTP/gRPC/DB/Cache全链路覆盖方案

为实现端到端可观测性,需融合自动化注入与精准手动埋点:HTTP层通过中间件自动采集请求路径、状态码与耗时;gRPC 利用拦截器(Interceptor)透传 trace_id;DB 层基于 JDBC/ORM 插桩捕获 SQL 类型与执行时长;Cache 层在 RedisTemplate 或 Lettuce 客户端封装中注入上下文传播逻辑。

数据同步机制

// 手动埋点示例:DB 操作增强
Tracer tracer = GlobalTracer.get();
Span span = tracer.buildSpan("db.query").asChildOf(parentSpan).start();
try (Scope scope = tracer.scopeManager().activate(span)) {
    // 执行查询
    return jdbcTemplate.query(sql, rowMapper);
} finally {
    span.setTag("sql.type", "SELECT");
    span.setTag("db.instance", "user_db");
    span.finish(); // 必须显式结束以触发上报
}

该代码确保 DB 调用被独立追踪并携带父 Span 上下文;asChildOf(parentSpan) 维持链路连续性;sql.typedb.instance 为关键业务维度标签,供后续多维分析使用。

协同埋点能力对比

组件 自动化覆盖率 手动扩展点 典型埋点粒度
HTTP 100% 请求体采样开关 接口级 + 参数脱敏
gRPC 95% 方法级自定义 tag RPC 方法 + 错误码
DB 70%(仅连接) SQL 执行前后钩子 语句类型 + 慢查标记
Cache 60% Key 命名空间标注 缓存命中率 + TTL

链路贯通流程

graph TD
    A[HTTP Gateway] -->|trace_id| B[gRPC Client]
    B -->|inject context| C[DB Access]
    C -->|propagate| D[Redis Cache]
    D -->|return result| A

2.3 Trace上下文透传与跨服务Span生命周期管理(含Context取消与超时控制)

上下文透传的核心机制

OpenTracing/OTel SDK 依赖 TextMapPropagator 在 HTTP Header 中注入/提取 traceparenttracestate。关键在于保持 TraceID-SpanID-ParentID-Flags 四元组的端到端一致性。

跨服务 Span 生命周期协同

Span 创建即绑定至当前 Context,跨线程/异步调用需显式传递:

// 使用 OpenTelemetry Java SDK 显式传播上下文
Context parent = Context.current().with(Span.current());
CompletableFuture.runAsync(() -> {
  try (Scope scope = parent.makeCurrent()) {
    Span span = tracer.spanBuilder("db-query").startSpan();
    // ... 执行数据库操作
    span.end();
  }
}, executor);

逻辑分析parent.makeCurrent() 将父 Span 注入当前线程的 ContextRegistryScope 确保 span.end()Span.current() 始终可查。若未显式传播,子任务将生成孤立 Span,破坏链路完整性。

Context 取消与超时联动

场景 行为
Context.withCancel() 返回可取消 Context + CancellationToken
Timeout.timeout(...) 自动注册 CancellationToken 并触发 Span.setStatus(StatusCode.ERROR)
graph TD
  A[发起请求] --> B[创建带超时的Context]
  B --> C[启动Span并绑定]
  C --> D{超时是否触发?}
  D -->|是| E[自动结束Span+标记ERROR]
  D -->|否| F[正常endSpan]

2.4 Metrics采集建模:Go运行时指标+业务自定义指标双轨采集器设计

为实现可观测性闭环,需同时捕获底层运行时状态与高层业务语义。双轨采集器采用 prometheus.Collector 接口统一抽象,但内部隔离数据源与生命周期。

架构分层

  • 运行时轨:基于 runtime.ReadMemStats + debug.ReadGCStats 定期快照
  • 业务轨:通过 prometheus.NewGaugeVec 动态注册带标签的指标(如 order_status{state="paid"}

核心采集器实现

type DualMetricsCollector struct {
    rtMetrics *runtimeMetrics // Go runtime 指标集
    bizGauges *prometheus.GaugeVec // 业务指标向量
}

func (d *DualMetricsCollector) Collect(ch chan<- prometheus.Metric) {
    d.rtMetrics.Collect(ch) // 同步采集内存/GC/协程数
    d.bizGauges.Collect(ch) // 异步采集业务状态(避免阻塞)
}

Collect() 方法不加锁调用两个子采集器,依赖 Prometheus 的拉取周期天然串行化;bizGauges 使用 WithLabelValues() 动态构造指标,支持高基数业务维度。

指标分类对照表

维度 运行时指标示例 业务指标示例
类型 Gauge Counter / Histogram
更新频率 每10s一次 事件驱动(如支付成功)
标签粒度 无标签 service, endpoint, region
graph TD
    A[HTTP /metrics] --> B[Prometheus Scrape]
    B --> C{DualMetricsCollector}
    C --> D[Runtime Sub-collector]
    C --> E[Business Sub-collector]
    D --> F[memstats, goroutines, gc_pauses]
    E --> G[order_count_total, payment_latency_seconds]

2.5 Logs-to-Metrics转换实践:结构化日志提取关键观测维度并注入Prometheus Label

将半结构化日志(如 JSON 格式 Nginx 访问日志)转化为高维时序指标,是可观测性落地的关键跃迁。

日志解析与维度提取

使用 promtailpipeline_stages 提取路径、状态码、服务名等标签:

- json:
    expressions:
      path: "http.request.path"
      status: "http.response.status_code"
      service: "service.name"
- labels:
    path: ""
    status: ""
    service: ""

该配置从 JSON 日志中提取字段,并将其自动注入为 Prometheus label;空字符串 "" 表示启用该字段作为 label 键,不作值映射。

指标聚合逻辑

通过 loki-metrics-generator 将带 label 的日志流聚合为 http_requests_total 计数器:

Label Key Example Value Purpose
path /api/users 路由粒度分析
status 200 状态码分布监控
service user-service 多租户/多服务隔离

数据同步机制

graph TD
  A[JSON Log Stream] --> B[Promtail Parse & Label]
  B --> C[Loki Storage]
  C --> D[Loki Metrics Generator]
  D --> E[Prometheus /metrics endpoint]

标签一致性保障了 service="auth" 的日志可直接关联至 http_requests_total{service="auth"} 指标。

第三章:Prometheus服务端深度调优与Go生态适配

3.1 Go应用专属Exporter开发:基于promhttp与Gauge/Counter/Histogram的实时指标暴露

Go 应用内建 Prometheus Exporter 的核心在于将业务状态映射为标准指标类型,并通过 promhttp 暴露 HTTP 接口。

指标注册与初始化

import (
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

// 注册三种核心指标
reqCounter := prometheus.NewCounter(prometheus.CounterOpts{
    Name: "app_http_requests_total",
    Help: "Total number of HTTP requests processed",
})
reqDuration := prometheus.NewHistogram(prometheus.HistogramOpts{
    Name:    "app_http_request_duration_seconds",
    Help:    "Latency distribution of HTTP requests",
    Buckets: prometheus.DefBuckets, // [0.005, 0.01, ..., 10]
})
activeGauge := prometheus.NewGauge(prometheus.GaugeOpts{
    Name: "app_active_connections",
    Help: "Current number of active connections",
})

// 必须显式注册到默认注册表
prometheus.MustRegister(reqCounter, reqDuration, activeGauge)

逻辑分析Counter 仅支持 Inc()/Add(),适用于累计事件;Histogram 自动分桶并计算 _sum/_count/_bucketGauge 支持 Set()/Add(),适合瞬时值(如连接数)。所有指标需调用 MustRegister() 才能被 /metrics 端点采集。

HTTP 暴露配置

http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(":9090", nil))

常见指标语义对照表

指标类型 典型用途 是否可减 示例操作
Counter 请求总数、错误次数 Inc(), Add()
Gauge 内存使用、并发数 Set(), Add()
Histogram 响应延迟、处理耗时 Observe(0.23)

数据同步机制

  • 指标更新在业务逻辑关键路径中同步调用(如 HTTP middleware 中 Observe(time.Since(start))
  • 无额外 goroutine 或缓冲,避免竞态与延迟
  • promhttp.Handler() 在每次请求时原子读取指标快照,保证一致性

3.2 Prometheus联邦与分片采集架构:支撑万声音乐千级Go微服务实例的高效抓取

为应对千级Go微服务实例带来的指标爆炸(单集群超120万series/秒),万声音乐采用两级联邦+水平分片采集架构。

分片策略设计

  • 按服务拓扑维度划分:api-*auth-*billing-* 三组独立Prometheus实例
  • 每组配置专属scrape_interval: 15ssample_limit: 50000
  • 全局联邦节点仅拉取关键SLO指标(http_request_duration_seconds_bucket等)

联邦配置示例

# global-federate.yml
scrape_configs:
- job_name: 'federate-api'
  metrics_path: '/federate'
  params:
    'match[]':
      - '{job="api-service", le="0.2"}'  # 只聚合P90以下延迟桶
      - 'rate(http_requests_total[1h])'
  static_configs:
    - targets: ['prom-api-01:9090', 'prom-api-02:9090']

该配置使联邦层带宽降低76%,避免全量指标回传导致的网络拥塞;match[]精准限定聚合范围,防止高基数标签污染全局TSDB。

数据同步机制

graph TD
  A[Go微服务] -->|15s scrape| B[Shard Prometheus]
  B -->|/federate + match[]| C[Global Federator]
  C --> D[Grafana + Alertmanager]
维度 分片层 联邦层
存储周期 4h 30d
查询延迟
标签保留粒度 全量 仅保留job/instance/le

3.3 Recording Rules预聚合优化:降低高基数标签对TSDB写入压力与查询延迟

高基数标签(如 user_idrequest_id)直接写入Prometheus会导致时间序列爆炸,显著增加存储开销与查询延迟。Recording Rules通过服务端预聚合,将高频原始指标降维为稳定、低基数的衍生指标。

预聚合核心实践

  • prometheus.yml 中启用 rule_files 并定义 .rules.yml
  • 聚合粒度需匹配查询 SLA(如 5m 窗口适配分钟级监控看板)
  • 避免在 recording rule 中使用 count by (user_id) 等保留高基数标签的操作

示例:HTTP 请求速率降维

# http_aggr.rules.yml
groups:
- name: http_recording_rules
  rules:
  - record: job:http_requests_total:rate5m
    expr: sum by (job, code, method) (rate(http_requests_total[5m]))

逻辑分析rate() 先做滑动窗口斜率计算,sum by (job, code, method) 移除实例、pod 等高基数标签,仅保留业务维度。结果序列数从 O(10k+) 降至 O(100) 级别,写入吞吐提升3–5倍。

维度 原始指标(每秒) Recording后(每5分钟)
时间序列数 24,860 132
平均写入延迟 18ms 2.1ms
查询 P95 延迟 1.2s 140ms

执行流程示意

graph TD
  A[Raw Metrics<br>http_requests_total{instance,pod,user_id}] --> B[rate[5m]]
  B --> C[sum by job,code,method]
  C --> D[Stored Series<br>job:...:rate5m]
  D --> E[Low-latency Query]

第四章:Grafana可视化体系与SLO驱动告警工程

4.1 Go服务黄金信号看板:Latency、Traffic、Errors、Saturation四维动态仪表盘模板

核心指标采集骨架

// Prometheus 指标注册示例(Go服务端)
var (
    reqLatency = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "Latency distribution of HTTP requests",
            Buckets: prometheus.DefBuckets, // [0.005, 0.01, ..., 10]
        },
        []string{"method", "status_code", "route"},
    )
    reqTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total number of HTTP requests",
        },
        []string{"method", "status_code"},
    )
)

func init() {
    prometheus.MustRegister(reqLatency, reqTotal)
}

该代码注册了延迟(Latency)与流量(Traffic)两大黄金信号基础指标。HistogramVec支持按路由/状态码多维切片,DefBuckets覆盖典型Web延时分布;CounterVec自动累加请求总量,为Errors(失败率=5xx计数/总计数)提供分母支撑。

四维联动逻辑

维度 数据源 关键衍生指标
Latency http_request_duration_seconds P95/P99 延迟、慢请求占比
Traffic http_requests_total QPS、同比/环比流量变化率
Errors http_requests_total{status_code=~"5.."} / ignoring(status_code) sum(http_requests_total) 错误率、错误突增检测阈值
Saturation process_resident_memory_bytes, go_goroutines 内存使用率、协程堆积速率

动态告警触发路径

graph TD
    A[指标采集] --> B[Prometheus拉取]
    B --> C[Rule评估:latency > 2s AND error_rate > 5%]
    C --> D{Saturation > 90%?}
    D -->|是| E[触发P0告警:扩容+熔断]
    D -->|否| F[触发P2告警:定位慢接口]

4.2 基于Service Level Objective的告警规则DSL设计与PromQL表达式验证

SLO驱动的告警需脱离“阈值漂移”陷阱,转向对错误预算消耗速率的量化感知。

DSL核心结构

# slo-rule.yaml
slo_name: "api-availability-999"
objective: 0.999
window: 7d
target_metric: 'rate(http_requests_total{code=~"5.."}[1h])'
total_metric: 'rate(http_requests_total[1h])'

objective 定义目标可用性;window 决定错误预算计算周期;target_metric/total_metric 共同导出错误率,支撑动态预算余量计算。

PromQL验证逻辑

# 验证错误预算消耗速率 > 5%/h(临界恶化信号)
1 - (1 - (rate(http_requests_total{code=~"5.."}[1h]) / rate(http_requests_total[1h]))) 
  * (7 * 24) 
  / (7 * 24 - (time() - start_time())) > 0.05

该表达式实时归一化错误预算消耗进度,避免静态窗口偏差。

SLO告警状态机

graph TD
    A[预算充足] -->|消耗速率 > 5%/h| B[预警中]
    B -->|持续2h| C[预算濒临耗尽]
    C -->|触发SLO breach| D[升级告警]

4.3 多级告警分级机制:P0-P3阈值清单(含GC Pause >100ms、HTTP 5xx率>0.5%、goroutine泄漏>5k等12项生产级阈值)

告警分级不是简单阈值堆砌,而是基于故障影响面、恢复时效与系统关键性构建的响应契约。

核心阈值示例(节选)

  • P0(秒级响应)GC Pause > 100ms(连续3次)、HTTP 5xx率 > 0.5%(1分钟滑动窗口)
  • P2(分钟级响应)goroutine 数量 > 5000(持续5分钟)、Redis 连接池饱和率 > 95%

阈值配置片段(Prometheus Alerting Rule)

- alert: HighGCPause
  expr: histogram_quantile(0.99, sum by (le) (rate(jvm_gc_pause_seconds_bucket[5m]))) > 0.1
  labels:
    severity: p0
  annotations:
    summary: "JVM GC pause exceeds 100ms at P99"

▶️ 逻辑说明:采用 histogram_quantile 计算P99暂停时长,避免单次毛刺误报;rate(...[5m]) 消除瞬时抖动,0.1 即100ms,单位为秒。

级别 响应SLA 典型指标示例
P0 ≤30s DB主库不可用、全站5xx
P3 ≤2h 日志采集延迟 > 15min
graph TD
  A[原始指标] --> B{是否触发P0条件?}
  B -->|是| C[自动熔断+电话告警]
  B -->|否| D{是否触发P2/P3?}
  D -->|是| E[企业微信+邮件]

4.4 告警静默、抑制与路由策略:结合Alertmanager与企业微信/飞书多通道精准触达

静默与抑制的语义差异

  • 静默(Silence):临时屏蔽匹配告警,基于标签精确生效,支持时间范围与时区;
  • 抑制(Inhibit):上游故障触发时自动压制下游衍生告警,避免告警风暴。

路由策略驱动多通道分发

route:
  group_by: ['alertname', 'cluster']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 24h
  receiver: 'default-webhook'
  routes:
  - match:
      severity: critical
    receiver: 'feishu-critical'
  - match:
      team: 'backend'
    receiver: 'wx-backend'

该配置实现按 severityteam 标签分流:critical 级别告警直送飞书机器人,后端团队告警推送至企业微信专属群。group_by 减少重复通知,repeat_interval 防止刷屏。

多通道接收器配置对比

通道 认证方式 消息模板支持 支持@人员
企业微信 Secret + AgentID ✅(Go template)
飞书 Bot Token ✅(需open_id)
graph TD
  A[Alertmanager] -->|匹配路由规则| B{路由决策}
  B -->|severity=critical| C[飞书Webhook]
  B -->|team=backend| D[企业微信Webhook]
  C --> E[飞书群内高亮+卡片]
  D --> F[微信图文+@值班人]

第五章:总结与展望

技术栈演进的实际影响

在某大型电商平台的微服务重构项目中,团队将原有单体架构迁移至基于 Kubernetes 的云原生体系。迁移后,平均部署耗时从 47 分钟缩短至 92 秒,CI/CD 流水线失败率下降 63%。关键变化在于:

  • 使用 Argo CD 实现 GitOps 自动同步,配置变更通过 PR 审核后 12 秒内生效;
  • Prometheus + Grafana 告警规则覆盖全部核心链路,P95 延迟突增检测响应时间 ≤ 8 秒;
  • Istio 服务网格启用 mTLS 后,跨集群调用成功率从 92.4% 提升至 99.997%。

生产环境中的可观测性实践

以下为某金融级风控系统在灰度发布阶段采集的真实指标对比(单位:毫秒):

阶段 平均延迟 P99 延迟 错误率 日志采样率
v2.3.0(旧版) 142 387 0.18% 100%
v2.4.0(灰度) 89 213 0.02% 3%(OpenTelemetry 动态采样)

该系统通过 OpenTelemetry Collector 的 tail-based sampling 策略,在保障异常链路 100% 捕获的同时,将日志存储成本降低 71%。

多云架构下的安全治理落地

某政务云平台采用“策略即代码”模式统一管理 AWS、阿里云、华为云三套环境。使用 OPA(Open Policy Agent)嵌入 CI/CD 流程,强制校验所有 Terraform 模块:

# 示例:禁止公网暴露 RDS 实例的策略片段
package terraform.aws

deny[msg] {
  resource := input.resource.aws_db_instance[_
  resource.allocated_storage < 100
  resource.publicly_accessible == true
  msg := sprintf("RDS 实例 %v 存储不足 100GB 且设为公网可访问,违反安全基线", [resource.name])
}

上线半年内拦截高危资源配置 217 次,其中 89% 涉及未加密的 S3 存储桶或开放 0.0.0.0/0 的安全组。

开发者体验的量化提升

通过构建内部 CLI 工具 devkit,集成环境准备、本地调试代理、日志实时聚合等功能。在 12 个业务线推广后:

  • 新成员首次提交代码平均耗时从 3.2 小时降至 24 分钟;
  • 本地联调环境启动失败率由 41% 降至 2.3%;
  • devkit logs --service payment --tail 1000 命令替代了原先需登录 3 台跳板机的手动排查流程。

未来技术债的主动管理

当前已建立自动化技术债看板,每日扫描代码库中硬编码密钥、过期 TLS 版本、废弃 API 调用等 17 类风险模式。最近一次扫描发现:

  • 3 个服务仍在使用 SHA-1 签名的 JWT Token(已触发 Jira 自动工单);
  • 供应链依赖中存在 12 个 CVE-2023-XXXX 级别漏洞,其中 5 个已通过 dependabot 自动创建 PR 并完成测试验证;
  • 遗留 Python 2.7 脚本在 CI 中被标记为“仅限紧急修复”,下季度起将禁用执行权限。

边缘计算场景的持续验证

在智能工厂 IoT 平台中,将模型推理任务下沉至 NVIDIA Jetson AGX Orin 设备。实测数据显示:

  • 本地推理吞吐量达 142 FPS(YOLOv8n),较云端回落延迟降低 98.6%;
  • 使用 eBPF 程序监控设备资源,当 GPU 利用率连续 5 分钟 >95% 时自动触发模型量化降级;
  • OTA 升级包签名验证改用硬件 TPM 2.0 模块,固件篡改检测时间从 8.3 秒压缩至 117 毫秒。

组织协同模式的迭代

采用“SRE 共同体”机制,将运维指标反向嵌入研发 OKR:每个服务 Owner 必须对 SLI 达成率、变更失败率、MTTR 三项指标负责。2024 年 Q2 数据显示,跨团队故障协同定位平均耗时从 47 分钟缩短至 11 分钟,其中 64% 的根因直接关联开发阶段埋点缺失。

专注后端开发日常,从 API 设计到性能调优,样样精通。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注