延迟、流量、错误率、饱和度这四大黄金信号是 SRE 的最佳实践,可用来帮助 SRE 团队快速评估系统状态,在异常情况下及时介入,保证系统始终工作在健康状态。原文: Four Golden Signals Of Monitoring: Site Reliability Engineering (SRE) Metrics
黄金信号(Golden Signals)最初是谷歌在站点可靠性工程(SRE)实践的背景下引入的,由谷歌软件工程师 Dave Rensin 和 Kevin Smathers 在 2016 年 O 'Reilly Velocity Conference 上的一次演讲中提出,其背后的想法是提供一组关键性能指标(KPI),用于测量和监控复杂分布式系统的运行状况。
引入黄金信号是为了帮助 SRE 团队关注系统可靠性和性能方面真正重要的东西。黄金信号不依赖于难以解释的大量指标和告警,而是提供一组简单且易于理解的指标,用于快速评估系统健康状况。
自从这一概念提出以来,黄金信号已在 SRE 社区中得到广泛采用,并被认为是监控和管理分布式系统运行状况的最佳实践。虽然最初黄金信号专注于延迟、流量、错误和饱和指标,但一些组织已经调整了这个概念,引入了特定于其系统和用例的附加指标。不过,通过一组 KPI 来度量和监控系统健康的核心思想仍然是黄金信号概念的核心。
什么是黄金信号?
黄金信号是 SRE 用来衡量其系统健康状况的一组四个关键指标,包括:
- 延迟(Latency) —— 延迟用来度量系统响应请求所需的时间,延迟高表明系统可能过载或遇到其他性能问题。
延迟
Prometheus 查询histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{job="fastapi-app"}[5m])) by (le, method, endpoint))
通过直方图指标(histogram metric)度量来检测 FastAPI 应用程序 HTTP 请求的 P95 延迟。
该查询计算过去 5 分钟内 http_request_duration_seconds_bucket 度量值(表示落入特定延迟桶的请求数量)的速率总和,并按延迟(le)、HTTP 方法和端点分组。然后,histogram_quantile
函数使用这些值计算每个 HTTP 方法和端点组合的 P95 延迟。
- 流量(Traffic) —— 流量衡量流经系统的数据或请求的数量,流量高表明系统可能正在处理大量请求,或者系统容量存在问题。
流量
Prometheus 查询rate(http_requests_total{job="fastapi-app"}[$__rate_interval])
通过计数器指标(counter metric)度量 FastAPI 应用程序每秒 HTTP 请求的速率。
该查询使用rate
函数来计算http_requests_total
计数器指标的每秒增长率,计算向 FastAPI 应用程序发出的 HTTP 请求总数。job="fastapi-app"
标签选择器过滤度量数据,使其只包含来自 FastAPI 的数据。
$__rate_interval
变量是模板变量,表示计算速率的持续时间,该变量值由用户在 Prometheus 查询界面中设置,用于确定计算速率的时间范围。
例如,如果用户将$__rate_interval
设置为5m
,查询将计算过去 5 分钟内 HTTP 请求的每秒速率。此查询可用于监控 FastAPI 应用程序的流量,并识别请求量随时间变化的模式或异常情况。
- 错误(Errors) —— 错误度量系统中发生的错误数量,错误率高表明系统中可能存在 bug 或其他问题。
错误数
Prometheus 查询http_requests_total {endpoint="/generate_error", http_status="500"}
检索 web 应用程序的"/generate_error"端点的 HTTP 请求并且 HTTP 状态码为 500(内部服务器错误)的数量。
该查询使用http_requests_total
计数器指标,计算向 web 应用程序发出的 HTTP 请求总数。查询通过指定endpoint="/generate_error"
标签选择器过滤度量数据,使其只包括对"/generate_error"端点的请求。此外,查询通过指定http_status="500"
标签选择器过滤数据,只包括 HTTP 状态码为 500 的请求。
通过运行这个查询,可以深入了解 web 应用中错误发生率,以及哪些端点容易出错。这些信息可以帮助识别和修复应用中的问题,提高可靠性,并确保为用户提供更好的体验。
- 饱和度(Saturation) —— 饱和度衡量系统的资源利用率,饱和度高表明系统资源(例如 CPU 或内存)可能正在被耗尽。
饱和度
Prometheus 查询clamp_max(active_requests{job="fastapi-app"} / 10, 1)
用于计算活动请求与最大并发请求数的比率,并将该比率的值限制为不超过 1。
该查询使用active_requests
度量(gauge)指标检索 FastAPI 应用程序中的当前活动请求数。job="fastapi-app"
标签选择器过滤度量数据,使其只包含来自 FastAPI 的数据。
然后,查询将活动请求数除以 10,表示系统可以处理的最大并发请求数。然后使用clamp_max
函数将该比率的值限制为不超过 1。这意味着,如果活动请求与最大并发请求数之比大于 1,则查询将返回值 1。
通过这个查询,可以监控系统饱和情况,并确定系统何时因请求而过载。如果活动请求与最大并发请求数之比接近 1,可能需要扩容系统以处理增加的请求。此查询可以帮助我们确保系统在高负载下仍可保持可靠和高性能。
为什么黄金信号很重要?
因为黄金信号使 SRE 们可以清楚了解系统的运行情况,因此非常重要。通过测量和监控这些关键指标,SRE 可以快速识别问题,并在问题变得严重之前采取纠正措施,即使这么做增加了系统复杂性,也可以有助于确保系统的可靠性、可伸缩性和高性能。
如何使用黄金信号来提高系统可靠性?
黄金信号可以通过几种方式来提高系统可靠性:
- 主动监控(Proactive Monitoring) —— 通过持续监控黄金信号,SRE 可以在问题变得严重之前识别问题,从而能够采取主动措施来防止停机或其他性能问题。
- 容量规划(Capacity Planning) —— 黄金信号可用于识别系统何时达到其容量限制。通过监控流量和饱和度指标,SRE 可以做出明智决定,决定何时升级或扩容系统以满足需求。
- 根因分析(Root Cause Analysis) —— 当系统出现问题时,SRE 可以使用黄金信号来帮助确定问题的根本原因。通过查看延迟、流量、错误和饱和度指标,SRE 可以深入了解出了什么问题,并采取措施防止将来发生类似问题。
了解如何在实践中实现这些指标也很重要。实现黄金信号的一种方法是使用内置对其支持的监控工具和库,比如 Prometheus。在下面代码示例中,Python FastAPI 应用程序通过 Prometheus 来实现黄金信号。
from fastapi import FastAPI, Request, HTTPException, Response from prometheus_client import Counter, Gauge, Histogram, generate_latest, CONTENT_TYPE_LATEST from starlette.responses import StreamingResponse import time app = FastAPI() # Define Prometheus metrics http_requests_total = Counter( "http_requests_total", "Total number of HTTP requests", ["method", "endpoint", "http_status"] ) http_request_duration_seconds = Histogram( "http_request_duration_seconds", "HTTP request duration in seconds", ["method", "endpoint"] ) http_request_size_bytes = Histogram( "http_request_size_bytes", "HTTP request size in bytes", ["method", "endpoint"] ) http_response_size_bytes = Histogram( "http_response_size_bytes", "HTTP response size in bytes", ["method", "endpoint"] ) active_requests = Gauge( "active_requests", "Number of active requests" ) error_counter = Counter( "error_counter", "Total number of HTTP errors", ["method", "endpoint", "http_status"] ) @app.middleware("http") async def record_request_start_time(request: Request, call_next): request.state.start_time = time.time() response = await call_next(request) return response @app.middleware("http") async def record_request_end_time(request: Request, call_next): response = await call_next(request) latency = time.time() - request.state.start_time http_request_duration_seconds.labels( request.method, request.url.path ).observe(latency) http_request_size_bytes.labels( request.method, request.url.path ).observe(request.headers.get("Content-Length", 0)) if isinstance(response, StreamingResponse): response_size = 0 else: response_size = len(response.content) http_response_size_bytes.labels( request.method, request.url.path ).observe(response_size) http_requests_total.labels( request.method, request.url.path, response.status_code ).inc() return response @app.middleware("http") async def increment_counter(request: Request, call_next): active_requests.inc() response = await call_next(request) active_requests.dec() return response @app.middleware("http") async def log_saturation(request: Request, call_next): max_concurrent_requests = 10 # set the maximum number of concurrent requests saturation_ratio = active_requests._value._value / max_concurrent_requests print(f"Saturation: {saturation_ratio}") return await call_next(request) @app.middleware("http") async def increment_error_counter(request: Request, call_next): try: response = await call_next(request) return response except HTTPException as e: error_counter.labels( request.method, request.url.path, e.status_code ).inc() print(f"Incremented error counter for {request.method} {request.url.path} {e.status_code}") raise e @app.get("/") async def root(): return {"message": "Hello, World!"} @app.get("/generate_traffic") async def generate_traffic(): for i in range(100): response = await root() print(response) return {"message": "Generated traffic successfully."} @app.get("/generate_error") async def generate_error(): raise HTTPException(status_code=500, detail="Generated an error.") @app.get("/metrics") async def metrics(): return Response(content=generate_latest(), media_type=CONTENT_TYPE_LATEST)
复制代码
requirements.txt:
anyio==3.6.2 click==8.1.3 fastapi==0.92.0 h11==0.14.0 idna==3.4 prometheus-client==0.16.0 pydantic==1.10.5 sniffio==1.3.0 starlette==0.25.0 typing_extensions==4.5.0 uvicorn==0.20.0
复制代码
在 K8S 上部署
使用 Prometheus 在 FastAPI 应用程序中实现了黄金信号后,可能希望将其部署到 Kubernetes 集群中,以确保可伸缩性和高可用性。下面的 Kubernetes 清单文件可以用来部署 FastAPI 应用程序和 Grafana 仪表板:
fastapi-app.yaml
# @format apiVersion: apps/v1 kind: Deployment metadata: name: fastapi-app spec: selector: matchLabels: app: fastapi-app replicas: 2 template: metadata: labels: app: fastapi-app annotations: prometheus.io/scrape: "true" prometheus.io/path: "/" prometheus.io/port: "80" spec: containers: - name: fastapi-app image: rtiwariops/fastapi-app:v1 ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: fastapi-app spec: selector: app: fastapi-app ports: - name: http protocol: TCP port: 80 targetPort: 80
复制代码
grafana.yaml
# @format apiVersion: apps/v1 kind: Deployment metadata: name: grafana spec: selector: matchLabels: app: grafana replicas: 1 template: metadata: labels: app: grafana spec: containers: - name: grafana image: grafana/grafana:latest ports: - containerPort: 3000 --- apiVersion: v1 kind: Service metadata: name: grafana spec: selector: app: grafana ports: - name: http protocol: TCP port: 3000 targetPort: 3000
复制代码
prometheus.yaml
apiVersion: v1 kind: Service metadata: name: prometheus spec: selector: app: prometheus ports: - name: web port: 9090 targetPort: 9090 --- apiVersion: apps/v1 kind: Deployment metadata: name: prometheus spec: selector: matchLabels: app: prometheus replicas: 1 template: metadata: labels: app: prometheus spec: containers: - name: prometheus image: prom/prometheus:v2.28.1 ports: - name: web containerPort: 9090 command: - "/bin/prometheus" args: - "--config.file=/etc/prometheus/prometheus.yml" volumeMounts: - name: config-volume mountPath: /etc/prometheus volumes: - name: config-volume configMap: name: prometheus-config
复制代码
总之,黄金信号是 SRE 工具箱中的关键工具。通过测量和监控延迟、流量、错误和饱和度指标,即使面对日益增加的复杂性和需求,SRE 也可以确保其系统保持可靠、可扩展和高性能。
完整代码示例: https://github.com/PolyCloudNative/Golden-Rule-Demo
你好,我是俞凡,在 Motorola 做过研发,现在在 Mavenir 做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI 等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。
微信公众号:DeepNoMind