浏览代码

+ 性能限制,防止造成服务器卡顿

zbLiuLiu 1 周之前
父节点
当前提交
92ff80edfd
共有 7 个文件被更改,包括 147 次插入2 次删除
  1. 2 0
      README.md
  2. 3 0
      deploy/backend.Dockerfile
  3. 2 0
      deploy/frontend.Dockerfile
  4. 13 0
      docs/deployment-guide.md
  5. 6 1
      scripts/dev-down.sh
  6. 6 1
      scripts/dev-up.sh
  7. 115 0
      scripts/docker-limits.sh

+ 2 - 0
README.md

@@ -20,6 +20,8 @@ scripts/dev-up.sh
 - 前端:http://localhost:5173
 - 后端:http://localhost:8080
 - Swagger UI:http://localhost:8080/swagger-ui.html
+
+启动脚本会生成 `storage/runtime/docker-compose.limits.yml` 并叠加启动:Docker 项目总 CPU 预算按宿主机 80% 计算,其它主要运行资源按宿主机 50% 计算,构建过程默认串行,降低服务器构建卡死风险。
 - 健康检查:http://localhost:8080/actuator/health
 
 停止:

+ 3 - 0
deploy/backend.Dockerfile

@@ -1,5 +1,7 @@
 FROM maven:3.9.11-eclipse-temurin-21 AS build
 WORKDIR /workspace
+ARG MAVEN_OPTS="-XX:MaxRAMPercentage=50 -Djava.awt.headless=true"
+ENV MAVEN_OPTS=${MAVEN_OPTS}
 COPY backend/pom.xml ./pom.xml
 RUN mvn -q -DskipTests dependency:go-offline
 COPY backend/src ./src
@@ -7,6 +9,7 @@ RUN mvn -q -DskipTests package
 
 FROM eclipse-temurin:21-jre
 WORKDIR /app
+ENV JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=50"
 RUN useradd -r -u 10001 appuser && mkdir -p /app/storage && chown -R appuser:appuser /app
 USER appuser
 COPY --from=build /workspace/target/organization-people-platform-1.0.0.jar /app/app.jar

+ 2 - 0
deploy/frontend.Dockerfile

@@ -1,5 +1,7 @@
 FROM node:22-alpine AS build
 WORKDIR /workspace
+ARG NODE_OPTIONS="--max-old-space-size=1024"
+ENV NODE_OPTIONS=${NODE_OPTIONS}
 COPY frontend/package*.json ./
 RUN npm ci
 COPY frontend ./

+ 13 - 0
docs/deployment-guide.md

