前言

  稍微复杂一点的互联网项目,技术选型都可能会涉及Redis,.NetCore的生态越发完善,支持.NetCore的Redis客户端越来越多,

下面三款常见的Redis客户端,相信大家平时或多或少用到一些,结合平时对三款客户端的使用,有些心得体会。

先比较宏观的背景: 

包名称 背景 github star .NetStandard2.0目标框架上 依赖
Stackexchange.redis 老牌.Net Redis客户端,免费无限制,Stackoverflow背书 3700
* Pipelines.Sockets.Unofficial
<https://www.nuget.org/packages/Pipelines.Sockets.Unofficial/> (>= 2.0.22)
* System.Diagnostics.PerformanceCounter
<https://www.nuget.org/packages/System.Diagnostics.PerformanceCounter/> (>=
4.5.0)
* System.IO.Pipelines <https://www.nuget.org/packages/System.IO.Pipelines/>
 (>= 4.5.1)
* System.Threading.Channels
<https://www.nuget.org/packages/System.Threading.Channels/>
Microsoft.Extensions.Caching.StackExchangeRedis .Netcore 2.2针对IDistributedCache

<https://docs.microsoft.com/dotnet/api/microsoft.extensions.caching.distributed.idistributedcache>
接口实现的Redis分布式缓存  
* Microsoft.Extensions.Caching.Abstractions
<https://www.nuget.org/packages/Microsoft.Extensions.Caching.Abstractions/> (>=
2.2.0)
* Microsoft.Extensions.Options
<https://www.nuget.org/packages/Microsoft.Extensions.Options/> (>= 2.2.0)
* StackExchange.Redis <https://www.nuget.org/packages/StackExchange.Redis/> 
CSRedisCore 国人实现的著名第三方客户端 894
* Newtonsoft.Json <https://www.nuget.org/packages/Newtonsoft.Json/> (>=
12.0.2)
* SafeObjectPool <https://www.nuget.org/packages/SafeObjectPool/> (>= 2.1.1)
* System.ValueTuple <https://www.nuget.org/packages/System.ValueTuple/> (>=
4.5.0)
使用心得

三款客户端Redis支持的连接字符串配置基本相同
"connectionstrings": { "redis": "
localhost:6379,password=abcdef,connectTimeout=5000,writeBuffer=40960" }
StackExchange.Redis

  定位是高性能、通用的Redis .Net客户端;方便地应用Redis全功能;支持Redis Cluster

* 高性能的核心在于 多路复用器(支持在多个调用线程高效共享Redis连接), 服务器端操作使用ConnectionMultiplexer 类
ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("
server1:6379,server2:6379"); // 日常应用的核心类库是IDatabase IDatabase db =
redis.GetDatabase();// 支持Pub/Sub ISubscriber sub = redis.GetSubscriber();
sub.Subscribe("messages", (channel, message) => { Console.WriteLine((string
)message); });--- sub.Publish("messages", "hello"); 也正是因为多路复用,
StackExchange.Redis唯一不支持的Redis特性是 "blocking pops"
<https://stackexchange.github.io/StackExchange.Redis/PipelinesMultiplexers>
,这个特性是RedisMQ的关键理论。 如果你需要blocking pops, StackExchange.Redis官方推荐使用pub/sub模型模拟实现。
 

* 日常操作的API请关注IDatabase接口,支持异步方法,这里我对【客户端操作Redis尽量不要使用异步方法
<https://www.cnblogs.com/yilezhu/p/9941208.html>
】的说法不敢苟同,对于异步方法我认为还是遵守微软最佳实践:对于IO密集的操作,能使用异步尽量使用异步
_redisDB0.StringDecrementAsync("ProfileUsageCap", (double)1)     //
对应redis自增api:DECR mykey _redisDB0.HashGetAsync(profileUsage,
eqidPair.ProfileId))   // 对应redis api: hget key field1
_redisDB0.HashDecrementAsync(profileUsage, eqidPair.ProfileId,1) //
对应redis哈希自增api: HINCRBY myhash field -1
 

* ConnectionMultiplexer 方式支持随时切换Redis DB,对于多个Redis DB的操作,我封装了一个常用的Redis DB
操作客户端。 public class RedisStore { private static Lazy<ConnectionMultiplexer>
LazyConnection;private static string connectionRedis = "localhost:6379"; public
RedisStore(string connectiontring) { connectionRedis = connectiontring ?? "
localhost:6379"; LazyConnection = new Lazy<ConnectionMultiplexer>(() =>
ConnectionMultiplexer.Connect(connectionRedis)); }public static
ConnectionMultiplexer Connection => LazyConnection.Value; public RedisDatabase
RedisCache =>new RedisDatabase(Connection); } public class RedisDatabase {
private Dictionary<int, IDatabase> DataBases = new Dictionary<int, IDatabase>();
public ConnectionMultiplexer RedisConnection { get; } public
RedisDatabase(ConnectionMultiplexer Connection) { DataBases= new Dictionary<int
, IDatabase>{ }; for(var i=0;i<16;i++) { DataBases.Add(i,
Connection.GetDatabase(i)); } RedisConnection= Connection; } public IDatabase
this[int index] { get { if (DataBases.ContainsKey(index)) return
DataBases[index];else return DataBases[0]; } } } RedisCache
 

