'); })();

# 缓存拦截

在silky框架中,为提高rpc通信性能,使用缓存拦截的设计,减少网络io操作,提高分布式应用的性能。

要想使用缓存拦截,必须要在将服务治理属性的Governance.CacheEnabled设置为true(该属性的缺省值为true)。如果将该属性值被设置为false,那么在rpc通信过程中,缓存拦截将无效。

缓存拦截在应用服务接口方法上通过缓存拦截特性进行设置,在silky框架中,存在如下三中类型的缓存特性,分别对数据缓存进行新增、更新、删除。

# 设置缓存特性--GetCachingInterceptAttribute

在rpc通信过程中,如果通过缓存的Key在分布式缓存中能够从分布式缓存中命中相应的缓存数据,那么就就直接从分布式缓存中间件中获取数据,并返回给服务调用者。如果没有命中缓存数据,那么就会通过网络通信的方式从服务提供者获取数据,并且将返回的数据新增到缓存中间件,在下次rpc通信过程中,就可以直接从缓存中间件中获取数据,从而达到减少io操作,提高分布式应用的性能。

设置缓存特性方式如下所示:

// 在应用服务接口方法中通过`GetCachingIntercept`设置缓存拦截
[GetCachingIntercept("name:{0}")]
Task<TestOut> Create(TestInput input);


// 通过输入参数对模板参数的占位符进行设值 
public class TestInput  
{
    [CacheKey(0)] 
    [HashKey] 
    [Required(ErrorMessage = "名称不允许为空")]
    public string Name { get; set; }

    [Required(ErrorMessage = "地址不允许为空")]
    public string Address { get; set; }

    [Phone(ErrorMessage = "手机号码格式不正确")] 
    public string Phone { get; set; }
    
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

GetCachingInterceptAttribute特性存在一个参数--keyTemplete,该参数是一个字符串类型,可以为该字符串指定字符串占位符,索引从0开始计算,占位符的值通过CacheKeyAttribute在输入参数中指定。如果是简单类型的参数,直接在参数名前面通过[CacheKey(keyTempleteIndex)](keyTempleteIndex指占位符的索引)指定,如果是参数类型是一个复杂参数,则通过对属性的特性进行标识。实际上生成的缓存的key会根据keyTemplete和对输入参数设置的[CacheKey(keyTempleteIndex)]的值以及返回结果类型(ReturnType)共同确认。

除此之外,GetCachingInterceptAttribute特性还存在一个OnlyCurrentUserData属性,如果当前缓存数据只与当前登录用户相关,那么需要将OnlyCurrentUserData属性值设置为true,生成的缓存的key会加上将当前用户id。

WARNING

需要注意的是,如果OnlyCurrentUserData属性值设置为true,需要确保,当前接口需要用户登录认证。

# 更新缓存特性--UpdateCachingInterceptAttribute

在rpc通信过程中,对数据进行更新操作后,为保证缓存一致性,同时需要对缓存数据进行更新操作。开发者可以通过UpdateCachingInterceptAttribute特性对缓存数据进行更新。UpdateCachingInterceptAttribute特性的参数与属性与GetCachingInterceptAttribute一致,用法基本上也一致。

与被GetCachingInterceptAttribute特性标注的应用服务接口不同的是,在rpc通信过程中,被UpdateCachingInterceptAttribute特性标识的方法都会得到执行,只是将服务提供者返回的结果更新到缓存服务中。被更新的缓存数据与生成的缓存的key以及返回结果类型(ReturnType)有关。

// 在应用服务接口方法中通过`GetCachingIntercept`设置缓存拦截
[UpdateCachingIntercept("name:{0}")]
Task<TestOut> Update(UpdateTestInput input);

// 通过输入参数对模板参数的占位符进行设值 
public class UpdateTestInput  
{
    public long Id { get; set; }

    [CacheKey(0)] 
    [HashKey] 
    [Required(ErrorMessage = "名称不允许为空")]
    public string Name { get; set; }

    [Required(ErrorMessage = "地址不允许为空")]
    public string Address { get; set; }

