编程知识 cdmana.com

Teach you to write a simple redis Client Framework -. Net core

Recently, boss Ye wrote a FreeRedis, It's powerful , Amazing performance , Faster than a bullet , More powerful than an engine , Just a while ago I was learning Redis, So try to follow the trend and write a simple RedisClient.

FreeRedis Project address :https://github.com/2881099/FreeRedis

This article tutorial source code Github Address :https://github.com/whuanle/RedisClientLearn

Because the code is simple , Don't think about too many features , Password login is not supported ; No clustering support ; Concurrency is not supported ;

First of all, install it on your own computer redis,Windows Version download address :https://github.com/MicrosoftArchive/redis/releases

Then download Windows Version of Redis Manager

Windows Version of Redis Desktop Manager 64 position 2019.1( Chinese version ) Download address https://www.7down.com/soft/233274.html

Download the latest version of the official legitimate version https://redisdesktop.com/download

0, About Redis RESP

RESP Full name REdis Serialization Protocol , namely Redis Serialization protocol , For protocol client use socket Connect Redis when , Data transmission rules .

The official agreement says :https://redis.io/topics/protocol

that RESP The agreement is with Redis In communication request - Respond to The way is as follows :

  • The client will command As RESP Large array of strings ( namely C# Use in byte[] Store string command ) Send to Redis The server .
  • The server is implemented according to the command RESP type Reply .

RESP The type in does not refer to Redis Basic data types for , It's about the response format of the data :

stay RESP in , The type of some data depends on the first byte :

  • about Simple string , The first byte of the reply is “ +”
  • about error , The first byte of the reply is “-”
  • about Integers , The first byte of the reply is “:”
  • about Batch string , The first byte of the reply is “ $”
  • about Array , The first byte of the reply is “ *

For these , Maybe beginners don't know much about , Now let's do it in practice .

We turn on Redis Desktop Manager , And then click on the console , Input :

set a 12
set b 12
set c 12
MGET abc

Press enter on each line of the above command .MGET yes Redis The command to retrieve the values of multiple keys at once .

The output is as follows :

 Local :0>SET a 12
"OK"
 Local :0>SET b 12
"OK"
 Local :0>SET c 12
"OK"
 Local :0>MGET a b c
 1)  "12"
 2)  "12"
 3)  "12"

But this management tool and removed RESP Protocol identifier in , Let's write a demo Code , Restore RESP The essence of .

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(" Connect  Redis  Server failed !");
                Console.Read();
            }

            Console.WriteLine(" Congratulations , Connect  Redis  Server success ");


            //  Receiving messages in the background 
            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();
                //  The order sent must be in the form of  \r\n  ending 
                int size = client.Send(Encoding.UTF8.GetBytes(command + "\r\n"));
                Thread.Sleep(100);
            }
        }
    }
}

Input and output results :

$> SET a 123456789
+OK
$> SET b 123456789
+OK
$> SET c 123456789
+OK
$> MGET a b c

*3
$9
123456789
$9
123456789
$9
123456789

so ,Redis The message content of the response , In order to $、*、+ At the beginning of a character , And use \r\n Separate .

We write Redis Client The way to do this is to receive socket Content , And then we can parse out the actual data .

Each time the setting command is sent successfully , Will return to +OK;*3 Indicates that there are three arrays ;$9 Indicates that the received data length is 9;

That's about it , Let's write a simple Redis Client frame , Then go to bed .

Remember to use netstandard2.1, Because there are some byte[] 、string、ReadOnlySpan<T> Transformation , need netstandard2.1 To be more convenient .

1, Define data types

According to the preceding demo, Let's define a type , Store those special symbols :

    /// <summary>
    /// RESP Response  type 
    /// </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, Define an asynchronous message state machine

Create a MessageStrace class , It works as an asynchronous state machine in response to a message , And it has the function of parsing data stream .

    /// <summary>
    ///  Custom message queuing state machine 
    /// </summary>
    public abstract class MessageStrace
    {
        protected MessageStrace()
        {
            TaskCompletionSource = new TaskCompletionSource<string>();
            Task = TaskCompletionSource.Task;
        }

        protected readonly TaskCompletionSource<string> TaskCompletionSource;

        /// <summary>
        ///  Mark whether the task is completed , And receive  redis  Response string data stream 
        /// </summary>
        public Task<string> Task { get; private set; }

        /// <summary>
        ///  Receiving data streams 
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="length"> Actual length </param>
        public abstract void Receive(MemoryStream stream, int length);

        /// <summary>
        ///  The response has been completed 
        /// </summary>
        /// <param name="data"></param>
        protected void SetValue(string data)
        {
            TaskCompletionSource.SetResult(data);
        }


        /// <summary>
        ///  analysis  $  or  *  The number after the symbol , The subscript of the last digit after the character must be passed 
        /// </summary>
        /// <param name="data"></param>
        /// <param name="index"> Resolved to the location </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(" overflow ");

                // \r\n
                if (data[index].CompareTo(RedisValueType.R) == 0 && data[index + 1].CompareTo(RedisValueType.N) == 0)
                {
                    index += 2;     //  Point to  \n  Next to 
                    break;
                }
                end++;
                index++;
            }

            //  Intercept  $2    *3   The number after the symbol 
            return Convert.ToInt32(Encoding.UTF8.GetString(data.Slice(start, end - start).ToArray()));
        }
    }

