Featured image of post 模型安全检测 11:开源版生产化改造清单

模型安全检测 11:开源版生产化改造清单

数据持久化、mq 补实现、热更新完善、水平扩展、监控告警,逐项按现状→风险→改法→验证展开。

状态:✅ 已完成 前置阅读:02-Mac本地部署实操.md(部署与踩坑实录)、03-技术架构与业务模块分析.md(模块与链路全景) 日期:2026-09-14 素材来源:JoySafety 仓库源码逐一核实(compose / Dockerfile / Java 源码),引用格式 path:line;与提纲素材不符处以实际代码为准(见 0.3 勘误表) 读者定位:已跑通开源版,想系统评估"离上生产还差什么、怎么补、补完怎么验"的读者


〇、先建全局认识:差距在哪、先补哪块

0.1 两条链路回顾(所有改造都落在这两条链路上)

配置链路(配置怎么流转):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
运维/脚本 → admin(8006) 版本化状态机 → MySQL(online 配置落库)
                                          │ api 每 60s HTTP 轮询
                                          ▼
            api 侧 DynamicConfigService(service/DynamicConfigService.java:65)
            POST {conf.dynamic.url}(=admin /config/defense/api/conf,
            example/safety-api/conf/application_example.properties:5)
                                          │
        按每个业务的 lastChangeTimestamp 判变更(admin 侧取 business/dag/function
        三类记录 updateTime 的最大值:safety-admin .../business/DefenseConfigServiceImpl.java:274-291)
                                          ▼
   volatile 内存 Map 全量替换 → 回调 listener → FunctionChainBuilderImpl 重建所有业务 DAG
   (groovy 路由脚本重新 warmup 编译;但 OkHttp client 按 type$url 静态缓存不重建 → 02 篇坑 5 根因)

请求链路(请求怎么流转):

1
2
3
4
5
6
7
8
9
业务方 → POST /llmsec/api/defense/v2/{accessKey}
  → checkParams → rateLimited(Guava 单机限流,RateLimitServiceImpl.java:30)
  → verify(HMAC-SHA1 验签;accessKey 以 test 开头跳过,DefenseApiV2Controller.java:74-75)
  → 按 responseMode 分派:sync 同步跑 DAG;free_taxi/http/mq 投递 Redis 队列异步跑
  → DAG 逐节点执行(while 逐跳,service/execute/FunctionExecutorImpl.java:289-333)
      → 节点 Function 经 OkHttp 调 skill 服务(keywords/bert/knowledge)
      → 每个结果带耗时 appendResult 进 ctx(core/engine/AbstractFunction.java:41-59)
      → groovy router 求值返回下一跳 nodeId,返回 null 则链路结束
  → sync 直接返回;异步模式把结果 lpush 进 Redis 结果队列(TTL 300s,TaskExecutor.java:15)

0.2 差距清单总表

#改造项现状一句话爆炸半径优先级
①数据持久化MySQL/Redis/Vearch 全没挂数据卷down 一次全丢P0
②安全加固admin 8006 无任何鉴权且直接映射宿主机拿到 8006 ≈ 关掉整条安全防线P0
③启动依赖治理sleep 30/60 硬编码排序,healthcheck 被注释且路径写错冷启动靠运气P1
④配置热更新60s 轮询能感知变更,但 OkHttp 静态缓存使超时类变更必须重启变更滞后/需重启P1
⑤mq responseMode实现是空 stub,mq 模式实际等价 http 模式业务方按 README 用会拿不到结果P1
⑥可观测性埋点调用遍布代码,但 MonitorAlarmService 方法体全空出事故无指标可查P1
⑦水平扩展free_taxi 分布式锁天然支持多实例,但锁实现有两处原子性瑕疵;skill 地址写死单点会话锁死/双跑P2
附技术栈升级Spring Boot 2.0.9 / Jedis 2.5.2 / fastjson 双栈混用漏洞包袱+演进成本P2

