MIT License

技术解读

以下内容由 GitRead 中文社区 编辑团队基于项目 README 与官方资料整理, 结合中文开发者的常见使用场景补充技术选型建议。具体以项目官方文档为准。

项目背景与痛点

在现代互联网应用中,高效的数据访问是至关重要的。Redis 作为内存数据存储系统,被广泛用于快速读写操作、缓存数据、数据持久化以及发布/订阅模式等场景。然而,如何在 .NET 平台下高效地与 Redis 进行交互,一直是开发者面临的一大挑战。FreeRedis 的出现,正是为了解决这个痛点,提供了强大的功能支持,包括集群连接、哨兵模式、主从模式、发布/订阅、Lua 脚本、管道、事务、流、搜索以及客户端缓存等特性,从而为 .NET 开发者提供了一种高效、灵活的 Redis 接入方式。

技术核心揭秘

FreeRedis 的技术实现堪称精妙,特别值得我们一探究竟。

技术栈与设计模式

该项目采用了高性能的 .NET Framework 4.0 及以上版本进行开发。其设计中涉及了多种设计模式,如工厂模式用于创建连接实例,单例模式确保连接池的唯一性,以及命令模式用于处理 Redis 命令的封装和执行。

数据流及架构解析

FreeRedis 的整体架构简洁明了,分为多个层级:

  • 客户端底层协议实现:封装了 Redis 协议,处理了底层连接和数据传输的细节。
  • 连接池管理:通过连接池管理器来复用和分配资源,提高资源利用率。
  • 命令执行:支持管道、事务等高级功能,通过命令抽象进行封装。
  • 高级特性支持:涵盖 Redis 的诸多高级特性,如 Redis Streams、RediSearch、客户端缓存等。

下图给出了 FreeRedis 的关键数据流过程:

graph TD A[客户端请求] --> B[创建连接] B --> C{"连接池复用?"} C -->|"是"| D[复用现有连接] C -->|"否"| E[建立新连接] D --> F[执行命令] E --> F F --> G{"结果处理?"} G -->|"是"| H[响应客户端] G -->|"否"| I[异常处理] H --> J[关闭连接] I --> J

核心算法与实现细节

FreeRedis 支持多种高级功能,如 Redis Streams 和 RediSearch。其中,Redis Streams 依赖于高效的数据结构和算法来确保消息的顺序和一致性,而 RediSearch 则通过构建倒排索引来快速实现全文检索。

功能亮点与差异

与同类竞品相比,FreeRedis 的优势在于其对 .NET 框架的全面支持,尤其是对 Redis 各类高级特性的无缝集成。此外,其完善的连接池管理和优化的命令执行方式,使得 FreeRedis 在高并发场景下具有更高的性能表现。

应用场景与落地建议

FreeRedis 适用于需要快速读写操作的场景,如缓存、会话存储、持久化等。但在部署时,应注意连接池的大小、数据一致性、以及网络延迟对整体性能的影响。

开发人员在使用 FreeRedis 时,需注意以下几点:

  1. 连接池设置:合理配置连接池大小,避免资源浪费或不足。
  2. 数据安全:重视对连接设置的加密,防止敏感数据泄露。
  3. 监控与反馈:定期监控 Redis 实例的工作状态,及时处理瓶颈和异常。

综合评价

FreeRedis 提供了丰富的功能和高效的性能,但其复杂的设计也带来了学习曲线的问题。优点方面,它可以无缝对接 .NET 生态系统中的大部分框架和工具,并支持 Redis 的各项高级功能。缺点方面,虽然功能全面,但初学者可能需要较长时间掌握其底层架构及使用技巧,且在某些特定场景下的优化程度仍有提升空间。

关于

GitHub 原始描述(英文)

🦄 FreeRedis is .NET40+ redis client. supports cluster, sentinel, master-slave, pub-sub, lua, pipeline, transaction, streams, redisearch, client-side-caching, and pooling.

描述取自 GitHub 官方 API,保持仓库作者书写的原文。

简要分析

热度分
1364
价值分
642
活跃状态
活跃

语言占比

C#
Shell

Release

README

🦄 FreeRedis

FreeRedis is a redis client based on .NET, supports .NET Core 2.1+, .NET Framework 4.0+, Xamarin, and AOT.

nuget stats GitHub license

English | 中文

  • 🌈 RedisClient Keep all method names consistent with redis-cli
  • 🌌 Support Redis Cluster (requires redis-server 3.2 and above)
  • ⛳ Support Redis Sentinel
  • 🎣 Support Redis Master-Slave
  • 📡 Support Redis Pub-Sub
  • 📃 Support Redis Lua Scripting
  • 💻 Support Pipeline, Transaction, DelayQueue, RediSearch
  • 🌴 Support Geo type commands (requires redis-server 3.2 and above)
  • 🌲 Support Streams type commands (requires redis-server 5.0 and above)
  • ⚡ Support Client-side-caching (requires redis-server 6.0 and above)
  • 🌳 Support Redis 6 RESP3 Protocol

QQ Groups:4336577(full)、8578575(available)、52508226(available)

🚀 Quick start

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");

Supports 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), suitable for connecting to remote redis server
connectTimeout 10000 Connection timeout (MS)
receiveTimeout 10000 Receive timeout (MS)
sendTimeout 10000 Send timeout (MS)
encoding utf-8 string charset
retry 0 Protocol error retry execution times
ssl false Enable encrypted transmission
name <empty> Connection name, use client list command to view
prefix <empty> The prefix of the key, all methods will have this prefix. cli.Set(prefix + “key”, 111);
exitAutoDisposePool true AppDomain.CurrentDomain.ProcessExit/Console.CancelKeyPress auto disposed
subscribeReadbytes false Subscribe read bytes

