.NET?Core實(shí)現(xiàn)簡(jiǎn)單的Redis?Client框架
0,關(guān)于 Redis RESP
RESP 全稱 REdis Serialization Protocol ,即 Redis 序列化協(xié)議,用于協(xié)定客戶端使用 socket 連接 Redis 時(shí),數(shù)據(jù)的傳輸規(guī)則。
官方協(xié)議說明:https://redis.io/topics/protocol
那么 RESP 協(xié)議在與 Redis 通訊時(shí)的 請(qǐng)求-響應(yīng) 方式如下:
- 客戶端將命令作為 RESP 大容量字符串?dāng)?shù)組(即 C# 中使用 byte[] 存儲(chǔ)字符串命令)發(fā)送到 Redis 服務(wù)器。
- 服務(wù)器根據(jù)命令實(shí)現(xiàn)以 RESP 類型進(jìn)行回復(fù)。
RESP 中的類型并不是指 Redis 的基本數(shù)據(jù)類型,而是指數(shù)據(jù)的響應(yīng)格式:
在 RESP 中,某些數(shù)據(jù)的類型取決于第一個(gè)字節(jié):
- 對(duì)于簡(jiǎn)單字符串,答復(fù)的第一個(gè)字節(jié)為“ +”
- 對(duì)于錯(cuò)誤,回復(fù)的第一個(gè)字節(jié)為“-”
- 對(duì)于整數(shù),答復(fù)的第一個(gè)字節(jié)為“:”
- 對(duì)于批量字符串,答復(fù)的第一個(gè)字節(jié)為“ $”
- 對(duì)于數(shù)組,回復(fù)的第一個(gè)字節(jié)為“
*”
對(duì)于這些,可能初學(xué)者不太了解,下面我們來實(shí)際操作一下。
我們打開 Redis Desktop Manager ,然后點(diǎn)擊控制臺(tái),輸入:
set a 12 set b 12 set c 12 MGET abc
以上命令每行按一下回車鍵。MGET 是 Redis 中一次性取出多個(gè)鍵的值的命令。
輸出結(jié)果如下:
本地:0>SET a 12 "OK" 本地:0>SET b 12 "OK" 本地:0>SET c 12 "OK" 本地:0>MGET a b c 1) "12" 2) "12" 3) "12"
但是這個(gè)管理工具以及去掉了 RESP 中的協(xié)議標(biāo)識(shí)符,我們來寫一個(gè) demo 代碼,還原 RESP 的本質(zhì)。
using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp
{
class Program
{
static async Task Main(string[] args)
{
IPAddress IP = IPAddress.Parse("127.0.0.1");
IPEndPoint IPEndPoint = new IPEndPoint(IP, 6379);
Socket client = new Socket(IP.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
await client.ConnectAsync(IPEndPoint);
if (!client.Connected)
{
Console.WriteLine("連接 Redis 服務(wù)器失敗!");
Console.Read();
}
Console.WriteLine("恭喜恭喜,連接 Redis 服務(wù)器成功");
// 后臺(tái)接收消息
new Thread(() =>
{
while (true)
{
byte[] data = new byte[100];
int size = client.Receive(data);
Console.WriteLine();
Console.WriteLine(Encoding.UTF8.GetString(data));
Console.WriteLine();
}
}).Start();
while (true)
{
Console.Write("$> ");
string command = Console.ReadLine();
// 發(fā)送的命令必須以 \r\n 結(jié)尾
int size = client.Send(Encoding.UTF8.GetBytes(command + "\r\n"));
Thread.Sleep(100);
}
}
}
}輸入以及輸出結(jié)果:
$> SET a 123456789 +OK $> SET b 123456789 +OK $> SET c 123456789 +OK $> MGET a b c *3 $9 123456789 $9 123456789 $9 123456789
可見,Redis 響應(yīng)的消息內(nèi)容,是以 $、*、+ 等字符開頭的,并且使用 \r\n 分隔。
我們寫 Redis Client 的方法就是接收 socket 內(nèi)容,然后從中解析出實(shí)際的數(shù)據(jù)。
每次發(fā)送設(shè)置命令成功,都會(huì)返回 +OK;*3 表示有三個(gè)數(shù)組;$9 表示接收的數(shù)據(jù)長(zhǎng)度是 9;
大概就是這樣了,下面我們來寫一個(gè)簡(jiǎn)單的 Redis Client 框架,然后睡覺。
記得使用 netstandard2.1,因?yàn)橛行?byte[] 、string、ReadOnlySpan<T> 的轉(zhuǎn)換,需要 netstandard2.1 才能更加方便。
1,定義數(shù)據(jù)類型
根據(jù)前面的 demo,我們來定義一個(gè)類型,存儲(chǔ)那些特殊符號(hào):
/// <summary>
/// RESP Response 類型
/// </summary>
public static class RedisValueType
{
public const byte Errors = (byte)'-';
public const byte SimpleStrings = (byte)'+';
public const byte Integers = (byte)':';
public const byte BulkStrings = (byte)'$';
public const byte Arrays = (byte)'*';
public const byte R = (byte)'\r';
public const byte N = (byte)'\n';
}2,定義異步消息狀態(tài)機(jī)
創(chuàng)建一個(gè) MessageStrace 類,作用是作為消息響應(yīng)的異步狀態(tài)機(jī),并且具有解析數(shù)據(jù)流的功能。
/// <summary>
/// 自定義消息隊(duì)列狀態(tài)機(jī)
/// </summary>
public abstract class MessageStrace
{
protected MessageStrace()
{
TaskCompletionSource = new TaskCompletionSource<string>();
Task = TaskCompletionSource.Task;
}
protected readonly TaskCompletionSource<string> TaskCompletionSource;
/// <summary>
/// 標(biāo)志任務(wù)是否完成,并接收 redis 響應(yīng)的字符串?dāng)?shù)據(jù)流
/// </summary>
public Task<string> Task { get; private set; }
/// <summary>
/// 接收數(shù)據(jù)流
/// </summary>
/// <param name="stream"></param>
/// <param name="length">實(shí)際長(zhǎng)度</param>
public abstract void Receive(MemoryStream stream, int length);
/// <summary>
/// 響應(yīng)已經(jīng)完成
/// </summary>
/// <param name="data"></param>
protected void SetValue(string data)
{
TaskCompletionSource.SetResult(data);
}
/// <summary>
/// 解析 $ 或 * 符號(hào)后的數(shù)字,必須傳遞符后后一位的下標(biāo)
/// </summary>
/// <param name="data"></param>
/// <param name="index">解析到的位置</param>
/// <returns></returns>
protected int BulkStrings(ReadOnlySpan<byte> data, ref int index)
{
int start = index;
int end = start;
while (true)
{
if (index + 1 >= data.Length)
throw new ArgumentOutOfRangeException("溢出");
// \r\n
if (data[index].CompareTo(RedisValueType.R) == 0 && data[index + 1].CompareTo(RedisValueType.N) == 0)
{
index += 2; // 指向 \n 的下一位
break;
}
end++;
index++;
}
// 截取 $2 *3 符號(hào)后面的數(shù)字
return Convert.ToInt32(Encoding.UTF8.GetString(data.Slice(start, end - start).ToArray()));
}
}3,定義命令發(fā)送模板
由于 Redis 命令非常多,為了更加好的封裝,我們定義一個(gè)消息發(fā)送模板,規(guī)定五種類型分別使用五種類型發(fā)送 Client。
定義一個(gè)統(tǒng)一的模板類:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace CZGL.RedisClient
{
/// <summary>
/// 命令發(fā)送模板
/// </summary>
public abstract class CommandClient<T> where T : CommandClient<T>
{
protected RedisClient _client;
protected CommandClient()
{
}
protected CommandClient(RedisClient client)
{
_client = client;
}
/// <summary>
/// 復(fù)用
/// </summary>
/// <param name="client"></param>
/// <returns></returns>
internal virtual CommandClient<T> Init(RedisClient client)
{
_client = client;
return this;
}
/// <summary>
/// 請(qǐng)求是否成功
/// </summary>
/// <param name="value">響應(yīng)的消息</param>
/// <returns></returns>
protected bool IsOk(string value)
{
if (value[0].CompareTo('+') != 0 || value[1].CompareTo('O') != 0 || value[2].CompareTo('K') != 0)
return false;
return true;
}
/// <summary>
/// 發(fā)送命令
/// </summary>
/// <param name="command">發(fā)送的命令</param>
/// <param name="strace">數(shù)據(jù)類型客戶端</param>
/// <returns></returns>
protected Task SendCommand<TStrace>(string command, out TStrace strace) where TStrace : MessageStrace, new()
{
strace = new TStrace();
return _client.SendAsync(strace, command);
}
}
}4,定義 Redis Client
RedisClient 類用于發(fā)送 Redis 命令,然后將任務(wù)放到隊(duì)列中;接收 Redis 返回的數(shù)據(jù)內(nèi)容,并將數(shù)據(jù)流寫入內(nèi)存中,調(diào)出隊(duì)列,設(shè)置異步任務(wù)的返回值。
Send 過程可以并發(fā),但是接收消息內(nèi)容使用單線程。為了保證消息的順序性,采用隊(duì)列來記錄 Send - Receive 的順序。
C# 的 Socket 比較操蛋,想搞并發(fā)和高性能 Socket 不是那么容易。
以下代碼有三個(gè)地方注釋了,后面繼續(xù)編寫其它代碼會(huì)用到。
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace CZGL.RedisClient
{
/// <summary>
/// Redis 客戶端
/// </summary>
public class RedisClient
{
private readonly IPAddress IP;
private readonly IPEndPoint IPEndPoint;
private readonly Socket client;
//private readonly Lazy<StringClient> stringClient;
//private readonly Lazy<HashClient> hashClient;
//private readonly Lazy<ListClient> listClient;
//private readonly Lazy<SetClient> setClient;
//private readonly Lazy<SortedClient> sortedClient;
// 數(shù)據(jù)流請(qǐng)求隊(duì)列
private readonly ConcurrentQueue<MessageStrace> StringTaskQueue = new ConcurrentQueue<MessageStrace>();
public RedisClient(string ip, int port)
{
IP = IPAddress.Parse(ip);
IPEndPoint = new IPEndPoint(IP, port);
//stringClient = new Lazy<StringClient>(() => new StringClient(this));
//hashClient = new Lazy<HashClient>(() => new HashClient(this));
//listClient = new Lazy<ListClient>(() => new ListClient(this));
//setClient = new Lazy<SetClient>(() => new SetClient(this));
//sortedClient = new Lazy<SortedClient>(() => new SortedClient(this));
client = new Socket(IP.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
}
/// <summary>
/// 開始連接 Redis
/// </summary>
public async Task<bool> ConnectAsync()
{
await client.ConnectAsync(IPEndPoint);
new Thread(() => { ReceiveQueue(); })
{
IsBackground = true
}.Start();
return client.Connected;
}
/// <summary>
/// 發(fā)送一個(gè)命令,將其加入隊(duì)列
/// </summary>
/// <param name="task"></param>
/// <param name="command"></param>
/// <returns></returns>
internal Task<int> SendAsync(MessageStrace task, string command)
{
var buffer = Encoding.UTF8.GetBytes(command + "\r\n");
var result = client.SendAsync(new ArraySegment<byte>(buffer, 0, buffer.Length), SocketFlags.None);
StringTaskQueue.Enqueue(task);
return result;
}
/*
Microsoft 對(duì)緩沖區(qū)輸入不同大小的數(shù)據(jù),測(cè)試響應(yīng)時(shí)間。
1024 - real 0m0,102s; user 0m0,018s; sys 0m0,009s
2048 - real 0m0,112s; user 0m0,017s; sys 0m0,009s
8192 - real 0m0,163s; user 0m0,017s; sys 0m0,007s
256 - real 0m0,101s; user 0m0,019s; sys 0m0,008s
16 - real 0m0,144s; user 0m0,016s; sys 0m0,010s
.NET Socket,默認(rèn)緩沖區(qū)的大小為 8192 字節(jié)。
Socket.ReceiveBufferSize: An Int32 that contains the size, in bytes, of the receive buffer. The default is 8192.
但響應(yīng)中有很多只是 "+OK\r\n" 這樣的響應(yīng),并且 MemoryStream 剛好默認(rèn)是 256(當(dāng)然,可以自己設(shè)置大小),緩沖區(qū)過大,浪費(fèi)內(nèi)存;
超過 256 這個(gè)大小,MemoryStream 會(huì)繼續(xù)分配新的 256 大小的內(nèi)存區(qū)域,會(huì)消耗性能。
BufferSize 設(shè)置為 256 ,是比較合適的做法。
*/
private const int BufferSize = 256;
/// <summary>
/// 單線程串行接收數(shù)據(jù)流,調(diào)出任務(wù)隊(duì)列完成任務(wù)
/// </summary>
private void ReceiveQueue()
{
while (true)
{
MemoryStream stream = new MemoryStream(BufferSize); // 內(nèi)存緩存區(qū)
byte[] data = new byte[BufferSize]; // 分片,每次接收 N 個(gè)字節(jié)
int size = client.Receive(data); // 等待接收一個(gè)消息
int length = size; // 數(shù)據(jù)流總長(zhǎng)度
while (true)
{
stream.Write(data, 0, size); // 分片接收的數(shù)據(jù)流寫入內(nèi)存緩沖區(qū)
// 數(shù)據(jù)流接收完畢
if (size < BufferSize) // 存在 Bug ,當(dāng)數(shù)據(jù)流的大小或者數(shù)據(jù)流分片最后一片的字節(jié)大小剛剛好為 BufferSize 大小時(shí),無法跳出 Receive
{
break;
}
length += client.Receive(data); // 還沒有接收完畢,繼續(xù)接收
}
stream.Seek(0, SeekOrigin.Begin); // 重置游標(biāo)位置
// 調(diào)出隊(duì)列
StringTaskQueue.TryDequeue(out var tmpResult);
// 處理隊(duì)列中的任務(wù)
tmpResult.Receive(stream, length);
}
}
/// <summary>
/// 復(fù)用
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="client"></param>
/// <returns></returns>
public T GetClient<T>(T client) where T : CommandClient<T>
{
client.Init(this);
return client;
}
///// <summary>
///// 獲取字符串請(qǐng)求客戶端
///// </summary>
///// <returns></returns>
//public StringClient GetStringClient()
//{
// return stringClient.Value;
//}
//public HashClient GetHashClient()
//{
// return hashClient.Value;
//}
//public ListClient GetListClient()
//{
// return listClient.Value;
//}
//public SetClient GetSetClient()
//{
// return setClient.Value;
//}
//public SortedClient GetSortedClient()
//{
// return sortedClient.Value;
//}
}
}5,實(shí)現(xiàn)簡(jiǎn)單的 RESP 解析
下面使用代碼來實(shí)現(xiàn)對(duì) Redis RESP 消息的解析,時(shí)間問題,我只實(shí)現(xiàn) +、-、$、* 四個(gè)符號(hào)的解析,其它符號(hào)可以自行參考完善。
創(chuàng)建一個(gè) MessageStraceAnalysis`.cs ,其代碼如下:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace CZGL.RedisClient
{
/// <summary>
/// RESP 解析數(shù)據(jù)流
/// </summary>
public class MessageStraceAnalysis<T> : MessageStrace
{
public MessageStraceAnalysis()
{
}
/// <summary>
/// 解析協(xié)議
/// </summary>
/// <param name="data"></param>
public override void Receive(MemoryStream stream, int length)
{
byte firstChar = (byte)stream.ReadByte(); // 首位字符,由于游標(biāo)已經(jīng)到 1,所以后面 .GetBuffer(),都是從1開始截?cái)啵孜蛔址釛?
if (firstChar.CompareTo(RedisValueType.SimpleStrings) == 0) // 簡(jiǎn)單字符串
{
SetValue(Encoding.UTF8.GetString(stream.GetBuffer()));
return;
}
else if (firstChar.CompareTo(RedisValueType.Errors) == 0)
{
TaskCompletionSource.SetException(new InvalidOperationException(Encoding.UTF8.GetString(stream.GetBuffer())));
return;
}
// 不是 + 和 - 開頭
stream.Position = 0;
int index = 0;
ReadOnlySpan<byte> data = new ReadOnlySpan<byte>(stream.GetBuffer());
string tmp = Analysis(data, ref index);
SetValue(tmp);
}
// 進(jìn)入遞歸處理流程
private string Analysis(ReadOnlySpan<byte> data, ref int index)
{
// *
if (data[index].CompareTo(RedisValueType.Arrays) == 0)
{
string value = default;
index++;
int size = BulkStrings(data, ref index);
if (size == 0)
return string.Empty;
else if (size == -1)
return null;
for (int i = 0; i < size; i++)
{
var tmp = Analysis(data, ref index);
value += tmp + ((i < (size - 1)) ? "\r\n" : string.Empty);
}
return value;
}
// $..
else if (data[index].CompareTo(RedisValueType.BulkStrings) == 0)
{
index++;
int size = BulkStrings(data, ref index);
if (size == 0)
return string.Empty;
else if (size == -1)
return null;
var value = Encoding.UTF8.GetString(data.Slice(index, size).ToArray());
index += size + 2; // 脫離之前,將指針移動(dòng)到 \n 后
return value;
}
throw new ArgumentException("解析錯(cuò)誤");
}
}
}6,實(shí)現(xiàn)命令發(fā)送客戶端
由于 Redis 命令太多,如果直接將所有命令封裝到 RedisClient 中,必定使得 API 過的,而且代碼難以維護(hù)。因此,我們可以拆分,根據(jù) string、hash、set 等 redis 類型,來設(shè)計(jì)客戶端。
下面來設(shè)計(jì)一個(gè) StringClient:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CZGL.RedisClient
{
/// <summary>
/// 字符串類型
/// </summary>
public class StringClient : CommandClient<StringClient>
{
internal StringClient()
{
}
internal StringClient(RedisClient client) : base(client)
{
}
/// <summary>
/// 設(shè)置鍵值
/// </summary>
/// <param name="key">key</param>
/// <param name="value">value</param>
/// <returns></returns>
public async Task<bool> Set(string key, string value)
{
await SendCommand<MessageStraceAnalysis<string>>($"{StringCommand.SET} {key} {value}", out MessageStraceAnalysis<string> strace);
var result = await strace.Task;
return IsOk(result);
}
/// <summary>
/// 獲取一個(gè)鍵的值
/// </summary>
/// <param name="key">鍵</param>
/// <returns></returns>
public async Task<string> Get(string key)
{
await SendCommand($"{StringCommand.GET} {key}", out MessageStraceAnalysis<string> strace);
var result = await strace.Task;
return result;
}
/// <summary>
/// 從指定鍵的值中截取指定長(zhǎng)度的數(shù)據(jù)
/// </summary>
/// <param name="key">key</param>
/// <param name="start">開始下標(biāo)</param>
/// <param name="end">結(jié)束下標(biāo)</param>
/// <returns></returns>
public async Task<string> GetRance(string key, uint start, int end)
{
await SendCommand($"{StringCommand.GETRANGE} {key} {start} {end}", out MessageStraceAnalysis<string> strace);
var result = await strace.Task;
return result;
}
/// <summary>
/// 設(shè)置一個(gè)值并返回舊的值
/// </summary>
/// <param name="key"></param>
/// <param name="newValue"></param>
/// <returns></returns>
public async Task<string> GetSet(string key, string newValue)
{
await SendCommand($"{StringCommand.GETSET} {key} {newValue}", out MessageStraceAnalysis<string> strace);
var result = await strace.Task;
return result;
}
/// <summary>
/// 獲取二進(jìn)制數(shù)據(jù)中某一位的值
/// </summary>
/// <param name="key"></param>
/// <param name="index"></param>
/// <returns>0 或 1</returns>
public async Task<int> GetBit(string key, uint index)
{
await SendCommand($"{StringCommand.GETBIT} {key} {index}", out MessageStraceAnalysis<string> strace);
var result = await strace.Task;
return Convert.ToInt32(result);
}
/// <summary>
/// 設(shè)置某一位為 1 或 0
/// </summary>
/// <param name="key"></param>
/// <param name="index"></param>
/// <param name="value">0或1</param>
/// <returns></returns>
public async Task<bool> SetBit(string key, uint index, uint value)
{
await SendCommand($"{StringCommand.SETBIT} {key} {index} {value}", out MessageStraceAnalysis<string> strace);
var result = await strace.Task;
return IsOk(result);
}
/// <summary>
/// 獲取多個(gè)鍵的值
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public async Task<string[]> MGet(params string[] key)
{
await SendCommand($"{StringCommand.MGET} {string.Join(" ", key)}", out MessageStraceAnalysis<string> strace);
var result = await strace.Task;
return result.Split("\r\n");
}
private static class StringCommand
{
public const string SET = "SET";
public const string GET = "GET";
public const string GETRANGE = "GETRANGE";
public const string GETSET = "GETSET";
public const string GETBIT = "GETBIT";
public const string SETBIT = "SETBIT";
public const string MGET = "MGET";
// ... ... 更多 字符串的命令
}
}
}StringClient 實(shí)現(xiàn)了 7個(gè) Redis String 類型的命令,其它命令觸類旁通。
我們打開 RedisClient.cs,解除以下部分代碼的注釋:
private readonly Lazy<StringClient> stringClient; // 24 行
stringClient = new Lazy<StringClient>(() => new StringClient(this)); // 38 行
// 146 行
/// <summary>
/// 獲取字符串請(qǐng)求客戶端
/// </summary>
/// <returns></returns>
public StringClient GetStringClient()
{
return stringClient.Value;
}7,如何使用
RedisClient 使用示例:
static async Task Main(string[] args)
{
RedisClient client = new RedisClient("127.0.0.1", 6379);
var a = await client.ConnectAsync();
if (!a)
{
Console.WriteLine("連接服務(wù)器失敗");
Console.ReadKey();
return;
}
Console.WriteLine("連接服務(wù)器成功");
var stringClient = client.GetStringClient();
var result = await stringClient.Set("a", "123456789");
Console.Read();
}封裝的消息命令支持異步。
8,更多客戶端
光 String 類型不過癮,我們繼續(xù)封裝更多的客戶端。
哈希:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CZGL.RedisClient
{
public class HashClient : CommandClient<HashClient>
{
internal HashClient(RedisClient client) : base(client)
{
}
/// <summary>
/// 設(shè)置哈希
/// </summary>
/// <param name="key">鍵</param>
/// <param name="values">字段-值列表</param>
/// <returns></returns>
public async Task<bool> HmSet(string key, Dictionary<string, string> values)
{
await SendCommand($"{StringCommand.HMSET} {key} {string.Join(" ", values.Select(x => $"{x.Key} {x.Value}").ToArray())})", out MessageStraceAnalysis<string> strace);
var result = await strace.Task;
return IsOk(result);
}
public async Task<bool> HmSet<T>(string key, T values)
{
Dictionary<string, string> dic = new Dictionary<string, string>();
foreach (var item in typeof(T).GetProperties())
{
dic.Add(item.Name, (string)item.GetValue(values));
}
await SendCommand($"{StringCommand.HMSET} {key} {string.Join(" ", dic.Select(x => $"{x.Key} {x.Value}").ToArray())})", out MessageStraceAnalysis<string> strace);
var result = await strace.Task;
return IsOk(result);
}
public async Task<object> HmGet(string key, string field)
{
await SendCommand($"{StringCommand.HMGET} {key} {field}", out MessageStraceAnalysis<string> strace);
var result = await strace.Task;
return IsOk(result);
}
private static class StringCommand
{
public const string HMSET = "HMSET ";
public const string HMGET = "HMGET";
// ... ... 更多 字符串的命令
}
}
}列表:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace CZGL.RedisClient
{
public class ListClient : CommandClient<ListClient>
{
internal ListClient(RedisClient client) : base(client)
{
}
/// <summary>
/// 設(shè)置鍵值
/// </summary>
/// <param name="key">key</param>
/// <param name="value">value</param>
/// <returns></returns>
public async Task<bool> LPush(string key, string value)
{
await SendCommand($"{StringCommand.LPUSH} {key} {value}", out MessageStraceAnalysis<string> strace);
var result = await strace.Task;
return IsOk(result);
}
public async Task<string> LRange(string key, int start, int end)
{
await SendCommand($"{StringCommand.LRANGE} {key} {start} {end}", out MessageStraceAnalysis<string> strace);
var result = await strace.Task;
return result;
}
private static class StringCommand
{
public const string LPUSH = "LPUSH";
public const string LRANGE = "LRANGE";
// ... ... 更多 字符串的命令
}
}
}集合:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace CZGL.RedisClient
{
public class SetClient : CommandClient<SetClient>
{
internal SetClient() { }
internal SetClient(RedisClient client) : base(client)
{
}
public async Task<bool> SAdd(string key, string value)
{
await SendCommand($"{StringCommand.SADD} {key} {value}", out MessageStraceAnalysis<string> strace);
var result = await strace.Task;
return IsOk(result);
}
public async Task<string> SMembers(string key)
{
await SendCommand($"{StringCommand.SMEMBERS} {key}", out MessageStraceAnalysis<string> strace);
var result = await strace.Task;
return result;
}
private static class StringCommand
{
public const string SADD = "SADD";
public const string SMEMBERS = "SMEMBERS";
// ... ... 更多 字符串的命令
}
}
}有序集合:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace CZGL.RedisClient
{
public class SortedClient : CommandClient<SortedClient>
{
internal SortedClient(RedisClient client) : base(client)
{
}
public async Task<bool> ZAdd(string key, string value)
{
await SendCommand($"{StringCommand.ZADD} {key} {value}", out MessageStraceAnalysis<string> strace);
var result = await strace.Task;
return IsOk(result);
}
private static class StringCommand
{
public const string ZADD = "ZADD";
public const string SMEMBERS = "SMEMBERS";
// ... ... 更多 字符串的命令
}
}
}這樣,我們就有一個(gè)具有簡(jiǎn)單功能的 RedisClient 框架了。
9,更多測(cè)試
為了驗(yàn)證功能是否可用,我們寫一些示例:
static RedisClient client = new RedisClient("127.0.0.1", 6379);
static async Task Main(string[] args)
{
var a = await client.ConnectAsync();
if (!a)
{
Console.WriteLine("連接服務(wù)器失敗");
Console.ReadKey();
return;
}
Console.WriteLine("連接服務(wù)器成功");
await StringSETGET();
await StringGETRANGE();
await StringGETSET();
await StringMGet();
Console.ReadKey();
}
static async Task StringSETGET()
{
var stringClient = client.GetStringClient();
var b = await stringClient.Set("seta", "6666");
var c = await stringClient.Get("seta");
if (c == "6666")
{
Console.WriteLine("true");
}
}
static async Task StringGETRANGE()
{
var stringClient = client.GetStringClient();
var b = await stringClient.Set("getrance", "123456789");
var c = await stringClient.GetRance("getrance", 0, -1);
if (c == "123456789")
{
Console.WriteLine("true");
}
var d = await stringClient.GetRance("getrance", 0, 3);
if (d == "1234")
{
Console.WriteLine("true");
}
}
static async Task StringGETSET()
{
var stringClient = client.GetStringClient();
var b = await stringClient.Set("getrance", "123456789");
var c = await stringClient.GetSet("getrance", "987654321");
if (c == "123456789")
{
Console.WriteLine("true");
}
}
static async Task StringMGet()
{
var stringClient = client.GetStringClient();
var a = await stringClient.Set("stra", "123456789");
var b = await stringClient.Set("strb", "123456789");
var c = await stringClient.Set("strc", "123456789");
var d = await stringClient.MGet("stra", "strb", "strc");
if (d.Where(x => x == "123456789").Count() == 3)
{
Console.WriteLine("true");
}
}10,性能測(cè)試
因?yàn)橹皇菍懙帽容^簡(jiǎn)單,而且是單線程,并且內(nèi)存比較浪費(fèi),我覺得性能會(huì)比較差。但真相如何呢?我們來測(cè)試一下:
static RedisClient client = new RedisClient("127.0.0.1", 6379);
static async Task Main(string[] args)
{
var a = await client.ConnectAsync();
if (!a)
{
Console.WriteLine("連接服務(wù)器失敗");
Console.ReadKey();
return;
}
Console.WriteLine("連接服務(wù)器成功");
var stringClient = client.GetStringClient();
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 3000; i++)
{
var guid = Guid.NewGuid().ToString();
_ = await stringClient.Set(guid, guid);
_ = await stringClient.Get(guid);
}
watch.Stop();
Console.WriteLine($"總共耗時(shí):{watch.ElapsedMilliseconds} ms");
Console.ReadKey();
}耗時(shí):
總共耗時(shí):1003 ms
大概就是 1s,3000 個(gè) SET 和 3000 個(gè) GET 共 6000 個(gè)請(qǐng)求。看來單線程性能也是很強(qiáng)的。
本文教程源碼 Github 地址:https://github.com/whuanle/RedisClientLearn
以上所述是小編給大家介紹的.NET Core實(shí)現(xiàn)簡(jiǎn)單的Redis Client框架,希望對(duì)大家有所幫助。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- Redis數(shù)據(jù)庫基礎(chǔ)與ASP.NET?Core緩存實(shí)現(xiàn)
- ASP.NET?Core擴(kuò)展庫ServiceStack.Redis用法介紹
- Asp.net core中RedisMQ的簡(jiǎn)單應(yīng)用實(shí)現(xiàn)
- 詳解ASP.Net Core 中如何借助CSRedis實(shí)現(xiàn)一個(gè)安全高效的分布式鎖
- .NET Core中使用Redis與Memcached的序列化問題詳析
- .net core如何使用Redis發(fā)布訂閱
- .net core使用redis基于StackExchange.Redis
- 詳解Asp.net Core 使用Redis存儲(chǔ)Session
- 詳解如何在ASP.NET Core中使用Redis
相關(guān)文章
.Net Core項(xiàng)目中NLog整合Exceptionless實(shí)例
這篇文章主要介紹了.Net Core項(xiàng)目中NLog整合Exceptionless實(shí)例,NLog主要是收集程序中的日志,Exceptionless可以統(tǒng)一收集管理并展示出來程序的日志,兩者結(jié)合使用,相得益彰。感興趣的小伙伴可以參考這篇文章2021-09-09
ASP.NET實(shí)現(xiàn)推送文件到瀏覽器的方法
這篇文章主要介紹了ASP.NET實(shí)現(xiàn)推送文件到瀏覽器的方法,可實(shí)現(xiàn)將文件推送到瀏覽器供用戶瀏覽或下載的功能,需要的朋友可以參考下2015-06-06
DataGridView中綁定DataTable數(shù)據(jù)及相關(guān)操作實(shí)現(xiàn)代碼
DataGridView中綁定DataTable數(shù)據(jù)及相關(guān)操作2010-02-02
Asp.net FCKEditor 2.6.3 上傳文件沒有權(quán)限解決方法
到Fckeditor官方網(wǎng)站下載FredCK.FCKeditorV2.vs2005 (asp.net)2009-02-02
.NET6?ConfigurationManager的實(shí)現(xiàn)及使用方式
這篇文章主要介紹了.NET6?ConfigurationManager的實(shí)現(xiàn),我們上面展示的這一部分的ConfigurationManager代碼,其實(shí)就是替代了原來的ConfigurationBuilder類的功能,需要的朋友可以參考下2021-12-12

