StackExchange.Redis Client
這期我們來看StackExchange.Redis
,這是redis 的.net客戶端之一。Redis是一個開源的內存數據存儲,可以用來做數據庫,緩存或者消息代理服務。目前有不少人在使用ServiceStack.Redis
這個.net客戶端,但是這個的最新版本目前已經變成了商業軟件。對於ServiceStack.Redis
這種行為,我們沒有什么好說的,留給我們的選擇是使用低版本的開源版本或者轉向其他的客戶端。
要說到StackExchange.Redis,就不得不說它和BookSleeve的關系。BookSleeve已經是比較完善的redis sdk,但是為什么 BookSleeve 的作者要重新寫一個redis 的客戶端sdk呢? 有興趣的同學可以看這里why i wrote another redis client 歸納起來其實就一句話:覺得不爽就推倒重來。
(╯◕◞౪◟◕‵)╯︵ ┴─┴ (╯-_-)╯╧╧ (╯‵□′)╯︵┴─┴ (╯' - ')╯︵ ┻━┻ ┬─┬ ノ
StackExchange.Redis 安裝
直接命令或者手動NuGet。
PM> Install-Package StackExchange.Redis
如果需要強簽名的版本走下面的命令,當然作者對於強簽名的事也是充滿了怨念
PM> Install-Package StackExchange.Redis.StrongName
ConnectionMultiplexer
ConnectionMultiplexer對象是StackExchange.Redis最中樞的對象。這個類的實例需要被整個應用程序域共享和重用的,你不要在每個操作中不停的創建該對象的實例,所以使用單例來創建和存放這個對象是必須的。
public static ConnectionMultiplexer Manager { get { if (_redis == null) { lock (_locker) { if (_redis != null) return _redis; _redis = GetManager(); return _redis; } } return _redis; } } private static ConnectionMultiplexer GetManager(string connectionString = null) { if (string.IsNullOrEmpty(connectionString)) { connectionString = GetDefaultConnectionString(); } return ConnectionMultiplexer.Connect(connectionString); }
雖然ConnectionMultiplexer是實現了IDisposable接口的,但是我們基於重用的考慮,一般不需要去釋放它。
當作內存數據庫使用
IDatabase db = redis.GetDatabase();
這里的GetDatabase() 返回的db對象是很輕量級別的,不需要被緩存起來,每次用每次拿即可。IDatabase 的所有方法都有同步和異步的實現。其中的異步實現都是可以await的。
一些基礎的操作的封裝。
public bool Remove(string key) { key = MergeKey(key); var db = RedisManager.Manager.GetDatabase(Database); return db.KeyDelete(key); } public string Get(string key) { key = this.MergeKey(key); var db = RedisManager.Manager.GetDatabase(Database); return db.StringGet(key); } public bool Set(string key, string value, int expireMinutes = 0) { key = MergeKey(key); var db = RedisManager.Manager.GetDatabase(Database); if (expireMinutes > 0) { db.StringSet(key, value, TimeSpan.FromMinutes(expireMinutes)); } else { db.StringSet(key, value); } return db.StringSet(key, value); } public bool HasKey(string key) { key = MergeKey(key); var db = RedisManager.Manager.GetDatabase(Database); return db.KeyExists(key); }
這里的MergeKey
用來拼接Key
的前綴,具體不同的業務模塊使用不同的前綴。
這里有個和ServiceStack.Redis
大的區別是沒有默認的連接池管理了。沒有連接池自然有其利弊,最大的好處在於等待獲取連接的等待時間沒有了,也不會因為連接池里面的連接由於沒有正確釋放等原因導致無限等待而處於死鎖狀態。缺點在於一些低質量的代碼可能導致服務器資源耗盡。不過提供連接池等阻塞和等待的手段是和作者的設計理念相違背的。StackExchange.Redis這里使用管道和多路復用的技術來實現減少連接,這里后續展開再討論。
當作消息代理中間件使用
消息組建中,重要的概念便是生產者,消費者,消息中間件。
ISubscriber sub = redis.GetSubscriber();
首先,先拿到一個ISubscriber對象。在生產者端我們發布一條消息:
sub.Publish("messages", "hello");
,在消費者端得到該消息並輸出
sub.Subscribe("messages", (channel, message) => { Console.WriteLine((string)message); });
一般使用更專業的消息隊列來處理這種業務場景,因此這里就略過了。
三種命令模式
Sync vs Async vs Fire-and-Forget
最后,這里有三種命令模式分別對應StackExchange.Redis
的三類不同的使用場景。
Sync,同步模式會直接阻塞調用者,但是顯然不會阻塞其他線程。
Async,異步模式直接走的是Task模型。
Fire-and-Forget,就是發送命令,然后完全不關心最終什么時候完成命令操作。
db.StringIncrement(pageKey, flags: CommandFlags.FireAndForget);
這里值得注意的是,在Fire-and-Forget模式下,所有命令都會立即得到返回值,當然該值都是該返回值類型的默認值,比如操作返回類型是bool將會立即得到false,因為false = default(bool)。