3, Define command sending template

because Redis There are so many orders , For better packaging , We define a message sending template , Five types are specified to be sent with five types respectively Client.

Define a unified template class :

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace CZGL.RedisClient
{
    /// <summary>
    ///  Command send template 
    /// </summary>
    public abstract class CommandClient<T> where T : CommandClient<T>
    {
        protected RedisClient _client;
        protected CommandClient()
        {

        }
        protected CommandClient(RedisClient client)
        {
            _client = client;
        }

        /// <summary>
        ///  Reuse 
        /// </summary>
        /// <param name="client"></param>
        /// <returns></returns>
        internal virtual CommandClient<T> Init(RedisClient client)
        {
            _client = client;
            return this;
        }


        /// <summary>
        ///  Is the request successful 
        /// </summary>
        /// <param name="value"> Response message </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>
        ///  dispatch orders 
        /// </summary>
        /// <param name="command"> Orders sent </param>
        /// <param name="strace"> Data type client </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, Definition Redis Client

RedisClient Class is used to send Redis command , Then put the task in the queue ; receive Redis Data content returned , And write the data stream to memory , Call up the queue , Set the return value of the asynchronous task .

Send Processes can be concurrent , But receiving message content uses a single thread . To ensure the sequence of messages , Using queues to record Send - Receive The order of .

C# Of Socket Comparison of egg exercises , Want to do concurrency and high performance Socket It's not that easy .

The following code is annotated in three places , I'll use it later to write other code .

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  client 
    /// </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;

        //  Data flow request queue 
        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>
        ///  Start connecting  Redis
        /// </summary>
        public async Task<bool> ConnectAsync()
        {
            await client.ConnectAsync(IPEndPoint);
            new Thread(() => { ReceiveQueue(); })
            {
                IsBackground = true
            }.Start();
            return client.Connected;
        }

        /// <summary>
        ///  Send a command , Add it to the queue 
        /// </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  Input different sizes of data into the buffer , Test response time .

        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, The default buffer size is  8192  byte .
        Socket.ReceiveBufferSize: An Int32 that contains the size, in bytes, of the receive buffer. The default is 8192.
        

         But a lot of the response was just  "+OK\r\n"  Such a response , also  MemoryStream  The default is  256( Of course , You can set your own size ), The buffer is too large , Waste of memory ;
         exceed  256  This size ,MemoryStream  Will continue to allocate new  256  The size of the memory area , Will consume performance .
        BufferSize  Set to  256 , It's a better way to do it .
         */

        private const int BufferSize = 256;

        /// <summary>
        ///  Single thread serial receive data stream , Call up the task queue to complete the task 
        /// </summary>
        private void ReceiveQueue()
        {
            while (true)
            {
                MemoryStream stream = new MemoryStream(BufferSize);  //  Memory cache 

                byte[] data = new byte[BufferSize];        //  Fragmentation , Every time I receive  N  Bytes 

                int size = client.Receive(data);           //  Waiting to receive a message 
                int length = size;                         //  Total length of data stream 

                while (true)
                {
                    stream.Write(data, 0, size);            //  The data stream received by fragmentation is written to the memory buffer 

                    //  Data stream received 
                    if (size < BufferSize)      //  There is  Bug , When the size of the data stream or the byte size of the last slice of the data stream is just as good as  BufferSize  Big hour , Can't jump out of  Receive
                    {
                        break;
                    }

                    length += client.Receive(data);       //  It's not finished yet , Continue to receive 
                }

                stream.Seek(0, SeekOrigin.Begin);         //  Reset cursor position 

                //  Call up the queue 
                StringTaskQueue.TryDequeue(out var tmpResult);

                //  Process the tasks in the queue 
                tmpResult.Receive(stream, length);
            }
        }

        /// <summary>
        ///  Reuse 
        /// </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>
        /////  Get string request client 
        ///// </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, Simple implementation RESP analysis

The following use of code to achieve the Redis RESP Message parsing , Time issues , I only realize +、-、$、* The analysis of the four symbols , Other symbols can be improved by reference .

Create a MessageStraceAnalysis`.cs , The code is as follows :

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace CZGL.RedisClient
{
    /// <summary>
    /// RESP  Parse data flow 
    /// </summary>
    public class MessageStraceAnalysis<T> : MessageStrace
    {
        public MessageStraceAnalysis()
        {

        }

        /// <summary>
        ///  Parsing protocols 
        /// </summary>
        /// <param name="data"></param>
        public override void Receive(MemoryStream stream, int length)
        {
            byte firstChar = (byte)stream.ReadByte(); //  First character , Because the cursor has reached  1, So behind  .GetBuffer(), from 1 Start cutting off , The first character is discarded ;

            if (firstChar.CompareTo(RedisValueType.SimpleStrings) == 0)    //  Simple string 
            {
                SetValue(Encoding.UTF8.GetString(stream.GetBuffer()));
                return;
            }

            else if (firstChar.CompareTo(RedisValueType.Errors) == 0)
            {
                TaskCompletionSource.SetException(new InvalidOperationException(Encoding.UTF8.GetString(stream.GetBuffer())));
                return;
            }

            //  No  +  and  -  start 

            stream.Position = 0;
            int index = 0;
            ReadOnlySpan<byte> data = new ReadOnlySpan<byte>(stream.GetBuffer());

            string tmp = Analysis(data, ref index);
            SetValue(tmp);
        }

        //  Enter the recursive processing flow 
        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; //  Before leaving , Move the pointer to  \n  after 
                return value;
            }

            throw new ArgumentException(" Parse error ");
        }
    }
}

6, Realize the command sending client

because Redis Too many orders , If you encapsulate all commands directly into RedisClient in , Must make API Yes , And the code is hard to maintain . therefore , We can split , according to string、hash、set etc. redis type , To design clients .

Let's design a StringClient:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CZGL.RedisClient
{
    /// <summary>
    ///  String type 
    /// </summary>
    public class StringClient : CommandClient<StringClient>
    {
        internal StringClient()
        {

        }

        internal StringClient(RedisClient client) : base(client)
        {
        }

        /// <summary>
        ///  set key value 
        /// </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>
        ///  Get the value of a key 
        /// </summary>
        /// <param name="key"> 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>
        ///  Intercepts data of specified length from the value of the specified key 
        /// </summary>
        /// <param name="key">key</param>
        /// <param name="start"> Start subscript </param>
        /// <param name="end"> End subscript </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>
        ///  Set a value and return the old value 
        /// </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>
        ///  Get the value of a bit in binary data 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="index"></param>
        /// <returns>0  or  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>
        ///  Set a bit to  1  or  0
        /// </summary>
        /// <param name="key"></param>
        /// <param name="index"></param>
        /// <param name="value">0 or 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>
        ///  Get the value of multiple keys 
        /// </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";
            // ... ...  more   String command 
        }
    }
}

StringClient Realized 7 individual Redis String Command of type , Other commands are similar .

We turn on RedisClient.cs, The following part of the release code :

private readonly Lazy<StringClient> stringClient;	// 24  That's ok 

stringClient = new Lazy<StringClient>(() => new StringClient(this));  // 38  That's ok 

         // 146  That's ok 
        /// <summary>
        ///  Get string request client 
        /// </summary>
        /// <returns></returns>
        public StringClient GetStringClient()
        {
            return stringClient.Value;
        }

7, How to use

RedisClient Examples of use :

        static async Task Main(string[] args)
        {
            RedisClient client = new RedisClient("127.0.0.1", 6379);
            var a = await client.ConnectAsync();
            if (!a)
            {
                Console.WriteLine(" Failed to connect to server ");
                Console.ReadKey();
                return;
            }

            Console.WriteLine(" Successfully connected to the server ");

            var stringClient = client.GetStringClient();
            var result = await stringClient.Set("a", "123456789");

            Console.Read();
        }

Encapsulated message commands support asynchronous .

8, More clients

light String The type is not good , We continue to package more clients .

Hash :

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>
        ///  Set hash 
        /// </summary>
        /// <param name="key"> key </param>
        /// <param name="values"> Field - List of 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";
            // ... ...  more   String command 
        }
    }
}

list :

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>
        ///  set key value 
        /// </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";
            // ... ...  more   String command 
        }
    }
}

aggregate :

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";
            // ... ...  more   String command 
        }
    }
}

Ordered set :

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";
            // ... ...  more   String command 
        }
    }
}

such , We have a simple function RedisClient The framework .

9, More tests

To verify that functionality is available , Let's write some examples :

        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(" Failed to connect to server ");
                Console.ReadKey();
                return;
            }

            Console.WriteLine(" Successfully connected to the server ");

            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, Performance testing

Because it's just easy to write , And it's single threaded , And it's a waste of memory , I think the performance will be worse . But what about the truth ? So let's test that out :

        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(" Failed to connect to server ");
                Console.ReadKey();
                return;
            }

            Console.WriteLine(" Successfully connected to the server ");

            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($" The total time is :{watch.ElapsedMilliseconds/10} ms");
            Console.ReadKey();
        }

Time consuming :

 The total time is :1003 ms

Is probably 1s,3000 individual SET and 3000 individual GET common 6000 A request . It seems that single thread performance is also very strong .

Before you know it 11 O 'clock , No more , I went to bed .

I have other Redis article :

Build distributed Redis Cluster Cluster and Redis introduction

Redis Introduction and ASP.NET Core cache

11, About NCC

.NET Core Community (.NET Central Community , abbreviation NCC) It's based on and around .NET The unofficial organization and activities of the technology stack 、 Non profit private open source community . We hope that through us NCC Community efforts , Working with open source communities for .NET Ecology infuses more energy .

Join in NCC, There's a bunch of frames inside , Teach you to write frames , Participate in open source projects , Make your contribution . Remember to join NCC yo ~

版权声明
本文为[A fool is a good worker]所创,转载请带上原文链接,感谢

Scroll to Top