0.3 素材坐标勘误(提纲 vs 实际代码)

写作前逐一核实,提纲有三处包路径偏差,本文一律以实际代码为准:

提纲写法实际位置
service/impl/MessageQueueServiceImpl.java:32service/storage/MessageQueueServiceImpl.java(空方法 appendResponseData 恰好在 :32,行号仍对得上)
engine/FunctionChainBuilderImpl.javaservice/execute/FunctionChainBuilderImpl.java:37
engine/functions/ParallelFunction.javaservice/execute/functions/ParallelFunction.java:41

其余坐标(DynamicConfigService.java:45 类声明、GroovyRouter.java:27、contoller/rpc/DefenseApiServiceImpl.java:25)均核实无误。


一、数据持久化(最紧急,P0)

现状:根目录 docker-compose.yaml 三个有状态组件都没挂数据卷——

  • MySQL(:8-33):volumes 只有 my.cnf 和 3 个初始化 SQL(:19-23,挂进 /docker-entrypoint-initdb.d/),/var/lib/mysql 留在容器可写层。
  • Redis(:35-48):command 只有 redis-server --requirepass(:39),无 volume、未开 AOF。
  • Vearch(:50-66):只挂了 config.toml(:58-59);而 deploy/thirdparty/vearch/config.toml:7 声明 data = ["datas/","datas1/"] 是相对路径,数据落在容器内工作目录(官方镜像 WORKDIR=/vearch)。
  • 对照组:deploy/docker-compose.yml:20 其实给 MySQL 挂了 ./thirdparty/mysql/data:/var/lib/mysql——源码构建路径做了持久化,发布版反而没做(该文件里 Redis/Vearch 同样没挂)。

风险:docker compose down / 误删容器 → 策略、业务、敏感词、知识条目、知识向量全丢(02 篇 4.1 已实测,4.2 的恢复流程本质就是"认命重灌")。分层看爆炸半径:MySQL 与 Vearch 是硬损失(向量库重建还依赖 MySQL 知识表先保住);Redis 里全是短 TTL 易失数据(free_taxi 任务/结果队列 300s、识别结果缓存 900s——TaskExecutor.java:15、FunctionExecutorImpl.java:247),丢了只是在途检测任务和缓存命中率,Redis 持久化是锦上添花,不是生死线。

改法(compose diff 示意):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
  safety-mysql:
    volumes:
      - mysql-data:/var/lib/mysql          # 新增:数据本体
      - ./deploy/thirdparty/mysql/my.cnf:/etc/my.cnf.d/my.cnf   # 原有:配置与 init SQL
  safety-redis:
    command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}",
              "--appendonly", "yes", "--appendfsync", "everysec"]   # 新增 AOF
    volumes:
      - redis-data:/data                   # 新增
  safety-vearch:
    volumes:
      - ./deploy/thirdparty/vearch/config.toml:/vearch/config.toml  # 原有
      - vearch-datas:/vearch/datas         # 新增,对齐 config.toml:7 的相对路径
      - vearch-datas1:/vearch/datas1
volumes:
  mysql-data: {}
  redis-data: {}
  vearch-datas: {}
  vearch-datas1: {}

两个注意点:① MySQL 的 /docker-entrypoint-initdb.d/ 脚本只在空数据目录首次启动时执行——挂上已有数据的卷后改 init SQL 不会重放;② 持久化 ≠ 备份,生产上另加定时 mysqldump 与 vearch 快照策略。

验证:docker compose down && docker compose --env-file .env up -d 后,admin 里策略/词库原样在(02 篇 4.2 的"重跑附录 A/B"步骤应可省掉);docker exec safety-redis ls /data 能看到 appendonlydir;删掉 vearch 容器重建后 curl http://localhost:8817 查 space 仍在。


二、启动依赖治理(P1)

