95 lines
4.8 KiB
Markdown
95 lines
4.8 KiB
Markdown
# 4.8 缓存架构设计
|
||
|
||
:::tip 概述
|
||
缓存实现为 utility/cache 统一门面:Redis 优先,连接失败或运行期出错自动降级进程内存 gcache(UsingMemory() 可查)。三层缓存各司其职,Redis 键统一前缀 easygoadmin:。
|
||
:::
|
||
|
||
## 缓存分层图
|
||
|
||
```text
|
||
请求 ──▶ ① 进程内缓存(sync.Map,无 Redis 交互,重启即清)
|
||
dictTextCache(300s) · reqFieldCache/textPlanCache(反射计划,永不过期)
|
||
maskReCache(正则,永不过期)
|
||
──▶ ② Redis(跨实例共享,TTL 自动回收)
|
||
黑名单/在线/验证码/字典/租户摘要/身份/限流/任务锁
|
||
──▶ ③ 数据库(唯一真相,dao.Ctx 全量过滤条件)
|
||
|
||
设计原则:② ③ 之间的一切缓存都必须有明确的失效点或短 TTL 兜底;
|
||
① 仅缓存"计算计划/只读文本",不允许缓存业务状态。
|
||
```
|
||
|
||
## Redis 键全集
|
||
|
||
| 键模式 | TTL | 写入方 | 读取方 |
|
||
|--------|-----|--------|--------|
|
||
| `easygoadmin:token:blacklist:{jti}` | 剩余有效期+60s | Logout/Refresh 轮换/RemoveToken/ForceLogout | JWT 中间件 Exists 判定 |
|
||
| `easygoadmin:online:jti:set`(zset) | 成员随 info(score=过期 ts) | MarkOnline/MarkOffline | OnlineUser.Page(ZRangeByScore≥now) |
|
||
| `easygoadmin:online:info:{jti}` | access 剩余+60s | MarkOnline/MarkOffline | OnlineUser.Page/Detail/ForceLogout |
|
||
| `easygoadmin:captcha:{uuid}` | 300s | Captcha 生成 | Login/Verify(一次性) |
|
||
| `easygoadmin:dict:item:{code}` | 3600s | dict.setCache/RefreshCache | dict_item 快捷接口/AliasList |
|
||
| `easygoadmin:tenant:info:{id}` | 60s | Tenant 中间件/Login 路径 | Tenant 中间件/Login |
|
||
| `easygoadmin:user:identity:{uid}` | 300s | JWT 中间件(回源 Loader) | JWT 中间件(Roles/Perms/DataScope/DeptId JSON) |
|
||
| `easygoadmin:user:perms:{uid}` / `user:roles:{uid}` | 约定键(保留扩展) | — | 主路径用 identity 合并键,变更时一并清理 |
|
||
| `ratelimit:{ip}:{path}:{窗口起点}` | window+5s | RateLimit incr | RateLimit |
|
||
| `job:lock:{jobId}` | 300s 兜底 | jobTryLock(NX) | jobTryLock(执行完 defer Del) |
|
||
|
||
## 主动失效点清单
|
||
|
||
**身份缓存 `user:identity:{uid}` 删除时机**:
|
||
|
||
```text
|
||
* role.SavePermission 保存角色权限 → clearRoleIdentityCache(roleId)
|
||
→ 查该角色全部 user → 逐个 Del
|
||
* role.Update 修改角色(含 data_scope 变更)→ AfterUpdate → 同上
|
||
* user.AssignRoles 分配/调整角色 → clearIdentityCache(userId)
|
||
(user.Update / user.Add 均经 AssignRoles——改部门 dept_id 亦在此清缓存)
|
||
* 版本兼容:Identity 新增字段反序列化缺省为 0,读侧 GetDataScope 回退"全部"
|
||
——升级无需强清存量缓存
|
||
```
|
||
|
||
**其他**:租户摘要在 tenant 状态/过期变更时由 AfterUpdate clearCache 钩子清理;字典 Redis 缓存由 RefreshCache 主动重建(dict_item 变更不主动清,最长 1 小时延迟);黑名单靠 TTL 自然过期(过期即令牌本身已失效);验证码命中即删。
|
||
|
||
## 进程内缓存明细
|
||
|
||
| 缓存 | 位置 | 内容 | TTL |
|
||
|------|------|------|-----|
|
||
| dictTextCache | base.go | {code}:{value} → 显示名 | 300s |
|
||
| reqFieldCache / textPlanCache / childrenIdxCache | module_spec.go | 反射索引与填充计划 | 永不过期(类型不变即安全) |
|
||
| maskReCache | middleware/util.go | 脱敏正则 | 永不过期 |
|
||
|
||
## 内存降级的影响面
|
||
|
||
Redis 不可用时 `UsingMemory()=true`,以下能力**退化单机语义**:
|
||
|
||
```text
|
||
限流计数 各实例各自计数(阈值实际翻倍)
|
||
任务锁 job:lock 退化单机,多实例可能并发执行任务
|
||
在线用户 只看本实例登录的会话
|
||
JWT 黑名单 本实例签发的登出/强退不拦截其他实例
|
||
验证码 单实例内有效
|
||
字典缓存 各实例独立回源
|
||
```
|
||
|
||
运维可通过 `GET /api/cache/info` 的 `mode` 字段确认当前模式(redis/memory)。
|
||
|
||
## TTL 设计原则(新增缓存遵循)
|
||
|
||
```text
|
||
1) 状态判定类(黑名单/租户状态):TTL ≤ 业务有效期,且必须有主动失效点;
|
||
宁可短 TTL 多回源,不可长 TTL 陈旧放行
|
||
2) 展示文本类(字典/归属地):TTL 300s~3600s + 刷新接口,接受分钟级延迟
|
||
3) 高频读聚合类(identity):合并为单键 JSON(一次回源装多个集合),
|
||
TTL 300s,变更路径主动 Del
|
||
4) 计数/锁类:键内嵌窗口起点或场景唯一 id,TTL 只做回收不参与语义
|
||
5) 任何经 cache.Set 的值必须可 JSON 序列化;读侧 Unmarshal 失败视为未命中回源
|
||
```
|
||
|
||
## 排障速查
|
||
|
||
```text
|
||
键不存在/值陈旧 先查是否内存降级(redis-cli KEYS 'easygoadmin:*' 对比)
|
||
权限不生效 DEL easygoadmin:user:identity:{uid}
|
||
租户被禁仍可访问 等 60s 或 DEL easygoadmin:tenant:info:{id}
|
||
多实例行为不一致 确认 Redis 可用(/api/cache/info mode 应为 redis)
|
||
```
|