疑问:在断点调试时在BaseService的更新方法突然跳转到RedisCache的GetAllKey方法。
由于好奇所以查了查,了解到了这个技术。
//BaseService
public int Update(List<T> parm, Expression<Func<T, object>> columns)
{
return Db.Updateable(parm).WhereColumns(columns).RemoveDataCache().ExecuteCommand();
}
//RedisCache
public IEnumerable<string> GetAllKey<V>()
{
return RedisServer.Cache.Keys("Cache:SqlSugarDataCache.*");
}
SqlSugar 的缓存机制
SqlSugar
是一个功能强大的 .NET ORM 框架,它提供了一套灵活的缓存机制,旨在提高数据访问的性能。缓存机制可以避免频繁的数据库查询操作,通过缓存来加快查询速度。SqlSugar 支持多种缓存方案,并允许开发者自定义缓存策略。
1. 缓存的类型
SqlSugar 的缓存机制主要包括两种类型:
-
一级缓存(Session Cache):
- 作用范围:仅在当前的数据库上下文(
DbContext
)中有效。 - 特性:SqlSugar 的一级缓存是基于会话的缓存(类似于传统 ORM 中的 Session Cache),即在同一个上下文对象中,如果多次查询相同的数据,会直接从内存中读取,而不再执行数据库查询。
- 生命周期:当
DbContext
实例销毁时,一级缓存也会被销毁。 - 使用场景:适用于短时间内频繁访问同一数据的情况,在同一个上下文实例中多次查询相同的数据。
- 作用范围:仅在当前的数据库上下文(
-
二级缓存(Global Cache):
- 作用范围:在整个应用程序中共享。
- 特性:二级缓存通常是基于分布式缓存(如 Redis 或内存缓存)的持久性缓存。它能够在不同的上下文实例之间共享缓存数据。
- 生命周期:缓存数据的生命周期可以自定义,如设定缓存过期时间(TTL)。
- 使用场景:适用于多个上下文实例之间需要共享数据缓存的情况,例如,在高并发读取场景下,使用二级缓存来减轻数据库压力。
2. 缓存的使用方式
在 SqlSugar 中,缓存的使用非常灵活。它通过一个 CacheService
接口来定义缓存的存储和管理方式。开发者可以基于此接口自定义缓存策略,或者使用框架内置的缓存方案。
1. 内置缓存机制
SqlSugar 提供了一个简单的基于内存的内置缓存机制,可以直接启用:
SqlSugarScope db = new SqlSugarScope(new ConnectionConfig()
{
DbType = DbType.SqlServer,
ConnectionString = "Server=.;Database=MyDb;Trusted_Connection=True;",
IsAutoCloseConnection = true,
ConfigureExternalServices = new ConfigureExternalServices()
{
DataInfoCacheService = new SqlSugar.MemoryCache() // 使用内置内存缓存
}
});
2. 自定义缓存机制
你可以实现 ICacheService
接口来自定义缓存逻辑,例如使用 Redis 作为缓存存储:
public class RedisCache : ICacheService
{
// 实现 Add、Get、Remove 等方法,以控制缓存的添加、获取、删除
public void Add<V>(string key, V value)
{
// 实现 Redis 的缓存添加逻辑
}
public V Get<V>(string key)
{
// 实现 Redis 的缓存获取逻辑
}
public void Remove(string key)
{
// 实现 Redis 的缓存删除逻辑
}
}
在数据库上下文配置中使用自定义缓存:
上述问题就是用了这个
SqlSugarScope db = new SqlSugarScope(new ConnectionConfig()
{
DbType = DbType.SqlServer,
ConnectionString = "Server=.;Database=MyDb;Trusted_Connection=True;",
IsAutoCloseConnection = true,
ConfigureExternalServices = new ConfigureExternalServices()
{
DataInfoCacheService = new RedisCache() // 使用自定义的 Redis 缓存
}
});
3. 缓存的操作方法
SqlSugar 提供了几个常用的方法用于管理缓存:
RemoveDataCache():用于清除特定的数据缓存,常在更新、删除操作之后调用,以防止过期数据的继续使用。
ClearCacheAll():清除所有的缓存数据,通常在需要刷新所有缓存的场景下使用。
其他:WithCache,
WithCacheIdentity,RemoveCache,EnableQueryCache,RemoveAllCache,IsCache
属性....
4. 缓存的优缺点
-
优点:
- 提高性能:减少重复的数据库查询,提高数据访问速度。
- 减少数据库负载:通过缓存的使用,显著减少数据库的读取压力,适用于高并发环境。
- 灵活性高:支持自定义缓存策略,可以根据业务需求灵活配置缓存的存储方式和管理策略。
-
缺点:
- 数据一致性问题:缓存的数据可能会变得过期,如果不及时清除或更新,可能导致读取到旧数据。
- 内存占用:缓存的数据会占用一定的内存资源,在缓存的数据量非常大时可能会导致内存溢出问题。
- 缓存管理复杂性:在使用分布式缓存(如 Redis)时,需要额外的缓存管理和维护工作。
5. 使用缓存的最佳实践
- 合理设定缓存策略:根据数据的变更频率和访问频率,合理选择数据的缓存时间和策略。
- 避免过度缓存:并不是所有数据都需要缓存,应根据实际业务需求决定哪些数据需要缓存。
- 清理过期缓存:确保在数据更新、删除操作后及时清理相关缓存,保持数据的一致性。
- 监控和优化缓存使用:定期监控缓存的使用情况,分析缓存命中率,优化缓存策略。
疑问原因分析
原因分析
-
缓存机制的工作原理: SqlSugar 的缓存机制依赖于缓存键(Key)的管理。缓存键通常包含特定的前缀(例如 "Cache
.*")来标识缓存项。在执行数据更新或删除操作后,RemoveDataCache
方法会被调用,以确保缓存中存储的数据与数据库的实际数据保持一致。 -
查找和清除缓存:
." 为前缀的缓存键。RemoveDataCache
方法需要清除与某个表或查询相关的缓存项。为了找到这些缓存项,SqlSugar 需要查找符合特定规则的所有缓存键。例如,通过RedisServer.Cache.Keys("Cache:SqlSugarDataCache.*")
查找所有以 "CacheGetAllKey<V>()
方法就是用来获取这些符合条件的缓存键的列表。 -
调用的具体过程: 当
RemoveDataCache
方法执行时:- 它会调用
ICacheService
接口中定义的GetAllKey<V>()
方法。 - 该方法会返回一个符合特定前缀(如 "Cache.*")的所有缓存键的集合。
- 随后,
RemoveDataCache
会根据这些键执行缓存删除操作,从而清除与这些键相关的缓存数据。
- 它会调用
为什么会跳转到 GetAllKey<V>()
GetAllKey<V>()
的目的是获取所有符合特定规则的缓存键,以便 RemoveDataCache
方法能够正确地定位并清除所有需要删除的缓存项。这种设计确保了在数据变化(如插入、更新、删除)时,能够及时清除相关的缓存数据,防止客户端读取到过期数据。
实现符合 ICacheService
接口(使用 Redis 实现缓存查找和清除)的自定义缓存服务的其他方法
内存缓存(Memory Cache)
文件系统缓存
分布式缓存(如 Memcached 或其他 NoSQL 缓存)
数据库缓存
项目实现 ICacheService
接口的整个过程
1.在Progaram.cs中配置数据库:
services.AddScoped<ISqlSugarClient>(x =>
{
return new SqlSugarClient(new ConnectionConfig()
{
ConnectionString = AppSettings.Configuration["DbConnection:ConnectionString"],
DbType = (DbType)Convert.ToInt32(AppSettings.Configuration["DbConnection:DbType"]),
IsAutoCloseConnection = true,
InitKeyType = InitKeyType.Attribute,
ConfigureExternalServices = new ConfigureExternalServices()
{
//配置RedisCache类
DataInfoCacheService = new RedisCache()
},
MoreSettings = new ConnMoreSettings()
{
//自动清除缓存
IsAutoRemoveDataCache = true
}
});
});
2.实现上面new出来的RedisCache配置类
namespace Meiam.System.Core
{
//配合sqlsugar在操作数据库时顺便操作redis中缓存的数据,保证两个库数据的统一
public class RedisCache : ICacheService
{
public void Add<V>(string key, V value)
{
RedisServer.Cache.Set(key, value);
}
public void Add<V>(string key, V value, int cacheDurationInSeconds)
{
RedisServer.Cache.Set(key, value, cacheDurationInSeconds);
}
public bool ContainsKey<V>(string key)
{
return RedisServer.Cache.Exists(key);
}
public V Get<V>(string key)
{
return RedisServer.Cache.Get<V>(key);
}
public IEnumerable<string> GetAllKey<V>()
{
return RedisServer.Cache.Keys("Cache:SqlSugarDataCache.*");
}
public V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = int.MaxValue)
{
if (ContainsKey<V>(cacheKey))
{
return Get<V>(cacheKey);
}
else
{
var result = create();
Add(cacheKey, result, cacheDurationInSeconds);
return result;
}
}
public void Remove<V>(string key)
{
RedisServer.Cache.Del(key.Remove(0, 6));
}
}
}
3.在需要的地方使用
如BaseService
...
public List<T> GetWhere(Expression<Func<T, bool>> where,
bool useCache = false,
int cacheSecond = 3600)
{
var query = Db.Queryable<T>().Where(where).WithCacheIF(useCache,
cacheSecond);
return query.ToList();
}
#region 修改操作
public int Update(T parm)
{
return Db.Updateable(parm).RemoveDataCache().ExecuteCommand();
}
public int Update(T parm, Expression<Func<T, object>> columns)
{
return Db.Updateable(parm).WhereColumns(columns).
RemoveDataCache().ExecuteCommand();
}
...
public int Update(List<T> parm, Expression<Func<T, object>> columns)
{
return Db.Updateable(parm).WhereColumns(columns).
RemoveDataCache().ExecuteCommand();
}
...
#endregion
#region 删除操作
public int Delete(object id)
{
return Db.Deleteable<T>(id).RemoveDataCache().ExecuteCommand();
}
...
什么样的数据会使用到sqlsugar和 Redis 实现缓存查找和清除?
通常会涉及到频繁查询、不常变化或者允许短时间内数据一致性稍微延迟的数据。这种类型的数据适合缓存以提升查询性能和减少数据库的负载。
1. 适合缓存的数据类型
基础数据,热门数据,字典表数据,统计数据,分页查询结果,配置数据....
2. 使用缓存的场景
- 读取操作频繁,写入操作较少:例如,商品列表、文章列表等,特别是在有热门内容的情况。
- 读取性能要求高:例如,用户体验要求较高的应用场景,比如社交媒体、电子商务网站的首页加载。
- 数据不会快速过期或变化:例如,固定的配置数据、国家和城市等相对稳定的数据。