现状:根 compose 用硬编码 sleep 做启动排序——admin sleep 30(:84)、api sleep 60(:118)、keywords sleep 30(:153)、example-init sleep 60(:228)。健康检查层面更尴尬:

  • MySQL/Redis/Vearch/Knowledge 有 healthcheck(:29-33/:44-48/:62-66/:193-197,其中 vearch 的 nc 检查是假警报,见 02 篇坑 3);
  • admin/api 的 healthcheck 被整段注释(:94-98/:128-132),且路径写的是 /health——实际是 /basic/healthcheck(contoller/HealthCheckController.java:14-24,类级 @RequestMapping("/basic"));该控制器故意注入 ConfigService(:17-19),保证返回 ok 时配置已首载完成,语义很对;
  • admin 源码里根本没有 health 端点(全模块 grep health 无匹配);
  • deploy/docker-compose.yml 反面教材:给 api→admin、knowledge→vearch 配了 condition: service_healthy(:137-138/:83-85),但被依赖方既无 compose healthcheck、镜像 Dockerfile 里也无 HEALTHCHECK 指令(safety-admin/Dockerfile、safety-api/Dockerfile 均无),compose v2 会因"依赖未声明 healthcheck"直接报错拒启动。

风险:sleep 是"按京东内网机器速度拍的数"——Mac 模拟 x86 场景 Java 启动更慢,60s 未必够;api 在配置首载完成前接流量就是 02 篇的 token错误/业务不存在 窗口;example-init 的 sleep 60 同理,快了白等、慢了初始化脚本直接打挂。

改法:① 给 admin 补一个同款 /basic/healthcheck 端点(或引 actuator);② 放开 api/admin/keywords 的 healthcheck 并把路径改成 /basic/healthcheck;③ depends_on 全部升级为 condition: service_healthy;④ 删掉四处 sleep;⑤ example-init 把 sleep 60 换成对 admin/api 健康端点的轮询循环(容器内 curl 重试到 200 再执行初始化)。

验证:time docker compose --env-file .env up -d 对比冷启动总时长;docker inspect --format '{{.State.Health.Status}}' safety-api 看到 healthy 后再跑 quickstart 四案例,不应再出现 token 错误窗口;故意 docker stop safety-mysql 再恢复,观察依赖级联行为符合预期。


三、配置热更新完善(P1)

现状:轮询机制本身是能自洽工作的,核心代码:

