vlambda博客
学习文章列表

.NET redis 客户端开源组件 FreeRedis (继 CSRedisCore 之后重写)

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 https://github.com/2881099/FreeRedis


2|0项目由来


说来话长,2016 年之前本人写了一年多 nodejs 服务端应用,使用过 node-redis 组件,真心好用。在此期间有同事不停安利 .NET 可以跨平台了,劝我快回来搞 .NET,开始我是抗拒做螃蟹第一人的,不知道是哪天下午闲着蛋疼去体验了一把 .NETCore 1.0-previewXX(不记得哪个版本了)。试了一把被吸引住了,体验感受和 expressjs 像极了,再也看不见以往 webform/mvc 的缺点。

于是我准备入坑了,入坑第一件事除了 hello world,还需要做相关调研:

  • 性能OK

  • 设计OK

  • 发展OK(暂时的定级)

  • 相关组件OK(HttpClient、Redis、Ado.NET、等等基础组件)

初始调研完成之后,接下下就要抽时间选型框架了,最终从众多框架中选择了合适团队的一款:https://github.com/simplcommerce/SimplCommerce ,在这个项目原有基础之上,结合企业规范要求定制改造,大约两个月时间完成了可生产的状态。(框架不求开始尽善尽美,只求使用中不断打磨,最终走向完美)

理想丰满现实骨干,接下来的故事就是遇到生产故障了,StackExchange.Redis、HttpClient 关于这两个组件的问题,以前讲过现在就不说了(万万没想到这么大的组件使用都能出现问题)。吃螃蟹就会掉坑,掉了坑就要想办法解决,最终与 csredis 组件结缘。

以当时的情形纵观 .NET 所有 redis 客户端组件,只有 csredis 源码最易改造支持 .NETCore(水平有限见谅),csredis 2014 年停止更新,本人于 2016 年将其改造支持 .NETCore 为主,以及增加连接池管理、集群、哨兵、redis2.8 以上的命令,在公司项目生产环境使用一年半载之后开源。

CSRedisCore 开源这到久,nuget 下载量达到 60W,收集需求若干,bug 若干(有解决了的、也有未能重现的),基于我已经对 redis 这块很熟悉,然后 redis 5.0/6.0 又新增了蛮多特性,重新写一款 bug 更少、可维护性更好的想法产生了。

经过几个月的墨迹终于走通可用了,项目最终命名:FreeRedis

感谢 Nuget 转让包的大兄弟。


3|0如何使用


🌈 Single machine redis (单机)

 
   
   
 

public static RedisClient cli = new RedisClient( "127.0.0.1:6379,password=123,defaultDatabase=13" ); 3,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");
}

4|0结束语



目前项目仍在起步阶段,欢迎小伙伴参与进来,贡献测试、或代码、或建议都可以。

FreeRedis 使用最宽松的开源协议 MIT https://github.com/2881099/FreeRedis

如果你有好的 redis 实现想法,欢迎给作者留言讨论,谢谢观看!