稍微複雜一點的互聯網項目,技術選型均可能會涉及Redis,.NetCore的生態愈加完善,支持.NetCore的Redis客戶端愈來愈多,html
下面三款常見的Redis客戶端,相信你們平時或多或少用到一些,結合平時對三款客戶端的使用,有些心得體會。git
先比較宏觀的背景: github
包名稱面試 |
背景redis |
github star數據庫 |
.NetStandard2.0目標框架上 依賴api |
Stackexchange.redis緩存 |
老牌.Net Redis客戶端,免費無限制,Stackoverflow背書服務器 |
3700+框架 |
|
Microsoft.Extensions.Caching.StackExchangeRedis |
.Netcore 2.2針對IDistributedCache接口實現的Redis分佈式緩存 |
|
|
CSRedisCore |
國人實現的著名第三方客戶端 |
894+ |
|
三款客戶端Redis支持的鏈接字符串配置基本相同
"connectionstrings": { "redis": "localhost:6379,password=abcdef,connectTimeout=5000,writeBuffer=40960" }
定位是高性能、通用的Redis .Net客戶端;方便地應用Redis全功能;支持Redis Cluster
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",這個特性是RedisMQ的關鍵理論。
若是你須要blocking pops, StackExchange.Redis官方推薦使用pub/sub模型模擬實現。
平常操做的API請關注IDatabase接口,支持異步方法,這裏我對【客戶端操做Redis儘可能不要使用異步方法】的說法不敢苟同,對於異步方法我認爲仍是遵照微軟最佳實踐:對於IO密集的操做,能使用異步儘可能使用異步
// 對應redis自增api:DECR mykey
_redisDB0.StringDecrementAsync("ProfileUsageCap", (double)1)
// 對應redis api: HGET KEY field1
_redisDB0.HashGetAsync(profileUsage, eqidPair.ProfileId))
// 對應redis哈希自增api: HINCRBY myhash field -1
_redisDB0.HashDecrementAsync(profileUsage, eqidPair.ProfileId, 1)
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]; } } }
從nuget doc可知,該組件庫依賴於 StackExchange.Redis 客戶端; 是.NetCore針對分佈式緩存提供的客戶端,側重點在 Redis的緩存特性。
// 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的形式存儲
- 普通模式
- 官方集羣模式 redis cluster
- 分區模式(做者實現)
var redisDB = new CSRedisClient[16]; // 多客戶端
for (var a = 0; a < redisDB.Length; a++)
redisDB[a] = new CSRedisClient(Configuration.GetConnectionString("redis") + ",defaultDatabase=" + 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());
對本身要使用的Redis API 的時間複雜度內心要有數,儘可能不要使用長時間運行的命令如keys *,可經過redis.io SlowLog命令觀測 哪些命令耗費較長時間
Redis Key可按照「:」分隔定義成有業務意義的字符串,如NewUsers:201909:666666(某些Redis UI可直觀友好查看該業務)
合適肯定Key-Value的大小:Redis對於small value更友好, 若是值很大,考慮劃分到多個key
關於緩存穿透,面試的時候會問,自行搜索布隆過濾器。
redis雖然有持久化機制,但在實際中會將key-value 持久化到關係型數據庫,由於對於某些結構化查詢,SQL更爲有效。
----- 20190829 多說兩句-------
以上三大客戶端,Microsoft.Extensions.Caching.StackExchangeRedis 與其餘二者的定位仍是有很大差距的,單純使用Redis 緩存特性, 有微軟出品,必屬精品情結的可以使用此客戶端;
StackExchange.Redis、CSRedisCore 對於Redis全功能特性支持的比較全,可是我也始終沒有解決StackExchange.Redis : RedisTimeoutException 超時的問題,換成CSRedisCore 確實沒有出現Redis相關異常。
---- 2019-09-25 update--------
- 受到公號一些網友的啓發,再次使用StackExchange.Redis , 並在redis配置中增長 abortConnect= false(缺省爲true,表示不嘗試重連),超時問題再也不出現。