刘光辉
9 小时以前 34981c30a78e8bbd7791131059a9210f9928b62c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#!/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