31、数据科学系统的健康与性能监控

数据科学系统的健康与性能监控

在开发和维护数据科学应用程序时,监控系统的健康和性能至关重要。本文将介绍如何使用 Loguru 进行日志记录,以及如何添加 Prometheus 指标来监控应用程序。

1. 使用 Loguru 进行日志记录

1.1 配置 Loguru 作为中央记录器

在 FastAPI 应用程序中添加日志可以帮助我们了解不同路由和依赖项中发生的情况。以下是一个示例,我们添加了一个全局依赖项来检查请求头中的秘密值,并添加了调试日志和警告日志:

from loguru import logger
from fastapi import Header, HTTPException, status

def secret_header(secret_header: str | None = Header(None)) -> None:
    logger.debug("Check secret header")
    if not secret_header or secret_header != "SECRET_VALUE":
        logger.warning("Invalid or missing secret header")
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)

当我们使用 Uvicorn 运行此应用程序并发送一个包含无效头的请求时,会发现 Uvicorn 有自己的日志格式,与我们使用 Loguru 定义的格式不同。为了解决这个问题,我们需要进行一些配置。

1.2 配置和使用 Loguru 日志工具

我们创建一个名为 logger.py 的模块,将所有日志记录器配置放在这里。以下是配置 Loguru 的代码:

import sys
from loguru import logger

LOG_LEVEL = "DEBUG"
logger.remove()
logger.add(
    sys.stdout,
    level=LOG_LEVEL,
    format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
           "<level>{level: <8}</level> | "
           "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
           "<level>{message}</level>"
           " - {extra}",
)

我们还需要一个自定义处理程序 InterceptHandler 来将标准日志调用转发到 Loguru:

import logging
from loguru import logger

class InterceptHandler(logging.Handler):
    def emit(self, record):
        # 从 Loguru 文档中获取的代码,用于将标准日志调用转发到 Loguru
        try:
            level = logger.level(record.levelname).name
        except ValueError:
            level = record.levelno

        frame, depth = logging.currentframe(), 2
        while frame.f_code.co_filename == logging.__file__:
            frame = frame.f_back
            depth += 1

        logger.opt(depth=depth, exception=record.exc_info).log(
            level, record.getMessage()
        )

logging.basicConfig(handlers=[InterceptHandler()], level=0, force=True)
for uvicorn_logger_name in ["uvicorn.error", "uvicorn.access"]:
    uvicorn_logger = logging.getLogger(uvicorn_logger_name)
    uvicorn_logger.propagate = False
    uvicorn_logger.handlers = [InterceptHandler()]

__all__ = ["logger"]

1.3 使用配置好的日志记录器

现在,我们可以在代码中使用配置好的日志记录器:

from chapter15.logger import logger
from fastapi import Header, HTTPException, status

def secret_header(secret_header: str | None = Header(None)) -> None:
    logger.debug("Check secret header")
    if not secret_header or secret_header != "SECRET_VALUE":
        logger.warning("Invalid or missing secret header")
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)

当我们使用 Uvicorn 运行应用程序时,所有日志都将采用相同的格式。

1.4 日志记录总结

通过以上配置,我们可以将 Loguru 作为中央记录器,统一应用程序和外部库的日志格式。具体步骤如下:
1. 创建 logger.py 模块,配置 Loguru 并设置日志级别和格式。
2. 定义 InterceptHandler 类,将标准日志调用转发到 Loguru。
3. 配置标准日志模块,确保所有日志调用都通过 InterceptHandler 处理。
4. 对于有自己日志记录器的库(如 Uvicorn),手动更改其处理程序。

以下是配置日志记录器的流程图:

graph LR
    A[创建 logger.py 模块] --> B[配置 Loguru]
    B --> C[定义 InterceptHandler 类]
    C --> D[配置标准日志模块]
    D --> E[处理有自己日志记录器的库]

2. 添加 Prometheus 指标

2.1 理解 Prometheus 及其指标类型

日志可以帮助我们详细了解程序的操作,但对于全局监控和快速发现问题,指标更为有用。我们将使用 Prometheus 和 Grafana 来添加和监控指标。

Prometheus 由以下三部分组成:
- 多种编程语言(包括 Python)的库,用于向应用程序添加指标。
- 一个服务器,用于聚合和存储指标。
- 查询语言 PromQL,用于从指标中提取数据到可视化工具。

Prometheus 定义了四种不同类型的指标:
| 指标类型 | 描述 | 示例 |
| ---- | ---- | ---- |
| 计数器(Counter) | 测量随时间增加的值 | 处理的请求数、完成的预测数 |
| 仪表盘(Gauge) | 测量随时间增加或减少的值 | 当前内存使用量、工作队列中的待处理任务数 |
| 直方图(Histogram) | 测量值并将其分组到桶中 | API 响应时间的分布 |
| 摘要(Summary) | 类似于直方图,但使用滑动分位数 | |