IPv6: [fe80::b164:55b3:4b4f:7ce6%15]:6379

//FreeRedis.DistributedCache
//services.AddSingleton<IDistributedCache>(new FreeRedis.DistributedCache(cli));

🎣 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");

Write data at 127.0.0.1:6379; randomly read data from port 6380 or 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 //This variable indicates whether to use the read-write separation mode.
    );

🌌 Redis Cluster

Suppose, a Redis cluster has three master nodes (7001-7003) and three slave nodes (7004-7006), then use the following code to connect to the cluster:

public static RedisClient cli = new RedisClient(
    new ConnectionStringBuilder[] { "192.168.0.2:7001", "192.168.0.2:7002", "192.168.0.2:7003" }
    );

⚡ Client-side-caching

requires redis-server 6.0 and above

cli.UseClientSideCaching(new ClientSideCachingOptions
{
    //Client cache capacity
    Capacity = 3,
    //Filtering rules, which specify which keys can be cached locally
    KeyFilter = key => key.StartsWith("Interceptor"),
    //Check long-term unused cache
    CheckExpired = (key, dt) => DateTime.Now.Subtract(dt) > TimeSpan.FromSeconds(2)
});

📡 Subscribe

using (cli.Subscribe("abc", ondata)) //wait .Dispose()
{
    Console.ReadKey();
}

void ondata(string channel, string data) =>
    Console.WriteLine($"{channel} -> {data}");

xadd + xreadgroup:

using (cli.SubscribeStream("stream_key", ondata)) //wait .Dispose()
{
    Console.ReadKey();
}

void ondata(Dictionary<string, string> streamValue) =>
    Console.WriteLine(JsonConvert.SerializeObject(streamValue));

// NoAck xpending
cli.XPending("stream_key", "FreeRedis__group", "-", "+", 10);

lpush + blpop:

using (cli.SubscribeList("list_key", ondata)) //wait .Dispose()
{
    Console.ReadKey();
}

void ondata(string listValue) =>
    Console.WriteLine(listValue);

📃 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]);
}

📰 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]);
}

📯 GetDatabase: switch database

using (var db = cli.GetDatabase(10))
{
    db.Set("key1", 10);
    var val1 = db.Get("key1");
}

🔍 Scan

Support cluster mode

foreach (var keys in cli.Scan("*", 10, null))
{
    Console.WriteLine(string.Join(", ", keys));
}

🍡 DelayQueue

var delayQueue = cli.DelayQueue("TestDelayQueue");

//Add queue
delayQueue.Enqueue($"Execute in 5 seconds.", TimeSpan.FromSeconds(5));
delayQueue.Enqueue($"Execute in 10 seconds.", DateTime.Now.AddSeconds(10));
delayQueue.Enqueue($"Execute in 15 seconds.", DateTime.Now.AddSeconds(15));
delayQueue.Enqueue($"Execute in 20 seconds.", TimeSpan.FromSeconds(20));
delayQueue.Enqueue($"Execute in 25 seconds.", DateTime.Now.AddSeconds(25));
delayQueue.Enqueue($"Execute in 2024-07-02 14:30:15", DateTime.Parse("2024-07-02 14:30:15"));

//Consumption queue
await delayQueue.DequeueAsync(s =>
{
    output.WriteLine($"{DateTime.Now}:{s}");

    return Task.CompletedTask;
});

🐆 RediSearch

cli.FtCreate(...).Execute();
cli.FtSearch(...).Execute();
cli.FtAggregate(...).Execute();
//... or ...

[FtDocument("index_post", Prefix = "blog:post:")]
class TestDoc
{
    [FtKey]
    public int Id { get; set; }

    [FtTextField("title", Weight = 5.0)]
    public string Title { get; set; }

    [FtTextField("category")]
    public string Category { get; set; }

    [FtTextField("content", Weight = 1.0, NoIndex = true)]
    public string Content { get; set; }

    [FtTagField("tags")]
    public string[] Tags { get; set; } //or string

    [FtNumericField("views")]
    public int Views { get; set; }
}

var repo = cli.FtDocumentRepository<TestDoc>();
repo.CreateIndex();

repo.Save(new TestDoc { Id = 1, Title = "test title1 word", Category = "class 1", Content = "test content 1 suffix", Tags = "user1,user2", Views = 101 });
repo.Save(new TestDoc { Id = 2, Title = "prefix test title2", Category = "class 2", Content = "test infix content 2", Tags = "user2,user3", Views = 201 });
repo.Save(new TestDoc { Id = 3, Title = "test title3 word", Category = "class 1", Content = "test word content 3", Tags = "user2,user5", Views = 301 });

repo.Delete(1, 2, 3);

repo.Save(new[]
{
    new TestDoc { Id = 1, Title = "test title1 word", Category = "class 1", Content = "test content 1 suffix", Tags = "user1,user2", Views = 101 },
    new TestDoc { Id = 2, Title = "prefix test title2", Category = "class 2", Content = "test infix content 2", Tags = "user2,user3", Views = 201 },
    new TestDoc { Id = 3, Title = "test title3 word", Category = "class 1", Content = "test word content 3", Tags = "user2,user5", Views = 301 }
});

var list = repo.Search("*").InFields(a => new { a.Title }).ToList();
list = repo.Search("*").Return(a => new { a.Title, a.Tags }).ToList();
list = repo.Search("*").Return(a => new { tit1 = a.Title, tgs1 = a.Tags, a.Title, a.Tags }).ToList();

list = repo.Search(a => a.Title == "word" && a.Tags.Contains("user1")).Filter(a => a.Views, 1, 1000).ToList();
list = repo.Search("word").ToList();
list = repo.Search("@title:word").ToList();

👯 Contributors

💕 Donation

Thank you for your donation

🗄 License

MIT

评论

暂无评论