Redis 使用(配置 / 运维 / 监控)
Redis 使用(配置 / 运维 / 监控)
Redis 在本项目里承担三类职责,三者的开关相互独立:
| 用途 | 开关 | 说明 |
|---|---|---|
| 业务缓存与会话 | RedisServer:open = 1 | 由 CacheStore 自动切换内存/Redis,见缓存 |
| SqlSugar 二级缓存 | RedisServer:dbCache = true | ORM 查询缓存落 Redis,见数据库操作 |
| 定时任务 / 限流 / 计数 | 依赖上面第一项 | 每一样都依赖缓存后端是 Redis 才能跨节点生效 |
配置
ZR.Admin.WebApi/appsettings.json:
"RedisServer": {
"open": 1, // 只有字符串 "1" 才初始化(写 true / 1.0 都不生效)
"dbCache": false, // SqlSugar 二级缓存放 Redis,与 open 独立
"Cache": "127.0.0.1:6379,defaultDatabase=0,poolsize=50,ssl=false,writeBuffer=10240,prefix=cache:",
"Session": "127.0.0.1:6379,defaultDatabase=0,poolsize=50,ssl=false,writeBuffer=10240,prefix=session:"
}
| 连接串 | 语义 | 谁在用 |
|---|---|---|
Cache | 业务缓存库 | CacheStore.Default、登录锁定/状态复核/字典版本/AI 额度、SqlSugarRedisCache |
Session | 会话态库 | 单设备登录会话、会话吊销、2FA 票据、WebAuthn challenge |
连接串参数(CSRedis 语法,逗号分隔):
| 参数 | 说明 |
|---|---|
defaultDatabase | 库号,Cache 与 Session 建议分开,排查时不会混 |
poolsize | 连接池上限,默认 50;高并发下单机可加到 100~200 |
password | 有密码时必填:,password=xxx |
ssl | true 时走 TLS(云 Redis 常见) |
writeBuffer | 发送缓冲区,默认 10240 |
prefix | 键前缀:本项目已用 cache: / session: 区分两类用途 |
生产环境请把连接串(含密码)用环境变量注入:
RedisServer__Cache/RedisServer__Session,不要写进仓库里的 appsettings。
初始化与回退
// Program.cs
var openRedis = builder.Configuration["RedisServer:open"];
if (openRedis == "1")
{
RedisServer.Initalize(); // new CSRedisClient(Cache) + new CSRedisClient(Session)
}
open != "1"→RedisServer.Cache/Session均为null,CacheStore自动回退本地内存,功能不报错,只是多节点语义失效;open = "1"但连接串错误 →Initalize()抛异常,直接启动失败(这是有意的,避免带病运行);- 两个宿主(
ZR.Admin.WebApi/ZR.Front.WebApi)各自初始化,需配置同一套 Redis; - 改配置要重启,Redis 配置不支持热更新。
业务如何使用
不要直接调 RedisServer.Cache.Set(...):那样会绕过"没有 Redis 时回退内存"的逻辑,open=0 时直接空引用。
// 业务缓存(默认 Cache 库)
CacheStore.Default.Set("my:key", obj, 30); // 过期单位:分钟
var obj = CacheStore.Default.Get<MyType>("my:key");
// 会话态(Session 库)
CacheStore.For(CacheBackend.Session).Set("sid", id, 30);
// 跨节点原子操作
CacheStore.Default.Increment("login:fail:123", 15);
CacheStore.Default.TrySet("alert:lock:123", 1, 60);
业务里更推荐走 CacheService(会话、2FA 票据、登录锁定、状态复核等都已封装好 key 规则与 TTL),详见缓存。
目前只有
SqlSugarRedisCache仍在直接使用RedisServer.Cache—— 它属于 ORM 二级缓存实现,不受CacheStore管理。
SqlSugar 二级缓存(dbCache)
dbCache = true时,SqlsugarSetup用SqlSugarRedisCache作为DataInfoCacheService;否则用进程内SqlSugarCache;- 缓存键统一前缀
SqlSugarDataCache.*,存放在Cache连接对应的库里; - 需要
open = 1且 Redis 可用,否则查询缓存写入会失败; - 多实例且开了二级缓存时必须用 Redis,否则各节点缓存互不可见,出现"更新了却读到旧数据";
- 清理接口内部用
KEYS SqlSugarDataCache.*:key 数量很大时KEYS会阻塞 Redis,生产建议用独立的库或定期重建。
缓存监控
当前是占位实现
GET /monitor/cache 目前直接 return SUCCESS(1),没有返回真实数据,前端「缓存监控」页面因此看不到指标。需要真实指标时按下面的方式自行实现。
参考实现(放回 MonitorController.GetCache();需要 using Infrastructure.Cache; 与 using CSRedis;):
[HttpGet("monitor/cache")]
[ActionPermissionFilter(Permission = "monitor:cache:list")]
public IActionResult GetCache()
{
// open=0 时 RedisServer.Cache 为 null,直接调会空引用
if (RedisServer.Cache == null)
{
return SUCCESS(new { enabled = false, info = new Dictionary<string, string>() });
}
var infos = RedisServer.Cache.NodesServerManager.Info();
var commandStats = RedisServer.Cache.NodesServerManager.Info(CSRedis.InfoSection.CommandStats);
var dbSize = RedisServer.Cache.NodesServerManager.DbSize();
// INFO 返回多行 key:value,按行解析成字典
var infoDic = infos[0].Item2
.Split("\r\n", StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Split(':', 2))
.Where(x => x.Length == 2)
.GroupBy(x => x[0])
.ToDictionary(g => g.Key, g => g.First()[1]);
var commandStatDic = commandStats[0].Item2
.Split("\r\n", StringSplitOptions.RemoveEmptyEntries)
.Where(x => x.StartsWith("cmdstat_"))
.Select(x => x.Split(':', 2))
.Where(x => x.Length == 2)
.Select(x => new
{
name = x[0].Replace("cmdstat_", ""),
value = x[1].Split(',')[0].Replace("calls=", "") // 只看调用次数
})
.ToList();
return SUCCESS(new
{
enabled = true,
db = dbSize[0].Item1,
keys = dbSize[0].Item2, // 当前库 key 总数
commandStats = commandStatDic,
info = infoDic // 含 redis_version / used_memory_human / connected_clients …
});
}
更省事的方式(不写代码):直接连服务器看
redis-cli -h 127.0.0.1 -p 6379 -a 密码 info stats | head -30
redis-cli -a 密码 dbsize
redis-cli -a 密码 --scan --pattern 'cache:CACHE-*' | head
redis-cli -a 密码 --scan --pattern 'session:CACHE-USER-SESSION_*' | wc -l
用
--scan而不是KEYS *:后者在几十万 key 时会长时间阻塞主线程。
生产运维要点
| 项 | 建议 |
|---|---|
| 密码 | 必须设置,用环境变量注入;不要用默认空密码监听公网 |
| 网络 | 只监听内网/回环,安全组不放 6379 到公网 |
| 持久化 | 缓存可丢,appendonly no + save "" 即可(会话态也允许重登) |
| 内存策略 | 缓存类数据用 maxmemory-policy allkeys-lru,并设 maxmemory 上限 |
| 库划分 | Cache 与 Session 分库或分前缀,便于单独清理 |
| 哨兵/集群 | CSRedis 支持,连接串按 CSRedis 文档填写;主从切换后需确认客户端能重连 |
| 监控 | 关注 used_memory、connected_clients、evicted_keys、慢查询 |
| 大 key | 避免把大列表/大 JSON 当缓存值(如整个菜单树),必要时拆 key |
常见错误
NOAUTH Authentication required
连接串里没写 password=xxx,或密码含特殊字符没转义。云 Redis 还要确认是否开启了 TLS(ssl=true)。
启动直接失败,日志指向 CSRedisClient
open=1 但连接串格式错误/端口不通。先在服务器上 redis-cli -h <host> -p <port> ping 验证,再检查连接串参数名是否写错。
MISCONF Redis is configured to save RDB snapshots...
磁盘写满导致 RDB 落盘失败,Redis 拒绝写入。清理磁盘或按需关闭持久化(缓存场景可关)。
多实例下"偶发登录失败 / 状态不一致"
多半是只有部分节点连上 Redis(或 open 写成了 true)。逐节点执行 redis-cli client list 看连接数,确认每个节点都连上了。
二级缓存改了数据还读到旧值
检查 dbCache 是否为 true、是否所有节点都连的同一个 Redis 库;单实例时也可能因为 IsAutoRemoveDataCache 之外的直查 SQL 绕过 ORM 缓存清理。