2.2 测量和暴露指标

定义指标后,我们可以在程序运行期间测量值。Prometheus 应用程序通常会暴露一个 /metrics 端点,用于返回所有指标的当前值。Prometheus 服务器会定期轮询此端点,存储指标并通过 PromQL 提供访问。

需要注意的是,每次重启应用程序时,指标值将重置为零,因为指标值仅存储在应用程序的内存中,永久存储指标的责任由 Prometheus 服务器承担。

2.3 向 FastAPI 添加 Prometheus 指标

我们可以使用 Prometheus FastAPI Instrumentator 库来简化向 FastAPI 应用程序添加 Prometheus 指标的过程。以下是具体步骤:
1. 安装库:

(venv) $ pip install prometheus_fastapi_instrumentator
  1. 实现一个简单的 FastAPI 应用程序并启用 Instrumentator:
from fastapi import FastAPI
from prometheus_fastapi_instrumentator import Instrumentator, metrics

app = FastAPI()

@app.get("/")
async def hello():
    return {"hello": "world"}

instrumentator = Instrumentator()
instrumentator.add(metrics.default())
instrumentator.instrument(app).expose(app)

当我们运行此应用程序并访问 /metrics 端点时,可以看到记录的指标,例如请求数:

# HELP http_requests_total Total number of requests by method, status and handler.
# TYPE http_requests_total counter
http_requests_total{handler="/",method="GET",status="2xx"} 1.0

2.4 添加自定义指标

除了内置指标,我们可能需要添加自定义指标来测量特定于应用程序的内容。例如,我们想实现一个掷骰子的函数,并统计每个面出现的次数。以下是如何声明和使用自定义指标:

from prometheus_client import Counter
import random

DICE_COUNTER = Counter(
    "app_dice_rolls_total",
    "Total number of dice rolls labelled per face",
    labelnames=["face"],
)

def roll_dice() -> int:
    result = random.randint(1, 6)
    DICE_COUNTER.labels(result).inc()
    return result

添加自定义指标的步骤如下:
1. 实例化 Counter 对象,指定指标名称、描述和标签。
2. 在需要测量的代码中,使用 labels 方法设置标签,然后使用 inc 方法增加计数器。

以下是添加 Prometheus 指标的流程图:

graph LR
    A[安装 prometheus_fastapi_instrumentator] --> B[实例化 Instrumentator]
    B --> C[启用默认指标]
    C --> D[连接到 FastAPI 应用程序并暴露 /metrics 端点]
    D --> E[添加自定义指标]

2.5 处理多进程

在生产部署中,FastAPI 应用程序通常使用多个工作进程运行。这对于 Prometheus 指标来说可能会有问题,因为指标值仅存储在内存中并通过 /metrics 端点暴露。后续文章将探讨如何解决这个问题。

通过以上步骤,我们可以使用 Loguru 进行详细的日志记录,并使用 Prometheus 指标进行全局监控,从而更好地了解和维护数据科学应用程序的健康和性能。

2.6 多进程环境下的指标处理挑战

在生产环境中,为了提高应用程序的并发处理能力,FastAPI 应用通常会使用多个工作进程运行,例如借助 Gunicorn 来实现。但这会给 Prometheus 指标带来问题。因为 Prometheus 指标仅存储在应用程序的内存中,并且通过 /metrics 端点暴露。每个工作进程都有自己独立的内存空间,这就导致每个进程的指标数据是相互隔离的。当 Prometheus 服务器通过 /metrics 端点获取指标时,只能获取到单个进程的指标数据,无法得到整个应用程序的全局指标。

2.7 解决多进程指标问题的思路

为了解决多进程环境下的指标数据聚合问题,我们可以采用以下几种常见的方法:
- 使用 Prometheus 的多进程支持 :Prometheus 提供了一些机制来处理多进程的指标数据。例如,在 Python 中可以使用 prometheus_client 库的 multiprocess 模块。这个模块允许不同的进程将指标数据存储到共享的文件系统中,然后通过特定的方式进行聚合。
- 操作步骤
1. 安装 prometheus_client 库(如果尚未安装):

pip install prometheus_client
    2. 在应用程序中进行相应的配置。以下是一个简单的示例:
from prometheus_client import multiprocess
from prometheus_client import CollectorRegistry, generate_latest, CONTENT_TYPE_LATEST
from fastapi import FastAPI, Response

app = FastAPI()
registry = CollectorRegistry()
multiprocess.MultiProcessCollector(registry)

