跳到主要内容
版本:3.0.0

Redis 缓存(Aegis.Caching.Redis)

Aegis.Caching.Redis 是 Aegis 中与 Redis 交互的基础能力包。它负责提供 RedisSourceBase、脚本管理和多级缓存所依赖的底座能力。它不通过 Component.deps.json 自动装配,而是由项目在代码里显式注册。

组件概览

字段说明
组件名称Redis 缓存
真实类库Aegis.Caching.Redis
组件定位Redis 数据源、脚本管理与缓存扩展底座
引入方式安装 NuGet,并在代码中调用 AddRedisSource<T>()
是否需要 Component.deps.json
配置节点Redis
核心能力Redis 数据源、脚本统一加载、故障降级参数、本地 L1 缓存配置
注册入口AddRedisSource<T>()

什么时候要用它

适合场景:

  • 业务服务需要直接访问 Redis
  • 认证会话要切到 Redis
  • ID 生成器要接 Redis 容器
  • 事件总线要改成 Redis 容器
  • 需要统一管理 Lua 脚本
  • 需要给缓存拦截器或多级缓存提供 Redis 底座

先记住它和其他 Redis 相关组件的关系

组件负责什么
Aegis.Caching.RedisRedis 基础能力
Aegis.Authorization.RedisUserManagerRedis 会话
Aegis.IdGenerator.Provider.RedisID 生成器 Redis 容器
Aegis.EventBus.Container.Redis事件总线 Redis 容器

也就是说,很多 Redis 能力最终都依赖当前组件先把数据源接起来。

当前这层还直接提供两类常见上层能力:

  • 基于脚本的 BiLocker
  • 基于拦截器的多级缓存

最小可运行路径

第一步:准备 Redis 配置

{
"Redis": {
"RedisMode": "Standalone",
"ConnectionString": "127.0.0.1:6379,defaultDatabase=2",
"SentinelString": "",
"IsRWSplitting": true,
"ScriptPath": "{CurrentDirectory}/Scripts/",
"Resilience": {
"Score": 5,
"RecoverScore": 3,
"HeartBeatInterval": 5,
"FallbackPolicy": "Fallback"
},
"L1Cache": {
"Enabled": true
}
}
}

第二步:定义自己的 RedisSource

public class AegisRedisSource : RedisSourceBase
{
public AegisRedisSource(RedisOptions redisOptions) : base(redisOptions)
{
}
}

第三步:在服务注册阶段显式注入

services.AddRedisSource<AegisRedisSource>(
ConfigManager.Get<RedisOptions>("Redis"));

第四步:在业务类中注入 AegisRedisSource

public class UserService : IUserContract
{
private readonly AegisRedisSource _redis;

public UserService(AegisRedisSource redis)
{
_redis = redis;
}
}

这组配置分别控制什么

配置项作用
RedisModeRedis 模式,支持 StandaloneMasterSlaveSentinelCluster
ConnectionString主连接串,单实例或多节点地址都写在这里
SentinelString哨兵模式下的补充参数
IsRWSplitting是否开启读写分离
ScriptPathLua 脚本目录
Resilience.ScoreRedis 进入降级前的分值上限
Resilience.RecoverScore从降级恢复时需要达到的分值
Resilience.HeartBeatInterval心跳检测间隔,单位秒
Resilience.FallbackPolicy故障策略,通常是 FallbackThrow
L1Cache.Enabled是否开启本地一级缓存

最常见的三种配置场景

单实例

{
"Redis": {
"RedisMode": "Standalone",
"ConnectionString": "127.0.0.1:6379,defaultDatabase=2",
"SentinelString": "",
"IsRWSplitting": false,
"ScriptPath": ""
}
}

主从

{
"Redis": {
"RedisMode": "MasterSlave",
"ConnectionString": "10.0.0.11:6379;10.0.0.12:6379,defaultDatabase=2",
"SentinelString": "",
"IsRWSplitting": true,
"ScriptPath": ""
}
}

哨兵

{
"Redis": {
"RedisMode": "Sentinel",
"ConnectionString": "10.0.0.11:26379;10.0.0.12:26379;10.0.0.13:26379",
"SentinelString": "mymaster,defaultDatabase=2",
"IsRWSplitting": true,
"ScriptPath": "{CurrentDirectory}/Scripts/"
}
}

最常见的使用方式

直接使用 RedisSource 的字符串操作