    [Phone(ErrorMessage = "手机号码格式不正确")] 
    public string Phone { get; set; }
    
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 删除缓存特性--RemoveCachingInterceptAttribute

在rpc通信过程中,对数据进行删除操作后,为保证缓存一致性,同时需要对缓存数据进行删除操作。开发者可以通过RemoveCachingInterceptAttribute特性对缓存数据进行移除操作。

RemoveCachingInterceptAttribute特性除了指定keyTempalte模板参数之外,还需要指定缓存名称(CacheName),一般地,在缓存拦截中,CacheName与要缓存数据(即:ReturnType)的类的完全限定名一致。

[RemoveCachingIntercept("ITestApplication.Test.Dtos.TestOut", "name:{0}")]
[Transaction]
Task<string> Delete([CacheKey(0)] string name);
1
2
3

# 分布式缓存接口

silky框架提供分布式缓存接口IDistributedCache<T>,通过分布式缓存接口实现对缓存数据的增删改查。其中,泛型T指的是缓存数据的类型。在分布式缓存中,T与应用服务接口方法定义的返回值类型一致。

分布式缓存接口可以通过构造器注入,在使用分布式缓存接口是,必须要指定泛型参数T

public class TestAppService : ITestAppService
{

    private readonly IDistributedCache<TestOut> _distributedCache;
    public TestAppService(IDistributedCache<TestOut> distributedCache)  
    {
        // 通过构造器注入分布式缓存接口后,可以通过该接口实现对该缓存数据的增删改查。
        _distributedCache = distributedCache;
    }
}
1
2
3
4
5
6
7
8
9
10

# 缓存数据的一致性

缓存一致性的概念: 缓存一致性的本质是数据一致性。换句话说,就是在缓存服务中的数据与数据库中存储的数据要保证数据一致性。开发者在开发过程中,要特别注意对缓存数据的一致性。也就是说,如果对某个类型的数据进行缓存,那么,对其进行更新、删除操作时,需要同时对缓存服务中的缓存数据进行更新或是、删除操作。

在rpc通信过程中,使用缓存拦截,同一数据的缓存依据可能会不同(设置的KeyTemplate,例如:缓存依据可能会根据IdNameCode分别进行缓存),从而产生不同的缓存数据,但是在对数据进行更新、删除操作时,由于无法通过RemoveCachingInterceptAttribute特性一次性删除该类型数据的所有缓存数据,这个时候,在实现业务代码过程中,就需要通过分布式缓存接口IDistributedCache<T>实现缓存数据的更新、删除操作。

缓存数据的更新、命中如下图所示:

caching1.png

caching2.png

# 缓存模板参数的设置

在上述文档中,我们看到,如果在服务通信过程中,如果想要使用缓存拦截功能,就必不可少的对缓存拦截的key进行模板配置,我们在上述模板中看到,多数情况下使用{0}以及配合[CacheKey(0)]等这样的占位符的方式配置缓存拦截Key的模板,这样使用起来无疑是不方便的。

在Silky的新版本中,我们支持通过参数或是属性名称实现对缓存拦截模板的设置。

例如:


[HttpGet("{id:long}")]
[GetCachingIntercept("id:{id}", OnlyCurrentUserData = true)]
Task<GetOrganizationOutput> GetAsync(long id);

1
2
3
4
5

在上述代码中,缓存拦截特性[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; }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

上述代码中显示,如果在更新某个组织机构的数据后,数据变化后需要清除缓存中的某些数据,缓存模板中指定的参数{Id}指的就是参数UpdateOrganizationInput类型的一个属性Id

我们明显的可以看到,在使用缓存拦截的功能上,如果我们通过参数的名称这是缓存模板参数占位符会更加的方便和自由,也无需通过[CacheKey(0)]来指定参数的占位位置。

# 使用redis作为缓存中间件

silky框架默认使用MemoryCaching作为缓存,但是如果开发者需要将微服务集群部署到多台服务器,那么您需要使用redis服务作为缓存服务。

silky使用redis作为分布式缓存服务非常简单,您只要提供一个redis服务(集群),通过配置文件就可以将缓存中间件替换为redis服务。

配置如下所示:

distributedCache:
  redis:
    isEnabled: true
    configuration: 127.0.0.1:6379,defaultDatabase=0,password=passW0rd //redis缓存链接配置
1
2
3
4