# 元数据变更后的缓存失效 当 PM 在**后台**保存变更(给表单加列、更新权限、注册新模块)时,每个运行节点都必须丢弃对旧元数据的缓存解释。xly 通过 JMS 完成,而不是轮询。 ## 路径 ```text PM 在**后台**保存 ↓ **后台**控制器写入变更的 gds_* 行 ↓ Controller 发布 JMS “module changed” 消息 ↓ 每个节点的 xlyErpJmsConsumer 收到消息 ↓ ConsumerChangeGdsModuleThread.run() 清除相关 Redis key ↓ 任意节点下一次 /business/getModelBysId 调用重新读表, 并用新值重新填充缓存 ``` 处理器位于 `xlyErpJmsConsumer/src/main/java/com/xly/xlyerpjmsconsumer/thread/ConsumerChangeGdsModuleThread.java`。 ## 为什么是 JMS,不是轮询后清除 xly 经常跨多个节点运行(xlyEntry、xlyApi、xlyInterface 各自 JVM,有时水平扩展)。轮询“元数据是否变了”要么很慢(直到下一次轮询才可见),要么很吵(持续心跳)。JMS 可以在毫秒级把失效广播到每个节点。 代码库同时使用 **ActiveMQ** 和 **RocketMQ**,但这里记录的元数据变更路径是 **ActiveMQ / JMS**:`xlyErpJmsConsumer` 用 `@JmsListener` 监听 `P2pQueue.ERP_JMS_ACTIVEMQ_CHANGE_GDS_MODULE`,`ConsumerChangeGdsModuleThread` 负责清缓存。`RocketMQServiceImpl` 用于其他集成流。 ## 会清哪些 key Redis 缓存包含: - 按 `sId` 的模块元数据。 - 按 `sId` 的表单元数据。 - 按表单 `sId` 的字段从表列表。 - 每租户覆盖合并结果(派生缓存)。 - 每(模块、角色)的权限规则。 consumer thread 收到变更行 ID 后,会清掉每个可能包含它的缓存 key 家族。**这里过度失效是安全选择**:下一次请求多读一次 DB 的成本,远小于继续服务陈旧元数据的成本。 ## 直接用 SQL 修改元数据时 通过 MyBatis 或通过**后台**做的 insert / update 会*触发* JMS 事件。工程师直接在生产 DB 执行 `UPDATE gdsmodule SET ...` 不会触发。缓存会持续提供旧元数据,直到: 1. 缓存 TTL 过期(实际 TTL 看缓存配置)。 2. 重启应用服务器。 3. 手工发送 JMS 消息(见 `xlyBusinessService` 中的 `BusinessCleanRedisDataImpl`)。 第三种是受支持的 workaround;第二种是粗暴 fallback。 ## 常见 bug:问题其实是缓存 当现象像是“我改了但页面还是旧值”时,按顺序检查: 1. 变更是否实际提交?(对 DB 执行 `SELECT` 确认。) 2. **后台**节点能否访问 JMS broker?(不能访问时,失效事件不会发布。) 3. 所有 consumer 节点是否在运行?(暂停的节点会继续服务旧元数据直到重启。) 4. 变更是否通过原始 SQL 完成?(那就没有 JMS 事件,需要手工触发。) [切片 1](../../slices/01-hello-world.md) 的“五表读取”会从缓存重新运行;理解哪一层陈旧是定位 bug 的关键。