简介以及区别
ASP.NET Core 缓存Caching,.NET Core 中为我们提供了Caching 的组件。
目前Caching 组件提供了三种存储方式。
Memory
Redis
SqlServer
1.MemoryCache
Cache是一个绝大多数项目会用到的一个技术 为了减少磁盘的读取次数,提高程序性能,将频繁读取的配置文件缓存到内存中,加速配置的读取。并且,在磁盘的配置文件更改后,更新缓存
- 绝对过期支持
- 滑动过期支持(指定一个时间,TimeSpan,指定时间内有被Get缓存时间则顺延,否则过期)
- 过期回调
- 自定义过期
仓库地址是:https://github.com/aspnet/Caching
IMemoryCache,它表示存储在 Web 服务器内存中的缓存,内存缓存可以存储任何对象,存储形式键值对
Memorycache无法进行持久化
Memorycache只支持简单的key/value数据结构
Memorycache 是多线程 速度快
2.Redis
简单来说 redis 就是一个数据库,不过与传统数据库不同的是 redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向。另外,redis 也经常用来做分布式锁。redis 提供了多种数据类型来支持不同的业务场景。除此之外,redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。
Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化 ,当Redis重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。
如果你对数据持久化和数据同步有所要求,那么推荐你选择Redis,因为这两个特性Memcached都不具备。即使你只是希望在升级或者重启系统后缓存数据不会丢失,选择Redis也是明智的。
Redis支持数据类型比如key-value string list hash zset 等
Redis缓存数据库(多个) Redis缓存文件夹(多个目录)
Redis只能使用单线程,性能受限于CPU性能
区别
对于 redis 和 memcached 我总结了下面四点。现在公司一般都是用 redis 来实现缓存,而且 redis 自身也越来越强大了!
1、redis支持更丰富的数据类型(支持更复杂的应用场景):Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String。
2、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中。
3、集群模式:memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 redis 目前是原生支持 cluster 模式的.
4、Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路 IO 复用模型。
应用场景
redis:数据量较小的更性能操作和运算上。
memcache:用于在动态系统中减少数据库负载,提升性能;做缓存,提高性能(适合读多写少,对于数据量比较大,可以采用sharding)。
1.MemoryCache使用
Session缓存和Cache缓存的区别如下:
(1)最大的区别是Cache提供缓存依赖来更新数据,而Session只能依靠定义的缓存时间来判断缓存数据是否有效。
(2)即使应用程序终止,只要Cache.Add方法中定义的缓存时间未过期,下次开启应用程序时,缓存的数据依然存在。而Session缓存只是存在于一次会话中,会话结束后,数据也就失效了。
(3)Session容易丢失,导致数据的不确定性,而Cache不会出现这种情况。
(4)由于Session是每次会话就被加载,所以不适宜存放大量信息,否则会导致服务器的性能降低。而Cache则主要用来保存大容量信息,如数据库中的多个表。
需要特别注意:为了提高Cache的有效利用率,建议对于不经常改动的数据使用Cache。
public void ConfigureServices(IServiceCollection services) { //如何处理session services.AddSession(); //memoryCache services.AddMemoryCache(); //....... } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { //启用session app.UseSession(); app.UseRouting(); //...... }
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
// Add framework services.
}
private IMemoryCache _cache;
public LongLongController(IMemoryCache memoryCache)
{
_cache = memoryCache;
}
1、方法:TryGetValue 及 方法缓存的存取(在TryGetValue 中,Out 参数的类型应与缓存中存储的值的类型一致。否则TryGetValue 中的Out参数永远为NULL。)
public IActionResult Index()
{
string cacheKey_2 = "CacheKey";
List<string> cacheEntry;
//如果缓存没有过期,则Out测试就是缓存存储的值,注意存放值的类型应该和Out参数一致。
var bol = _cache.TryGetValue<List<string>>(cacheKey_2, out cacheEntry);
//判断缓存是否存在
if (!bol)
{
List<string> lst = new List<string>() { "陈大六", "陈卧龙", "陈新宇", "刘丽", "花国锋" };
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(10));
_cache.Set(cacheKey_2, lst, cacheEntryOptions);
}
ViewBag.cacheEntry = _cache.Get(cacheKey_2);
return View();
}
2、设置缓存的过期时间,可采用绝对过期时间或相对过期时间两种模式;
相对过期时间设置方式:
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(10));
注意上述代码中的备注;假设我们设置一个缓存的相对过期时间为10秒,缓存由A创建,十秒中内,B进入可系统,并读取了缓存,这时缓存的有效时间又会变成十秒,同理,只要缓存在十秒钟内不间断有其他人访问,则缓存永远不会过期。如果大于十秒,则缓存过期。
绝对过期时间设置方式:
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromSeconds(10));
绝对过期时间不管期间有没有人访问,在时间过后,就会过期,清空。
3、移除缓存
_cache.Remove(cacheKey_2);
4、缓存的优先级分为四种:永不过期 大于 高优先级 大于 一般优先级 大于 低优先级。
/缓存优先级 (程序压力大时,会根据优先级自动回收)
5、缓存过期后,执行回调函数,
public IActionResult cacheCallback()
{
List<string> lst = new List<string>() { "陈大六", "陈卧龙", "陈新宇", "刘丽", "花国锋" };
//缓存回调 10秒过期会回调
string cacheKey = "cacheKey";
_cache.Set(cacheKey, lst, new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromSeconds(10))
.RegisterPostEvictionCallback((key, value, reason, substate) =>
{
//调用回调函数
GetIntList(key,value,reason,substate);
}));
//
return View();
}
public void GetIntList(object key,object value, object reason, object substate)
{
List<string> lst=(List<string>)value;
//说白了就是被释放了
Console.WriteLine($"键{key}的缓存因:{reason}而改变。");
foreach(var item in lst)
{
Console.WriteLine(item);
}
}
6、缓存回调 根据Token过期
public ActionResult CacheToken()
{
List<string> lst = new List<string>() { "陈大六", "陈卧龙", "陈新宇", "刘丽", "花国锋" };
string cacheKey = "CacheToken";
//缓存回调 根据Token过期
var cts = new CancellationTokenSource();
_cache.Set(cacheKey, lst, new MemoryCacheEntryOptions()
.AddExpirationToken(new CancellationChangeToken(cts.Token))
.RegisterPostEvictionCallback((key, value, reason, substate) =>
{
Console.WriteLine($"键{key}值{value}改变,因为{reason}");
}));
cts.Cancel(); //执行到Cancel()方法时,会执行回调删除
return View();
}
2.Redis下载
redis安装包:https://github.com/microsoftarchive/redis/releases
redis客户端:https://redisdesktop.com/download 最新的要付费可以使用旧版
https://github.com/uglide/RedisDesktopManager/releases/tag/0.8.8
为什么要用 redis?/为什么要用缓存?
主要从“高性能”和“高并发”这两点来看待这个问题。
高性能:
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
高并发:
直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
redis 的线程模型
redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理。
文件事件处理器的结构包含 4 个部分:
多个 socket
IO 多路复用程序
文件事件分派器
事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将 socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
3.StackExchange.Redis
在NuGet上安装StackExchange.Redis,然后在appsettings.json文件里面添加Redis相关配置信息:
{ "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*", "Redis": { "Default": { "Connection": "127.0.0.1:6379", "InstanceName": "local", "DefaultDB": 8 } } }
帮助类
using StackExchange.Redis; using System; using System.Collections.Concurrent; namespace RedisDemo { public class RedisHelper : IDisposable { //连接字符串 private string _connectionString; //实例名称 private string _instanceName; //默认数据库 private int _defaultDB; private ConcurrentDictionary<string, ConnectionMultiplexer> _connections; public RedisHelper(string connectionString, string instanceName, int defaultDB = 0) { _connectionString = connectionString; _instanceName = instanceName; _defaultDB = defaultDB; _connections = new ConcurrentDictionary<string, ConnectionMultiplexer>(); } /// <summary> /// 获取ConnectionMultiplexer /// </summary> /// <returns></returns> private ConnectionMultiplexer GetConnect() { return _connections.GetOrAdd(_instanceName, p => ConnectionMultiplexer.Connect(_connectionString)); } /// <summary> /// 获取数据库 /// </summary> /// <param name="configName"></param> /// <param name="db">默认为0:优先代码的db配置,其次config中的配置</param> /// <returns></returns> public IDatabase GetDatabase() { return GetConnect().GetDatabase(_defaultDB); } public IServer GetServer(string configName = null, int endPointsIndex = 0) { var confOption = ConfigurationOptions.Parse(_connectionString); return GetConnect().GetServer(confOption.EndPoints[endPointsIndex]); } public ISubscriber GetSubscriber(string configName = null) { return GetConnect().GetSubscriber(); } public void Dispose() { if (_connections != null && _connections.Count > 0) { foreach (var item in _connections.Values) { item.Close(); } } } } }
在Startup.cs类的ConfigureServices方法里面添加服务注入:
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace RedisDemo { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { //redis缓存 var section = Configuration.GetSection("Redis:Default"); //连接字符串 string _connectionString = section.GetSection("Connection").Value; //实例名称 string _instanceName = section.GetSection("InstanceName").Value; //默认数据库 int _defaultDB = int.Parse(section.GetSection("DefaultDB").Value ?? "0"); services.AddSingleton(new RedisHelper(_connectionString, _instanceName, _defaultDB)); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc(); } } }
新建一个控制器,然后通过构造函数注入:
using Microsoft.AspNetCore.Mvc; using StackExchange.Redis; namespace RedisDemo.Controllers { [Route("api/redis")] [ApiController] public class RedisController : ControllerBase { private readonly IDatabase _redis; public RedisController(RedisHelper client) { _redis = client.GetDatabase(); } [HttpGet] public string Get() { // 往Redis里面存入数据 _redis.StringSet("Name", "Tom"); // 从Redis里面取数据 string name = _redis.StringGet("Name"); return name; } } }
CSRedisCore
在NuGet上安装CSRedisCore,然后在appsettings.json文件里面添加Redis相关配置信息:
{ "RedisServer": { "Cache": "192.168.0.3:6379,password=redis,preheat=5,idleTimeout=600,defaultDatabase=13,prefix=Cache" }, "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Trace", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } } }
新建一个 IRedisClient 接口
1 public interface IRedisClient 2 { 3 string Get(string key); 4 Task<string> GetAsync(string key); 5 void Set(string key, object t, int expiresSec = 0); 6 Task SetAsync(string key, object t, int expiresSec = 0); 7 T Get<T>(string key) where T : new(); 8 Task<T> GetAsync<T>(string key) where T : new(); 9 }
实现接口
1 public class CustomerRedis : IRedisClient 2 { 3 public string Get(string key) 4 { 5 return RedisHelper.Get(key); 6 } 7 8 public T Get<T>(string key) where T : new() 9 { 10 return RedisHelper.Get<T>(key); 11 } 12 13 public async Task<string> GetAsync(string key) 14 { 15 return await RedisHelper.GetAsync(key); 16 } 17 18 public async Task<T> GetAsync<T>(string key) where T : new() 19 { 20 return await RedisHelper.GetAsync<T>(key); 21 } 22 23 public void Set(string key, object t, int expiresSec = 0) 24 { 25 RedisHelper.Set(key, t, expiresSec); 26 } 27 28 public async Task SetAsync(string key, object t, int expiresSec = 0) 29 { 30 await RedisHelper.SetAsync(key, t, expiresSec); 31 } 32 }
Startup
1 services.AddScoped<IRedisClient, CustomerRedis>(); 2 3 var redisConn = Configuration["Cache:RedisConnection"]; 4 //services.Configure<WeChatPayOptions>(Configuration.GetSection("WeChatPay")); 5 var csredis = new CSRedis.CSRedisClient(redisConn); 6 RedisHelper.Initialization(csredis);
调用
1 private readonly IRedisClient _redisClient; 2 public ValuesController(IRedisClient redisClient) 3 { 4 this._redisClient = redisClient; 5 } 6 7 [HttpGet("test")] 8 public async Task<ActionResult> Test() 9 { 10 await this._redisClient.SetAsync("test", "test",100); 11 var res = await this._redisClient.GetAsync("test"); 12 return Ok(res); 13 }
github地址:https://github.com/2881099/csredis