1
2
3
4
5
6
7
// service/DynamicConfigService.java:65-77
@Scheduled(fixedRate = 60000, initialDelay = 60000)   // :65 固定 60s 轮询
private void loadConf() throws IOException {
    ...
    ConfigResponse dynamicConf = loadDynamic(...);                 // :71 HTTP 拉全量 online 配置
    boolean shouldUpdate = shouldUpdate(preDynamicConf, dynamicConf); // :72 逐业务比 lastChangeTimestamp
    if (!shouldUpdate) { logger.info("配置无变更"); return; }        // :73-76 无变更直接返回

判变更依据的时间戳在 admin 侧取三方最大值(DefenseConfigServiceImpl.java:274-291:business_info / execute_dag_conf / function_conf 的 updateTime),所以改 function 也能被 api 感知——感知后全量替换 volatile Map 并回调 listener 重建 DAG(FunctionChainBuilderImpl.java:47-51 注册 this::loadConf,:53-71 逐业务重建;Router 反射实例化在 :101-106,groovy 引擎在 GroovyRouter.java:76-82 → GroovyHelper.java:18-22 重建并 warmup 编译)。

真正的坑在资源缓存:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// service/execute/functions/FunctionResourcePool.java:20-29
public synchronized static Object addFuncIfAbsent(RiskCheckType type, String id, Object resource) {
    String key = type + "$" + id;        // key 只有 类型+url,不含 timeout
    Object oldFunc = pool.get(key);
    if (oldFunc != null) {
        return oldFunc;                  // 已有就直接用旧的——新 timeout 永远进不来
    } else {
        pool.put(key, resource);
        return resource;
    }
}

各 HTTP Function 的 OkHttp client 在 doInit 时经此池 get-or-add(如 KeywordFunction.java:58-65),callTimeout 在首次创建时烤死。结论:改 url 有效(新 key 建新 client),同 url 只改 timeout 无效,必须重启 api——这就是 02 篇坑 5 的代码级根因。

风险:① 变更最长滞后 60s,且 listener 重建走 cached 线程池异步执行(DynamicConfigService.java:106-110、:195),实际生效还要再加一段不确定延迟;② 多实例各自轮询,配置切换不齐,灰度期间同业务不同实例行为可能不同;③ 单业务重建失败只记日志并保留旧 DAG(:63-68),其余业务照常替换——半新半旧状态只能靠人看日志发现;④ 长期频繁改 url 会让旧 client 在静态池里累积(连接池+线程不释放)。

改法(三档,按投入选):

档位做法解决什么
L1 小修轮询周期配置化;FunctionResourcePool 的 key 加 timeout(或重建前按业务 invalidate)重启才生效、滞后窗口
L2 推送admin 的 online/upgrade 成功后 webhook 回调 api——api 需新增一个 HTTP 端点包装已有的 FunctionChainBuilderImpl.refresh(businessName)(:128-140,现无对外端点,已核实)秒级生效、多实例齐切
L3 基建变更事件走 MQ 广播(正好复用第五节 mq 基建)或引入 Nacos/Apollo彻底的事件驱动配置中心

验证:L1 后改 function 超时不重启,重跑 02 篇案例 3 观察耗时从 ~2000ms 截断变为正常命中;api 日志出现"配置更新成功";L2 后从 admin upgrade 到 api 行为变化端到端 < 数秒,且双实例日志时间戳一致。


四、mq responseMode 补实现(P1)

现状:接口实现是空 stub,全文就这么多(service/storage/MessageQueueServiceImpl.java:30-37):

1
2
3
4
5
6
7
8
//  todo 增加输出到mq
@Override
public void appendResponseData(SessionContext context, String accessKey, String sessionId, DefenseApiResponse response) {
}

@Override
public void appendContextData(String accessKey, String sessionId, String contextData) {
}

唯一调用点在异步任务落结果之后(service/task/TaskExecutorImpl.java:222);而请求分派处 case mq: 直接落入 case http: break;(service/DefenseServiceImpl.java:171-173)。即 mq 模式今天 = http 模式 + 一次空调用,结果只进 Redis 结果队列(TTL 300s)。

风险:业务方按 README 选 mq 模式却收不到任何消息;不接 Redis 拉取接口的话结果 5 分钟后蒸发;检测数据无法进数仓做拦截率/误杀率分析(第六节也依赖它)。

改法(实现要点):

  1. init() 里建 producer(rocketmq-spring-boot 或 kafka-clients),topic 按 safety-result-{group} 规划、tag=accessKey;
  2. appendResponseData() 里异步 send,message key=sessionId,body 即 response JSON;send 失败只记日志+计数,绝不阻塞/拖垮主检测链路;
  3. appendContextData() 投递会话上下文(供多轮检测外部分析用);
  4. 消费契约文档化:按 tag 过滤、按 key 归并同会话结果;
  5. 引依赖时留意与现有 fastjson 双栈、Spring Boot 2.0.9 的版本兼容(见第七节)。

验证:compose 加一个 namesrv+broker(或 kafka)单节点;发 mq 模式请求后消费端收到结构化结果;docker stop broker 期间主链路审核不受影响、错误日志有兜底记录。


五、高可用与水平扩展(P2)

现状:free_taxi 的多实例互斥是设计内自带的(service/task/TaskService.java:20-25 注释:“每个处理线程使用分布式锁独占会话,顺序处理”),锁实现:

1
2
3
4
5
6
7
8
9
// service/storage/RedisStorageService.java:102-113
public boolean lock(String key, int expireSeconds) throws Exception {
    String value = GlobalConf.lockId();                       // ip/线程名/时间戳(GlobalConf.java:97-99)
    Long rawValue = (Long) this.execute(jedis -> jedis.setnx(key, value));
    boolean returnOk = Objects.equals(1L, rawValue);
    if (returnOk) {
        this.execute(jedis -> jedis.expire(key, expireSeconds));  // 第二条命令,与 setnx 非原子
        return true;
    } ...

配套机制:抢锁只试 1 次(TaskExecutorImpl.java:73 TASK_WAIT_LOOP = 1)、抢到后每批处理前 expire 续约(:135)、finally 里 unlock(:156)即直接 del(:116-118)。容量侧两组线程池都偏小:并行节点池 200 线程/队列 1/CallerRunsPolicy(service/execute/functions/ParallelFunction.java:84-93,:86 留着官方 todo"根据实际情况调整"),异步任务池 200/队列 10(TaskExecutorImpl.java:56-63);限流是实例级 Guava RateLimiter 内存 Map(RateLimitServiceImpl.java:30);skill 地址写死在 function 配置的 url 里(如 KeywordFunction.java:48-50 校验 url 非空)。

风险:① setnx 与 expire 两条命令之间进程崩溃 → 锁无 TTL → 该会话永久无人处理(直到人工 del);② 锁到期被别的实例抢到后,原持有者 finally 里的 del 不校验 owner,会误删新锁 → 同会话双跑、结果重复 lpush;③ 队列 1 意味着几乎无缓冲,CallerRunsPolicy 把压力回压给调用线程(DAG 主线程/Tomcat worker),同步响应被拖长——注意 Tomcat 默认 maxThreads 也是 200,三层 200 要对齐着扩容;④ N 副本实际限流 = qpsLimit × N;⑤ skill 单点。

改法:锁原子化——set(key, val, NX, PX, ttl) 一条命令 + Lua 脚本校验 value 后再 del(标准分布式锁写法,顺手把 jedis 升到 4.x/5.x,见第七节);api 多副本前置 LB(nginx/网关按 URL 前缀转发即可,api 本身无状态);skill 多副本后把 function url 换成 LB 地址或 K8s Service 名;限流换 Redis Lua 令牌桶做全局限额,或按副本数拆分配额;线程池按"峰值并发 × 单请求占用 function 数"公式估算并配置化。

验证:双副本 api 下 kill -9 其一,另一副本应在锁 10s 过期后接管会话(日志按 lockId 对账无重复处理);压测观察 CallerRunsPolicy 触发点即容量水位;双副本下接口限流阈值实测确认。


六、可观测性(P1)

现状:最大的发现——埋点"管线"已铺遍关键路径,但汇集类是空壳:

1
2
3
4
5
6
7
8
// service/MonitorAlarmService.java:11-29(全文 69 行,所有方法体皆空)
@Service
public class MonitorAlarmService {
    // todo 增加监控方案                     // :12
    ...
    public static void basicIncr(String type) { }                       // :28-29
    public static void parallelIncr(String biz, String fn, String reason) { }  // :34-35
    public static void functionIncr(...) { }   // requestIncr/apiPlanIncr/taskIncr/riskIncr 同理全空

调用点却一个不少:节点异常计数(FunctionExecutorImpl.java:311)、并行子项失败(ParallelFunction.java:108/134/169)、异步任务全流程(TaskExecutorImpl.java:118/137/139/163/177/185)、DAG 构建失败(FunctionChainBuilderImpl.java:60/67)。耗时埋点也已就绪:AbstractFunction.run() 给每个结果 setCost 并 appendResult 进 ctx(:41-59)。日志侧 log4j2 配置(example/safety-api/conf/log4j2-example.xml):RollingRandomAccessFile 100MB×10 滚动、console 只打 ERROR、com.jd logger 重复声明两段(:35-40)、无 JSON 格式无 traceId。

风险:线上无 QPS/拦截率/错误率/function 耗时任何指标,事故定位全靠捞 app.log;队列积压、锁竞争、慢 skill 全部不可见。

改法:用 Micrometer(Spring Boot 2.x 自带)填 MonitorAlarmService 各方法——counter 类(riskIncr/taskIncr/requestIncr)+ timer 类(包装 appendResult 处的 cost);引 actuator 暴露 /actuator/prometheus(内网或网关后,别裸奔,见第八节);log4j2 改 JSON layout + MDC 塞 requestId/sessionId,与日志平台对接;Python skill 侧用 prometheus_client 暴露推理耗时;拦截/误杀回流分析借第四节 mq 把结果入仓。

验证:curl /actuator/prometheus | grep safety 出指标;构造一次 BERT 超时,对应 parallel 错误计数 +1;Grafana 出 QPS/耗时/拦截率三块面板。


七、技术栈升级风险评估(P2)

现状:版本底账(均已核实)——

依赖版本出处备注
Spring Boot2.0.9.RELEASEsafety-api/pom.xml:82018 年末,早已 EOL 无安全补丁
JDK1.8 / temurin-8pom.xml:20-21、safety-api/Dockerfile:1
Jedis2.5.2pom.xml:542015 年代,无 SetParams、无集群/ssl 现代特性
fastjsoncom.alibaba:fastjson:2.0.32pom.xml:481.x 包名 API 的 2.x 兼容发行版,代码里 com.alibaba.fastjson.* import 共 19 处
fastjson2com.alibaba.fastjson2:fastjson2:2.0.46safety-basic/pom.xml:90-93DynamicConfigService/RedisStorageService 等在用——两套 JSON 栈混用
groovy-all4.0.21pom.xml:190
okhttp4.12.0pom.xml:184反而很新

并发瓶颈在 groovy 求值串行化(core/engine/routers/GroovyRouter.java:42-46):

1
2
3
4
5
synchronized (engine) {                          // :42 每个 router 实例一把锁
    Bindings bindings = engine.createBindings();
    bindings.put("ctx", context);
    eval = engine.eval(conf.getScript(), bindings);  // :45 所有并发请求在此排队
}

parallel 归约脚本同款 synchronized(ParallelFunction.java:155-161)。ScriptEngine 非线程安全才出此下策,后果是同一路由节点上所有请求的脚本求值串行,高并发下直接顶到 p99。

风险:EOL 组件无补丁;fastjson 双栈序列化行为差异(SerializerFeature 语义不同)埋数据类 bug;升级联动面广(Boot 3.x 要 javax→jakarta 全量替换,knife4j 4.5.0 也得换成 springdoc)。

改法:保守档——Boot 2.0.9→2.7.18(javax 不变,得补丁与新版 micrometer)、jedis 2.5.2→4.x(API 变动集中在 JedisPool 构造与 set 参数)、fastjson 统一收敛到 fastjson2 或 Jackson;激进档——Boot 3.x + JDK 17,按模块灰度。groovy 瓶颈:脚本预编译缓存(GroovyShell.parse 一次、运行期只跑 Script.run() 并池化 Binding),或把热点路由逻辑下沉成 Java Router 实现(ROUTER_MAP 本就支持新增类型)。

验证:升级后全量回归 quickstart 四案例 + admin 全接口 CRUD + free_taxi 链路;JMeter 对 groovy 路由节点压测对比升级前后 QPS/p99。


八、安全加固(P0)

现状:admin 全部管理接口零鉴权——safety-admin 源码 grep Interceptor|Filter|Authorization|auth 无任何匹配,pom 也无 spring-security/shiro;而根 compose 把 8006 直接映射宿主机(:87-88)。其余面:test 开头 accessKey 免签名是写死在代码里的特赦(DefenseApiV2Controller.java:74-75)而非配置项;演示 secretKey=123456、.env 默认口令 123456;vearch skip_auth = true(config.toml:16)、signkey = "secret"(:13);RPC 控制器 DefenseApiServiceImpl 整体被注释停用(:24-25 //todo + //@Service)——当前无 RPC 暴露面,但改造时切勿顺手放开注释而不加鉴权。

风险:拿到 8006 即可下线/篡改全部安全策略,等于从内部关掉整条防线——比任何单个绕过都危险;test 后门在生产可被直接免验签调用;弱口令 + 镜像未扫描。

改法:网络层先行(最便宜最有效):compose 不映射 8006 到宿主机、仅内网可达,前置 nginx 加 basic auth/IP 白名单;应用层跟上:admin 加统一签名校验过滤器(可直接复用 SignUtil 的 HMAC-SHA1),test 特赦改成配置开关且生产默认关;密钥轮换:business 增加"上一密钥"字段,双密钥并行验签一个窗口期再作废旧钥;容器加固:非 root 运行、只读根文件系统、镜像扫描进 CI。

验证:无凭证 curl 8006 各管理接口应一律 401;生产配置下 test accessKey 请求被拒;轮换演练:换钥后旧 key 在窗口内通过、窗口外 403;docker compose config 确认 8006 不再暴露宿主机。


九、改造路线图与坐标速查

9.1 建议实施波次

波次内容理由
第一波(本周)①数据持久化 + ②安全加固网络层都是纯 compose/部署改动,不改一行代码,堵住两个 P0
第二波(两周)③启动治理 + ⑥可观测性 + ⑤mq建立"看得见、起得稳、流得出"的运维底座
第三波(排期)④热更新 L2/L3 + ⑦水平扩展 + 附·技术栈保守档要动代码和压测,放在底座稳固之后

9.2 本文引用坐标速查

主题位置(均相对仓库根目录)
根 compose(无数据卷/sleep)docker-compose.yaml:19-23,39,58-59,84,118,153,228
开发版 compose 对照deploy/docker-compose.yml:20,83-85,137-138
源码构建入口deploy/build_all.sh:11-33、safety/safety-api/build/build.sh:4-6、safety/safety-api/Dockerfile:1-27
配置轮询safety/safety-api/.../service/DynamicConfigService.java:65,106-110,113-151
变更时间戳(admin 侧)safety/safety-admin/.../service/business/DefenseConfigServiceImpl.java:274-291
DAG 重建safety/safety-api/.../service/execute/FunctionChainBuilderImpl.java:47-71,128-140
OkHttp 静态缓存(坑 5 根因)safety/safety-api/.../service/execute/functions/FunctionResourcePool.java:20-29
mq 空 stubsafety/safety-api/.../service/storage/MessageQueueServiceImpl.java:30-37
mq 分派落空safety/safety-api/.../service/DefenseServiceImpl.java:171-173
free_taxi 锁safety/safety-api/.../service/storage/RedisStorageService.java:102-118、service/task/TaskExecutorImpl.java:73-188
线程池参数service/execute/functions/ParallelFunction.java:84-93、service/task/TaskExecutorImpl.java:56-63
监控空壳safety/safety-api/.../service/MonitorAlarmService.java:11-69
耗时埋点safety/safety-basic/.../core/engine/AbstractFunction.java:41-59
groovy 串行求值safety/safety-basic/.../core/engine/routers/GroovyRouter.java:42-46
版本底账safety/safety-api/pom.xml:8,48,54,190、safety/safety-basic/pom.xml:90-93
健康端点safety/safety-api/.../contoller/HealthCheckController.java:14-24
停用的 RPC 控制器safety/safety-api/.../contoller/rpc/DefenseApiServiceImpl.java:24-25

与前几篇的联动:02 篇坑 4(两层超时)的完整答案在 03 篇 3.4;坑 5(改配置需重启)的代码级根因在本文第三节;4.2 的灾后重灌流程在本文第一节落地数据卷后可退役。

使用 Hugo 构建
主题 Stack 由 Jimmy 设计