Microsoft.Extensions.Caching.StackExchangeRedis

    从nuget doc可知,该组件库依赖于 StackExchange.Redis 客户端; 是.NetCore针对分布式缓存提供的客户端,侧重点在
Redis的缓存特性。
该库是基于 IDistributedCache
<https://docs.microsoft.com/dotnet/api/microsoft.extensions.caching.distributed.idistributedcache>
接口实现的,该接口为实现分布式缓存的通用性,缓存内容将以byte[] 形式读写 ; 另外能使用的函数签名也更倾向于【通用的 增、查操作】 // add
Redis cache service services.AddStackExchangeRedisCache(options => {
  options.Configuration= Configuration.GetConnectionString("redis");
  options.InstanceName= "SampleInstance"; }); // Set Cache Item (by byte[])
lifetime.ApplicationStarted.Register(() => { var currentTimeUTC =
DateTime.UtcNow.ToString();byte[] encodedCurrentTimeUTC =
Encoding.UTF8.GetBytes(currentTimeUTC);var options = new
DistributedCacheEntryOptions() .SetSlidingExpiration(TimeSpan.FromMinutes(20));
cache.Set("cachedTimeUTC", encodedCurrentTimeUTC, options); }); // Retrieve
Cache Item [HttpGet] [Route("CacheRedis")] public async Task<string> GetAsync()
{var ret = ""; var bytes = await _cache.GetAsync("cachedTimeUTC"); if (bytes !=
null) { ret = Encoding.UTF8.GetString(bytes); _logger.LogInformation(ret); }
return await Task.FromResult(ret); }
① 很明显,该Cache组件并不能做到自由切换 Redis DB, 目前可在redis连接字符串一次性配置项目要使用哪个Redis DB

② 会在指定DB(默认为0)生成key = SampleInstancecachedTimeUTC 的redis缓存项

③ Redis并不支持bytes[] 形式的存储值,以上byte[] 实际是以Hash的形式存储

 

 

CSRedisCore
该组件的功能更为强大,针对实际Redis应用场景有更多玩法。 - 普通模式 - 官方集群模式 redis cluster - 分区模式(作者实现)  
普通模式使用方法极其简单,这里要提示的是: 该客户端也不支持 随意切换 Redis DB, 但是原作者给出一种缓解的方式:构造多客户端。 var
redisDB =new CSRedisClient[16]; // 多客户端
for (var a = 0; a < redisDB.Length; a++)
  redisDB[a] = new CSRedisClient(Configuration.GetConnectionString("redis") +
",defualtDatabase=" + a);

services.AddSingleton(redisDB); // ---------------------------- _redisDB[0
].IncrByAsync("ProfileUsageCap", -1) _redisDB[0].HGetAsync(profileUsage,
eqidPair.ProfileId.ToString()) _redisDB[0].HIncrByAsync(profileUsage,
eqidPair.ProfileId.ToString(), -1);
 内置的静态操作类RedisHelper, 与Redis-Cli 命令完全一致, 故他能原生支持”blocking pops”。
// 实现后台服务,持续消费MQ消息 public class BackgroundJob : BackgroundService { private
readonly CSRedisClient[] _redisDB; private readonly IConfiguration _conf;
private readonly ILogger _logger; public BackgroundJob(CSRedisClient[]
csRedisClients,IConfiguration conf,ILoggerFactory loggerFactory) { _redisDB=
csRedisClients; _conf= conf; _logger =
loggerFactory.CreateLogger(nameof(BackgroundJob)); }// Background 需要实现的后台任务
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
_redisDB[0] = new CSRedisClient(_conf.GetConnectionString("redis") + "
,defualtDatabase=" + 0); RedisHelper.Initialization(_redisDB[0]); while (!
stoppingToken.IsCancellationRequested) {var key = $"
eqidpair:{DateTime.Now.ToString("yyyyMMdd")}"; // 阻塞式从右侧读取List首消息 var eqidpair
= RedisHelper.BRPop(5, key); // TODO Handler Message else await Task.Delay(1000
, stoppingToken); } } }-----RedisMQ 生产者--- // 将一个或多个msg插入List头部
RedisHelper.LPush(redisKey, eqidPairs.ToArray()); 简单有效RedisMQ
Redis的一点小经验:

*
对自己要使用的Redis API 的时间复杂度心里要有数,尽量不要使用长时间运行的命令如keys *,可通过redis.io SlowLog命令观测
哪些命令耗费较长时间

*
Redis Key可按照“:”分隔定义成有业务意义的字符串,如NewUsers:201909:666666(某些Redis UI可直观友好查看该业务)

*
合适确定Key-Value的大小:Redis对于small value更友好, 如果值很大,考虑划分到多个key

*
关于缓存穿透,面试的时候会问,自行搜索布隆过滤器。

*
redis虽然有持久化机制,但在实际中会将key-value 持久化到关系型数据库,因为对于某些结构化查询,SQL更为有效。

 

----- update 多说两句-------

以上三大客户端,Microsoft.Extensions.Caching.StackExchangeRedis 与其他两者的定位还是有很大差距的,单纯
使用Redis 缓存特性, 有微软出品,必属精品情结的可使用此客户端;

StackExchange.Redis、CSRedisCore
对于Redis全功能特性支持的比较全,但是我也始终没有解决StackExchange.Redis : RedisTimeoutException
超时的问题,换成CSRedisCore 确实没有出现Redis相关异常。