diff --git a/a_long_dianjing/settings.py b/a_long_dianjing/settings.py index 4bd6e5e..fc26cd8 100644 --- a/a_long_dianjing/settings.py +++ b/a_long_dianjing/settings.py @@ -56,6 +56,7 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'utils.slow_request_middleware.SlowRequestTraceMiddleware', ] # 日志配置 — 500 错误堆栈输出到 console,受 DEBUG_DETAIL_LOG 开关控制 @@ -126,7 +127,6 @@ DATABASES = { 'PASSWORD': app_secrets.DATABASE_PASSWORD, 'PORT': app_secrets.DATABASE_PORT, 'CONN_MAX_AGE': 600, - 'CONN_HEALTH_CHECKS': True, 'OPTIONS': { 'charset': 'utf8mb4', 'init_command': "SET time_zone='+08:00'", diff --git a/deploy/apply-lag-fix.sh b/deploy/apply-lag-fix.sh index f63c856..065c195 100644 --- a/deploy/apply-lag-fix.sh +++ b/deploy/apply-lag-fix.sh @@ -16,14 +16,6 @@ sleep 2 systemctl is-active gunicorn-dianjing.service pgrep -cf 'gunicorn.*a_long_dianjing' || true -echo "" -echo ">>> 安装 worker 保活 cron(空闲后不卡)" -chmod +x deploy/gunicorn-warmup.sh -cp deploy/gunicorn-warmup.cron /etc/cron.d/gunicorn-warmup -chmod 644 /etc/cron.d/gunicorn-warmup -bash deploy/gunicorn-warmup.sh -echo " cron 已安装,每2分钟预热 worker" - echo "" echo ">>> 最近慢请求 (rt>=0.5s):" grep -a 'hqhd' "$NGINX_LOG" | awk 'match($0,/rt=([0-9.]+)/,a){if(a[1]+0>=0.5)print}' | tail -5 diff --git a/deploy/disable-slow-trace.sh b/deploy/disable-slow-trace.sh new file mode 100644 index 0000000..8891743 --- /dev/null +++ b/deploy/disable-slow-trace.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# 关闭慢请求追踪 +rm -f /etc/systemd/system/gunicorn-dianjing.service.d/slow-trace.conf +systemctl daemon-reload +systemctl restart gunicorn-dianjing.service +echo "慢请求追踪已关闭" diff --git a/deploy/enable-slow-trace.sh b/deploy/enable-slow-trace.sh new file mode 100644 index 0000000..3d0abb6 --- /dev/null +++ b/deploy/enable-slow-trace.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# 开启慢请求追踪(只写日志,不改业务逻辑)。卡的时候跑这个,操作小程序,再 cat 日志。 +set -e +SVC=/etc/systemd/system/gunicorn-dianjing.service +mkdir -p /etc/systemd/system/gunicorn-dianjing.service.d +cat > /etc/systemd/system/gunicorn-dianjing.service.d/slow-trace.conf <<'EOF' +[Service] +Environment=SLOW_REQUEST_TRACE=1 +Environment=SLOW_REQUEST_THRESHOLD_MS=200 +Environment=SLOW_REQUEST_LOG=/tmp/slow-request-trace.log +EOF +: > /tmp/slow-request-trace.log +systemctl daemon-reload +systemctl restart gunicorn-dianjing.service +echo "已开启。现在去小程序复现卡顿,然后执行:" +echo " cat /tmp/slow-request-trace.log" +echo "关闭: bash deploy/disable-slow-trace.sh" diff --git a/utils/slow_request_middleware.py b/utils/slow_request_middleware.py new file mode 100644 index 0000000..7fb8a4a --- /dev/null +++ b/utils/slow_request_middleware.py @@ -0,0 +1,47 @@ +""" +可选:慢请求追踪(默认关闭,不影响现网)。 +启用: 在 gunicorn systemd 的 Environment 加 SLOW_REQUEST_TRACE=1 后 restart。 +日志: /tmp/slow-request-trace.log +""" +import logging +import os +import time + +logger = logging.getLogger('slow_request') + +_ENABLED = os.environ.get('SLOW_REQUEST_TRACE', '').strip() in ('1', 'true', 'yes') +_THRESHOLD_MS = float(os.environ.get('SLOW_REQUEST_THRESHOLD_MS', '300')) +_LOG_PATH = os.environ.get('SLOW_REQUEST_LOG', '/tmp/slow-request-trace.log') + + +class SlowRequestTraceMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + if not _ENABLED: + return self.get_response(request) + + t0 = time.perf_counter() + from django.db import connection, reset_queries + reset_queries() + response = self.get_response(request) + elapsed_ms = (time.perf_counter() - t0) * 1000 + + if elapsed_ms >= _THRESHOLD_MS: + n_sql = len(connection.queries) + sql_ms = sum(float(q.get('time', 0)) for q in connection.queries) * 1000 + line = ( + f"{elapsed_ms:.0f}ms total | sql={n_sql}条/{sql_ms:.0f}ms | " + f"{request.method} {request.path} | status={response.status_code}\n" + ) + try: + with open(_LOG_PATH, 'a', encoding='utf-8') as f: + f.write(line) + if n_sql <= 30: + for q in sorted(connection.queries, key=lambda x: -float(x.get('time', 0)))[:5]: + sql = q['sql'][:200].replace('\n', ' ') + f.write(f" {float(q['time'])*1000:.0f}ms {sql}\n") + except OSError: + logger.warning('slow request trace write failed') + return response