From d3d057c3562ae3fdd75f02ba95971a2d2a6cace5 Mon Sep 17 00:00:00 2001 From: XingQue Date: Thu, 9 Jul 2026 04:39:32 +0800 Subject: [PATCH] =?UTF-8?q?add:=20=E6=9C=8D=E5=8A=A1=E5=99=A8=E5=8D=A1?= =?UTF-8?q?=E9=A1=BF=E8=AF=8A=E6=96=AD=E8=84=9A=E6=9C=AC=20profile-server-?= =?UTF-8?q?lag=20=E4=B8=8E=20fix-db-latency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- deploy/fix-db-latency.sh | 77 ++++++++++++++++ deploy/profile-server-lag.sh | 168 +++++++++++++++++++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 deploy/fix-db-latency.sh create mode 100644 deploy/profile-server-lag.sh diff --git a/deploy/fix-db-latency.sh b/deploy/fix-db-latency.sh new file mode 100644 index 0000000..d3656c5 --- /dev/null +++ b/deploy/fix-db-latency.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# 诊断并修复 API 延迟:对比本机 Docker MySQL vs 远程 gvsds.com +# 在服务器上执行:bash deploy/fix-db-latency.sh + +set -e +DJANGO_DIR="/opt/1panel/apps/openresty/nginx/www/sites/43.142.166.152.443/django" +cd "$DJANGO_DIR" + +echo "========== 1. Gunicorn 状态 ==========" +systemctl is-active gunicorn-dianjing.service || true +pgrep -af 'gunicorn.*a_long_dianjing' || echo "无 gunicorn 进程!" + +echo "" +echo "========== 2. 当前数据库配置 ==========" +python3 - <<'PY' +import app_secrets +print(f"HOST={app_secrets.DATABASE_HOST} PORT={app_secrets.DATABASE_PORT} DB={app_secrets.DATABASE_NAME}") +PY + +echo "" +echo "========== 3. 连接延迟对比(新建连接 + 简单查询)==========" +python3 - <<'PY' +import time +import pymysql +import app_secrets + +def test_host(host, port, user, password, db): + t0 = time.time() + try: + conn = pymysql.connect( + host=host, port=int(port), user=user, password=password, + database=db, connect_timeout=5, charset='utf8mb4', + ) + connect_ms = (time.time() - t0) * 1000 + t1 = time.time() + with conn.cursor() as cur: + cur.execute("SELECT 1") + cur.fetchone() + query_ms = (time.time() - t1) * 1000 + conn.close() + return connect_ms, query_ms, None + except Exception as e: + return None, None, str(e) + +user = app_secrets.DATABASE_USER +pwd = app_secrets.DATABASE_PASSWORD +db = app_secrets.DATABASE_NAME +cur_host = app_secrets.DATABASE_HOST +cur_port = app_secrets.DATABASE_PORT + +for label, host, port in [ + ("当前生产", cur_host, cur_port), + ("本机 Docker", "172.17.0.15", "3306"), +]: + c, q, err = test_host(host, port, user, pwd, db) + if err: + print(f" [{label}] {host}:{port} -> 失败: {err}") + else: + print(f" [{label}] {host}:{port} -> 握手 {c:.0f}ms, 查询 {q:.0f}ms") + +print("\n若本机 Docker 成功且 <20ms,应改 app_secrets.py 的 DATABASE_HOST=172.17.0.15 PORT=3306") +PY + +echo "" +echo "========== 4. 本地 API 响应(ddhq 需 token,测空接口)==========" +curl -s -o /dev/null -w "127.0.0.1:8001 响应: %{time_total}s\n" http://127.0.0.1:8001/hqhd/ 2>/dev/null || echo "8001 无响应" + +echo "" +echo "========== 5. Nginx 最近 hqhd 延迟(rt=)==========" +grep 'hqhd' /opt/1panel/apps/openresty/nginx/log/access.log 2>/dev/null | tail -5 || echo "无日志" + +echo "" +echo "========== 修复步骤(本机库可用时)==========" +echo "1. 备份: cp app_secrets.py app_secrets.py.bak.\$(date +%Y%m%d)" +echo "2. 改 DATABASE_HOST='172.17.0.15' DATABASE_PORT='3306'" +echo "3. systemctl restart gunicorn-dianjing.service" +echo "4. 再 grep hqhd access.log 看 rt= 是否回到 0.05~0.08" diff --git a/deploy/profile-server-lag.sh b/deploy/profile-server-lag.sh new file mode 100644 index 0000000..4218da9 --- /dev/null +++ b/deploy/profile-server-lag.sh @@ -0,0 +1,168 @@ +#!/bin/bash +# 服务器卡顿定位 — 一次跑完,输出「卡在哪一层」结论 +# 用法: bash deploy/profile-server-lag.sh +# 只读诊断,不改任何配置 + +set -e +DJANGO_DIR="/opt/1panel/apps/openresty/nginx/www/sites/43.142.166.152.443/django" +NGINX_LOG="/opt/1panel/apps/openresty/nginx/log/access.log" +cd "$DJANGO_DIR" + +echo "==============================================" +echo " 星阙服务器卡顿定位 — $(date '+%Y-%m-%d %H:%M:%S')" +echo "==============================================" + +# ---------- A. 进程层:Gunicorn 是否在排队 ---------- +echo "" +echo ">>> [A] Gunicorn 进程 / 端口" +systemctl is-active gunicorn-dianjing.service 2>/dev/null || echo "systemd: inactive" +GC=$(pgrep -cf 'gunicorn.*a_long_dianjing' 2>/dev/null || echo 0) +echo "gunicorn 进程数: $GC (正常=5: 1master+4worker)" +ss -ltn 2>/dev/null | grep ':8001' || netstat -ltn 2>/dev/null | grep ':8001' || echo "8001 未监听" + +echo "" +echo ">>> [A2] 本机直连 Gunicorn(绕过 Nginx)" +for path in "/" "/hqhd/"; do + T=$(curl -s -o /dev/null -w '%{time_total}' --connect-timeout 3 "http://127.0.0.1:8001${path}" 2>/dev/null || echo "FAIL") + echo " 127.0.0.1:8001${path} -> ${T}s" +done + +# ---------- B. 数据库层:握手 + 单条 SQL ---------- +echo "" +echo ">>> [B] MySQL 延迟(从本机 Django 目录测)" +python3 - <<'PY' +import time, sys +try: + import app_secrets + import pymysql +except Exception as e: + print(f" 无法导入: {e}") + sys.exit(0) + +host, port = app_secrets.DATABASE_HOST, int(app_secrets.DATABASE_PORT) +user, pwd, db = app_secrets.DATABASE_USER, app_secrets.DATABASE_PASSWORD, app_secrets.DATABASE_NAME +print(f" 当前配置: {host}:{port} / {db}") + +def bench(label, h, p): + try: + t0 = time.perf_counter() + conn = pymysql.connect(host=h, port=p, user=user, password=pwd, database=db, connect_timeout=5) + t_conn = (time.perf_counter() - t0) * 1000 + t1 = time.perf_counter() + with conn.cursor() as c: + c.execute("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=%s", (db,)) + c.fetchone() + t_q = (time.perf_counter() - t1) * 1000 + conn.close() + print(f" [{label}] {h}:{p} 新建连接={t_conn:.0f}ms 单条SQL={t_q:.0f}ms") + return t_conn, t_q + except Exception as e: + print(f" [{label}] {h}:{p} 失败: {e}") + return None, None + +cur_c, cur_q = bench("生产库", host, port) +loc_c, loc_q = bench("本机Docker", "172.17.0.15", 3306) + +print("") +if cur_q and cur_q > 30: + print(" >>> 结论B: 数据库在远程,每条SQL ~50ms+,接口跑10+条SQL就会慢200ms+") +if loc_q and loc_q < 10 and cur_q and cur_q > 30: + print(" >>> 修复: app_secrets.py 改 DATABASE_HOST=172.17.0.15 PORT=3306 后 restart gunicorn") +PY + +# ---------- C. Django 层:模拟 dddhq 打了多少条 SQL ---------- +echo "" +echo ">>> [C] /dingdan/ddhq 一次请求打几条 SQL、耗时多少" +python3 - <<'PY' +import os, sys, time +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'a_long_dianjing.settings') +sys.path.insert(0, '.') + +try: + import django + django.setup() + from django.db import connection, reset_queries + from django.conf import settings + settings.DEBUG = True # 仅本脚本进程内开启 query log + + reset_queries() + t0 = time.perf_counter() + + # 不跑完整 view(要 token),只测典型 ORM 链:订单列表 count + page + from orders.models import Order + from django.db.models import Q + qs = Order.query.filter(Q(Status__in=[1, 7])).order_by('-CreateTime') + total = qs.count() + page = list(qs[:5].values('OrderID', 'Status', 'CreateTime')) + + elapsed = (time.perf_counter() - t0) * 1000 + n = len(connection.queries) + sql_ms = sum(float(q.get('time', 0)) for q in connection.queries) * 1000 + + print(f" 仅订单 count+取5条: SQL条数={n} Django记录SQL耗时={sql_ms:.0f}ms 总耗时={elapsed:.0f}ms 订单总数={total}") + print(f" (完整 /ddhq 还有会员/标签/商家等查询,实际 SQL 通常 15~30 条)") + if n > 0: + print(" 最慢的3条SQL:") + for q in sorted(connection.queries, key=lambda x: float(x.get('time', 0)), reverse=True)[:3]: + sql = q['sql'][:120].replace('\n', ' ') + print(f" {float(q['time'])*1000:.0f}ms {sql}...") +except Exception as e: + print(f" Django 测速失败: {e}") + import traceback; traceback.print_exc() +PY + +# ---------- D. Nginx 层:最近真实接口 rt= 分布 ---------- +echo "" +echo ">>> [D] Nginx 最近 hqhd 接口 rt=(真实用户体感)" +if [ -f "$NGINX_LOG" ]; then + echo " 最近20条 hqhd:" + grep 'hqhd' "$NGINX_LOG" | tail -20 | while read -r line; do + RT=$(echo "$line" | grep -oP 'rt=\K[0-9.]+' || echo "?") + URI=$(echo "$line" | grep -oP '"(GET|POST) \K[^ ]+' || echo "?") + echo " rt=${RT}s $URI" + done + echo "" + echo " 各接口 rt 中位数(最近500条 hqhd):" + grep 'hqhd' "$NGINX_LOG" | tail -500 | grep -oP 'rt=\K[0-9.]+' | sort -n | awk ' + { a[NR]=$1; sum+=$1 } + END { + if(NR==0){ print " 无数据"; exit } + mid=(NR%2)?a[(NR+1)/2]:(a[NR/2]+a[NR/2+1])/2 + printf " 样本=%d 中位rt=%.3fs 平均rt=%.3fs 最大rt=%.3fs\n", NR, mid, sum/NR, a[NR] + }' +else + echo " 找不到 $NGINX_LOG" +fi + +# ---------- E. 系统层:CPU/内存/磁盘/journal ---------- +echo "" +echo ">>> [E] 系统资源" +echo " load: $(cat /proc/loadavg 2>/dev/null || uptime)" +free -h 2>/dev/null | head -2 || true +journalctl --disk-usage 2>/dev/null | head -1 || true + +# ---------- F. 并发层:谁在狂刷 ---------- +echo "" +echo ">>> [F] 最近1分钟请求最多的 IP+接口(是否有人刷接口拖慢)" +if [ -f "$NGINX_LOG" ]; then + tail -3000 "$NGINX_LOG" | grep 'hqhd' | awk '{ + for(i=1;i<=NF;i++) if($i ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) ip=$i + if(match($0, /"(GET|POST) ([^ ]+)/, m)) uri=m[2] + if(ip && uri) c[ip" "uri]++ + } END { + n=0 + for(k in c) arr[n++]=c[k]" "k + asort(arr) + for(i=n;i>0 && i>n-10;i--) print " "arr[i] + }' 2>/dev/null || echo " (需 gawk 支持 match 第三参数,可忽略)" +fi + +echo "" +echo "==============================================" +echo " 怎么看结果:" +echo " - A 里 curl >1s 且 B 里 SQL>30ms → 卡在数据库远程连接" +echo " - A 里 curl 快但 D 里 rt 慢 → 卡在 Nginx/上游配置或并发" +echo " - C 里 SQL条数>20 → 卡在接口 SQL 太多(后端代码)" +echo " - E 里 journal 很大/load 很高 → 卡在系统资源" +echo " - F 里同一 IP 每秒同一接口 → 卡在被人刷接口" +echo "=============================================="