缓存拦截
在silky框架中,为提高rpc通信性能,使用缓存拦截的设计,减少网络io操作,提高分布式应用的性能。
要想使用缓存拦截,必须要在将服务治理属性的Governance:EnableCachingInterceptor设置为true(该属性的缺省值为true)。如果将该属性值被设置为false,那么在rpc通信过程中,缓存拦截将无效。
缓存拦截在应用服务接口方法上通过缓存拦截特性进行设置,在silky框架中,存在如下三种类型的缓存特性,分别对数据缓存进行新增、更新、删除。
设置缓存特性--GetCachingInterceptAttribute
在rpc通信过程中,如果通过缓存的Key在分布式缓存中能够从分布式缓存中命中相应的缓存数据,那么就就直接从分布式缓存中间件中获取数据,并返回给服务调用者。如果没有命中缓存数据,那么就会通过网络通信的方式从服务提供者获取数据,并且将返回的数据新增到缓存中间件,在下次rpc通信过程中,就可以直接从缓存中间件中获取数据,从而达到减少io操作,提高分布式应用的性能。
设置缓存特性方式如下所示:
// 在应用服务接口方法中通过`GetCachingIntercept`设置缓存拦截
[GetCachingIntercept("name:{Name}")]
Task<TestOut> Create(TestInput input);
// 模板参数 {Name} 将自动匹配输入参数类型中同名的属性
public class TestInput
{
[Required(ErrorMessage = "名称不允许为空")]
public string Name { get; set; }
[Required(ErrorMessage = "地址不允许为空")]
public string Address { get; set; }
[Phone(ErrorMessage = "手机号码格式不正确")]
public string Phone { get; set; }
}
GetCachingInterceptAttribute特性存在一个参数--keyTemplate,该参数是一个字符串类型,可以使用 {参数名} 或 {属性名} 作为模板占位符。框架会自动根据占位符名称匹配输入参数或其属性值进行替换。实际上生成的缓存的key会根据keyTemplate、匹配到的参数或属性值以及返回结果类型(ReturnType)共同确定。
除此之外,GetCachingInterceptAttribute特性还存在一个OnlyCurrentUserData属性,如果当前缓存数据只与当前登录用户相关,那么需要将OnlyCurrentUserData属性值设置为true,生成的缓存的key会加上将当前用户id。
注意
需要注意的是,如果OnlyCurrentUserData属性值设置为true,需要确保,当前接口需要用户登录认证。
更新缓存特性--UpdateCachingInterceptAttribute
在rpc通信过程中,对数据进行更新操作后,为保证缓存一致性,同时需要对缓存数据进行更新操作。开发者可以通过UpdateCachingInterceptAttribute特性对缓存数据进行更新。UpdateCachingInterceptAttribute特性的参数与属性与GetCachingInterceptAttribute一致,用法基本上也一致。
与被GetCachingInterceptAttribute特性标注的应用服务接口不同的是,在rpc通信过程中,被UpdateCachingInterceptAttribute特性标识的方法都会得到执行,只是将服务提供者返回的结果更新到缓存服务中。被更新的缓存数据与生成的缓存的key以及返回结果类型(ReturnType)有关。
// 在应用服务接口方法中通过`GetCachingIntercept`设置缓存拦截
[UpdateCachingIntercept("name:{Name}")]
Task<TestOut> Update(UpdateTestInput input);
// 模板参数 {Name} 将自动匹配输入参数类型中同名的属性
public class UpdateTestInput
{
public long Id { get; set; }
[Required(ErrorMessage = "名称不允许为空")]
public string Name { get; set; }
[Required(ErrorMessage = "地址不允许为空")]
public string Address { get; set; }
[Phone(ErrorMessage = "手机号码格式不正确")]
public string Phone { get; set; }
}
删除缓存特性--RemoveCachingInterceptAttribute
在rpc通信过程中,对数据进行删除操作后,为保证缓存一致性,同时需要对缓存数据进行删除操作。开发者可以通过RemoveCachingInterceptAttribute特性对缓存数据进行移除操作。
RemoveCachingInterceptAttribute特性除了指定keyTempalte模板参数之外,还需要指定缓存名称(CacheName),一般地,在缓存拦截中,CacheName与要缓存数据(即:ReturnType)的类的完全限定名一致。
[RemoveCachingIntercept("ITestApplication.Test.Dtos.TestOut", "name:{name}")]
[Transaction]
Task<string> Delete(string name);
分布式缓存接口
silky框架提供分布式缓存接口IDistributedCache<T>,通过分布式缓存接口实现对缓存数据的增删改查。其中,泛型T指的是缓存数据的类型。在分布式缓存中,T与应用服务接口方法定义的返回值类型一致。
分布式缓存接口可以通过构造器注入,在使用分布式缓存接口是,必须要指定泛型参数T。
public class TestAppService : ITestAppService
{
private readonly IDistributedCache<TestOut> _distributedCache;
public TestAppService(IDistributedCache<TestOut> distributedCache)
{
// 通过构造器注入分布式缓存接口后,可以通过该接口实现对该缓存数据的增删改查。
_distributedCache = distributedCache;
}
}
缓存数据的一致性
缓存一致性的概念: 缓存一致性的本质是数据一致性。换句话说,就是在缓存服务中的数据与数据库中存储的数据要保证数据一致性。开发者在开发过程中,要特别注意对缓存数据的一致性。也就是说,如果对某个类型的数据进行缓存,那么,对其进行更新、删除操作时,需要同时对缓存服务中的缓存数据进行更新或是、删除操作。
在rpc通信过程中,使用缓存拦截,同一数据的缓存依据可能会不同(设置的KeyTemplate,例如:缓存依据可能会根据Id、Name、Code分别进行缓存),从而产生不同的缓存数据,但是在对数据进行更新、删除操作时,由于无法通过RemoveCachingInterceptAttribute特性一次性删除该类型数据的所有缓存数据,这个时候,在实现业务代码过程中,就需要通过分布式缓存接口IDistributedCache<T>实现缓存数据的更新、删除操作。
缓存数据的更新、命中如下图所示:


缓存模板参数的设置
在上述文档中,我们看到,缓存拦截的 keyTemplate 使用 {参数名} 或 {属性名} 作为占位符,框架会自动根据占位符名称匹配输入参数或其属性值进行替换。
例如:
[HttpGet("{id:long}")]
[GetCachingIntercept("id:{id}", OnlyCurrentUserData = true)]
Task<GetOrganizationOutput> GetAsync(long id);
在上述代码中,缓存拦截特性[GetCachingIntercept("id:{id}", OnlyCurrentUserData = true)]中的模板("id:{id}")参数{id}将会被id的实参替换掉,在RPC通信过程中,如果在分布式缓存中已经存在当前登录用户(OnlyCurrentUserData=true)对应的数据,那么缓存数据将会命中,直接从缓存中获取数据,从而减少网络请求。
缓存拦截的模板参数除了可以指定简单类型的实参之外,也可以指定复杂数据类型的属性名称,或是通过正则表达式模糊匹配,例如:
[RemoveMatchKeyCachingIntercept(typeof(GetOrganizationOutput), "id:{Id}:*")]
[RemoveMatchKeyCachingIntercept(typeof(ICollection<GetOrganizationTreeOutput>),"OrganizationTree:userId:*")]
[RemoveCachingIntercept(typeof(bool), "HasOrganization:{Id}")]
[RemoveCachingIntercept(typeof(ICollection<long>), "GetSelfAndChildrenOrganizationIds:{Id}")]
[Authorize(OrganizationPermissions.Organizations.Update)]
Task UpdateAsync(UpdateOrganizationInput input);
// 以下是UpdateOrganizationInput的定义
public class UpdateOrganizationInput : OrganizationDtoBase
{
/// <summary>
/// 主键Id
/// </summary>
public long Id { get; set; }
}
上述代码中显示,如果在更新某个组织机构的数据后,数据变化后需要清除缓存中的某些数据,缓存模板中指定的参数{Id}指的就是参数UpdateOrganizationInput类型的一个属性Id。
我们可以看到,在使用缓存拦截的功能上,通过参数或属性名称作为缓存模板参数占位符,使用起来更加方便和直观。
使用redis作为缓存中间件
silky框架默认使用MemoryCaching作为缓存,但是如果开发者需要将微服务集群部署到多台服务器,那么您需要使用redis服务作为缓存服务。
silky使用redis作为分布式缓存服务非常简单,您只要提供一个redis服务(集群),通过配置文件就可以将缓存中间件替换为redis服务。
配置如下所示:
distributedCache:
redis:
isEnabled: true
configuration: 127.0.0.1:6379,defaultDatabase=0,password=passW0rd # redis缓存连接配置
