.NET redis 客戶端開源組件 FreeRedis (繼 CSRedisCore 之後重寫)
什麼是 FreeRedis
FreeRedis 是一款 .NET redis 客戶端開源組件,以 MIT 協議開源託管於 github,目前支援 .NET 5、.NETCore 2.1+、.NETFramework 4.0+、Xamarin,有可能已經支援 AOT 編譯(目前未測試,但會往這個方向走)。
FreeRedis 會嚴格按照 FreeSql 的開源方式,做好單元測試,兼容平台,簡單易用,有問必答,有求必應的態度,為中國 .NET 開源事業做一點點貢獻。
感謝大家的支援,項目還未公開就已經獲得 66 星。目前項目仍在起步階段,歡迎小夥伴參與進來,貢獻測試、或程式碼、或建議都可以。
項目當前的狀態:
- 版本 0.0.8(目前不建議使用在生產環境)
- 單元測試 268 個
- 支援 集群、哨兵、主從(已通過測試)
- 支援 連接池
- 支援 .NET5/.NETCore 2.1+/.NET4.0+
- 支援 Redis6.0 所有類型
- 支援 Redis6.0 RESP3 協議
- API 仍然與 redis-cli 命令保持一致
- 採用最寬鬆的開源協議 MIT //github.com/2881099/FreeRedis
項目由來
說來話長,2016 年之前本人寫了一年多 nodejs 服務端應用,使用過 node-redis 組件,真心好用。在此期間有同事不停安利 .NET 可以跨平台了,勸我快回來搞 .NET,開始我是抗拒做螃蟹第一人的,不知道是哪天下午閑著蛋疼去體驗了一把 .NETCore 1.0-previewXX(不記得哪個版本了)。試了一把被吸引住了,體驗感受和 expressjs 像極了,再也看不見以往 webform/mvc 的缺點。
於是我準備入坑了,入坑第一件事除了 hello world,還需要做相關調研:
- 性能OK
- 設計OK
- 發展OK(暫時的定級)
- 相關組件OK(HttpClient、Redis、Ado.NET、等等基礎組件)
初始調研完成之後,接下下就要抽時間選型框架了,最終從眾多框架中選擇了合適團隊的一款://github.com/simplcommerce/SimplCommerce ,在這個項目原有基礎之上,結合企業規範要求訂製改造,大約兩個月時間完成了可生產的狀態。(框架不求開始盡善盡美,只求使用中不斷打磨,最終走向完美)
理想豐滿現實骨幹,接下來的故事就是遇到生產故障了,StackExchange.Redis、HttpClient 關於這兩個組件的問題,以前講過現在就不說了(萬萬沒想到這麼大的組件使用都能出現問題)。吃螃蟹就會掉坑,掉了坑就要想辦法解決,最終與 csredis 組件結緣。
以當時的情形縱觀 .NET 所有 redis 客戶端組件,只有 csredis 源碼最易改造支援 .NETCore(水平有限見諒),csredis 2014 年停止更新,本人於 2016 年將其改造支援 .NETCore 為主,以及增加連接池管理、集群、哨兵、redis2.8 以上的命令,在公司項目生產環境使用一年半載之後開源。
- csredis 原源碼地址://github.com/ctstone/csredis
- CSRedisCore 源碼地址://github.com/2881099/csredis
CSRedisCore 開源這到久,nuget 下載量達到 60W,收集需求若干,bug 若干(有解決了的、也有未能重現的),基於我已經對 redis 這塊很熟悉,然後 redis 5.0/6.0 又新增了蠻多特性,重新寫一款 bug 更少、可維護性更好的想法產生了。
經過幾個月的墨跡終於走通可用了,項目最終命名:FreeRedis
如何使用
🌈 Single machine redis (單機)
public static RedisClient cli = new RedisClient("127.0.0.1:6379,password=123,defaultDatabase=13");
//cli.Serialize = obj => JsonConvert.SerializeObject(obj);
//cli.Deserialize = (json, type) => JsonConvert.DeserializeObject(json, type);
cli.Notice += (s, e) => Console.WriteLine(e.Log); //print command log
cli.Set("key1", "value1");
cli.MSet("key1", "value1", "key2", "value2");
string value1 = cli.Get("key1");
string[] vals = cli.MGet("key1", "key2");
API 仍然與 redis-cli 命令保持一致,所以如果想了解 FreeRedis 每個方法怎麼使用,去百度搜索 「redis 命令」,有很多很多很多資料。don’t say so much!!!
支援 Redis6.0 支援的所有數據類型:strings, hashes, lists, sets, sorted sets, bitmaps, hyperloglogs, geo, streams And BloomFilter.
Parameter | Default | Explain |
---|---|---|
protocol | RESP2 | If you use RESP3, you need redis 6.0 environment |
user | <empty> | Redis server username, requires redis-server 6.0 |
password | <empty> | Redis server password |
defaultDatabase | 0 | Redis server database |
max poolsize | 100 | Connection max pool size |
min poolsize | 5 | Connection min pool size |
idleTimeout | 20000 | Idle time of elements in the connection pool (MS) |
connectTimeout | 10000 | Connection timeout (MS) |
receiveTimeout | 10000 | Receive timeout (MS) |
sendTimeout | 10000 | Send timeout (MS) |
encoding | utf-8 | string charset |
ssl | false | Enable encrypted transmission |
name | <empty> | Connection name, use client list command to view |
如果需要連接 IPv6,連接串請使用: [fe80::b164:55b3:4b4f:7ce6%15]:6379
🎣 Master-Slave (讀寫分離)
public static RedisClient cli = new RedisClient(
"127.0.0.1:6379,password=123,defaultDatabase=13",
"127.0.0.1:6380,password=123,defaultDatabase=13",
"127.0.0.1:6381,password=123,defaultDatabase=13"
);
var value = cli.Get("key1");
這樣創建的 cli,所有寫入命令都會連接 127.0.0.1:6379 執行,所有讀取命令只會隨機連接 127.0.0.1:6380 或 127.0.0.1:6381 執行。(內部已經為每個命令做好了讀寫標記)
⛳ Redis Sentinel (哨兵高可用)
public static RedisClient cli = new RedisClient(
"mymaster,password=123",
new [] { "192.169.1.10:26379", "192.169.1.11:26379", "192.169.1.12:26379" },
true //是否讀寫分離
);
哨兵是一個分散式系統,為 Redis 提供高可用性解決方案,當主機 master 宕機之後,會馬上出現一個新的 master 可使用,確保服務的正常運作。
哨兵模式還可以設置讀寫分離,緩解 master 頻繁讀取數據的壓力,缺點:有可能讀到的數據不是最新,因為 redis 從 master 同步到 slave 有延時。
🌌 Redis Cluster (集群)
假如你有一個 Redis Cluster 集群,其中有三個主節點(7001-7003)、三個從節點(7004-7006),則連接此集群的程式碼:
public static RedisClient cli = new RedisClient(
new ConnectionStringBuilder[] { "192.168.0.2:7001", "192.168.0.2:7001", "192.168.0.2:7003" }
);
📡 Subscribe (訂閱)
using (cli.Subscribe("abc", ondata)) //wait .Dispose()
{
Console.ReadKey();
}
void ondata(string channel, string data) =>
Console.WriteLine($"{channel} -> {data}");
📃 Scripting (腳本)
var r1 = cli.Eval("return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}",
new[] { "key1", "key2" }, "first", "second") as object[];
var r2 = cli.Eval("return {1,2,{3,'Hello World!'}}") as object[];
cli.Eval("return redis.call('set',KEYS[1],'bar')",
new[] { Guid.NewGuid().ToString() })
💻 Pipeline (管道)
using (var pipe = cli.StartPipe())
{
pipe.IncrBy("key1", 10);
pipe.Set("key2", Null);
pipe.Get("key1");
object[] ret = pipe.EndPipe();
Console.WriteLine(ret[0] + ", " + ret[2]);
}
// or Async Callback
using (var pipe = cli.StartPipe())
{
var tasks = new List<Task>();
long t0 = 0;
task.Add(pipe.IncrByAsync("key1", 10).ContinueWith(t => t0 = t.Result)); //callback
pipe.SetAsync("key2", Null);
string t2 = null;
task.Add(pipe.GetAsync("key1").ContinueWith(t => t2 = t.Result)); //callback
pipe.EndPipe();
Task.WaitAll(tasks.ToArray()); //wait all callback
Console.WriteLine(t0 + ", " + t2);
}
📰 Transaction (事務)
using (var tran = cli.Multi())
{
tran.IncrBy("key1", 10);
tran.Set("key2", Null);
tran.Get("key1");
object[] ret = tran.Exec();
Console.WriteLine(ret[0] + ", " + ret[2]);
}
// or Async Callback
using (var tran = cli.Multi())
{
var tasks = new List<Task>();
long t0 = 0;
task.Add(tran.IncrByAsync("key1", 10).ContinueWith(t => t0 = t.Result)); //callback
tran.SetAsync("key2", Null);
string t2 = null;
task.Add(tran.GetAsync("key1").ContinueWith(t => t2 = t.Result)); //callback
tran.Exec();
Task.WaitAll(tasks.ToArray()); //wait all callback
Console.WriteLine(t0 + ", " + t2);
}
📯 GetDatabase (切庫)
using (var db = cli.GetDatabase(10))
{
db.Set("key1", 10);
var val1 = db.Get("key1");
}
結束語
目前項目仍在起步階段,歡迎小夥伴參與進來,貢獻測試、或程式碼、或建議都可以。
FreeRedis 使用最寬鬆的開源協議 MIT //github.com/2881099/FreeRedis
如果你有好的 redis 實現想法,歡迎給作者留言討論,謝謝觀看!