@app.get("/metrics")
def metrics():
    data = generate_latest(registry)
    return Response(content=data, media_type=CONTENT_TYPE_LATEST)
    3. 在启动应用程序时,需要设置 `PROMETHEUS_MULTIPROC_DIR` 环境变量,指定一个共享的目录用于存储指标数据:
export PROMETHEUS_MULTIPROC_DIR=/tmp/prometheus_multiproc
  • 使用中间件进行指标聚合 :可以开发一个中间件,在每个请求处理完成后,将各个进程的指标数据收集起来并进行聚合,然后通过 /metrics 端点返回聚合后的指标数据。
    • 操作步骤
      1. 定义一个中间件类,在类中实现指标数据的收集和聚合逻辑。
from fastapi import Request
from prometheus_client import CollectorRegistry, generate_latest, CONTENT_TYPE_LATEST
import os

class PrometheusMetricsMiddleware:
    def __init__(self, app):
        self.app = app
        self.registry = CollectorRegistry()
        if 'PROMETHEUS_MULTIPROC_DIR' in os.environ:
            from prometheus_client import multiprocess
            multiprocess.MultiProcessCollector(self.registry)

    async def __call__(self, request: Request):
        response = await self.app(request)
        if request.url.path == "/metrics":
            data = generate_latest(self.registry)
            return Response(content=data, media_type=CONTENT_TYPE_LATEST)
        return response
    2. 将中间件添加到 FastAPI 应用程序中:
app = FastAPI()
app.add_middleware(PrometheusMetricsMiddleware)

2.8 多进程指标处理的流程图

graph LR
    A[启动多个工作进程] --> B[每个进程记录指标到内存]
    B --> C{处理请求}
    C -->|请求为 /metrics| D[收集各进程指标数据]
    D --> E[聚合指标数据]
    E --> F[返回聚合后指标数据]
    C -->|请求非 /metrics| G[正常处理请求并更新指标]

2.9 日志与指标的综合应用

日志和指标在监控数据科学系统的健康和性能方面都起着重要的作用,它们可以相互补充。
- 日志用于详细调试 :当系统出现问题时,日志可以提供详细的操作记录,帮助我们定位问题的根源。例如,当某个 API 请求返回错误状态码时,通过查看日志可以了解请求处理过程中的每一个步骤,包括输入参数、中间计算结果等。
- 指标用于全局监控 :指标可以提供系统的整体运行状况,帮助我们快速发现系统的性能瓶颈和异常情况。例如,通过监控 API 请求的响应时间指标,如果发现响应时间突然变长,就可以及时采取措施进行优化。

2.10 综合应用的最佳实践

为了更好地综合应用日志和指标,我们可以遵循以下最佳实践:
- 统一日志和指标的标签 :在日志和指标中使用相同的标签,这样可以方便我们将日志和指标关联起来。例如,在记录 API 请求的日志和指标时,都使用请求的路径、方法和状态码作为标签。
- 设置合理的日志级别和指标阈值 :根据不同的环境和需求,设置合理的日志级别,避免产生过多的无用日志。同时,为指标设置合理的阈值,当指标超过阈值时及时发出警报。
- 定期分析日志和指标数据 :定期对日志和指标数据进行分析,发现系统的潜在问题和优化点。例如,通过分析 API 请求的响应时间指标的历史数据,找出响应时间较长的时间段和请求类型,进行针对性的优化。

2.11 综合应用的操作流程

以下是综合应用日志和指标的操作流程:
1. 配置日志记录 :使用 Loguru 配置详细的日志记录,包括不同级别的日志和统一的日志格式。
2. 添加指标监控 :使用 Prometheus 和相关库为应用程序添加各种指标,包括内置指标和自定义指标。
3. 统一标签 :在日志和指标中使用相同的标签,方便数据关联。
4. 设置阈值和警报 :为重要的指标设置合理的阈值,当指标超过阈值时触发警报。
5. 定期分析 :定期对日志和指标数据进行分析,发现问题并进行优化。

graph LR
    A[配置 Loguru 日志记录] --> B[添加 Prometheus 指标监控]
    B --> C[统一日志和指标标签]
    C --> D[设置指标阈值和警报]
    D --> E[定期分析日志和指标数据]
    E -->|发现问题| F[进行系统优化]
    F --> A

通过综合使用 Loguru 进行详细的日志记录和 Prometheus 指标进行全局监控,我们可以全面了解数据科学系统的运行状况,及时发现和解决问题,确保系统的稳定和高效运行。同时,合理处理多进程环境下的指标数据,能够保证我们获取到准确的全局指标信息,为系统的性能优化提供有力支持。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值