#!/bin/bash # ───────────────────────────────────────────────────────────────────────────── # 分波启动全栈:先基础设施,再按依赖重要度分波放业务服务,波间带负载闸门。 # # 为什么需要:compose 的 depends_on 只保证「基础设施 healthy 才放业务」,但多个 # 业务 JVM 会在同一瞬间冷启动。本机(Apple Silicon 跑 amd64 模拟)CPU 被打满后, # Druid 建连超时 → Spring 启动失败 → 容器僵在 unhealthy(2026-08-06 恢复库后实测: # 一次性 up 大面积僵死,分波 + 负载闸门全绿)。交付服务器(原生 amd64)通常不需要 # 本脚本,直接 docker compose up -d 即可。 # # 用法(仓库根执行): # ./docker/up-staged.sh # 全栈分波起 # LOAD_GATE=6 ./docker/up-staged.sh # 收紧负载闸门(默认 9) # ───────────────────────────────────────────────────────────────────────────── set -uo pipefail cd "$(dirname "$0")/.." LOAD_GATE="${LOAD_GATE:-9}" WAIT_BUDGET="${WAIT_BUDGET:-420}" # 每波等 healthy 的秒数预算 load_now() { # macOS / Linux 双兼容:取 1 分钟负载的整数部分 if command -v sysctl >/dev/null 2>&1 && sysctl -n vm.loadavg >/dev/null 2>&1; then sysctl -n vm.loadavg | awk '{print int($2)}' else awk '{print int($1)}' /proc/loadavg fi } wait_load() { local l while :; do l=$(load_now) [ "$l" -lt "$LOAD_GATE" ] && return 0 echo " 负载 $l ≥ 闸门 ${LOAD_GATE},等 30s…" sleep 30 done } wait_healthy() { local deadline=$((SECONDS+WAIT_BUDGET)) all c s while [ $SECONDS -lt $deadline ]; do all=1 for c in "$@"; do s=$(docker inspect "$c" --format '{{.State.Health.Status}}' 2>/dev/null || echo missing) [ "$s" = "healthy" ] || all=0 done [ "$all" = 1 ] && return 0 sleep 15 done echo " ⚠️ 超时未全绿:$*(继续下一波,稍后可单独 docker restart)" return 1 } wave() { echo "── 波次:$* ──" wait_load docker compose up -d "$@" || exit 1 wait_healthy "$@" && echo " ✓ 全绿" } # 波 0:基础设施(PG/Redis)——compose depends_on 的根。 # 2026-08-10:PG/Redis 按 .env 坐标动态入波。坐标指 CVM 时它们已被 # docker-compose.override.yml 的 profiles 门控挡在栈外,此处再显式点名反而会把它们拉起来 # (compose 显式点名服务会自动激活其 profile),白占内存——所以只在本机栈形态才入波。 envval() { grep -E "^$1=" .env 2>/dev/null | tail -1 | cut -d= -f2- | tr -d '"'\''[:space:]'; } CDH="$(envval CUSTOMER_DB_HOST)"; CDH="${CDH:-cx-postgres}" # 未设 = compose 默认,即栈内 PG RDH="$(envval JNPF_REDIS_HOST)"; RDH="${RDH:-cx-redis}" # 同上 WAVE0=() [ "$CDH" = "cx-postgres" ] && WAVE0+=(cx-postgres) [ "$RDH" = "cx-redis" ] && WAVE0+=(cx-redis) # ⚠️ 变量必须写 ${VAR} 形式:紧跟中文全角标点时,裸 $VAR 会把全角字符的字节吞进变量名 # 2026-08-11 修:bash 3.2(macOS 自带)在 set -u 下,对**空数组**取 "${arr[@]}" 与 "${#arr[@]}" # 都会报 `WAVE0[@]: unbound variable` 直接退出。而 PG 与 Redis 双双外置到 CVM 时 WAVE0 正是空数组—— # 也就是说本脚本自 2026-08-10「按 .env 坐标动态入波」改动起,在当前数据面拓扑下**根本跑不起来**。 # 统一用 ${arr[@]+...} 形式取值绕开;顺带订正原注释「波 0 只起 MQ」——RocketMQ 已于同日移除, # 两者都在远端时波 0 是**空的**,直接跳过。 WAVE0_ITEMS="${WAVE0[@]+${WAVE0[@]}}" if [ -z "${WAVE0_ITEMS}" ]; then echo "数据面在远端(PG=${CDH} / Redis=${RDH}),波 0 无本地组件,跳过" else wave ${WAVE0_ITEMS} fi # 波 1:核心链(流程引擎 + 平台聚合服务 + 网关)——其余服务的运行时依赖 # 2026-08-11 平台聚合:cx-oauth / cx-system / cx-visualdev / cx-flowable / cx-file / # cx-message / cx-permission / cx-visualdata / cx-app 九个已合并为 cx-platform。 # ⚠️ wave() 是 `docker compose up -d "$@" || exit 1`——名字对不上会让整个分批启动 # **直接退出**,不是跳过。改 compose 服务名时本表必须同步(这条教训见上一条 cx-audit 注释)。 wave cx-flow-engine cx-platform cx-gateway # 波 2:主业务(原波 2 的 cx-flowable / cx-file / cx-permission 已在 cx-platform 内) wave cx-lims # 波 3:外围业务(原波 3 的 cx-message / cx-visualdev / cx-visualdata 已在 cx-platform 内) # cx-audit 于 2026-08-11 搬入 cx-biz-common;后者自 P1 建服起就漏在本表外,一并补上。 wave cx-biz-common cx-dms # 波 4:收尾(前端;原 cx-app 已在 cx-platform 内) # cx-scheduletask 保留为 extra profile,按需单独启动。 wave cx-web echo "══ 最终状态 ══" docker compose ps --format '{{.Name}}\t{{.Status}}' | sort