ASP.NET Core中使用EasyCaching作为缓存抽象层
⒈是什么?
和CacheManager差不多,两者的定位和功能都差不多。
EasyCaching主要提供了下面的几个功能
- 统一的抽象缓存接口
- 多种常用的缓存Provider(InMemory,Redis,Memcached,SQLite)
- 为分布式缓存的数据序列化提供了多种选择
- 二级缓存
- 缓存的AOP操作(able, put,evict)
- 多实例支持
- 支持Diagnostics
- Redis的特殊Provider
⒉示例(以InMemory为例)
1.安装Nuget包
EasyCaching.InMemory
2.在Startup中配置服务及请求管道
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Threading.Tasks; 5 using EasyCaching.Core; 6 using EasyCaching.InMemory; 7 using Microsoft.AspNetCore.Builder; 8 using Microsoft.AspNetCore.Hosting; 9 using Microsoft.AspNetCore.Http; 10 using Microsoft.AspNetCore.Mvc; 11 using Microsoft.Extensions.Configuration; 12 using Microsoft.Extensions.DependencyInjection; 13 14 namespace Coreqi.EasyCaching 15 { 16 public class Startup 17 { 18 public Startup(IConfiguration configuration) 19 { 20 Configuration = configuration; 21 } 22 23 public IConfiguration Configuration { get; } 24 25 // This method gets called by the runtime. Use this method to add services to the container. 26 public void ConfigureServices(IServiceCollection services) 27 { 28 services.AddEasyCaching(option => 29 { 30 // 使用InMemory最简单的配置 31 option.UseInMemory("default"); 32 33 //// 使用InMemory自定义的配置 34 //option.UseInMemory(options => 35 //{ 36 // // DBConfig这个是每种Provider的特有配置 37 // options.DBConfig = new InMemoryCachingOptions 38 // { 39 // // InMemory的过期扫描频率,默认值是60秒 40 // ExpirationScanFrequency = 60, 41 // // InMemory的最大缓存数量, 默认值是10000 42 // SizeLimit = 100 43 // }; 44 // // 预防缓存在同一时间全部失效,可以为每个key的过期时间添加一个随机的秒数,默认值是120秒 45 // options.MaxRdSecond = 120; 46 // // 是否开启日志,默认值是false 47 // options.EnableLogging = false; 48 // // 互斥锁的存活时间, 默认值是5000毫秒 49 // options.LockMs = 5000; 50 // // 没有获取到互斥锁时的休眠时间,默认值是300毫秒 51 // options.SleepMs = 300; 52 // }, "m2"); 53 54 //// 读取配置文件 55 //option.UseInMemory(Configuration, "m3"); 56 }); 57 58 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 59 } 60 61 // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 62 public void Configure(IApplicationBuilder app, IHostingEnvironment env) 63 { 64 if (env.IsDevelopment()) 65 { 66 app.UseDeveloperExceptionPage(); 67 } 68 else 69 { 70 app.UseExceptionHandler("/Home/Error"); 71 } 72 73 app.UseStaticFiles(); 74 app.UseCookiePolicy(); 75 76 // 如果使用的是Memcached或SQLite,还需要下面这个做一些初始化的操作 77 app.UseEasyCaching(); 78 79 80 app.UseMvc(routes => 81 { 82 routes.MapRoute( 83 name: "default", 84 template: "{controller=Home}/{action=Index}/{id?}"); 85 }); 86 } 87 } 88 }
3.创建一个实体类
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Threading.Tasks; 5 6 namespace Coreqi.EasyCaching.Models 7 { 8 [Serializable] 9 public class User 10 { 11 public int id { get; set; } 12 public string username { get; set; } 13 public string password { get; set; } 14 public int enabled { get; set; } 15 } 16 }
4.模拟一个服务层
1 using Coreqi.EasyCaching.Models; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Threading.Tasks; 6 7 namespace Coreqi.EasyCaching.Services 8 { 9 public class UserService:IUserService 10 { 11 public static IList<User> users; 12 public UserService() 13 { 14 users = new List<User> 15 { 16 new User{ id = 1,username = "fanqi",password = "admin",enabled = 1}, 17 new User{ id = 2,username = "gaoxing",password = "admin",enabled = 1} 18 }; 19 } 20 public IList<User> getAll() 21 { 22 return users; 23 } 24 public User getById(int id) 25 { 26 return users.FirstOrDefault(f => f.id == id); 27 } 28 public User add(User user) 29 { 30 users.Add(user); 31 return user; 32 } 33 public User modify(User user) 34 { 35 delete(user.id); 36 add(user); 37 return user; 38 } 39 public void delete(int id) 40 { 41 users.Remove(getById(id)); 42 } 43 } 44 }
5.控制器中使用缓存
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Threading.Tasks; 5 using Coreqi.EasyCaching.Models; 6 using Coreqi.EasyCaching.Services; 7 using EasyCaching.Core; 8 using Microsoft.AspNetCore.Mvc; 9 10 namespace Coreqi.EasyCaching.Controllers 11 { 12 [Route("api/[controller]")] 13 public class UserController : Controller 14 { 15 private readonly IEasyCachingProvider _cache; 16 private readonly IUserService _service; 17 public UserController(IEasyCachingProvider cache, IUserService service) 18 { 19 this._cache = cache; 20 this._service = service; 21 } 22 23 [HttpGet] 24 [Route("add")] 25 public async Task<IActionResult> Add() 26 { 27 IList<User> users = _service.getAll(); 28 _cache.Set("users", users, TimeSpan.FromMinutes(2)); 29 await _cache.SetAsync("users2", users, TimeSpan.FromMinutes(3)); 30 return await Task.FromResult(new JsonResult(new { message = "添加成功!" })); 31 } 32 33 [HttpGet] 34 [Route("remove")] 35 public async Task<IActionResult> Remove() 36 { 37 _cache.Remove("users"); 38 await _cache.RemoveAsync("users2"); 39 return await Task.FromResult(new JsonResult(new { message = "删除成功!" })); 40 } 41 42 [HttpGet] 43 [Route("get")] 44 public async Task<IActionResult> Get() 45 { 46 var users = _cache.Get<List<User>>("users"); 47 var users2 = await _cache.GetAsync<List<User>>("users2"); 48 return await Task.FromResult(new JsonResult(new { users1 = users.Value,users2 = users2.Value })); 49 } 50 } 51 }
⒊改造为Redis示例
1.安装Nuget包
EasyCaching.Redis
2.在Startup中配置服务及请求管道
1 // This method gets called by the runtime. Use this method to add services to the container. 2 public void ConfigureServices(IServiceCollection services) 3 { 4 services.AddSingleton<IUserService, UserService>(); 5 services.AddEasyCaching(option => 6 { 7 //使用redis 8 option.UseRedis(config => 9 { 10 config.DBConfig.Endpoints.Add(new ServerEndPoint("127.0.0.1", 6379)); 11 }, "localhostRedis"); 12 }); 13 14 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 15 }
3.启动程序
然后就发现数据缓存到Redis中了,不过,
对于序列化,一般都会有一个基于BinaryFormatter的默认实现,因为这个并不依赖于第三方类库,如果我们没有指定其它的,EasyCaching就会使用这个去进行数据的序列化,除了这个默认的实现,EasyCaching还提供了三种额外的选择。Newtonsoft.Json,MessagePack和Protobuf。切换方法如下:
1.安装以下Nuget包(你用那个序列化就装那个)
1 EasyCaching.Serialization.Json 2 EasyCaching.Serialization.MessagePack 3 EasyCaching.Serialization.Protobuf
2.配置序列化方式
1 // This method gets called by the runtime. Use this method to add services to the container. 2 public void ConfigureServices(IServiceCollection services) 3 { 4 services.AddSingleton<IUserService, UserService>(); 5 services.AddEasyCaching(option => 6 { 7 //使用redis 8 option.UseRedis(config => 9 { 10 config.DBConfig.Endpoints.Add(new ServerEndPoint("127.0.0.1", 6379)); 11 }, "localhostRedis") 12 //.WithMessagePack(); //使用MessagePack替换BinaryFormatter 13 //.WithProtobuf(); //使用Protobuf替换BinaryFormatter 14 .WithJson(); //使用Newtonsoft.Json替换BinaryFormatter 15 }); 16 17 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 18 }
⒋多示例支持
多实例指在同一个项目中同时使用多个缓存提供者,包括多个同一类型的缓存提供者或着是不同类型的缓存提供者。
在代码中借助IEasyCachingProviderFactory
来指定使用那个缓存提供者。
1.先添加两个缓存提供者
1 // This method gets called by the runtime. Use this method to add services to the container. 2 public void ConfigureServices(IServiceCollection services) 3 { 4 services.AddSingleton<IUserService, UserService>(); 5 services.AddEasyCaching(option => 6 { 7 option.UseInMemory("m1"); //配置一个InMemory,名称为m1 8 //使用redis 9 option.UseRedis(config => 10 { 11 config.DBConfig.Endpoints.Add(new ServerEndPoint("127.0.0.1", 6379)); 12 }, "localhostRedis") 13 //.WithMessagePack(); //使用MessagePack替换BinaryFormatter 14 //.WithProtobuf(); //使用Protobuf替换BinaryFormatter 15 .WithJson(); //使用Newtonsoft.Json替换BinaryFormatter 16 }); 17 18 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 19 }
2.在代码中使用
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Threading.Tasks; 5 using Coreqi.EasyCaching.Models; 6 using Coreqi.EasyCaching.Services; 7 using EasyCaching.Core; 8 using Microsoft.AspNetCore.Mvc; 9 10 namespace Coreqi.EasyCaching.Controllers 11 { 12 [Route("api/[controller]")] 13 public class LoginController : Controller 14 { 15 private readonly IEasyCachingProviderFactory _cacheFactory; 16 private readonly IUserService _userService; 17 public LoginController(IEasyCachingProviderFactory cacheFactory, IUserService userService) 18 { 19 this._cacheFactory = cacheFactory; 20 this._userService = userService; 21 } 22 23 [HttpGet] 24 [Route("add")] 25 public async Task<IActionResult> Add() 26 { 27 var _cache1 = _cacheFactory.GetCachingProvider("m1"); //获取名字为m1的provider 28 var _cache2 = _cacheFactory.GetCachingProvider("localhostRedis"); //获取名字为localhostRedis的provider 29 IList<User> users = _userService.getAll(); 30 IList<string> loginNames = users.Select(s => s.username).ToList(); 31 _cache1.Set("loginNames", loginNames, TimeSpan.FromMinutes(2)); 32 await _cache2.SetAsync("users", users, TimeSpan.FromMinutes(2)); 33 return await Task.FromResult(new JsonResult(new { message = "添加成功!" })); 34 } 35 36 [HttpGet] 37 [Route("get")] 38 public async Task<IActionResult> Get() 39 { 40 var _cache1 = _cacheFactory.GetCachingProvider("m1"); //获取名字为m1的provider 41 var _cache2 = _cacheFactory.GetCachingProvider("localhostRedis"); //获取名字为localhostRedis的provider 42 IList<string> loginNames = _cache1.Get<List<string>>("loginNames").Value; 43 IList<User> users = (await _cache2.GetAsync<List<User>>("users")).Value; 44 return await Task.FromResult(new JsonResult(new { loginNames = loginNames,users = users})); 45 } 46 } 47 }
⒌缓存的Aop操作
一句话,提供和Java Spring中的@Cacheable、@CacheEvict、@CachePut等注解类似的Aop操作
例如,我们以前的查询代码一般是这样的
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Threading.Tasks; 5 using Coreqi.EasyCaching.Models; 6 using Coreqi.EasyCaching.Services; 7 using EasyCaching.Core; 8 using Microsoft.AspNetCore.Mvc; 9 10 namespace Coreqi.EasyCaching.Controllers 11 { 12 [Route("api/[controller]")] 13 public class CacheAopController : Controller 14 { 15 private readonly IEasyCachingProvider _cache; 16 private readonly IUserService _service; 17 public CacheAopController(IEasyCachingProvider cache, IUserService service) 18 { 19 this._cache = cache; 20 this._service = service; 21 } 22 23 [HttpGet] 24 [Route("get/{id}")] 25 public async Task<User> GetUserByIdAsync(int id) 26 { 27 string cacheKey = $"user:{id}"; 28 var userInfo = await _cache.GetAsync<User>(cacheKey); 29 if (userInfo.HasValue) 30 { 31 return userInfo.Value; 32 } 33 var user = _service.getById(id); 34 if (user != null) 35 { 36 _cache.Set<User>(cacheKey, user, TimeSpan.FromHours(2)); 37 } 38 return user; 39 } 40 } 41 }
先去查询缓存数据,没有的话再去查库然后保存到缓存中,一个查询是这样,那么多的CRUD都这样岂不是要写到吐?
而我们使用EasyCaching的缓存AOP来简化这一操作。
1.Nuget包
EasyCaching.Interceptor.AspectCore
2.在接口的定义上添加一个Attribute标识。
1 using Coreqi.EasyCaching.Models; 2 using EasyCaching.Core.Interceptor; 3 using System; 4 using System.Collections.Generic; 5 using System.Linq; 6 using System.Threading.Tasks; 7 8 namespace Coreqi.EasyCaching.Services 9 { 10 public interface IUserService 11 { 12 IList<User> getAll(); 13 14 [EasyCachingAble(Expiration =10)] 15 User getById(int id); 16 17 User add(User user); 18 19 User modify(User user); 20 21 void delete(int id); 22 } 23 }
3.配置服务使其生效
1 // This method gets called by the runtime. Use this method to add services to the container. 2 public IServiceProvider ConfigureServices(IServiceCollection services) 3 { 4 services.AddSingleton<IUserService, UserService>(); //必须将Service添加到IOC中,否则AOP不成功 5 services.AddEasyCaching(option => 6 { 7 option.UseInMemory("m1"); //配置一个InMemory,名称为m1 8 //使用redis 9 option.UseRedis(config => 10 { 11 config.DBConfig.Endpoints.Add(new ServerEndPoint("127.0.0.1", 6379)); 12 }, "localhostRedis") 13 //.WithMessagePack(); //使用MessagePack替换BinaryFormatter 14 //.WithProtobuf(); //使用Protobuf替换BinaryFormatter 15 .WithJson(); //使用Newtonsoft.Json替换BinaryFormatter 16 }); 17 18 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 19 20 return services.ConfigureAspectCoreInterceptor(options => 21 { 22 options.CacheProviderName = "localhostRedis"; //指定要使用的缓存提供者名称 23 }); 24 }
*也可以在Attribute特性上指定
1 using Coreqi.EasyCaching.Models; 2 using EasyCaching.Core.Interceptor; 3 using System; 4 using System.Collections.Generic; 5 using System.Linq; 6 using System.Threading.Tasks; 7 8 namespace Coreqi.EasyCaching.Services 9 { 10 public interface IUserService 11 { 12 IList<User> getAll(); 13 14 [EasyCachingAble(Expiration =10,CacheProviderName ="m1")] 15 User getById(int id); 16 17 User add(User user); 18 19 User modify(User user); 20 21 void delete(int id); 22 } 23 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Threading.Tasks; 5 using Coreqi.EasyCaching.Models; 6 using Coreqi.EasyCaching.Services; 7 using EasyCaching.Core; 8 using Microsoft.AspNetCore.Mvc; 9 10 namespace Coreqi.EasyCaching.Controllers 11 { 12 [Route("api/[controller]")] 13 public class CacheAopController : Controller 14 { 15 private readonly IEasyCachingProvider _cache; 16 private readonly IUserService _service; 17 public CacheAopController(IEasyCachingProvider cache, IUserService service) 18 { 19 this._cache = cache; 20 this._service = service; 21 } 22 23 [HttpGet] 24 [Route("get/{id}")] 25 public User GetUserById(int id) 26 { 27 return _service.getById(id); 28 } 29 } 30 }
完成上面的操作后可以在调用方法的时候优先取缓存,没有缓存的时候才会去执行方法。
EasyCaching提供的AOP Attritebute有一些通用的属性。
配置名 | 说明 |
---|---|
CacheKeyPrefix | 指定生成缓存键的前缀,正常情况下是用在修改和删除的缓存上 |
CacheProviderName | 可以指定特殊的provider名字 |
IsHightAvailability | 缓存相关操作出现异常时,是否还能继续执行业务方法 |
EasyCachingAble和EasyCachingPut还有一个同名和配置。
配置名 | 说明 |
---|---|
Expiration | key的过期时间,单位是秒 |
EasyCachingEvict有两个特殊的配置。
配置名 | 说明 |
---|---|
IsAll | 这个要搭配CacheKeyPrefix来用,就是删除这个前缀的所有key |
IsBefore | 在业务方法执行之前删除缓存还是执行之后 |
⒍支持Diagnostics【诊断系统】
提供了Diagnostics的支持方便接入第三方的APM,实现追踪。
有一个接入Jaeger的一个案例。大家可以去看看。
⒎二级缓存
二级缓存,多级缓存,其实在缓存的小世界中还算是一个比较重要的东西!
一个最为头疼的问题就是不同级的缓存如何做到近似实时的同步。
在EasyCaching中,二级缓存的实现逻辑大致就是下面的这张图。
如果某个服务器上面的本地缓存被修改了,就会通过缓存总线去通知其他服务器把对应的本地缓存移除掉。
1.添加Nuget包。
1 EasyCaching.InMemory 2 EasyCaching.Redis 3 EasyCaching.HybridCache 4 EasyCaching.Bus.Redis
2.添加配置
1 services.AddEasyCaching(option => 2 { 3 option.UseInMemory("m1"); //配置一个InMemory,名称为m1 4 //使用redis 5 option.UseRedis(config => 6 { 7 config.DBConfig.Endpoints.Add(new ServerEndPoint("127.0.0.1", 6379)); 8 config.DBConfig.Database = 5; 9 }, "localhostRedis") 10 //.WithMessagePack(); //使用MessagePack替换BinaryFormatter 11 //.WithProtobuf(); //使用Protobuf替换BinaryFormatter 12 .WithJson(); //使用Newtonsoft.Json替换BinaryFormatter 13 14 //使用Hybird 15 option.UseHybrid(config => 16 { 17 config.EnableLogging = false; //是否开启日志 18 config.TopicName = "test_topic"; //缓存总线的订阅主题 19 config.LocalCacheProviderName = "m1"; //本地缓存的名字 20 config.DistributedCacheProviderName = "localhostRedis"; //分布式缓存的名字 21 }); 22 23 //使用redis作为缓存总线 24 option.WithRedisBus(config => 25 { 26 config.Endpoints.Add(new ServerEndPoint("127.0.0.1", 6379)); 27 config.Database = 6; 28 }); 29 30 //使用CSRedis作为缓存总线 31 //option.WithCSRedisBus(config => 32 //{ 33 // config.ConnectionStrings = new List<string> 34 // { 35 // "127.0.0.1:6379,defaultDatabase=6,poolsize=10" 36 // }; 37 //}); 38 39 //使用RabbitMq作为缓存总线 40 //option.WithRabbitMQBus(config => 41 //{ 42 // config = new RabbitMQBusOptions(); 43 //}); 44 });
3.使用
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Threading.Tasks; 5 using EasyCaching.Core; 6 using Microsoft.AspNetCore.Mvc; 7 8 namespace Coreqi.EasyCaching.Controllers 9 { 10 [Route("api/[controller]")] 11 public class HybridCachingController : Controller 12 { 13 private readonly IHybridCachingProvider _provider; 14 public HybridCachingController(IHybridCachingProvider provider) 15 { 16 this._provider = provider; 17 } 18 19 [HttpGet] 20 [Route("add")] 21 public string Add() 22 { 23 string cacheKey = "coreqiTest"; 24 _provider.Set(cacheKey, "1111111", TimeSpan.FromSeconds(30)); 25 return "添加成功!"; 26 } 27 } 28 }
⒏特殊的Redis缓存提供者
Redis支持多种数据结构,还有一些原子递增递减的操作等等。为了支持这些操作,EasyCaching提供了一个独立的接口,IRedisCachingProvider。
这个接口,目前也只支持了百分之六七十常用的一些操作,还有一些可能用的少的就没加进去。
同样的,这个接口也是支持多实例的,也可以通过IEasyCachingProviderFactory
来获取不同的provider实例。
在注入的时候,不需要额外的操作,和添加Redis是一样的。不同的是,在使用的时候,不再是用IEasyCachingProvider
,而是要用IRedisCachingProvider
。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Threading.Tasks; 5 using EasyCaching.Core; 6 using Microsoft.AspNetCore.Mvc; 7 8 namespace Coreqi.EasyCaching.Controllers 9 { 10 [Route("api/[controller]")] 11 public class MultiRedisController : Controller 12 { 13 private readonly IRedisCachingProvider _redis1; 14 private readonly IRedisCachingProvider _redis2; 15 16 public MultiRedisController(IEasyCachingProviderFactory factory) 17 { 18 this._redis1 = factory.GetRedisProvider("redis1"); 19 this._redis2 = factory.GetRedisProvider("redis2"); 20 } 21 22 [HttpGet] 23 [Route("get")] 24 public string Get() 25 { 26 _redis1.StringSet("keyredis1", "val"); 27 28 var res1 = _redis1.StringGet("keyredis1"); 29 var res2 = _redis2.StringGet("keyredis1"); 30 31 return $"redis1 cached value: {res1}, redis2 cached value : {res2}"; 32 } 33 } 34 }
⒐EasyCaching扩展性功能
除了这些基础功能,还有一些扩展性的功能,在这里要非常感谢yrinleung,他把EasyCaching和WebApiClient,CAP等项目结合起来了。感兴趣的可以看看这个项目EasyCaching.Extensions。
参考文章:一篇短文带您了解一下EasyCaching-----作者@Catcher ( 黄文清 )