public class UserCacheService
{
private readonly AegisRedisSource _redis;

public UserCacheService(AegisRedisSource redis)
{
_redis = redis;
}

public async Task SetUserAsync(string key, UserDto user)
{
await _redis.SetAsync(key, user, 300);
}

public async Task<UserDto?> GetUserAsync(string key)
{
return await _redis.GetAsync<UserDto>(key);
}

public async Task RemoveUserAsync(string key)
{
await _redis.DelAsync(key);
}
}

在集合里增删成员

await _redis.SAddAsync("role:admin", userId);
await _redis.SRemAsync("role:admin", userId);

var diff = await _redis.SDiffAsync("role:admin", "role:guest");

操作哈希字段

await _redis.HSetAsync("doctor:1001", "name", "张三");

var doctorName = await _redis.HGetAsync<string>("doctor:1001", "name");

var hasName = await _redis.HExistsAsync("doctor:1001", "name");

脚本管理怎么用

RedisSourceBase 会暴露 Scripts 管理器。
如果 ScriptPath 有值,启动时会把目录下的 *.lua 文件统一加载。

通过目录自动加载

{
"Redis": {
"ScriptPath": "{CurrentDirectory}/Scripts/"
}
}

手动追加脚本文件

_redis.Scripts.AddScriptByPath("C:\\Scripts\\GetBiLocker.lua");

手动追加脚本文本

_redis.Scripts.AddScript("GetBiLocker", luaContent);

执行脚本

var result = _redis.Scripts.ExecuteScript(
"GetBiLocker",
"lock:key",
new object[] { "inputKey", 30 });

BiLocker 在哪一层用

BiLocker 是建立在 Scripts 管理器上的一组扩展方法,适合需要双边互斥或成对绑定关系的场景。

var isLocked = await _redis.Scripts.SetBiLock(
"BedLock",
key1,
key2,
timeout: 30,
isForce: false,
side: BiLockSide.Left);

var removed = await _redis.Scripts.RemoveBiLock(
"BedLock",
key1,
BiLockSide.Left);

如果你准备使用它,先确保 ScriptPath 对应目录已经存在,且脚本能在启动时被正常加载。

多个 Redis 怎么接

如果一个项目里要连多个 Redis,不要共用一个 RedisSource,而是分别定义自己的实现类。

public class BizRedisSource : RedisSourceBase
{
public BizRedisSource(RedisOptions redisOptions) : base(redisOptions)
{
}
}

public class SessionRedisSource : RedisSourceBase
{
public SessionRedisSource(RedisOptions redisOptions) : base(redisOptions)
{
}
}

然后分别绑定不同配置节点。

services.AddRedisSource<BizRedisSource>(
ConfigManager.Get<RedisOptions>("BizRedis"));

services.AddRedisSource<SessionRedisSource>(
ConfigManager.Get<RedisOptions>("SessionRedis"));

如果要开启多级缓存,还要补什么

当前 Redis 组件本身只是底座。
如果你们要继续往“L1 本地缓存 + L2 Redis”推进,通常还要再补:

  • services.AddMemoryCache()
  • 缓存拦截器
  • 对应的缓存注解或策略配置

这条路线更适合缓存方案已经稳定的场景,不建议在首轮接 Redis 时一起把所有缓存拦截都堆上去。

更完整的接入方式见 多级缓存

接入完成后怎么确认成功

你至少应该确认这些点:

  • ConfigManager.Get<RedisOptions>("Redis") 能正常读取到配置
  • AddRedisSource<T>() 已执行
  • 容器内可以正常注入你的 RedisSource
  • RedisSource 暴露的方法能完成一次最小读写
  • 如果配置了 ScriptPath,启动时不会因为目录错误直接报错

常见问题

为什么注入不到 AegisRedisSource

最常见原因是服务注册阶段没有执行 AddRedisSource<AegisRedisSource>(...)

为什么一启动就报脚本路径错误

如果 ScriptPath 非空,运行目录里必须真的存在该目录。
如果当前项目没有 Lua 脚本,把它设为空字符串最稳。

为什么 Redis 相关组件接了,但会话或事件总线还是不工作

因为很多扩展组件只是依赖 Redis 底座,并不会自动完成自己的上层注册。
例如 Redis 会话、Redis 事件总线、Redis ID 容器都还需要各自继续接。

下一步看哪里