前言 我们在项目中使用Redis时通常是写一个单例模式的RedisHelper静态类,暴露一些常用的Get、Set等操作,在需要使用地方直接RedisHelper。StringGet(xx,xx)就可以了,这样虽然简单粗暴地满足我们对Redis的所有操作需要,但是这在Asp。NetCore的项目显得不是那么优雅了。首先你的RedisHelper静态类无法使用Asp。NetCore容器,又如何优雅的通过依赖注入获取IConfiguration中的配置项呢?既然我们使用Asp。NetCore这么优秀的框架,最佳实践当然就是遵循官方建议的开发规范优雅的编写代码。IDistributedCache 若要使用SQLServer分布式缓存,请添加对Microsoft。Extensions。Caching。SqlServer包的包引用。 若要使用Redis分布式缓存,请添加对Microsoft。Extensions。Caching。StackExchangeRedis包的包引用。 若要使用NCache分布式缓存,请添加对NCache。Microsoft。Extensions。Caching。OpenSource包的包引用。 无论选择哪种实现,应用都将使用IDistributedCache接口与缓存进行交互。 来看下IDistributedCache这个接口的定义namespaceMicrosoft。Extensions。Caching。Distributed;summaryRepresentsadistributedcacheofserializedvalues。summarypublicinterfaceIDistributedCache{summaryGetsavaluewiththegivenkey。summarybyte〔〕?Get(stringkey);summaryGetsavaluewiththegivenkey。summaryTaskbyte〔〕?GetAsync(stringkey,CancellationTokentokendefault(CancellationToken));voidSet(stringkey,byte〔〕value,DistributedCacheEntryOptionsoptions);summarySetsthevaluewiththegivenkey。summaryTaskSetAsync(stringkey,byte〔〕value,DistributedCacheEntryOptionsoptions,CancellationTokentokendefault(CancellationToken));summaryRefreshesavalueinthecachebasedonitskey,resettingitsslidingexpirationtimeout(ifany)。summaryvoidRefresh(stringkey);summaryRefreshesavalueinthecachebasedonitskey,resettingitsslidingexpirationtimeout(ifany)。summaryTaskRefreshAsync(stringkey,CancellationTokentokendefault(CancellationToken));summaryRemovesthevaluewiththegivenkey。summaryvoidRemove(stringkey);summaryRemovesthevaluewiththegivenkey。summaryTaskRemoveAsync(stringkey,CancellationTokentokendefault(CancellationToken));} IDistributedCache接口提供以下方法来处理分布式缓存实现中的项:Get、GetAsync:如果在缓存中找到,则接受字符串键并以byte〔〕数组的形式检索缓存项。Set、SetAsync:使用字符串键将项(作为byte〔〕数组)添加到缓存。Refresh、RefreshAsync:根据键刷新缓存中的项,重置其可调到期超时(如果有)。Remove、RemoveAsync:根据字符串键删除缓存项。干掉RedisHelper 官方不仅提出了如何最佳实践分布式缓存的使用,还提供了基本的实现库给我们直接用,比如我们在项目中用Redis为我们提供缓存服务:添加引用Microsoft。Extensions。Caching。StackExchangeRedis注册容器AddStackExchangeRedisCache,并配置参数builder。Services。AddStackExchangeRedisCache(options{options。Configurationbuilder。Configuration。GetConnectionString(MyRedisConStr);options。InstanceNameSampleInstance;});在需要使用Redis的地方通过构造函数注入IDistributedCache实例调用即可 这样就可以优雅的使用Redis了,更加符合Asp。NetCore的设计风格,养成通过容器注入的方式来调用我们的各种服务,而不是全局使用RedisHelper静态类,通过IOC的方式,结合面向接口开发,能方便的替换我们的实现类,统一由容器提供对象的创建,这种控制反转带来的好处只可意会不可言传,这里就不赘述了。AddStackExchangeRedisCache到底干了什么 上面已经知道如何优雅的使用我们的Redis了,但是不看下源码就不知道底层实现,总是心里不踏实的。 源码比较好理解的,因为这个Nuget包的源码也就四个类,而上面注册容器的逻辑也比较简单 AddStackExchangeRedisCache主要干的活1。启用Options以使用IOptionsservices。AddOptions();2。注入配置自定义配置,可以通过IOptionsT注入到需要使用该配置的地方services。Configure(setupAction);3。注入一个单例IDistributedCache的实现类RedisCacheservices。Add(ServiceDescriptor。SingletonIDistributedCache,RedisCache()); 所以我们在需要用Redis的地方通过构造函数注入IDistributedCache,而它对应的实现就是RedisCache,那看下它的源码。 这里就不细看所有的实现了,重点只需要知道它继承了IDistributedCache就行了,通过AddStackExchangeRedisCache传入的ConnectionString,实现IDistributedCache的Get、Set、Refresh、Remove四个核心的方法,我相信这难不倒你,而它也就是干了这么多事情,只不过它的实现有点巧妙。 通过LUA脚本和HSET数据结构实现,HashKey是我们传入的InstanceNamekey,做了一层包装。 源码中还有需要注意的就是,我们要保证Redis连接对象IConnectionMultiplexer的单例,不能重复创建多个实例,这个想必在RedisHelper中也是要保证的,而且是通过lock来实现的。 然而微软不是那么用的,玩了个花样,注意下面的connectionLock。Wait();:privatereadonlySemaphoreSlimconnectionLocknewSemaphoreSlim(initialCount:1,maxCount:1);〔MemberNotNull(nameof(cache),nameof(connection))〕privatevoidConnect(){CheckDisposed();if(cache!null){Debug。Assert(connection!null);return;}connectionLock。Wait();try{if(cachenull){if(options。ConnectionMultiplexerFactorynull){if(options。ConfigurationOptionsisnotnull){connectionConnectionMultiplexer。Connect(options。ConfigurationOptions);}else{connectionConnectionMultiplexer。Connect(options。Configuration);}}else{connectionoptions。ConnectionMultiplexerFactory()。GetAwaiter()。GetResult();}PrepareConnection();cacheconnection。GetDatabase();}}finally{connectionLock。Release();}Debug。Assert(connection!null);} 通过SemaphoreSlim限制同一时间只能有一个线程能访问connectionLock。Wait();后面的代码。 学到装逼技巧1思考 IDistributedCache只有四个操作:Get、Set、Refresh、Remove,我们表示很希望跟着官方走,但这个接口过于简单,不能满足我的其他需求咋办? 比如我们需要调用StackExchange。Redis封装的LockTake,LockRelease来实现分布式锁的功能,那该怎么通过注入IDistributedCache调用? 我们可以理解官方上面是给我们做了示范,我们完全可以自己定义一个接口,比如:publicinterfaceIDistributedCachePlus:IDistributedCache{boolLockRelease(stringkey,byte〔〕value);boolLockTake(stringkey,byte〔〕value,TimeSpanexpiry);} 继承IDistributedCache,对其接口进行增强,然后自己实现实现AddStackExchangeRedisCache的逻辑,我们不用官方给的实现,但是我们山寨官方的思路,实现任意标准的接口,满足我们业务。services。Add(ServiceDescriptor。SingletonIDistributedCachePlus,RedisCachePlus()); 在需要使用缓存的地方通过构造函数注入IDistributedCachePlus。总结 官方提供的IDistributedCache标准及其实现类库,能方便的实现我们对缓存的简单的需求,通过遵循官方的建议,我们干掉了RedisHelper,优雅的实现了分布式Redis缓存的使用,你觉得这样做是不是很优雅呢? 原文链接:https:www。cnblogs。comspringhguip16290803。html