@@ -6,6 +6,19 @@
 scripts/dev-up.sh
 ```
 
+`dev-up.sh` 会先生成 `storage/runtime/docker-compose.limits.yml`,再叠加到主 Compose 文件启动服务。默认策略:
+
+- Docker 项目总 CPU 预算按宿主机可用 CPU 的 80% 计算,并分配给数据库、中间件、后端和前端。
+- Docker 项目总内存预算按宿主机内存的 50% 计算,并为每个服务设置 `mem_limit` 与 `memswap_limit`。
+- Compose 构建默认串行执行,降低服务器构建时的资源尖峰。
+- 后端 Maven 构建使用 `MAVEN_OPTS` 限制 Java 构建进程的可见 CPU 与堆内存比例;前端 Node 构建使用 `NODE_OPTIONS` 限制 V8 老生代内存。
+
+如需查看实际限制值:
+
+```bash
+cat storage/runtime/docker-compose.limits.yml
+```
+
 服务端口:
 
 - Web UI:5173

+ 6 - 1
scripts/dev-down.sh

@@ -2,4 +2,9 @@
 set -eu
 SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
 ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
-docker compose -f "$ROOT_DIR/deploy/docker-compose.yml" down
+LIMIT_FILE="$ROOT_DIR/storage/runtime/docker-compose.limits.yml"
+if [ -f "$LIMIT_FILE" ]; then
+  docker compose -f "$ROOT_DIR/deploy/docker-compose.yml" -f "$LIMIT_FILE" down
+else
+  docker compose -f "$ROOT_DIR/deploy/docker-compose.yml" down
+fi

+ 6 - 1
scripts/dev-up.sh

@@ -2,7 +2,12 @@
 set -eu
 SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
 ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
-docker compose -f "$ROOT_DIR/deploy/docker-compose.yml" up -d --build
+LIMIT_FILE="$ROOT_DIR/storage/runtime/docker-compose.limits.yml"
+sh "$ROOT_DIR/scripts/docker-limits.sh" "$LIMIT_FILE"
+COMPOSE_PARALLEL_LIMIT="${COMPOSE_PARALLEL_LIMIT:-1}" docker compose \
+  -f "$ROOT_DIR/deploy/docker-compose.yml" \
+  -f "$LIMIT_FILE" \
+  up -d --build
 echo "前端: http://localhost:5173"
 echo "后端: http://localhost:8080"
 echo "接口文档: http://localhost:8080/swagger-ui.html"

+ 115 - 0
scripts/docker-limits.sh

@@ -0,0 +1,115 @@
+#!/usr/bin/env sh
+set -eu
+
+OUTPUT_FILE="${1:-}"
+if [ -z "$OUTPUT_FILE" ]; then
+  echo "用法: scripts/docker-limits.sh <output-compose-override.yml>" >&2
+  exit 1
+fi
+
+detect_cpus() {
+  if command -v nproc >/dev/null 2>&1; then
+    nproc
+    return
+  fi
+  if command -v getconf >/dev/null 2>&1; then
+    getconf _NPROCESSORS_ONLN
+    return
+  fi
+  if command -v sysctl >/dev/null 2>&1; then
+    sysctl -n hw.ncpu
+    return
+  fi
+  echo 2
+}
+
+detect_memory_mb() {
+  if [ -r /proc/meminfo ]; then
+    awk '/MemTotal/ { printf "%d\n", $2 / 1024 }' /proc/meminfo
+    return
+  fi
+  if command -v sysctl >/dev/null 2>&1; then
+    sysctl -n hw.memsize | awk '{ printf "%d\n", $1 / 1024 / 1024 }'
+    return
+  fi
+  echo 4096
+}
+
+cpu_value() {
+  awk -v total="$1" -v share="$2" 'BEGIN {
+    value = total * 0.80 * share / 100;
+    if (value < 0.10) value = 0.10;
+    printf "%.2f", value;
+  }'
+}
+
+memory_value() {
+  awk -v total="$1" -v share="$2" 'BEGIN {
+    value = int(total * 0.50 * share / 100);
+    if (value < 96) value = 96;
+    printf "%dm", value;
+  }'
+}
+
+integer_at_least_one() {
+  awk -v value="$1" 'BEGIN {
+    n = int(value);
+    if (n < 1) n = 1;
+    printf "%d", n;
+  }'
+}
+
+CPUS="$(detect_cpus)"
+MEMORY_MB="$(detect_memory_mb)"
+BUILD_CPUS="$(integer_at_least_one "$(awk -v total="$CPUS" 'BEGIN { print total * 0.80 }')")"
+NODE_OLD_SPACE_MB="$(awk -v total="$MEMORY_MB" 'BEGIN {
+  value = int(total * 0.50 * 0.25);
+  if (value < 512) value = 512;
+  printf "%d", value;
+}')"
+
+mkdir -p "$(dirname "$OUTPUT_FILE")"
+cat > "$OUTPUT_FILE" <<EOF
+services:
+  postgres:
+    cpus: "$(cpu_value "$CPUS" 20)"
+    mem_limit: "$(memory_value "$MEMORY_MB" 25)"
+    memswap_limit: "$(memory_value "$MEMORY_MB" 25)"
+    pids_limit: 512
+
+  redis:
+    cpus: "$(cpu_value "$CPUS" 5)"
+    mem_limit: "$(memory_value "$MEMORY_MB" 5)"
+    memswap_limit: "$(memory_value "$MEMORY_MB" 5)"
+    pids_limit: 256
+
+  rabbitmq:
+    cpus: "$(cpu_value "$CPUS" 15)"
+    mem_limit: "$(memory_value "$MEMORY_MB" 15)"
+    memswap_limit: "$(memory_value "$MEMORY_MB" 15)"
+    pids_limit: 512
+
+  backend:
+    cpus: "$(cpu_value "$CPUS" 50)"
+    mem_limit: "$(memory_value "$MEMORY_MB" 45)"
+    memswap_limit: "$(memory_value "$MEMORY_MB" 45)"
+    pids_limit: 1024
+    build:
+      args:
+        MAVEN_OPTS: "-XX:ActiveProcessorCount=$BUILD_CPUS -XX:MaxRAMPercentage=50 -Djava.awt.headless=true"
+    environment:
+      JAVA_TOOL_OPTIONS: "-XX:MaxRAMPercentage=50"
+
+  frontend:
+    cpus: "$(cpu_value "$CPUS" 10)"
+    mem_limit: "$(memory_value "$MEMORY_MB" 10)"
+    memswap_limit: "$(memory_value "$MEMORY_MB" 10)"
+    pids_limit: 256
+    build:
+      args:
+        NODE_OPTIONS: "--max-old-space-size=$NODE_OLD_SPACE_MB"
+EOF
+
+echo "已生成 Docker 资源限制: $OUTPUT_FILE"
+echo "宿主机 CPU: $CPUS, Docker CPU 总预算: 80%"
+echo "宿主机内存: ${MEMORY_MB}MB, Docker 内存总预算: 50%"