From e711e751c8e50dd65edb18e5fe08a1d6a1d5ada0 Mon Sep 17 00:00:00 2001 From: 2881099 <2881099@qq.com> Date: Wed, 7 Sep 2022 23:16:55 +0800 Subject: [PATCH] v1.0.1 add Async methods --- examples/console_netcore31_vs/Program.cs | 143 +- src/FreeRedis/FreeRedis.csproj | 6 +- src/FreeRedis/FreeRedis.xml | 1314 +++++++++++++++-- .../AsyncTestCode/AsyncRedisSocket.cs | 464 +++--- src/FreeRedis/Internal/DefaultRedisSocket.cs | 65 +- src/FreeRedis/Internal/IRedisSocket.cs | 5 + src/FreeRedis/Internal/RespHelper.cs | 62 +- src/FreeRedis/Internal/RespHelperAsync.cs | 439 +++--- src/FreeRedis/RedisClient.cs | 11 +- .../RedisClient/Adapter/ClusterAdapter.cs | 97 +- .../RedisClient/Adapter/NormanAdapter.cs | 62 +- .../RedisClient/Adapter/PoolingAdapter.cs | 49 +- .../RedisClient/Adapter/SentinelAdapter.cs | 50 +- .../Adapter/SingleInsideAdapter.cs | 9 +- .../RedisClient/Adapter/SingleTempAdapter.cs | 9 +- src/FreeRedis/RedisClient/Strings.cs | 28 + .../RedisClientTests/StringsAsyncTests.cs | 483 ++++++ 17 files changed, 2537 insertions(+), 759 deletions(-) create mode 100644 test/Unit/FreeRedis.Tests/RedisClientTests/StringsAsyncTests.cs diff --git a/examples/console_netcore31_vs/Program.cs b/examples/console_netcore31_vs/Program.cs index f366eed4..9c2acd1f 100644 --- a/examples/console_netcore31_vs/Program.cs +++ b/examples/console_netcore31_vs/Program.cs @@ -56,23 +56,23 @@ static void Main(string[] args) var tasks = new List(); var results = new ConcurrentQueue(); - //cli.FlushDb(); - //results.Clear(); - //sw.Reset(); - //sw.Start(); - //for (var a = 0; a < 100000; a++) - //{ - // var tmp = Guid.NewGuid().ToString(); - // sedb.StringSet(tmp, String); - // var val = sedb.StringGet(tmp); - // if (val != String) throw new Exception("not equal"); - // results.Enqueue(val); - //} - //sw.Stop(); - //Console.WriteLine("StackExchange(0-100000): " + sw.ElapsedMilliseconds + "ms results: " + results.Count); - //tasks.Clear(); - //results.Clear(); - //cli.FlushDb(); + cli.FlushDb(); + results.Clear(); + sw.Reset(); + sw.Start(); + for (var a = 0; a < 10000; a++) + { + var tmp = Guid.NewGuid().ToString(); + sedb.StringSet(tmp, String); + var val = sedb.StringGet(tmp); + if (val != String) throw new Exception("not equal"); + results.Enqueue(val); + } + sw.Stop(); + Console.WriteLine("StackExchange(0-10000): " + sw.ElapsedMilliseconds + "ms results: " + results.Count); + tasks.Clear(); + results.Clear(); + cli.FlushDb(); //sw.Reset(); //sw.Start(); @@ -95,24 +95,24 @@ static void Main(string[] args) //results.Clear(); //cli.FlushDb(); - //sw.Reset(); - //sw.Start(); - //Task.Run(async () => - //{ - // for (var a = 0; a < 100000; a++) - // { - // var tmp = Guid.NewGuid().ToString(); - // await sedb.StringSetAsync(tmp, String); - // var val = await sedb.StringGetAsync(tmp); - // if (val != String) throw new Exception("not equal"); - // results.Enqueue(val); - // } - //}).Wait(); - //sw.Stop(); - //Console.WriteLine("StackExchangeAsync(0-100000): " + sw.ElapsedMilliseconds + "ms results: " + results.Count); - //tasks.Clear(); - //results.Clear(); - //cli.FlushDb(); + sw.Reset(); + sw.Start(); + Task.Run(async () => + { + for (var a = 0; a < 10000; a++) + { + var tmp = Guid.NewGuid().ToString(); + await sedb.StringSetAsync(tmp, String); + var val = await sedb.StringGetAsync(tmp); + if (val != String) throw new Exception("not equal"); + results.Enqueue(val); + } + }).Wait(); + sw.Stop(); + Console.WriteLine("StackExchangeAsync(0-10000): " + sw.ElapsedMilliseconds + "ms results: " + results.Count); + tasks.Clear(); + results.Clear(); + cli.FlushDb(); sw.Reset(); sw.Start(); @@ -136,21 +136,21 @@ static void Main(string[] args) cli.FlushDb(); - //sw.Reset(); - //sw.Start(); - //for (var a = 0; a < 100000; a++) - //{ - // var tmp = Guid.NewGuid().ToString(); - // cli.Set(tmp, String); - // var val = cli.Get(tmp); - // if (val != String) throw new Exception("not equal"); - // results.Enqueue(val); - //} - //sw.Stop(); - //Console.WriteLine("FreeRedis(0-100000): " + sw.ElapsedMilliseconds + "ms results: " + results.Count); - //tasks.Clear(); - //results.Clear(); - //cli.FlushDb(); + sw.Reset(); + sw.Start(); + for (var a = 0; a < 10000; a++) + { + var tmp = Guid.NewGuid().ToString(); + cli.Set(tmp, String); + var val = cli.Get(tmp); + if (val != String) throw new Exception("not equal"); + results.Enqueue(val); + } + sw.Stop(); + Console.WriteLine("FreeRedisSync(0-10000): " + sw.ElapsedMilliseconds + "ms results: " + results.Count); + tasks.Clear(); + results.Clear(); + cli.FlushDb(); sw.Reset(); sw.Start(); @@ -168,7 +168,48 @@ static void Main(string[] args) } Task.WaitAll(tasks.ToArray()); sw.Stop(); - Console.WriteLine("FreeRedis(Task.WaitAll 100000): " + sw.ElapsedMilliseconds + "ms results: " + results.Count); + Console.WriteLine("FreeRedisSync(Task.WaitAll 100000): " + sw.ElapsedMilliseconds + "ms results: " + results.Count); + tasks.Clear(); + results.Clear(); + cli.FlushDb(); + + + sw.Reset(); + sw.Start(); + Task.Run(async () => + { + for (var a = 0; a < 10000; a++) + { + var tmp = Guid.NewGuid().ToString(); + await cli.SetAsync(tmp, String); + var val = await cli.GetAsync(tmp); + if (val != String) throw new Exception("not equal"); + results.Enqueue(val); + } + }).Wait(); + sw.Stop(); + Console.WriteLine("FreeRedisAsync(0-10000): " + sw.ElapsedMilliseconds + "ms results: " + results.Count); + tasks.Clear(); + results.Clear(); + cli.FlushDb(); + + sw.Reset(); + sw.Start(); + tasks = new List(); + for (var a = 0; a < 100000; a++) + { + tasks.Add(Task.Run(async () => + { + var tmp = Guid.NewGuid().ToString(); + await cli.SetAsync(tmp, String); + var val = await cli.GetAsync(tmp); + if (val != String) throw new Exception("not equal"); + results.Enqueue(val); + })); + } + Task.WaitAll(tasks.ToArray()); + sw.Stop(); + Console.WriteLine("FreeRedisAsync(Task.WaitAll 100000): " + sw.ElapsedMilliseconds + "ms results: " + results.Count); tasks.Clear(); results.Clear(); cli.FlushDb(); diff --git a/src/FreeRedis/FreeRedis.csproj b/src/FreeRedis/FreeRedis.csproj index a7dbdf8a..3961cc26 100644 --- a/src/FreeRedis/FreeRedis.csproj +++ b/src/FreeRedis/FreeRedis.csproj @@ -1,11 +1,11 @@  - net50;netstandard2.1;netstandard2.0;net451;net40 + net60;netstandard2.1;netstandard2.0;net451;net40 FreeRedis FreeRedis FreeRedis - 0.6.6 + 1.0.1 true https://github.com/2881099/FreeRedis FreeRedis is .NET redis client, supports cluster, sentinel, master-slave, pipeline, transaction and connection pool. @@ -32,7 +32,7 @@ net40 - isasync_close + isasync diff --git a/src/FreeRedis/FreeRedis.xml b/src/FreeRedis/FreeRedis.xml index a8ed3488..d93e9efa 100644 --- a/src/FreeRedis/FreeRedis.xml +++ b/src/FreeRedis/FreeRedis.xml @@ -585,9 +585,9 @@ Single inside RedisClient - + - DEL command (A Synchronized Version)

+ DEL command (An Asynchronous Version)


Removes the specified keys. A key is ignored if it does not exist.


@@ -599,9 +599,9 @@ Keys The number of keys that were removed. - + - DUMP command (A Synchronized Version)

+ DUMP command (An Asynchronous Version)


Serialize the value stored at key in a Redis-specific format and return it to the user. The returned value can be synthesized back into a Redis key using the RESTORE command.


@@ -613,9 +613,9 @@ Key The serialized value. - + - EXISTS command (A Synchronized Version)

+ EXISTS command (An Asynchronous Version)


Returns if key exists.


@@ -627,9 +627,9 @@ Key Specifically: True if the key exists. False if the key does not exist. - + - EXISTS command (A Synchronized Version)

+ EXISTS command (An Asynchronous Version)


Returns if key exists.


@@ -641,9 +641,9 @@ Keys The number of keys existing among the ones specified as arguments. Keys mentioned multiple times and existing are counted multiple times. - + - EXPIRE command (A Synchronized Version)

+ EXPIRE command (An Asynchronous Version)


Set a timeout on key. After the timeout has expired, the key will automatically be deleted. A key with an associated timeout is often said to be volatile in Redis terminology.


@@ -656,24 +656,9 @@ Expires time (seconds) Specifically: True if the timeout was set.False if key does not exist. - - - EXPIRE command (A Synchronized Version)

-
- Set a timeout on key. After the timeout has expired, the key will automatically be deleted. A key with an associated timeout is often said to be volatile in Redis terminology.

-
- 设置键的超时时间。到期后,键将被自动删除。

-
- Document link: https://redis.io/commands/expire
- Available since 1.0.0. -
- Key - Expires TimeSpan - Specifically: True if the timeout was set.False if key does not exist. -
- + - EXPIREAT command (A Synchronized Version)

+ EXPIREAT command (An Asynchronous Version)


EXPIREAT has the same effect and semantic as EXPIRE, but instead of specifying the number of seconds representing the TTL (time to live), it takes an absolute Unix timestamp (seconds since January 1, 1970). A timestamp in the past will delete the key immediately.


@@ -686,9 +671,9 @@ UNIX Timestamp Specifically: True if the timeout was set.False if key does not exist. - + - KEYS command (A Synchronized Version)

+ KEYS command (An Asynchronous Version)


Returns all keys matching pattern.


@@ -700,9 +685,9 @@ Pattern List of keys matching pattern. - + - MIGRATE command (A Synchronized Version)

+ MIGRATE command (An Asynchronous Version)


Atomically transfer a key from a source Redis instance to a destination Redis instance. On success the key is deleted from the original instance and is guaranteed to exist in the target instance.


@@ -723,9 +708,9 @@ Authenticate with the given password (Redis 6 or greater ACL auth style). If the key argument is an empty string, the command will instead migrate all the keys that follow the KEYS option (see the above section for more info). Available since 3.0.6. - + - MOVE command (A Synchronized Version)

+ MOVE command (An Asynchronous Version)


Move key from the currently selected database (see SELECT) to the specified destination database. When key already exists in the destination database, or it does not exist in the source database, it does nothing. It is possible to use MOVE as a locking primitive because of this.


@@ -739,9 +724,9 @@ Database Specifically: True if key was moved. False if key was not moved. - + - OBJECT REFCOUNT command (A Synchronized Version)

+ OBJECT REFCOUNT command (An Asynchronous Version)


Returns the number of references of the value associated with the specified key. This command is mainly useful for debugging.


@@ -753,9 +738,9 @@ Key Returns the number of references of the value associated with the specified key. - + - OBJECT IDLETIME command (A Synchronized Version)

+ OBJECT IDLETIME command (An Asynchronous Version)


returns the number of seconds since the object stored at the specified key is idle (not requested by read or write operations). While the value is returned in seconds the actual resolution of this timer is 10 seconds, but may vary in future implementations. This subcommand is available when maxmemory-policy is set to an LRU policy or noeviction and maxmemory is set.


@@ -767,9 +752,9 @@ Key Returns the number of seconds since the object stored at the specified key is idle - + - OBJECT ENCODING command (A Synchronized Version)

+ OBJECT ENCODING command (An Asynchronous Version)


Returns the kind of internal representation used in order to store the value associated with a key.


@@ -781,9 +766,9 @@ Key Returns the kind of internal representation used in order to store the value associated with a key. - + - OBJECT FREQ command (A Synchronized Version)

+ OBJECT FREQ command (An Asynchronous Version)


Returns the logarithmic access frequency counter of the object stored at the specified key. This subcommand is available when maxmemory-policy is set to an LFU policy.


@@ -795,9 +780,9 @@ Key Returns the logarithmic access frequency counter of the object stored at the specified key. - + - PERSIST command (A Synchronized Version)

+ PERSIST command (An Asynchronous Version)


Remove the existing timeout on key, turning the key from volatile (a key with an expire set) to persistent (a key that will never expire as no timeout is associated).


@@ -809,9 +794,9 @@ Key Specifically: True if the timeout was removed. False if key does not exist or does not have an associated timeout. - + - PEXPIRE command (A Synchronized Version)

+ PEXPIRE command (An Asynchronous Version)


This command works exactly like EXPIRE but the time to live of the key is specified in milliseconds instead of seconds.


@@ -824,9 +809,9 @@ Expires time (milliseconds) Specifically: True if the timeout was set.False if key does not exist. - + - PEXPIREAT command (A Synchronized Version)

+ PEXPIREAT command (An Asynchronous Version)


PEXPIREAT has the same effect and semantic as EXPIREAT, but the Unix time at which the key will expire is specified in milliseconds instead of seconds.


@@ -840,9 +825,9 @@ Specifically: True if the timeout was set.False if key does not exist. - + - PTTL command (A Synchronized Version)

+ PTTL command (An Asynchronous Version)


Like TTL this command returns the remaining time to live of a key that has an expire set, with the sole difference that TTL returns the amount of remaining time in seconds while PTTL returns it in milliseconds.
In Redis 2.6 or older the command returns -1 if the key does not exist or if the key exist but has no associated expire.
@@ -862,9 +847,9 @@ Key TTL in milliseconds, or a negative value in order to signal an error (see the description above). - + - RANDOMKEY command (A Synchronized Version)

+ RANDOMKEY command (An Asynchronous Version)


Return a random key from the currently selected database.


@@ -875,9 +860,9 @@
The random key, or nil when the database is empty.
- + - RENAME command (A Synchronized Version)

+ RENAME command (An Asynchronous Version)


Renames key to newkey. It returns an error when key does not exist. If newkey already exists it is overwritten, when this happens RENAME executes an implicit DEL operation, so if the deleted key contains a very big value it may cause high latency even if RENAME itself is usually a constant-time operation.
- Before Redis 3.2.0, an error is returned if source and destination names are the same.

@@ -892,9 +877,9 @@ Key New key - + - RENAMENX command (A Synchronized Version)

+ RENAMENX command (An Asynchronous Version)


Renames key to newkey. It returns an error when key does not exist. If newkey already exists it is overwritten, when this happens RENAME executes an implicit DEL operation, so if the deleted key contains a very big value it may cause high latency even if RENAME itself is usually a constant-time operation.
- Before Redis 3.2.0, an error is returned if source and destination names are the same.

@@ -910,9 +895,9 @@ New key specifically: True if key was renamed to newkey. False if newkey already exists. - + - RESTORE command (A Synchronized Version)

+ RESTORE command (An Asynchronous Version)


Create a key associated with a value that is obtained by deserializing the provided serialized value (obtained via DUMP).


@@ -924,9 +909,9 @@ Key Serialized value - + - RESTORE command (A Synchronized Version)

+ RESTORE command (An Asynchronous Version)


Create a key associated with a value that is obtained by deserializing the provided serialized value (obtained via DUMP).


@@ -943,9 +928,9 @@ IDLETIME modifier. Available since 5.0.0. FREQ modifier. Available since 5.0.0. - + - SCAN command (A Synchronized Version)

+ SCAN command (An Asynchronous Version)


The SCAN command and the closely related commands SSCAN, HSCAN and ZSCAN are used in order to incrementally iterate over a collection of elements.


@@ -960,9 +945,9 @@ TYPE option: the type argument is the same string name that the TYPE command returns. Available since 6.0 Return a two elements multi-bulk reply, where the first element is a string representing an unsigned 64 bit number (the cursor), and the second element is a multi-bulk with an array of elements. - + - SORT command (A Synchronized Version)

+ SORT command (An Asynchronous Version)


Returns or stores the elements contained in the list, set or sorted set at key. By default, sorting is numeric and elements are compared by their value interpreted as double precision floating point number.


@@ -980,9 +965,9 @@ ALPHA modifier, sort by lexicographically. A list of sorted elements - + - SORT command (A Synchronized Version)

+ SORT command (An Asynchronous Version)


Stores the elements contained in the list, set or sorted set at key. By default, sorting is numeric and elements are compared by their value interpreted as double precision floating point number.


@@ -1001,9 +986,9 @@ ALPHA modifier, sort by lexicographically. The number of sorted elements in the destination list. - + - TOUCH command (A Synchronized Version)

+ TOUCH command (An Asynchronous Version)


Alters the last access time of a key(s). A key is ignored if it does not exist.


@@ -1015,9 +1000,9 @@ Keys The number of keys that were touched. - + - TTL command (A Synchronized Version)

+ TTL command (An Asynchronous Version)


Returns the remaining time to live of a key that has a timeout. This introspection capability allows a Redis client to check how many seconds a given key will continue to be part of the dataset.
In Redis 2.6 or older the command returns -1 if the key does not exist or if the key exist but has no associated expire.
@@ -1037,9 +1022,9 @@ Key TTL in seconds, or a negative value in order to signal an error (see the description above). - + - TYPE command (A Synchronized Version)

+ TYPE command (An Asynchronous Version)


Returns the string representation of the type of the value stored at key. The different types that can be returned are: string, list, set, zset, hash and stream.


@@ -1051,9 +1036,9 @@ Key The type of key, or none when key does not exist. - + - UNLINK command (A Synchronized Version)

+ UNLINK command (An Asynchronous Version)


This command is very similar to DEL: it removes the specified keys. Just like DEL a key is ignored if it does not exist. However the command performs the actual memory reclaiming in a different thread, so it is not blocking, while DEL is.


@@ -1065,9 +1050,9 @@ Keys The number of keys that were unlinked. - + - WAIT command (A Synchronized Version)

+ WAIT command (An Asynchronous Version)


This command blocks the current client until all the previous write commands are successfully transferred and acknowledged by at least the specified number of replicas. If the timeout, specified in milliseconds, is reached, the command returns even if the specified number of replicas were not yet reached.


@@ -1081,79 +1066,1168 @@ Timeout milliseconds The command returns the number of replicas reached by all the writes performed in the context of the current connection. - + - 开启分布式锁,若超时返回null + DEL command (A Synchronized Version)

+
+ Removes the specified keys. A key is ignored if it does not exist.

+
+ 移除指定的键。如果键不存在,则忽略之。

+
+ Document link: https://redis.io/commands/del
+ Available since 2.6.0.
- 锁名称 - 超时(秒) - 自动延长锁超时时间,看门狗线程的超时时间为timeoutSeconds/2 , 在看门狗线程超时时间时自动延长锁的时间为timeoutSeconds。除非程序意外退出,否则永不超时。 - + Keys + The number of keys that were removed.
- + - 延长锁时间,锁在占用期内操作时返回true,若因锁超时被其他使用者占用则返回false + DUMP command (A Synchronized Version)

+
+ Serialize the value stored at key in a Redis-specific format and return it to the user. The returned value can be synthesized back into a Redis key using the RESTORE command.

+
+ 以 Redis 特有格式序列化指定的键值,并返回给用户。可结合 RESTORE 命令将序列化的结果重新写回 Redis。

+
+ Document link: https://redis.io/commands/dump
+ Available since 2.6.0.
- 延长的毫秒数 - 成功/失败 + Key + The serialized value.
- + - 刷新锁时间,把key的ttl重新设置为milliseconds,锁在占用期内操作时返回true,若因锁超时被其他使用者占用则返回false + EXISTS command (A Synchronized Version)

+
+ Returns if key exists.

+
+ 返回键是否存在。

+
+ Document link: https://redis.io/commands/exists
+ Available since 1.0.0.
- 刷新的毫秒数 - 成功/失败 + Key + Specifically: True if the key exists. False if the key does not exist.
- + - 释放分布式锁 + EXISTS command (A Synchronized Version)

+
+ Returns if key exists.

+
+ 返回键是否存在。

+
+ Document link: https://redis.io/commands/exists
+ Available since 3.0.3.
- 成功/失败 + Keys + The number of keys existing among the ones specified as arguments. Keys mentioned multiple times and existing are counted multiple times.
- + - 使用lpush + blpop订阅端(多端争抢模式),只有一端收到消息 + EXPIRE command (A Synchronized Version)

+
+ Set a timeout on key. After the timeout has expired, the key will automatically be deleted. A key with an associated timeout is often said to be volatile in Redis terminology.

+
+ 设置键的超时时间。到期后,键将被自动删除。

+
+ Document link: https://redis.io/commands/expire
+ Available since 1.0.0.
- list key(不含prefix前辍) - 接收消息委托 - + Key + Expires time (seconds) + Specifically: True if the timeout was set.False if key does not exist.
- + - 使用lpush + blpop订阅端(多端争抢模式),只有一端收到消息 + EXPIRE command (A Synchronized Version)

+
+ Set a timeout on key. After the timeout has expired, the key will automatically be deleted. A key with an associated timeout is often said to be volatile in Redis terminology.

+
+ 设置键的超时时间。到期后,键将被自动删除。

+
+ Document link: https://redis.io/commands/expire
+ Available since 1.0.0.
- 支持多个 key(不含prefix前辍) - 接收消息委托,参数1:key;参数2:消息体 - + Key + Expires TimeSpan + Specifically: True if the timeout was set.False if key does not exist.
- + - 使用lpush + blpop订阅端(多端非争抢模式),都可以收到消息 + EXPIREAT command (A Synchronized Version)

+
+ EXPIREAT has the same effect and semantic as EXPIRE, but instead of specifying the number of seconds representing the TTL (time to live), it takes an absolute Unix timestamp (seconds since January 1, 1970). A timestamp in the past will delete the key immediately.

+
+ EXPIREAT 具有与 EXPIRE 相同的作用和语义,但是它没有指定表示 TTL(生存时间)的秒数,而是使用了绝对的 Unix 时间戳(自1970年1月1日以来的秒数)。时间戳一旦过期就会被删除。

+
+ Document link: https://redis.io/commands/expireat
+ Available since 1.2.0.
- list key(不含prefix前辍) - 订阅端标识,若重复则争抢,若唯一必然收到消息 - 接收消息委托 - + Key + UNIX Timestamp + Specifically: True if the timeout was set.False if key does not exist.
- + - 随机返回N个元素 - Redis 6.2.0+以上才支持该命令 + KEYS command (A Synchronized Version)

+
+ Returns all keys matching pattern.

+
+ 返回所有与模式匹配的键。

+
+ Document link: https://redis.io/commands/keys
+ Available since 1.0.0. +
+ Pattern + List of keys matching pattern. +
+ + + MIGRATE command (A Synchronized Version)

+
+ Atomically transfer a key from a source Redis instance to a destination Redis instance. On success the key is deleted from the original instance and is guaranteed to exist in the target instance.

+
+ 原子地将键从 Redis 源实例转移到目标实例。转移成功后,Key 将从源实例中删除,并确保保存在目标实例之中。

+
+ Document link: https://redis.io/commands/migrate
+ Available since 2.6.0.
+ Destination Instance's Host + Destination Instance's Port Key - 返回的个数 - 是否允许有重复元素返回 - + Destination Instance's Database + Timeout milliseconds + Do not remove the key from the local instance. Available since 3.0.0. + Replace existing key on the remote instance. Available since 3.0.0. + Authenticate with the given password to the remote instance. Available since 4.0.7. + Authenticate with the given username (Redis 6 or greater ACL auth style). + Authenticate with the given password (Redis 6 or greater ACL auth style). + If the key argument is an empty string, the command will instead migrate all the keys that follow the KEYS option (see the above section for more info). Available since 3.0.6.
- + - 随机返回N个元素, 包含分数 - Redis 6.2.0+以上才支持该命令 + MOVE command (A Synchronized Version)

+
+ Move key from the currently selected database (see SELECT) to the specified destination database. When key already exists in the destination database, or it does not exist in the source database, it does nothing. It is possible to use MOVE as a locking primitive because of this.

+
+ 将指定的 Key 从当前数据库移动到目标数据库。
+ 如果目标数据库中已存在该键,或源数据库中不存在该键,则什么都不做。

+
+ Document link: https://redis.io/commands/move
+ Available since 1.0.0.
Key - 返回的个数 - 是否允许有重复元素返回 - + Database + Specifically: True if key was moved. False if key was not moved. +
+ + + OBJECT REFCOUNT command (A Synchronized Version)

+
+ Returns the number of references of the value associated with the specified key. This command is mainly useful for debugging.

+
+ 返回与指定键关联的值的引用数。

+
+ Document link: https://redis.io/commands/object
+ Available since 2.2.3. +
+ Key + Returns the number of references of the value associated with the specified key. +
+ + + OBJECT IDLETIME command (A Synchronized Version)

+
+ returns the number of seconds since the object stored at the specified key is idle (not requested by read or write operations). While the value is returned in seconds the actual resolution of this timer is 10 seconds, but may vary in future implementations. This subcommand is available when maxmemory-policy is set to an LRU policy or noeviction and maxmemory is set.

+
+ 返回指定键值的空闲时间,单位为秒。

+
+ Document link: https://redis.io/commands/object
+ Available since 2.2.3. +
+ Key + Returns the number of seconds since the object stored at the specified key is idle +
+ + + OBJECT ENCODING command (A Synchronized Version)

+
+ Returns the kind of internal representation used in order to store the value associated with a key.

+
+ 返回用于存储与键关联的值的内部表示形式的类型。 +
+ Document link: https://redis.io/commands/object
+ Available since 2.2.3. +
+ Key + Returns the kind of internal representation used in order to store the value associated with a key. +
+ + + OBJECT FREQ command (A Synchronized Version)

+
+ Returns the logarithmic access frequency counter of the object stored at the specified key. This subcommand is available when maxmemory-policy is set to an LFU policy.

+
+ 返回存储在指定键处的对象的对数访问频率计数器。

+
+ Document link: https://redis.io/commands/object
+ Available since 2.2.3. +
+ Key + Returns the logarithmic access frequency counter of the object stored at the specified key. +
+ + + PERSIST command (A Synchronized Version)

+
+ Remove the existing timeout on key, turning the key from volatile (a key with an expire set) to persistent (a key that will never expire as no timeout is associated).

+
+ 将指定键的超时设置移除,将键从可变键转换为持久键。

+
+ Document link: https://redis.io/commands/persist
+ Available since 2.2.0. +
+ Key + Specifically: True if the timeout was removed. False if key does not exist or does not have an associated timeout. +
+ + + PEXPIRE command (A Synchronized Version)

+
+ This command works exactly like EXPIRE but the time to live of the key is specified in milliseconds instead of seconds.

+
+ 此命令与 EXPIRE 相同,区别仅仅在于时间单位是毫秒,不是秒。

+
+ Document link: https://redis.io/commands/expire
+ Available since 1.0.0. +
+ Key + Expires time (milliseconds) + Specifically: True if the timeout was set.False if key does not exist. +
+ + + PEXPIREAT command (A Synchronized Version)

+
+ PEXPIREAT has the same effect and semantic as EXPIREAT, but the Unix time at which the key will expire is specified in milliseconds instead of seconds.

+
+ PEXPIREAT 具有与 EXPIREAT 相同的作用和语义,但 Key 的时间戳使用的是毫秒,而不是秒。

+
+ Document link: https://redis.io/commands/pexpireat
+ Available since 2.6.0. +
+ Key + UNIX Timestamp + Specifically: True if the timeout was set.False if key does not exist. + +
+ + + PTTL command (A Synchronized Version)

+
+ Like TTL this command returns the remaining time to live of a key that has an expire set, with the sole difference that TTL returns the amount of remaining time in seconds while PTTL returns it in milliseconds.
+ In Redis 2.6 or older the command returns -1 if the key does not exist or if the key exist but has no associated expire.
+ Starting with Redis 2.8 the return value in case of error changed:
+ - The command returns -2 if the key does not exist.
+ - The command returns -1 if the key exists but has no associated expire.

+
+ 与 TTL 命令一样,返回键的剩余生存时间,唯一的区别是 TTL 以秒为单位返回剩余时间,而 PTTL 以毫秒为单位返回。
+ 在 Redis 2.6 之前,如果 Key 不存在或 Key 未设置过期时间,则返回 -1
+ 从 Redis 2.8 开始,将针对不同的错误情况返回不同的值:
+ - 如果 Key 不存在,则返回 -2
+ - 如果 Key 存在,但没有设置过过期时间,则返回 -1

+
+ Document link: https://redis.io/commands/pttl
+ Available since 2.6.0. +
+ Key + TTL in milliseconds, or a negative value in order to signal an error (see the description above). +
+ + + RANDOMKEY command (A Synchronized Version)

+
+ Return a random key from the currently selected database.

+
+ 从当前选择的数据库返回一个随机密钥。

+
+ Document link: https://redis.io/commands/randomkey
+ Available since 1.0.0. +
+ The random key, or nil when the database is empty. +
+ + + RENAME command (A Synchronized Version)

+
+ Renames key to newkey. It returns an error when key does not exist. If newkey already exists it is overwritten, when this happens RENAME executes an implicit DEL operation, so if the deleted key contains a very big value it may cause high latency even if RENAME itself is usually a constant-time operation.
+ - Before Redis 3.2.0, an error is returned if source and destination names are the same.

+
+ 将 Key 重命名为 New Key。如果键不存在,返回错误。
+ 如果 New Key 已存在,则将被覆盖,此时类似 DEL 命令,由于 RENAME 是 constant-time operation,因此当删除的键有很大的值时会有较大的延迟。
+ - 在 Redis 3.2 之前,如果 Key 和 New Key 名字一样,将返回错误。

+
+ Document link: https://redis.io/commands/rename
+ Available since 1.0.0. +
+ Key + New key +
+ + + RENAMENX command (A Synchronized Version)

+
+ Renames key to newkey. It returns an error when key does not exist. If newkey already exists it is overwritten, when this happens RENAME executes an implicit DEL operation, so if the deleted key contains a very big value it may cause high latency even if RENAME itself is usually a constant-time operation.
+ - Before Redis 3.2.0, an error is returned if source and destination names are the same.

+
+ 将 Key 重命名为 New Key。如果键不存在,返回错误。
+ 如果 New Key 已存在,则将被覆盖,此时类似 DEL 命令,由于 RENAME 是 constant-time operation,因此当删除的键有很大的值时会有较大的延迟。
+ - 在 Redis 3.2 之前,如果 Key 和 New Key 名字一样,将返回错误。

+
+ Document link: https://redis.io/commands/renamenx
+ Available since 1.0.0. +
+ Key + New key + specifically: True if key was renamed to newkey. False if newkey already exists. +
+ + + RESTORE command (A Synchronized Version)

+
+ Create a key associated with a value that is obtained by deserializing the provided serialized value (obtained via DUMP).

+
+ 将经由 DUMP 命令序列化的值反序列化后,作为给定键的值进行保存。

+
+ Document link: https://redis.io/commands/restore
+ Available since 2.6.0. +
+ Key + Serialized value +
+ + + RESTORE command (A Synchronized Version)

+
+ Create a key associated with a value that is obtained by deserializing the provided serialized value (obtained via DUMP).

+
+ 将经由 DUMP 命令序列化的值反序列化后,作为给定键的值进行保存。

+
+ Document link: https://redis.io/commands/restore
+ Available since 2.6.0. +
+ Key + If ttl is 0 the key is created without any expire, otherwise the specified expire time (in milliseconds) is set. + Serialized value + REPLACE modifier: If the Key already exists, replace it. Available since 3.0.0. + Absolute Unix timestamp (in milliseconds) in which the key will expire. Available since 5.0.0. + IDLETIME modifier. Available since 5.0.0. + FREQ modifier. Available since 5.0.0. +
+ + + SCAN command (A Synchronized Version)

+
+ The SCAN command and the closely related commands SSCAN, HSCAN and ZSCAN are used in order to incrementally iterate over a collection of elements.

+
+ 使用 SCAN 命令及其关联的 SSCAN、HSCAN、ZSCAN 等命令来迭代返回元素集合。

+
+ Document link: https://redis.io/commands/scan
+ Available since 2.8.0. +
+ Cursor + MATCH option + COUNT option: while SCAN does not provide guarantees about the number of elements returned at every iteration, it is possible to empirically adjust the behavior of SCAN using the COUNT option, default is 10 + TYPE option: the type argument is the same string name that the TYPE command returns. Available since 6.0 + Return a two elements multi-bulk reply, where the first element is a string representing an unsigned 64 bit number (the cursor), and the second element is a multi-bulk with an array of elements. +
+ + + SORT command (A Synchronized Version)

+
+ Returns or stores the elements contained in the list, set or sorted set at key. By default, sorting is numeric and elements are compared by their value interpreted as double precision floating point number.

+
+ 返回含在 LIST、SET、ZSET 中的元素。默认情况下,排序是数字形式的,并且将元素的值进行比较以解释为双精度浮点数。

+
+ Document link: https://redis.io/commands/sort
+ Available since 1.0.0. +
+ Keys + BY modifier + The number of elements to skip. + Specifying the number of elements to return from starting at offset. + GET modifier + ASC | DESC modifier + ALPHA modifier, sort by lexicographically. + A list of sorted elements +
+ + + SORT command (A Synchronized Version)

+
+ Stores the elements contained in the list, set or sorted set at key. By default, sorting is numeric and elements are compared by their value interpreted as double precision floating point number.

+
+ 存储包含在 LIST、SET、ZSET 中的元素。默认情况下,排序是数字形式的,并且将元素的值进行比较以解释为双精度浮点数。

+
+ Document link: https://redis.io/commands/sort
+ Available since 1.0.0. +
+ Storing the result of a SORT operation to the destination key. + Keys + BY modifier + The number of elements to skip. + Specifying the number of elements to return from starting at offset. + GET modifier + ASC | DESC modifier + ALPHA modifier, sort by lexicographically. + The number of sorted elements in the destination list. +
+ + + TOUCH command (A Synchronized Version)

+
+ Alters the last access time of a key(s). A key is ignored if it does not exist.

+
+ 更改 Key(s) 最后访问时间。如果键不存在,则忽略之。

+
+ Document link: https://redis.io/commands/touch
+ Available since 3.2.1. +
+ Keys + The number of keys that were touched. +
+ + + TTL command (A Synchronized Version)

+
+ Returns the remaining time to live of a key that has a timeout. This introspection capability allows a Redis client to check how many seconds a given key will continue to be part of the dataset.
+ In Redis 2.6 or older the command returns -1 if the key does not exist or if the key exist but has no associated expire.
+ Starting with Redis 2.8 the return value in case of error changed:
+ - The command returns -2 if the key does not exist.
+ - The command returns -1 if the key exists but has no associated expire.

+
+ 返回键的剩余生存时间。这种自省能力允许 Redis 客户端检查指定键还能再数据集中生存多少秒。
+ 在 Redis 2.6 之前,如果 Key 不存在或 Key 未设置过期时间,则返回 -1
+ 从 Redis 2.8 开始,将针对不同的错误情况返回不同的值:
+ - 如果 Key 不存在,则返回 -2
+ - 如果 Key 存在,但没有设置过过期时间,则返回 -1

+
+ Document link: https://redis.io/commands/ttl
+ Available since 1.0.0. +
+ Key + TTL in seconds, or a negative value in order to signal an error (see the description above). +
+ + + TYPE command (A Synchronized Version)

+
+ Returns the string representation of the type of the value stored at key. The different types that can be returned are: string, list, set, zset, hash and stream.

+
+ 获取键对应值的类型的字符串表达形式。可以返回的类型是:string,list,set,zset,hash 以及 stream。

+
+ Document link: https://redis.io/commands/type
+ Available since 1.0.0. +
+ Key + The type of key, or none when key does not exist. +
+ + + UNLINK command (A Synchronized Version)

+
+ This command is very similar to DEL: it removes the specified keys. Just like DEL a key is ignored if it does not exist. However the command performs the actual memory reclaiming in a different thread, so it is not blocking, while DEL is.

+
+ 本命令与 DEL 相似,能删除指定的键值;如果键不存在,则忽略。与 DEL 不同的是,本命令将在另一个线程中执行实际的内存回收,因此是非阻塞的。

+
+ Document link: https://redis.io/commands/unlink
+ Available since 4.0.0. +
+ Keys + The number of keys that were unlinked. +
+ + + WAIT command (A Synchronized Version)

+
+ This command blocks the current client until all the previous write commands are successfully transferred and acknowledged by at least the specified number of replicas. If the timeout, specified in milliseconds, is reached, the command returns even if the specified number of replicas were not yet reached.

+
+ 本命令将阻塞当前客户端,直到所有写命令成功发送、且大于等于指定数量的副本进行了确认。
+ 如果超时(单位为毫秒),即便没能获得指定数量副本的确认,命令也会返回。

+
+ Document link: https://redis.io/commands/wait
+ Available since 3.0.0. +
+ The number of replicas + Timeout milliseconds + The command returns the number of replicas reached by all the writes performed in the context of the current connection. +
+ + + 开启分布式锁,若超时返回null + + 锁名称 + 超时(秒) + 自动延长锁超时时间,看门狗线程的超时时间为timeoutSeconds/2 , 在看门狗线程超时时间时自动延长锁的时间为timeoutSeconds。除非程序意外退出,否则永不超时。 + + + + + 延长锁时间,锁在占用期内操作时返回true,若因锁超时被其他使用者占用则返回false + + 延长的毫秒数 + 成功/失败 + + + + 刷新锁时间,把key的ttl重新设置为milliseconds,锁在占用期内操作时返回true,若因锁超时被其他使用者占用则返回false + + 刷新的毫秒数 + 成功/失败 + + + + 释放分布式锁 + + 成功/失败 + + + + 使用lpush + blpop订阅端(多端争抢模式),只有一端收到消息 + + list key(不含prefix前辍) + 接收消息委托 + + + + + 使用lpush + blpop订阅端(多端争抢模式),只有一端收到消息 + + 支持多个 key(不含prefix前辍) + 接收消息委托,参数1:key;参数2:消息体 + + + + + 使用lpush + blpop订阅端(多端非争抢模式),都可以收到消息 + + list key(不含prefix前辍) + 订阅端标识,若重复则争抢,若唯一必然收到消息 + 接收消息委托 + + + + + 随机返回N个元素 + Redis 6.2.0+以上才支持该命令 + + Key + 返回的个数 + 是否允许有重复元素返回 + + + + + 随机返回N个元素, 包含分数 + Redis 6.2.0+以上才支持该命令 + + Key + 返回的个数 + 是否允许有重复元素返回 + + + + + APPEND command (An Asynchronous Version)

+
+ If key already exists and is a string, this command appends the value at the end of the string. If key does not exist it is created and set as an empty string, so APPEND will be similar to SET in this special case.

+
+ 若键值已存在且为字符串,则此命令将值附加在字符串的末尾;
+ 若键值不存在,则会先创建一个空字符串,并附加值(在此情况下,APPEND 类似 SET 命令)。

+
+ Document link: https://redis.io/commands/append
+ Available since 2.0.0. +
+ Key + Value + + The length of the string after the append operation. +
+ + + BITCOUNT command (An Asynchronous Version)

+
+ Count the number of set bits (population counting) in a string.

+
+ 统计字符串中的位数。

+
+ Document link: https://redis.io/commands/bitcount
+ Available since 2.6.0. +
+ Key + Index of the start. It can contain negative values in order to index bytes starting from the end of the string. + Index of the end. It can contain negative values in order to index bytes starting from the end of the string. + The number of set bits in the string. Non-existent keys are treated as empty strings and will return zero. +
+ + + BITOP command (An Asynchronous Version)

+
+ Perform a bitwise operation between multiple keys (containing string values) and store the result in the destination key.

+
+ 在(包括字符串值)的多键之间按位运算,并将结果保存在目标键中。
+ 目前支持 AND、OR、XOR 与 NOT 四种运算方式。

+
+ Document link: https://redis.io/commands/bitop
+ Available since 2.6.0. +
+ Bit operation type: AND, OR, XOR or NOT + Destination Key + Multiple keys (containing string values) + The size of the string stored in the destination key, that is equal to the size of the longest input string. +
+ + + BITPOS command (An Asynchronous Version)

+
+ Return the position of the first bit set to 1 or 0 in a string.
+ The position is returned, thinking of the string as an array of bits from left to right, where the first byte's most significant bit is at position 0, the second byte's most significant bit is at position 8, and so forth.

+
+ 返回字符串中第一个 1 或 0 的位置。
+ 注意,本命令将字符串视作位数组,自左向右计算,第一个字节在位置 0,第二个字节在位置 8,以此类推。

+
+ Document link: https://redis.io/commands/bitpos
+ Available since 2.8.7. +
+ Key + Bit value, 1 is true, 0 is false. + Index of the start. It can contain negative values in order to index bytes starting from the end of the string. + Index of the end. It can contain negative values in order to index bytes starting from the end of the string. + Returns the position of the first bit set to 1 or 0 according to the request. +
+ + + DECR command (An Asynchronous Version)

+
+ Decrements the number stored at key by one.
+ If the key does not exist, it is set to 0 before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that can not be represented as integer.
+ This operation is limited to 64 bit signed integers.

+
+ 对该键的值减去 1。
+ 如果键值不存在,则在操作前先设置为 0。如果键对应的值是个错误类型或不能表达为整数的字符串,则返回错误。此操作仅限于 64 位有符号整数。

+
+ Document link: https://redis.io/commands/decr
+ Available since 1.0.0. +
+ Key + the value of key after the decrement. +
+ + + DECRBY command (An Asynchronous Version)

+
+ Decrements the number stored at key by decrement.
+ If the key does not exist, it is set to 0 before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that can not be represented as integer.
+ This operation is limited to 64 bit signed integers.

+
+ 对该键的值减去给定的值。
+ 如果键值不存在,则在操作前先设置为 0。如果键对应的值是个错误类型或不能表达为整数的字符串,则返回错误。此操作仅限于 64 位有符号整数。

+
+ Document link: https://redis.io/commands/decrby
+ Available since 1.0.0. +
+ Key + The given value to be decreased. + the value of key after the decrement. +
+ + + GET command (An Asynchronous Version)

+
+ Get the value of key. If the key does not exist the special value nil is returned. An error is returned if the value stored at key is not a string, because GET only handles string values.

+
+ 获得给定键的值。若键不存在,则返回特殊的 nil 值。如果给定键的值不是字符串,则返回错误,因为 GET 指令只能处理字符串。

+
+ Document link: https://redis.io/commands/get
+ Available since 1.0.0. +
+ Key + The value of key, or nil when key does not exist. +
+ + + GET command (An Asynchronous Version)

+
+ Get the value of key. If the key does not exist the special value nil is returned. An error is returned if the value stored at key is not a string, because GET only handles string values.

+
+ 获得给定键的值。若键不存在,则返回特殊的 nil 值。如果给定键的值不是字符串,则返回错误,因为 GET 指令只能处理字符串。

+
+ Document link: https://redis.io/commands/get
+ Available since 1.0.0. +
+ Key + + The value of key, or nil when key does not exist. +
+ + + GET command (A Synchronized Version)

+
+ Get the value of key and write to the stream.. If the key does not exist the special value nil is returned. An error is returned if the value stored at key is not a string, because GET only handles string values.

+
+ 获得给定键的值并写入流中。若键不存在,则返回特殊的 nil 值。如果给定键的值不是字符串,则返回错误,因为 GET 指令只能处理字符串。

+
+ Document link: https://redis.io/commands/get
+ Available since 1.0.0. +
+ Key + Destination stream + Size +
+ + + GETBIT command (An Asynchronous Version)

+
+ Returns the bit value at offset in the string value stored at key.

+
+ 返回键所对应字符串值中偏移量的位值。

+
+ Document link: https://redis.io/commands/getbit
+ Available since 2.2.0. +
+ Key + Offset + The bit value stored at offset. +
+ + + GETRANGE command (An Asynchronous Version)

+
+ Returns the substring of the string value stored at key, determined by the offsets start and end (both are inclusive).

+
+ 返回键值的子字符串该字符串由偏移量 start 和 end 来确定(两端均闭包)。

+
+ Document link: https://redis.io/commands/getrange
+ Available since 2.0.0. It is called SUBSTR in Redis versions <= 2.0. +
+ Key + Start + End + The substring of the string value +
+ + + GETRANGE command (An Asynchronous Version)

+
+ Returns the substring of the string value stored at key, determined by the offsets start and end (both are inclusive).

+
+ 返回键值的子字符串该字符串由偏移量 start 和 end 来确定(两端均闭包)。

+
+ Document link: https://redis.io/commands/getrange
+ Available since 2.0.0. It is called SUBSTR in Redis versions <= 2.0. +
+ Key + Start + End + + +
+ + + GETSET command (An Asynchronous Version)

+
+ Atomically sets key to value and returns the old value stored at key. Returns an error when key exists but does not hold a string value.

+
+ 以原子的方式将新值取代给定键的旧值,并返回旧值。如果该键存在但不包含字符串值时,返回错误。

+
+ Document link: https://redis.io/commands/getset
+ Available since 1.0.0. +
+ Key + New value + + The old value stored at key, or nil when key did not exist. +
+ + + INCR command (An Asynchronous Version)

+
+ Increments the number stored at key by one.
+ If the key does not exist, it is set to 0 before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that can not be represented as integer.
+ This operation is limited to 64 bit signed integers.

+
+ 对该键的值加上 1。
+ 如果键值不存在,则在操作前先设置为 0。如果键对应的值是个错误类型或不能表达为整数的字符串,则返回错误。此操作仅限于 64 位有符号整数。

+
+ Document link: https://redis.io/commands/incr
+ Available since 1.0.0. +
+ Key + The value of key after the increment +
+ + + INCRBY command (An Asynchronous Version)

+
+ Decrements the number stored at key by increment.
+ If the key does not exist, it is set to 0 before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that can not be represented as integer.
+ This operation is limited to 64 bit signed integers.

+
+ 对该键的值加上给定的值。
+ 如果键值不存在,则在操作前先设置为 0。如果键对应的值是个错误类型或不能表达为整数的字符串,则返回错误。此操作仅限于 64 位有符号整数。

+
+ Document link: https://redis.io/commands/incrby
+ Available since 1.0.0. +
+ Key + The given value to be increased. + The value of key after the increment +
+ + + INCRBYFLOAT command (An Asynchronous Version)

+
+ Increment the string representing a floating point number stored at key by the specified increment.
+ By using a negative increment value, the result is that the value stored at the key is decremented (by the obvious properties of addition).
+ If the key does not exist, it is set to 0 before performing the operation. An error is returned if one of the following conditions occur:
+ - The key contains a value of the wrong type (not a string).
+ - The current key content or the specified increment are not parsable as a double precision floating point number.
+ If the command is successful the new incremented value is stored as the new value of the key (replacing the old one), and returned to the caller as a string.

+
+ 对该键的值加上给定的值,可以通过给定负值来减小对应键的值。
+ 如果键值不存在,则在操作前先设置为 0。如果发生以下情况,则返回错误:
+ - 键所对应的值是错误的类型(不是字符串);
+ - 该键的内容不能被解析为双精度浮点数。
+ 如果命令执行成功,则将新值替换旧值,并返回给调用方。

+
+ Document link: https://redis.io/commands/incrby
+ Available since 2.6.0. +
+ Key + The given value to be increased. + The value of key after the increment. +
+ + + MGET command (An Asynchronous Version)

+
+ Returns the values of all specified keys. For every key that does not hold a string value or does not exist, the special value nil is returned. Because of this, the operation never fails.

+
+ 返回所有给定键的值,对于其中个别键不存在,或其值不为字符串的,反悔特殊的 nil 值,因此 MGET 指令永远不会执行失败。

+
+ Document link: https://redis.io/commands/mget
+ Available since 1.0.0. +
+ Key list + A list of values at the specified keys. +
+ + + MGET command (An Asynchronous Version)

+
+ Returns the values of all specified keys. For every key that does not hold a string value or does not exist, the special value nil is returned. Because of this, the operation never fails.

+
+ 返回所有给定键的值,对于其中个别键不存在,或其值不为字符串的,反悔特殊的 nil 值,因此 MGET 指令永远不会执行失败。

+
+ Document link: https://redis.io/commands/mget
+ Available since 1.0.0. +
+ Key list + + A list of values at the specified keys. +
+ + + MSET command (An Asynchronous Version)

+
+ Sets the given keys to their respective values. MSET replaces existing values with new values, just as regular SET.

+
+ 将给定键的值设置为对应的新值。

+
+ Document link: https://redis.io/commands/mset
+ Available since 1.0.1. +
+ Key + Value + Other key-value sets +
+ + + MSET command (An Asynchronous Version)

+
+ Sets the given keys to their respective values. MSET replaces existing values with new values, just as regular SET.

+
+ 将给定键的值设置为对应的新值。

+
+ Document link: https://redis.io/commands/mset
+ Available since 1.0.1. +
+ Key-value sets + +
+ + + MSETNX command (An Asynchronous Version)

+
+ Sets the given keys to their respective values. MSETNX will not perform any operation at all even if just a single key already exists.

+
+ 将给定键的值设置为对应的新值。只要有一个键已经存在,则整组键值都将不会被设置。

+
+ Document link: https://redis.io/commands/msetnx
+ Available since 1.0.1. +
+ Key + Value + Other key-value sets + True if the all the keys were set. False if no key was set (at least one key already existed). +
+ + + MSETNX command (An Asynchronous Version)

+
+ Sets the given keys to their respective values. MSETNX will not perform any operation at all even if just a single key already exists.

+
+ 将给定键的值设置为对应的新值。只要有一个键已经存在,则整组键值都将不会被设置。

+
+ Document link: https://redis.io/commands/msetnx
+ Available since 1.0.1. +
+ Key-value sets + + True if the all the keys were set. False if no key was set (at least one key already existed). +
+ + + MSET key value [key value ...] command (An Asynchronous Version)

+
+ Sets the given keys to their respective values. MSET replaces existing values with new values, just as regular SET. See MSETNX if you don't want to overwrite existing values.

+
+ 将给定键的值设置为对应的新值。如果不想覆盖现有的值,可以使用 MSETNX 指令。

+
+ Document link: https://redis.io/commands/mset
+ Available since 1.0.1. +
+ Mark whether it is NX mode. If it is, use the MSETNX command; otherwise, use the MSET command. + Key + Value + Other key-value sets + + Always OK since MSET can't fail. +
+ + + PSETEX command (An Asynchronous Version)

+
+ PSETEX works exactly like SETEX with the sole difference that the expire time is specified in milliseconds instead of seconds.

+
+ PSETEX 的工作方式与 SETEX 完全相同,唯一的区别是到期时间的单位是毫秒(ms),而不是秒(s)。

+
+ Document link: https://redis.io/commands/psetex
+ Available since 2.6.0. +
+ Key + Timeout milliseconds value + Value + +
+ + + SET key value EX seconds (An Asynchronous Version)

+
+ Set key to hold the string value.

+
+ 设置键和值。

+
+ Document link: https://redis.io/commands/set
+ Available since 2.6.12. +
+ Key + Value + Timeout seconds + +
+ + + SET key value KEEPTTL command (An Asynchronous Version)

+
+ Set key to hold the string value. Retain the time to live associated with the key.

+
+ 设置键和值。

+ Document link: https://redis.io/commands/set
+ Available since 6.0.0. +
+ Key + Value + Retain the time to live associated with the key + +
+ + + SET key value EX seconds NX command (An Asynchronous Version)

+
+ Set key to hold the string value. Only set the key if it does not already exist.

+
+ 设置键和值。当且仅当键值不存在时才执行命令。

+
+ Document link: https://redis.io/commands/set
+ Available since 2.6.12. +
+ Key + Value + Timeout seconds + + + Simple string reply: OK if SET was executed correctly.
+ Bulk string reply: when GET option is set, the old value stored at key, or nil when key did not exist.
+ Null reply: a Null Bulk Reply is returned if the SET operation was not performed because the user specified the NX or XX option but the condition was not met or if user specified the NX and GET options that do not met. +
+
+ + + SET key value EX seconds XX command (An Asynchronous Version)

+
+ Set key to hold the string value. Only set the key if it already exist.

+
+ 设置键和值。当且仅当键值已存在时才执行命令。

+
+ Document link: https://redis.io/commands/set
+ Available since 2.6.12. +
+ Key + Value + Timeout seconds + + + Simple string reply: OK if SET was executed correctly.
+ Bulk string reply: when GET option is set, the old value stored at key, or nil when key did not exist.
+ Null reply: a Null Bulk Reply is returned if the SET operation was not performed because the user specified the NX or XX option but the condition was not met or if user specified the NX and GET options that do not met. +
+
+ + + SET key value KEEPTTL XX command (An Asynchronous Version)

+
+ Set key to hold the string value. Only set the key if it already exist.

+
+ 设置键和值。当且仅当键值已存在时才执行命令。

+
+ Document link: https://redis.io/commands/set
+ Available since 6.0.0. +
+ Key + Value + Retain the time to live associated with the key + + + Simple string reply: OK if SET was executed correctly.
+ Bulk string reply: when GET option is set, the old value stored at key, or nil when key did not exist.
+ Null reply: a Null Bulk Reply is returned if the SET operation was not performed because the user specified the NX or XX option but the condition was not met or if user specified the NX and GET options that do not met. +
+
+ + + SET command (An Asynchronous Version)

+
+ Set key to hold the string value. If key already holds a value, it is overwritten, regardless of its type.

+
+ 设置键和值。如果该键已存在,则覆盖之。

+
+ Document link: https://redis.io/commands/set
+ Available since 1.0.0. +
+ Key + Value + Timeout value + Retain the time to live associated with the key + Only set the key if it does not already exist. + Only set the key if it already exist. + + + + Simple string reply: OK if SET was executed correctly.
+ Bulk string reply: when GET option is set, the old value stored at key, or nil when key did not exist.
+ Null reply: a Null Bulk Reply is returned if the SET operation was not performed because the user specified the NX or XX option but the condition was not met or if user specified the NX and GET options that do not met. +
+
+ + + SETBIT command (An Asynchronous Version)

+
+ Sets or clears the bit at offset in the string value stored at key.

+
+ 设置或清除键值字符串指定偏移量的位(bit)。

+
+ Document link: https://redis.io/commands/setbit
+ Available since 2.2.0. +
+ Key + Offset value + New value + The original bit value stored at offset. +
+ + + SETEX command (An Asynchronous Version)

+
+ Set key to hold the string value and set key to timeout after a given number of seconds.

+
+ 设置键值在给定的秒数后超时。

+
+ Document link: https://redis.io/commands/setex
+ Available since 2.0.0. +
+ Key + Seconds + Value + +
+ + + SETNX command (An Asynchronous Version)

+
+ Set key to hold string value if key does not exist. In that case, it is equal to SET. When key already holds a value, no operation is performed.
+ SETNX is short for "SET if Not eXists".

+
+ 如果键值不存在,则设置该键值,在此情况下与 SET 指令相似。当键值已经存在时,不执行任何操作。

+
+ Document link: https://redis.io/commands/setnx
+ Available since 1.0.0. +
+ Key + Value + + Set result, specifically: 1 is if the key was set; 0 is if the key was not set. +
+ + + SETRANGE command (An Asynchronous Version)

+
+ Overwrites part of the string stored at key, starting at the specified offset, for the entire length of value. If the offset is larger than the current length of the string at key, the string is padded with zero-bytes to make offset fit. Non-existing keys are considered as empty strings, so this command will make sure it holds a string large enough to be able to set value at offset.

+
+ 从给定的偏移量开始覆盖键所对应的字符串值。如果偏移量大于值的长度,则为该字符串填充零字节(zero-bytes)以满足偏移量的要求。若键值不存在则视作空字符串值。故本指令将确保有足够长度的字符串以适应偏移量的要求。

+
+ Document link: https://redis.io/commands/setrange
+ Available since 2.2.0. +
+ Key + Offset value + The value to be filled. + + The length of the string after it was modified by the command. +
+ + + STRLRN command (An Asynchronous Version)

+
+ Returns the length of the string value stored at key. An error is returned when key holds a non-string value.

+
+ 返回键对应值字符串的长度。当该键对应的值不是字符串,则返回错误。

+
+ Document link: https://redis.io/commands/strlen
+ Available since 2.2.0. +
+ Key + The length of the string at key, or 0 when key does not exist.
diff --git a/src/FreeRedis/Internal/AsyncTestCode/AsyncRedisSocket.cs b/src/FreeRedis/Internal/AsyncTestCode/AsyncRedisSocket.cs index 977ab094..0a2a62bb 100644 --- a/src/FreeRedis/Internal/AsyncTestCode/AsyncRedisSocket.cs +++ b/src/FreeRedis/Internal/AsyncTestCode/AsyncRedisSocket.cs @@ -1,243 +1,243 @@ -#if isasync -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; +//#if isasync +//using System; +//using System.Collections.Concurrent; +//using System.Collections.Generic; +//using System.Diagnostics; +//using System.IO; +//using System.Linq; +//using System.Net; +//using System.Net.Sockets; +//using System.Text; +//using System.Threading; +//using System.Threading.Tasks; -namespace FreeRedis.Internal -{ - class AsyncRedisSocket - { - internal class Manager - { - RedisClient.BaseAdapter _adapter; - public Manager(RedisClient.BaseAdapter adapter) - { - _adapter = adapter; - } +//namespace FreeRedis.Internal +//{ +// class AsyncRedisSocket +// { +// internal class Manager +// { +// RedisClient.BaseAdapter _adapter; +// public Manager(RedisClient.BaseAdapter adapter) +// { +// _adapter = adapter; +// } - List _asyncRedisSockets = new List(); - int _asyncRedisSocketsCount = 0; - long _asyncRedisSocketsConcurrentCounter = 0; - object _asyncRedisSocketsLock = new object(); +// List _asyncRedisSockets = new List(); +// int _asyncRedisSocketsCount = 0; +// long _asyncRedisSocketsConcurrentCounter = 0; +// object _asyncRedisSocketsLock = new object(); - public AsyncRedisSocket GetAsyncRedisSocket(CommandPacket cmd) - { - AsyncRedisSocket asyncRds = null; - Interlocked.Increment(ref _asyncRedisSocketsConcurrentCounter); - for (var limit = 0; limit < 1000; limit += 1) - { - if (_asyncRedisSocketsCount > 0) - { - lock (_asyncRedisSocketsLock) - { - if (_asyncRedisSockets.Count > 0) - { - asyncRds = _asyncRedisSockets[RedisClient.BaseAdapter._rnd.Value.Next(_asyncRedisSockets.Count)]; - Interlocked.Increment(ref asyncRds._writeCounter); - Interlocked.Decrement(ref _asyncRedisSocketsConcurrentCounter); - return asyncRds; - } - } - } - if (limit > 50 && _asyncRedisSocketsConcurrentCounter < 2) break; - if (_asyncRedisSocketsCount > 1) Thread.CurrentThread.Join(2); - } - NewAsyncRedisSocket(); - //AsyncRedisSocket.sb.AppendLine($"线程{Thread.CurrentThread.ManagedThreadId}:AsyncRedisSockets 数量 {_asyncRedisSocketsCount} {_asyncRedisSocketsConcurrentCounter}"); - Interlocked.Decrement(ref _asyncRedisSocketsConcurrentCounter); - return asyncRds; +// public AsyncRedisSocket GetAsyncRedisSocket(CommandPacket cmd) +// { +// AsyncRedisSocket asyncRds = null; +// Interlocked.Increment(ref _asyncRedisSocketsConcurrentCounter); +// for (var limit = 0; limit < 1000; limit += 1) +// { +// if (_asyncRedisSocketsCount > 0) +// { +// lock (_asyncRedisSocketsLock) +// { +// if (_asyncRedisSockets.Count > 0) +// { +// asyncRds = _asyncRedisSockets[RedisClient.BaseAdapter._rnd.Value.Next(_asyncRedisSockets.Count)]; +// Interlocked.Increment(ref asyncRds._writeCounter); +// Interlocked.Decrement(ref _asyncRedisSocketsConcurrentCounter); +// return asyncRds; +// } +// } +// } +// if (limit > 50 && _asyncRedisSocketsConcurrentCounter < 2) break; +// if (_asyncRedisSocketsCount > 1) Thread.CurrentThread.Join(2); +// } +// NewAsyncRedisSocket(); +// //AsyncRedisSocket.sb.AppendLine($"线程{Thread.CurrentThread.ManagedThreadId}:AsyncRedisSockets 数量 {_asyncRedisSocketsCount} {_asyncRedisSocketsConcurrentCounter}"); +// Interlocked.Decrement(ref _asyncRedisSocketsConcurrentCounter); +// return asyncRds; - void NewAsyncRedisSocket() - { - var rds = _adapter.GetRedisSocket(cmd); - var key = Guid.NewGuid(); - asyncRds = new AsyncRedisSocket(rds, () => - { - if (_asyncRedisSocketsConcurrentCounter > 0 || _asyncRedisSocketsCount > 1) Thread.CurrentThread.Join(8); - Interlocked.Decrement(ref _asyncRedisSocketsCount); - lock (_asyncRedisSocketsLock) - _asyncRedisSockets.Remove(asyncRds); +// void NewAsyncRedisSocket() +// { +// var rds = _adapter.GetRedisSocket(cmd); +// var key = Guid.NewGuid(); +// asyncRds = new AsyncRedisSocket(rds, () => +// { +// if (_asyncRedisSocketsConcurrentCounter > 0 || _asyncRedisSocketsCount > 1) Thread.CurrentThread.Join(8); +// Interlocked.Decrement(ref _asyncRedisSocketsCount); +// lock (_asyncRedisSocketsLock) +// _asyncRedisSockets.Remove(asyncRds); - }, (innerRds, ioex) => - { - if (ioex != null && ioex is ProtocolViolationException == false) (rds as DefaultRedisSocket.TempProxyRedisSocket)._pool.SetUnavailable(ioex); - innerRds.Dispose(); - }, () => - { - lock (_asyncRedisSocketsLock) - { - if (asyncRds._writeCounter == 0) return true; - } - return false; - }); - lock (_asyncRedisSocketsLock) - { - Interlocked.Increment(ref asyncRds._writeCounter); - _asyncRedisSockets.Add(asyncRds); - } - Interlocked.Increment(ref _asyncRedisSocketsCount); - } - } - } +// }, (innerRds, ioex) => +// { +// if (ioex != null && ioex is ProtocolViolationException == false) (rds as DefaultRedisSocket.TempProxyRedisSocket)._pool.SetUnavailable(ioex); +// innerRds.Dispose(); +// }, () => +// { +// lock (_asyncRedisSocketsLock) +// { +// if (asyncRds._writeCounter == 0) return true; +// } +// return false; +// }); +// lock (_asyncRedisSocketsLock) +// { +// Interlocked.Increment(ref asyncRds._writeCounter); +// _asyncRedisSockets.Add(asyncRds); +// } +// Interlocked.Increment(ref _asyncRedisSocketsCount); +// } +// } +// } - internal readonly IRedisSocket _rds; - readonly Action _begin; - readonly Action _end; - readonly Func _finish; - internal long _writeCounter; - public AsyncRedisSocket(IRedisSocket rds, Action begin, Action end, Func finish) - { - _rds = rds; - _begin = begin; - _end = end; - _finish = finish; - _writeCounter = 0; - } +// internal readonly IRedisSocket _rds; +// readonly Action _begin; +// readonly Action _end; +// readonly Func _finish; +// internal long _writeCounter; +// public AsyncRedisSocket(IRedisSocket rds, Action begin, Action end, Func finish) +// { +// _rds = rds; +// _begin = begin; +// _end = end; +// _finish = finish; +// _writeCounter = 0; +// } - public Task WriteAsync(CommandPacket cmd) - { - var iq = WriteInQueue(cmd); - if (iq == null) return Task.FromResult(new RedisResult(null, true, RedisMessageType.SimpleString)); - return iq.TaskCompletionSource.Task; - } +// public Task WriteAsync(CommandPacket cmd) +// { +// var iq = WriteInQueue(cmd); +// if (iq == null) return Task.FromResult(new RedisResult(null, true, RedisMessageType.SimpleString)); +// return iq.TaskCompletionSource.Task; +// } - class WriteAsyncInfo - { - public TaskCompletionSource TaskCompletionSource; - public CommandPacket Command; - } - ConcurrentQueue _writeQueue = new ConcurrentQueue(); - object _writeAsyncLock = new object(); - MemoryStream _bufferStream; - bool _isfirst = false; - WriteAsyncInfo WriteInQueue(CommandPacket cmd) - { - var ret = new WriteAsyncInfo { Command = cmd }; - if (_rds.ClientReply == ClientReplyType.on) ret.TaskCompletionSource = new TaskCompletionSource(); - var isnew = false; - lock (_writeAsyncLock) - { - _writeQueue.Enqueue(ret); - if (_isfirst == false) - isnew = _isfirst = true; - if (_bufferStream == null) _bufferStream = new MemoryStream(); - new RespHelper.Resp3Writer(_bufferStream, _rds.Encoding, _rds.Protocol).WriteCommand(cmd); - } - if (isnew) - { - //Thread.CurrentThread.Join(TimeSpan.FromTicks(1000)); - try - { - if (_rds.IsConnected == false) _rds.Connect(); - } - catch (Exception ioex) - { - lock (_writeAsyncLock) - { - while (_writeQueue.TryDequeue(out var wq)) wq.TaskCompletionSource?.TrySetException(ioex); - _bufferStream.Close(); - _bufferStream.Dispose(); - _bufferStream = null; - } - _rds.ReleaseSocket(); - _end(_rds, ioex); - throw; - } +// class WriteAsyncInfo +// { +// public TaskCompletionSource TaskCompletionSource; +// public CommandPacket Command; +// } +// ConcurrentQueue _writeQueue = new ConcurrentQueue(); +// object _writeAsyncLock = new object(); +// MemoryStream _bufferStream; +// bool _isfirst = false; +// WriteAsyncInfo WriteInQueue(CommandPacket cmd) +// { +// var ret = new WriteAsyncInfo { Command = cmd }; +// if (_rds.ClientReply == ClientReplyType.on) ret.TaskCompletionSource = new TaskCompletionSource(); +// var isnew = false; +// lock (_writeAsyncLock) +// { +// _writeQueue.Enqueue(ret); +// if (_isfirst == false) +// isnew = _isfirst = true; +// if (_bufferStream == null) _bufferStream = new MemoryStream(); +// new RespHelper.Resp3Writer(_bufferStream, _rds.Encoding, _rds.Protocol).WriteCommand(cmd); +// } +// if (isnew) +// { +// //Thread.CurrentThread.Join(TimeSpan.FromTicks(1000)); +// try +// { +// if (_rds.IsConnected == false) _rds.Connect(); +// } +// catch (Exception ioex) +// { +// lock (_writeAsyncLock) +// { +// while (_writeQueue.TryDequeue(out var wq)) wq.TaskCompletionSource?.TrySetException(ioex); +// _bufferStream.Close(); +// _bufferStream.Dispose(); +// _bufferStream = null; +// } +// _rds.ReleaseSocket(); +// _end(_rds, ioex); +// throw; +// } - for (var a = 0; a < 100; a++) - { - var cou = _writeQueue.Count; - if (cou > 100) break; - } - var localQueue = new Queue(); - long localQueueThrowException(Exception exception) - { - long counter = 0; - while (localQueue.Any()) - { - var witem = localQueue.Dequeue(); - if (exception != null) witem.TaskCompletionSource?.TrySetException(exception); - else witem.TaskCompletionSource?.TrySetCanceled(); - counter = Interlocked.Decrement(ref _writeCounter); - } - return counter; - } +// for (var a = 0; a < 100; a++) +// { +// var cou = _writeQueue.Count; +// if (cou > 100) break; +// } +// var localQueue = new Queue(); +// long localQueueThrowException(Exception exception) +// { +// long counter = 0; +// while (localQueue.Any()) +// { +// var witem = localQueue.Dequeue(); +// if (exception != null) witem.TaskCompletionSource?.TrySetException(exception); +// else witem.TaskCompletionSource?.TrySetCanceled(); +// counter = Interlocked.Decrement(ref _writeCounter); +// } +// return counter; +// } - var iswait = _writeCounter > 1; - if (iswait) Thread.CurrentThread.Join(10); - _begin(); - while (true) - { - if (_writeQueue.Any() == false) - { - Thread.CurrentThread.Join(10); - continue; - } - lock (_writeAsyncLock) - { - while (_writeQueue.TryDequeue(out var wq)) - localQueue.Enqueue(wq); - _bufferStream.Position = 0; - try - { - _bufferStream.CopyTo(_rds.Stream); - _bufferStream.Close(); - _bufferStream.Dispose(); - _bufferStream = null; - } - catch (Exception ioex) - { - localQueueThrowException(ioex); - _bufferStream.Close(); - _bufferStream.Dispose(); - _bufferStream = null; - _rds.ReleaseSocket(); - _end(_rds, ioex); - throw; - } - } - long counter = 0; - RedisResult rt = null; - //sb.AppendLine($"{name} 线程{Thread.CurrentThread.ManagedThreadId}:合并读取 {localQueue.Count} 个命令 total:{sw.ElapsedMilliseconds} ms"); - while (localQueue.Any()) - { - var witem = localQueue.Dequeue(); - try - { - rt = _rds.Read(witem.Command); - } - catch (Exception ioex) - { - localQueueThrowException(ioex); - _rds.ReleaseSocket(); - _end(_rds, ioex); - throw; - } - witem.TaskCompletionSource.TrySetResult(rt); - counter = Interlocked.Decrement(ref _writeCounter); - } - if (counter == 0 && _finish()) - break; - Thread.CurrentThread.Join(1); - //sb.AppendLine($"{name} 线程{Thread.CurrentThread.ManagedThreadId}:等待 1ms + {_writeQueue.Count} total:{sw.ElapsedMilliseconds} ms"); - } - //sb.AppendLine($"{name} 线程{Thread.CurrentThread.ManagedThreadId}:退出 total:{sw.ElapsedMilliseconds} ms"); - _end(_rds, null); - } - return ret; - } +// var iswait = _writeCounter > 1; +// if (iswait) Thread.CurrentThread.Join(10); +// _begin(); +// while (true) +// { +// if (_writeQueue.Any() == false) +// { +// Thread.CurrentThread.Join(10); +// continue; +// } +// lock (_writeAsyncLock) +// { +// while (_writeQueue.TryDequeue(out var wq)) +// localQueue.Enqueue(wq); +// _bufferStream.Position = 0; +// try +// { +// _bufferStream.CopyTo(_rds.Stream); +// _bufferStream.Close(); +// _bufferStream.Dispose(); +// _bufferStream = null; +// } +// catch (Exception ioex) +// { +// localQueueThrowException(ioex); +// _bufferStream.Close(); +// _bufferStream.Dispose(); +// _bufferStream = null; +// _rds.ReleaseSocket(); +// _end(_rds, ioex); +// throw; +// } +// } +// long counter = 0; +// RedisResult rt = null; +// //sb.AppendLine($"{name} 线程{Thread.CurrentThread.ManagedThreadId}:合并读取 {localQueue.Count} 个命令 total:{sw.ElapsedMilliseconds} ms"); +// while (localQueue.Any()) +// { +// var witem = localQueue.Dequeue(); +// try +// { +// rt = _rds.Read(witem.Command); +// } +// catch (Exception ioex) +// { +// localQueueThrowException(ioex); +// _rds.ReleaseSocket(); +// _end(_rds, ioex); +// throw; +// } +// witem.TaskCompletionSource.TrySetResult(rt); +// counter = Interlocked.Decrement(ref _writeCounter); +// } +// if (counter == 0 && _finish()) +// break; +// Thread.CurrentThread.Join(1); +// //sb.AppendLine($"{name} 线程{Thread.CurrentThread.ManagedThreadId}:等待 1ms + {_writeQueue.Count} total:{sw.ElapsedMilliseconds} ms"); +// } +// //sb.AppendLine($"{name} 线程{Thread.CurrentThread.ManagedThreadId}:退出 total:{sw.ElapsedMilliseconds} ms"); +// _end(_rds, null); +// } +// return ret; +// } - //public string name = Guid.NewGuid().ToString(); - //public static StringBuilder sb = new StringBuilder(); - //public static Stopwatch sw = new Stopwatch(); - } -} -#endif \ No newline at end of file +// //public string name = Guid.NewGuid().ToString(); +// //public static StringBuilder sb = new StringBuilder(); +// //public static Stopwatch sw = new Stopwatch(); +// } +//} +//#endif \ No newline at end of file diff --git a/src/FreeRedis/Internal/DefaultRedisSocket.cs b/src/FreeRedis/Internal/DefaultRedisSocket.cs index 9923e358..c7c52b5f 100644 --- a/src/FreeRedis/Internal/DefaultRedisSocket.cs +++ b/src/FreeRedis/Internal/DefaultRedisSocket.cs @@ -67,6 +67,11 @@ public TempProxyRedisSocket(IRedisSocket owner, Action dispose) public void Write(CommandPacket cmd) => _owner.Write(cmd); public RedisResult Read(CommandPacket cmd) => _owner.Read(cmd); public void ReadChunk(Stream destination, int bufferSize = 1024) => _owner.ReadChunk(destination, bufferSize); +#if isasync + public Task WriteAsync(CommandPacket cmd) => _owner.WriteAsync(cmd); + public Task ReadAsync(CommandPacket cmd) => _owner.ReadAsync(cmd); + public Task ReadChunkAsync(Stream destination, int bufferSize = 1024) => _owner.ReadChunkAsync(destination, bufferSize); +#endif } public string Host { get; private set; } @@ -122,17 +127,8 @@ public DefaultRedisSocket(string host, bool ssl) Ssl = ssl; } - public void Write(CommandPacket cmd) + void WriteAfter(CommandPacket cmd) { - LastCommand = cmd; - if (IsConnected == false) Connect(); - using (var ms = new MemoryStream()) //Writing data directly to will be very slow - { - new RespHelper.Resp3Writer(ms, Encoding, Protocol).WriteCommand(cmd); - ms.Position = 0; - ms.CopyTo(Stream); - ms.Close(); - } switch (cmd._command) { case "CLIENT": @@ -157,6 +153,19 @@ public void Write(CommandPacket cmd) } cmd.WriteTarget = $"{this.Host}/{this.Database}"; } + public void Write(CommandPacket cmd) + { + LastCommand = cmd; + if (IsConnected == false) Connect(); + using (var ms = new MemoryStream()) //Writing data directly to will be very slow + { + new RespHelper.Resp3Writer(ms, Encoding, Protocol).WriteCommand(cmd); + ms.Position = 0; + ms.CopyTo(Stream); + ms.Close(); + } + WriteAfter(cmd); + } public RedisResult Read(CommandPacket cmd) { LastCommand = cmd; @@ -178,6 +187,42 @@ public void ReadChunk(Stream destination, int bufferSize = 1024) Reader.ReadBlobStringChunk(destination, bufferSize); } } +#if isasync + async public Task WriteAsync(CommandPacket cmd) + { + LastCommand = cmd; + if (IsConnected == false) Connect(); + using (var ms = new MemoryStream()) //Writing data directly to will be very slow + { + new RespHelper.Resp3Writer(ms, Encoding, Protocol).WriteCommand(cmd); + ms.Position = 0; + await ms.CopyToAsync(Stream); + ms.Close(); + } + WriteAfter(cmd); + } + async public Task ReadAsync(CommandPacket cmd) + { + LastCommand = cmd; + if (ClientReply == ClientReplyType.on) + { + if (IsConnected == false) Connect(); + var rt = await Reader.ReadObjectAsync(cmd?._flagReadbytes == true ? null : Encoding); + rt.Encoding = Encoding; + cmd?.OnDataTrigger(rt); + return rt; + } + return new RedisResult(null, true, RedisMessageType.SimpleString) { Encoding = Encoding }; + } + async public Task ReadChunkAsync(Stream destination, int bufferSize = 1024) + { + if (ClientReply == ClientReplyType.on) + { + if (IsConnected == false) Connect(); + await Reader.ReadBlobStringChunkAsync(destination, bufferSize); + } + } +#endif object _connectLock = new object(); public void Connect() diff --git a/src/FreeRedis/Internal/IRedisSocket.cs b/src/FreeRedis/Internal/IRedisSocket.cs index a4ab2932..44e3f614 100644 --- a/src/FreeRedis/Internal/IRedisSocket.cs +++ b/src/FreeRedis/Internal/IRedisSocket.cs @@ -31,6 +31,11 @@ public interface IRedisSocket : IDisposable void Write(CommandPacket cmd); RedisResult Read(CommandPacket cmd); void ReadChunk(Stream destination, int bufferSize = 1024); +#if isasync + Task WriteAsync(CommandPacket cmd); + Task ReadAsync(CommandPacket cmd); + Task ReadChunkAsync(Stream destination, int bufferSize = 1024); +#endif ClientReplyType ClientReply { get; } long ClientId { get; } int Database { get; } diff --git a/src/FreeRedis/Internal/RespHelper.cs b/src/FreeRedis/Internal/RespHelper.cs index 361895bc..93635c43 100644 --- a/src/FreeRedis/Internal/RespHelper.cs +++ b/src/FreeRedis/Internal/RespHelper.cs @@ -232,55 +232,55 @@ public RedisResult ReadObject(Encoding encoding) default: if (b == -1) return new RedisResult(null, true, RedisMessageType.Null); //if (b == -1) return new RedisResult(null, true, RedisMessageType.Null); - var allBytes = ReadAll(); + var allBytes = DebugReadAll(); throw new ProtocolViolationException($"Expecting fail MessageType '{b},{string.Join(",", allBytes)}'"); } } + } - Byte[] ReadAll() + Byte[] DebugReadAll() + { + var ns = _stream as NetworkStream; + if (ns != null) { - var ns = _stream as NetworkStream; - if (ns != null) + using (var ms = new MemoryStream()) { - using (var ms = new MemoryStream()) + try { - try + var data = new byte[1024]; + while (ns.DataAvailable && ns.CanRead) { - var data = new byte[1024]; - while (ns.DataAvailable && ns.CanRead) - { - int numBytesRead = numBytesRead = ns.Read(data, 0, data.Length); - if (numBytesRead <= 0) break; - ms.Write(data, 0, numBytesRead); - if (numBytesRead < data.Length) break; - } + int numBytesRead = numBytesRead = ns.Read(data, 0, data.Length); + if (numBytesRead <= 0) break; + ms.Write(data, 0, numBytesRead); + if (numBytesRead < data.Length) break; } - catch { } - return ms.ToArray(); } + catch { } + return ms.ToArray(); } - var ss = _stream as SslStream; - if (ss != null) + } + var ss = _stream as SslStream; + if (ss != null) + { + using (var ms = new MemoryStream()) { - using (var ms = new MemoryStream()) + try { - try + var data = new byte[1024]; + while (ss.CanRead) { - var data = new byte[1024]; - while (ss.CanRead) - { - int numBytesRead = numBytesRead = ss.Read(data, 0, data.Length); - if (numBytesRead <= 0) break; - ms.Write(data, 0, numBytesRead); - if (numBytesRead < data.Length) break; - } + int numBytesRead = numBytesRead = ss.Read(data, 0, data.Length); + if (numBytesRead <= 0) break; + ms.Write(data, 0, numBytesRead); + if (numBytesRead < data.Length) break; } - catch { } - return ms.ToArray(); } + catch { } + return ms.ToArray(); } - return new byte[0]; } + return new byte[0]; } void Read(Stream outStream, int len, int bufferSize = 1024) diff --git a/src/FreeRedis/Internal/RespHelperAsync.cs b/src/FreeRedis/Internal/RespHelperAsync.cs index ffebe7c7..78e03ddf 100644 --- a/src/FreeRedis/Internal/RespHelperAsync.cs +++ b/src/FreeRedis/Internal/RespHelperAsync.cs @@ -1,262 +1,191 @@ -//#if isasync -//using System; -//using System.Collections; -//using System.Collections.Concurrent; -//using System.Collections.Generic; -//using System.Globalization; -//using System.IO; -//using System.Linq; -//using System.Linq.Expressions; -//using System.Net; -//using System.Net.Security; -//using System.Net.Sockets; -//using System.Numerics; -//using System.Reflection; -//using System.Text; -//using System.Threading.Tasks; +#if isasync +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using System.Threading.Tasks; -//namespace FreeRedis -//{ -// partial class RespHelper -// { -// partial class Resp3Reader -// { -// //Stream _stream; //test code +namespace FreeRedis +{ + partial class RespHelper + { + partial class Resp3Reader + { + async public Task ReadBlobStringChunkAsync(Stream destination, int bufferSize) + { + char c = (char)_stream.ReadByte(); + switch (c) + { + case '$': + case '=': + case '!': await ReadBlobStringAsync(c, null, destination, bufferSize); break; + default: throw new ProtocolViolationException($"Expecting fail MessageType '{c}'"); + } + } -// async Task ReadByteAsync() -// { -// var bytes = new byte[1]; -// var readed = await _stream.ReadAsync(bytes, 0, 1); -// if (readed <= 0) throw new ProtocolViolationException($"Expecting fail ReadByte end of stream"); -// return bytes.FirstOrDefault(); -// } -// async Task ReadCharAsync() => (char)(await ReadByteAsync()); + async Task ReadBlobStringAsync(char msgtype, Encoding encoding, Stream destination, int bufferSize) + { + var clob = await ReadClobAsync(); + if (encoding == null) return clob; + if (clob == null) return null; + return encoding.GetString(clob); -// async public Task ReadBlobStringChunkAsync(Stream destination, int bufferSize) -// { -// char c = await ReadCharAsync(); -// switch (c) -// { -// case '$': -// case '=': -// case '!': await ReadBlobStringAsync(c, null, destination, bufferSize); break; -// default: throw new ProtocolViolationException($"Expecting fail MessageType '{c}'"); -// } -// } + async Task ReadClobAsync() + { + MemoryStream ms = null; + try + { + if (destination == null) destination = ms = new MemoryStream(); + var lenstr = ReadLine(null); + if (int.TryParse(lenstr, out var len)) + { + if (len < 0) return null; + if (len > 0) await ReadAsync(destination, len, bufferSize); + ReadLine(null); + if (len == 0) return new byte[0]; + return ms?.ToArray(); + } + if (lenstr == "?") + { + while (true) + { + char c = (char)_stream.ReadByte(); + if (c != ';') throw new ProtocolViolationException($"Expecting fail Streamed strings ';', got '{c}'"); + var clenstr = ReadLine(null); + if (int.TryParse(clenstr, out var clen)) + { + if (clen == 0) break; + if (clen > 0) + { + await ReadAsync(destination, clen, bufferSize); + ReadLine(null); + continue; + } + } + throw new ProtocolViolationException($"Expecting fail Streamed strings ';0', got ';{clenstr}'"); + } + return ms?.ToArray(); + } + throw new ProtocolViolationException($"Expecting fail Blob string '{msgtype}0', got '{msgtype}{lenstr}'"); + } + finally + { + ms?.Close(); + ms?.Dispose(); + } + } + } -// async Task ReadBlobStringAsync(char msgtype, Encoding encoding, Stream destination, int bufferSize) -// { -// var clob = await ReadClob(); -// if (encoding == null) return clob; -// if (clob == null) return null; -// return encoding.GetString(clob); + async Task ReadArrayAsync(char msgtype, Encoding encoding) + { + var lenstr = ReadLine(null); + if (int.TryParse(lenstr, out var len)) + { + if (len < 0) return null; + var arr = new object[len]; + for (var a = 0; a < len; a++) + arr[a] = (await ReadObjectAsync(encoding)).Value; + if (len == 1 && arr[0] == null) return new object[0]; + return arr; + } + if (lenstr == "?") + { + var arr = new List(); + while (true) + { + var ro = await ReadObjectAsync(encoding); + if (ro.IsEnd) break; + arr.Add(ro.Value); + } + return arr.ToArray(); + } + throw new ProtocolViolationException($"Expecting fail Array '{msgtype}3', got '{msgtype}{lenstr}'"); + } + async Task ReadMapAsync(char msgtype, Encoding encoding) + { + var lenstr = ReadLine(null); + if (int.TryParse(lenstr, out var len)) + { + if (len < 0) return null; + var arr = new object[len * 2]; + for (var a = 0; a < len; a++) + { + arr[a * 2] = (await ReadObjectAsync(encoding)).Value; + arr[a * 2 + 1] = (await ReadObjectAsync(encoding)).Value; + } + return arr; + } + if (lenstr == "?") + { + var arr = new List(); + while (true) + { + var rokey = await ReadObjectAsync(encoding); + if (rokey.IsEnd) break; + var roval = await ReadObjectAsync(encoding); + arr.Add(rokey.Value); + arr.Add(roval.Value); + } + return arr.ToArray(); + } + throw new ProtocolViolationException($"Expecting fail Map '{msgtype}3', got '{msgtype}{lenstr}'"); + } -// async Task ReadClob() -// { -// MemoryStream ms = null; -// try -// { -// if (destination == null) destination = ms = new MemoryStream(); -// var lenstr = await ReadLineAsync(null); -// if (int.TryParse(lenstr, out var len)) -// { -// if (len < 0) return null; -// if (len > 0) await ReadAsync(destination, len, bufferSize); -// await ReadLineAsync(null); -// if (len == 0) return new byte[0]; -// return ms?.ToArray(); -// } -// if (lenstr == "?") -// { -// while (true) -// { -// char c = await ReadCharAsync(); -// if (c != ';') throw new ProtocolViolationException($"Expecting fail Streamed strings ';', got '{c}'"); -// var clenstr = await ReadLineAsync(null); -// if (int.TryParse(clenstr, out var clen)) -// { -// if (clen == 0) break; -// if (clen > 0) -// { -// await ReadAsync(destination, clen, bufferSize); -// await ReadLineAsync(null); -// continue; -// } -// } -// throw new ProtocolViolationException($"Expecting fail Streamed strings ';0', got ';{clenstr}'"); -// } -// return ms?.ToArray(); -// } -// throw new ProtocolViolationException($"Expecting fail Blob string '{msgtype}0', got '{msgtype}{lenstr}'"); -// } -// finally -// { -// ms?.Close(); -// ms?.Dispose(); -// } -// } -// } -// Task ReadSimpleStringAsync() -// { -// return ReadLineAsync(null); -// } -// async Task ReadNumberAsync(char msgtype) -// { -// var numstr = await ReadLineAsync(null); -// if (long.TryParse(numstr, out var num)) return num; -// throw new ProtocolViolationException($"Expecting fail Number '{msgtype}0', got '{msgtype}{numstr}'"); -// } -// async Task ReadBigNumberAsync(char msgtype) -// { -// var numstr = await ReadLineAsync(null); -// if (BigInteger.TryParse(numstr, NumberStyles.Any, null, out var num)) return num; -// throw new ProtocolViolationException($"Expecting fail BigNumber '{msgtype}0', got '{msgtype}{numstr}'"); -// } -// async Task ReadDoubleAsync(char msgtype) -// { -// var numstr = await ReadLineAsync(null); -// switch (numstr) -// { -// case "inf": return double.PositiveInfinity; -// case "-inf": return double.NegativeInfinity; -// } -// if (double.TryParse(numstr, NumberStyles.Any, null, out var num)) return num; -// throw new ProtocolViolationException($"Expecting fail Double '{msgtype}1.23', got '{msgtype}{numstr}'"); -// } -// async Task ReadBooleanAsync(char msgtype) -// { -// var boolstr = await ReadLineAsync(null); -// switch (boolstr) -// { -// case "t": return true; -// case "f": return false; -// } -// throw new ProtocolViolationException($"Expecting fail Boolean '{msgtype}t', got '{msgtype}{boolstr}'"); -// } + async public Task ReadObjectAsync(Encoding encoding) + { + while (true) + { + var b = _stream.ReadByte(); + var c = (char)b; + //debugger++; + //if (debugger > 10000 && debugger % 10 == 0) + // throw new ProtocolViolationException($"Expecting fail MessageType '{b},{string.Join(",", ReadAll())}'"); + switch (c) + { + case '$': return new RedisResult(await ReadBlobStringAsync(c, encoding, null, 1024), false, RedisMessageType.BlobString); + case '+': return new RedisResult(ReadSimpleString(), false, RedisMessageType.SimpleString); + case '=': return new RedisResult(await ReadBlobStringAsync(c, encoding, null, 1024), false, RedisMessageType.VerbatimString); + case '-': return new RedisResult(ReadSimpleString(), false, RedisMessageType.SimpleError); + case '!': return new RedisResult(await ReadBlobStringAsync(c, encoding, null, 1024), false, RedisMessageType.BlobError); + case ':': return new RedisResult(ReadNumber(c), false, RedisMessageType.Number); + case '(': return new RedisResult(ReadBigNumber(c), false, RedisMessageType.BigNumber); + case '_': ReadLine(null); return new RedisResult(null, false, RedisMessageType.Null); + case ',': return new RedisResult(ReadDouble(c), false, RedisMessageType.Double); + case '#': return new RedisResult(ReadBoolean(c), false, RedisMessageType.Boolean); -// async Task ReadArrayAsync(char msgtype, Encoding encoding) -// { -// var lenstr = await ReadLineAsync(null); -// if (int.TryParse(lenstr, out var len)) -// { -// if (len < 0) return null; -// var arr = new object[len]; -// for (var a = 0; a < len; a++) -// arr[a] = (await ReadObjectAsync(encoding)).Value; -// if (len == 1 && arr[0] == null) return new object[0]; -// return arr; -// } -// if (lenstr == "?") -// { -// var arr = new List(); -// while (true) -// { -// var ro = await ReadObjectAsync(encoding); -// if (ro.IsEnd) break; -// arr.Add(ro.Value); -// } -// return arr.ToArray(); -// } -// throw new ProtocolViolationException($"Expecting fail Array '{msgtype}3', got '{msgtype}{lenstr}'"); -// } -// async Task ReadMapAsync(char msgtype, Encoding encoding) -// { -// var lenstr = await ReadLineAsync(null); -// if (int.TryParse(lenstr, out var len)) -// { -// if (len < 0) return null; -// var arr = new object[len * 2]; -// for (var a = 0; a < len; a++) -// { -// arr[a * 2] = (await ReadObjectAsync(encoding)).Value; -// arr[a * 2 + 1] = (await ReadObjectAsync(encoding)).Value; -// } -// return arr; -// } -// if (lenstr == "?") -// { -// var arr = new List(); -// while (true) -// { -// var rokey = await ReadObjectAsync(encoding); -// if (rokey.IsEnd) break; -// var roval = await ReadObjectAsync(encoding); -// arr.Add(rokey.Value); -// arr.Add(roval.Value); -// } -// return arr.ToArray(); -// } -// throw new ProtocolViolationException($"Expecting fail Map '{msgtype}3', got '{msgtype}{lenstr}'"); -// } + case '*': return new RedisResult(await ReadArrayAsync(c, encoding), false, RedisMessageType.Array); + case '~': return new RedisResult(await ReadArrayAsync(c, encoding), false, RedisMessageType.Set); + case '>': return new RedisResult(await ReadArrayAsync(c, encoding), false, RedisMessageType.Push); + case '%': return new RedisResult(await ReadMapAsync(c, encoding), false, RedisMessageType.Map); + case '|': return new RedisResult(await ReadMapAsync(c, encoding), false, RedisMessageType.Attribute); + case '.': ReadLine(null); return new RedisResult(null, true, RedisMessageType.SimpleString); //无类型 + case ' ': continue; + default: + if (b == -1) return new RedisResult(null, true, RedisMessageType.Null); + //if (b == -1) return new RedisResult(null, true, RedisMessageType.Null); + var allBytes = DebugReadAll(); + throw new ProtocolViolationException($"Expecting fail MessageType '{b},{string.Join(",", allBytes)}'"); + } + } + } -// async public Task ReadObjectAsync(Encoding encoding) -// { -// while (true) -// { -// var c = await ReadCharAsync(); -// switch (c) -// { -// case '$': return new RedisResult(await ReadBlobStringAsync(c, encoding, null, 1024), false, RedisMessageType.BlobString); -// case '+': return new RedisResult(await ReadSimpleStringAsync(), false, RedisMessageType.SimpleString); -// case '=': return new RedisResult(await ReadBlobStringAsync(c, encoding, null, 1024), false, RedisMessageType.VerbatimString); -// case '-': return new RedisResult(await ReadSimpleStringAsync(), false, RedisMessageType.SimpleError); -// case '!': return new RedisResult(await ReadBlobStringAsync(c, encoding, null, 1024), false, RedisMessageType.BlobError); -// case ':': return new RedisResult(await ReadNumberAsync(c), false, RedisMessageType.Number); -// case '(': return new RedisResult(await ReadBigNumberAsync(c), false, RedisMessageType.BigNumber); -// case '_': await ReadLineAsync(null); return new RedisResult(null, false, RedisMessageType.Null); -// case ',': return new RedisResult(await ReadDoubleAsync(c), false, RedisMessageType.Double); -// case '#': return new RedisResult(await ReadBooleanAsync(c), false, RedisMessageType.Boolean); - -// case '*': return new RedisResult(await ReadArrayAsync(c, encoding), false, RedisMessageType.Array); -// case '~': return new RedisResult(await ReadArrayAsync(c, encoding), false, RedisMessageType.Set); -// case '>': return new RedisResult(await ReadArrayAsync(c, encoding), false, RedisMessageType.Push); -// case '%': return new RedisResult(await ReadMapAsync(c, encoding), false, RedisMessageType.Map); -// case '|': return new RedisResult(await ReadMapAsync(c, encoding), false, RedisMessageType.Attribute); -// case '.': await ReadLineAsync(null); return new RedisResult(null, true, RedisMessageType.SimpleString); //无类型 -// case ' ': continue; -// default: throw new ProtocolViolationException($"Expecting fail MessageType '{c}'"); -// } -// } -// } - -// async Task ReadAsync(Stream outStream, int len, int bufferSize = 1024) -// { -// if (len <= 0) return; -// var buffer = new byte[Math.Min(bufferSize, len)]; -// var bufferLength = buffer.Length; -// while (true) -// { -// var readed = await _stream.ReadAsync(buffer, 0, bufferLength); -// if (readed <= 0) throw new ProtocolViolationException($"Expecting fail Read surplus length: {len}"); -// if (readed > 0) await outStream.WriteAsync(buffer, 0, readed); -// len = len - readed; -// if (len <= 0) break; -// if (len < buffer.Length) bufferLength = len; -// } -// } -// async Task ReadLineAsync(Stream outStream) -// { -// var sb = outStream == null ? new StringBuilder() : null; -// var buffer = new byte[1]; -// var should_break = false; -// while (true) -// { -// var readed = await _stream.ReadAsync(buffer, 0, 1); -// if (readed <= 0) throw new ProtocolViolationException($"Expecting fail ReadLine end of stream"); -// if (buffer[0] == 13) -// should_break = true; -// else if (buffer[0] == 10 && should_break) -// break; -// else -// { -// if (outStream == null) sb.Append((char)buffer[0]); -// else await outStream.WriteAsync(buffer, 0, 1); -// should_break = false; -// } -// } -// return sb?.ToString(); -// } -// } -// } -//} -//#endif \ No newline at end of file + async Task ReadAsync(Stream outStream, int len, int bufferSize = 1024) + { + if (len <= 0) return; + var buffer = new byte[Math.Min(bufferSize, len)]; + var bufferLength = buffer.Length; + while (true) + { + var readed = await _stream.ReadAsync(buffer, 0, bufferLength); + if (readed <= 0) throw new ProtocolViolationException($"Expecting fail Read surplus length: {len}"); + if (readed > 0) await outStream.WriteAsync(buffer, 0, readed); + len = len - readed; + if (len <= 0) break; + if (len < buffer.Length) bufferLength = len; + } + } + } + } +} +#endif \ No newline at end of file diff --git a/src/FreeRedis/RedisClient.cs b/src/FreeRedis/RedisClient.cs index 9febf1c4..0e08c672 100644 --- a/src/FreeRedis/RedisClient.cs +++ b/src/FreeRedis/RedisClient.cs @@ -1,16 +1,9 @@ -using FreeRedis.Internal; -using FreeRedis.Internal.ObjectPool; -using System; +using System; using System.Collections.Generic; -using System.Collections.Concurrent; -using System.IO; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; -using System.Diagnostics; -using System.Collections; -using System.Runtime.InteropServices; -using System.Reflection; namespace FreeRedis { diff --git a/src/FreeRedis/RedisClient/Adapter/ClusterAdapter.cs b/src/FreeRedis/RedisClient/Adapter/ClusterAdapter.cs index 324f23c8..d88485b6 100644 --- a/src/FreeRedis/RedisClient/Adapter/ClusterAdapter.cs +++ b/src/FreeRedis/RedisClient/Adapter/ClusterAdapter.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using System.Text; +using System.Threading.Tasks; namespace FreeRedis { @@ -74,6 +75,7 @@ public override IRedisSocket GetRedisSocket(CommandPacket cmd) rdsproxy._pool = pool; return rdsproxy; } + public override TValue AdapterCall(CommandPacket cmd, Func parse) { if (cmd._keyIndexes.Count > 1) //Multiple key slot values not equal @@ -169,10 +171,99 @@ public override TValue AdapterCall(CommandPacket cmd, Func AdapterCallAsync(CommandPacket cmd, Func parse) + async public override Task AdapterCallAsync(CommandPacket cmd, Func parse) { - //Single socket not support Async Multiplexing - return Task.FromResult(AdapterCall(cmd, parse)); + if (cmd._keyIndexes.Count > 1) //Multiple key slot values not equal + { + cmd.Prefix(TopOwner.Prefix); + switch (cmd._command) + { + case "DEL": + case "UNLINK": + return cmd._keyIndexes.Select((_, idx) => AdapterCall(cmd._command.InputKey(cmd.GetKey(idx)), parse)).Sum(a => a.ConvertTo()).ConvertTo(); + case "MSET": + cmd._keyIndexes.ForEach(idx => AdapterCall(cmd._command.InputKey(cmd._input[idx].ToInvariantCultureToString()).InputRaw(cmd._input[idx + 1]), parse)); + return default; + case "MGET": + return cmd._keyIndexes.Select((_, idx) => + { + var rt = AdapterCall(cmd._command.InputKey(cmd.GetKey(idx)), parse); + return rt.ConvertTo().FirstOrDefault(); + }).ToArray().ConvertTo(); + case "PFCOUNT": + return cmd._keyIndexes.Select((_, idx) => AdapterCall(cmd._command.InputKey(cmd.GetKey(idx)), parse)).Sum(a => a.ConvertTo()).ConvertTo(); + } + } + return await TopOwner.LogCallAsync(cmd, async () => + { + RedisResult rt = null; + RedisClientPool pool = null; + var protocolRetry = false; + using (var rds = GetRedisSocket(cmd)) + { + var getTime = DateTime.Now; + pool = (rds as DefaultRedisSocket.TempProxyRedisSocket)._pool; + try + { + if (cmd._clusterMovedAsking) + { + cmd._clusterMovedAsking = false; + var askingCmd = "ASKING".SubCommand(null).FlagReadbytes(false); + await rds.WriteAsync(askingCmd); + await rds.ReadAsync(askingCmd); + } + await rds.WriteAsync(cmd); + rt = await rds.ReadAsync(cmd); + } + catch (ProtocolViolationException) + { + rds.ReleaseSocket(); + cmd._protocolErrorTryCount++; + if (cmd._protocolErrorTryCount <= pool._policy._connectionStringBuilder.Retry) + protocolRetry = true; + else + { + if (cmd.IsReadOnlyCommand() == false || cmd._protocolErrorTryCount > 1) throw; + protocolRetry = true; + } + } + catch (Exception ex) + { + if (pool?.SetUnavailable(ex, getTime) == true) + { + } + throw; + } + } + if (protocolRetry) return await AdapterCallAsync(cmd, parse); + if (rt.IsError && pool != null) + { + var moved = ClusterMoved.ParseSimpleError(rt.SimpleError); + if (moved != null && cmd._clusterMovedTryCount < 3) + { + cmd._clusterMovedTryCount++; + + if (moved.endpoint.StartsWith("127.0.0.1")) + moved.endpoint = $"{DefaultRedisSocket.SplitHost(pool._policy._connectionStringBuilder.Host).Key}:{moved.endpoint.Substring(10)}"; + else if (moved.endpoint.StartsWith("localhost", StringComparison.CurrentCultureIgnoreCase)) + moved.endpoint = $"{DefaultRedisSocket.SplitHost(pool._policy._connectionStringBuilder.Host).Key}:{moved.endpoint.Substring(10)}"; + + ConnectionStringBuilder connectionString = pool._policy._connectionStringBuilder.ToString(); + connectionString.Host = moved.endpoint; + RegisterClusterNode(connectionString); + + if (moved.ismoved) + _slotCache.AddOrUpdate(moved.slot, connectionString.Host, (k1, v1) => connectionString.Host); + + if (moved.isask) + cmd._clusterMovedAsking = true; + + TopOwner.OnNotice(null, new NoticeEventArgs(NoticeType.Info, null, $"{(cmd.WriteTarget ?? "Not connected").PadRight(21)} > {cmd}\r\n{rt.SimpleError} ", null)); + return await AdapterCallAsync(cmd, parse); + } + } + return parse(rt); + }); } #endif diff --git a/src/FreeRedis/RedisClient/Adapter/NormanAdapter.cs b/src/FreeRedis/RedisClient/Adapter/NormanAdapter.cs index da1ce363..e463f2c3 100644 --- a/src/FreeRedis/RedisClient/Adapter/NormanAdapter.cs +++ b/src/FreeRedis/RedisClient/Adapter/NormanAdapter.cs @@ -140,10 +140,66 @@ public override TValue AdapterCall(CommandPacket cmd, Func AdapterCallAsync(CommandPacket cmd, Func parse) + async public override Task AdapterCallAsync(CommandPacket cmd, Func parse) { - //Single socket not support Async Multiplexing - return Task.FromResult(AdapterCall(cmd, parse)); + if (cmd._keyIndexes.Count > 1) //Multiple key slot values not equal + { + cmd.Prefix(TopOwner.Prefix); + switch (cmd._command) + { + case "DEL": + case "UNLINK": + return cmd._keyIndexes.Select((_, idx) => AdapterCall(new CommandPacket(cmd._command).InputKey(cmd.GetKey(idx)), parse)).Sum(a => a.ConvertTo()).ConvertTo(); + case "MSET": + cmd._keyIndexes.ForEach(idx => AdapterCall(new CommandPacket(cmd._command).InputKey(cmd._input[idx].ToInvariantCultureToString()).InputRaw(cmd._input[idx + 1]), parse)); + return default; + case "MGET": + return cmd._keyIndexes.Select((_, idx) => + { + var rt = AdapterCall(cmd._command.InputKey(cmd.GetKey(idx)), parse); + return rt.ConvertTo().FirstOrDefault(); + }).ToArray().ConvertTo(); + case "PFCOUNT": + return cmd._keyIndexes.Select((_, idx) => AdapterCall(new CommandPacket(cmd._command).InputKey(cmd.GetKey(idx)), parse)).Sum(a => a.ConvertTo()).ConvertTo(); + } + } + return await TopOwner.LogCallAsync(cmd, async () => + { + RedisResult rt = null; + var protocolRetry = false; + using (var rds = GetRedisSocket(cmd)) + { + var getTime = DateTime.Now; + try + { + await rds.WriteAsync(cmd); + rt = await rds.ReadAsync(cmd); + } + catch (ProtocolViolationException) + { + var pool = (rds as DefaultRedisSocket.TempProxyRedisSocket)._pool; + rds.ReleaseSocket(); + cmd._protocolErrorTryCount++; + if (cmd._protocolErrorTryCount <= pool._policy._connectionStringBuilder.Retry) + protocolRetry = true; + else + { + if (cmd.IsReadOnlyCommand() == false || cmd._protocolErrorTryCount > 1) throw; + protocolRetry = true; + } + } + catch (Exception ex) + { + var pool = (rds as DefaultRedisSocket.TempProxyRedisSocket)._pool; + if (pool?.SetUnavailable(ex, getTime) == true) + { + } + throw; + } + } + if (protocolRetry) return await AdapterCallAsync(cmd, parse); + return parse(rt); + }); } #endif diff --git a/src/FreeRedis/RedisClient/Adapter/PoolingAdapter.cs b/src/FreeRedis/RedisClient/Adapter/PoolingAdapter.cs index a55d8bb4..75d6228b 100644 --- a/src/FreeRedis/RedisClient/Adapter/PoolingAdapter.cs +++ b/src/FreeRedis/RedisClient/Adapter/PoolingAdapter.cs @@ -34,10 +34,6 @@ public PoolingAdapter(RedisClient topOwner, ConnectionStringBuilder connectionSt if (_rw_splitting) foreach (var slave in slaveConnectionStrings) _ib.TryRegister($"slave_{slave.Host}", () => new RedisClientPool(slave, null, TopOwner)); - -#if isasync - _asyncManager = new AsyncRedisSocket.Manager(this); -#endif } bool isdisposed = false; @@ -110,29 +106,44 @@ public override TValue AdapterCall(CommandPacket cmd, Func AdapterCallAsync(CommandPacket cmd, Func parse) { return TopOwner.LogCallAsync(cmd, async () => { - var asyncRds = _asyncManager.GetAsyncRedisSocket(cmd); - try - { - var rt = await asyncRds.WriteAsync(cmd); - return parse(rt); - } - catch (ProtocolViolationException) + RedisResult rt = null; + var protocolRetry = false; + using (var rds = GetRedisSocket(cmd)) { - var pool = (asyncRds._rds as DefaultRedisSocket.TempProxyRedisSocket)?._pool; - cmd._protocolErrorTryCount++; - if (pool != null && cmd._protocolErrorTryCount <= pool._policy._connectionStringBuilder.Retry) - return await AdapterCallAsync(cmd, parse); - else + var getTime = DateTime.Now; + try + { + await rds.WriteAsync(cmd); + rt = await rds.ReadAsync(cmd); + } + catch (ProtocolViolationException) + { + var pool = (rds as DefaultRedisSocket.TempProxyRedisSocket)._pool; + rds.ReleaseSocket(); + cmd._protocolErrorTryCount++; + if (cmd._protocolErrorTryCount <= pool._policy._connectionStringBuilder.Retry) + protocolRetry = true; + else + { + if (cmd.IsReadOnlyCommand() == false || cmd._protocolErrorTryCount > 1) throw; + protocolRetry = true; + } + } + catch (Exception ex) { - if (cmd.IsReadOnlyCommand() == false || cmd._protocolErrorTryCount > 1) throw; - return await AdapterCallAsync(cmd, parse); + var pool = (rds as DefaultRedisSocket.TempProxyRedisSocket)._pool; + if (pool?.SetUnavailable(ex, getTime) == true) + { + } + throw; } } + if (protocolRetry) return await AdapterCallAsync(cmd, parse); + return parse(rt); }); } #endif diff --git a/src/FreeRedis/RedisClient/Adapter/SentinelAdapter.cs b/src/FreeRedis/RedisClient/Adapter/SentinelAdapter.cs index 1da5894b..06a1c05e 100644 --- a/src/FreeRedis/RedisClient/Adapter/SentinelAdapter.cs +++ b/src/FreeRedis/RedisClient/Adapter/SentinelAdapter.cs @@ -39,10 +39,6 @@ public SentinelAdapter(RedisClient topOwner, ConnectionStringBuilder sentinelCon _ib = new IdleBus(TimeSpan.FromMinutes(10)); ResetSentinel(); - -#if isasync - _asyncManager = new AsyncRedisSocket.Manager(this); -#endif } bool isdisposed = false; @@ -117,29 +113,45 @@ public override TValue AdapterCall(CommandPacket cmd, Func AdapterCallAsync(CommandPacket cmd, Func parse) { return TopOwner.LogCallAsync(cmd, async () => { - var asyncRds = _asyncManager.GetAsyncRedisSocket(cmd); - try - { - var rt = await asyncRds.WriteAsync(cmd); - return parse(rt); - } - catch (ProtocolViolationException) + RedisResult rt = null; + var protocolRetry = false; + using (var rds = GetRedisSocket(cmd)) { - var pool = (asyncRds._rds as DefaultRedisSocket.TempProxyRedisSocket)?._pool; - cmd._protocolErrorTryCount++; - if (pool != null && cmd._protocolErrorTryCount <= pool._policy._connectionStringBuilder.Retry) - return await AdapterCallAsync(cmd, parse); - else + var getTime = DateTime.Now; + try + { + await rds.WriteAsync(cmd); + rt = await rds.ReadAsync(cmd); + } + catch (ProtocolViolationException) + { + var pool = (rds as DefaultRedisSocket.TempProxyRedisSocket)._pool; + rds.ReleaseSocket(); + cmd._protocolErrorTryCount++; + if (cmd._protocolErrorTryCount <= pool._policy._connectionStringBuilder.Retry) + protocolRetry = true; + else + { + if (cmd.IsReadOnlyCommand() == false || cmd._protocolErrorTryCount > 1) throw; + protocolRetry = true; + } + } + catch (Exception ex) { - if (cmd.IsReadOnlyCommand() == false || cmd._protocolErrorTryCount > 1) throw; - return await AdapterCallAsync(cmd, parse); + var pool = (rds as DefaultRedisSocket.TempProxyRedisSocket)._pool; + if (pool?.SetUnavailable(ex, getTime) == true) + { + RecoverySentinel(); + } + throw; } } + if (protocolRetry) return await AdapterCallAsync(cmd, parse); + return parse(rt); }); } #endif diff --git a/src/FreeRedis/RedisClient/Adapter/SingleInsideAdapter.cs b/src/FreeRedis/RedisClient/Adapter/SingleInsideAdapter.cs index 8c15602c..e23ee71f 100644 --- a/src/FreeRedis/RedisClient/Adapter/SingleInsideAdapter.cs +++ b/src/FreeRedis/RedisClient/Adapter/SingleInsideAdapter.cs @@ -50,8 +50,13 @@ public override TValue AdapterCall(CommandPacket cmd, Func AdapterCallAsync(CommandPacket cmd, Func parse) { - //Single socket not support Async Multiplexing - return Task.FromResult(AdapterCall(cmd, parse)); + return TopOwner.LogCallAsync(cmd, async () => + { + await _redisSocket.WriteAsync(cmd); + var rt = await _redisSocket.ReadAsync(cmd); + if (cmd._command == "QUIT") _redisSocket.ReleaseSocket(); + return parse(rt); + }); } #endif diff --git a/src/FreeRedis/RedisClient/Adapter/SingleTempAdapter.cs b/src/FreeRedis/RedisClient/Adapter/SingleTempAdapter.cs index ab9e6d48..5e965a89 100644 --- a/src/FreeRedis/RedisClient/Adapter/SingleTempAdapter.cs +++ b/src/FreeRedis/RedisClient/Adapter/SingleTempAdapter.cs @@ -82,8 +82,13 @@ public override TValue AdapterCall(CommandPacket cmd, Func AdapterCallAsync(CommandPacket cmd, Func parse) { - //Single socket not support Async Multiplexing - return Task.FromResult(AdapterCall(cmd, parse)); + return TopOwner.LogCallAsync(cmd, async () => + { + await _redisSocket.WriteAsync(cmd); + var rt = await _redisSocket.ReadAsync(cmd); + if (cmd._command == "QUIT") _redisSocket.ReleaseSocket(); + return parse(rt); + }); } #endif diff --git a/src/FreeRedis/RedisClient/Strings.cs b/src/FreeRedis/RedisClient/Strings.cs index 98069b34..3132ca2e 100644 --- a/src/FreeRedis/RedisClient/Strings.cs +++ b/src/FreeRedis/RedisClient/Strings.cs @@ -147,6 +147,34 @@ public Task BitPosAsync(string key, bool bit, long? start = null, long? en /// The value of key, or nil when key does not exist. public Task GetAsync(string key) => CallAsync("GET".InputKey(key).FlagReadbytes(true), rt => rt.ThrowOrValue(a => DeserializeRedisValue(a.ConvertTo(), rt.Encoding))); + /// + /// GET command (A Synchronized Version)

+ ///
+ /// Get the value of key and write to the stream.. If the key does not exist the special value nil is returned. An error is returned if the value stored at key is not a string, because GET only handles string values.

+ ///
+ /// 获得给定键的值并写入流中。若键不存在,则返回特殊的 nil 值。如果给定键的值不是字符串,则返回错误,因为 GET 指令只能处理字符串。

+ ///
+ /// Document link: https://redis.io/commands/get
+ /// Available since 1.0.0. + ///
+ /// Key + /// Destination stream + /// Size + async public Task GetAsync(string key, Stream destination, int bufferSize = 1024) + { + var cmd = "GET".InputKey(key); + await Adapter.TopOwner.LogCallAsync(cmd, async () => + { + using (var rds = Adapter.GetRedisSocket(cmd)) + { + await rds.WriteAsync(cmd); + await rds.ReadChunkAsync(destination, bufferSize); + } + + return default(string); + }); + } + /// /// GETBIT command (An Asynchronous Version)

///
diff --git a/test/Unit/FreeRedis.Tests/RedisClientTests/StringsAsyncTests.cs b/test/Unit/FreeRedis.Tests/RedisClientTests/StringsAsyncTests.cs new file mode 100644 index 00000000..ba464fd4 --- /dev/null +++ b/test/Unit/FreeRedis.Tests/RedisClientTests/StringsAsyncTests.cs @@ -0,0 +1,483 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace FreeRedis.Tests.RedisClientTests +{ + public class StringsAsyncTests : TestBase + { + static Lazy _cliLazy = new Lazy(() => + { + var connectionStringBuilder = (ConnectionStringBuilder)Connection.ToString(); + connectionStringBuilder.Database = 13; + var r = new RedisClient(connectionStringBuilder); + r.Serialize = obj => JsonConvert.SerializeObject(obj); + r.Deserialize = (json, type) => JsonConvert.DeserializeObject(json, type); + r.Notice += (s, e) => Trace.WriteLine(e.Log); + return r; + }); + public new static RedisClient cli => _cliLazy.Value; + + [Fact] + async public Task AppendAsync() + { + var key = "TestAppend_null"; + await cli.SetAsync(key, String); + await cli.AppendAsync(key, Null); + Assert.Equal(await cli.GetAsync(key), String); + + key = "TestAppend_string"; + await cli.SetAsync(key, String); + await cli.AppendAsync(key, String); + Assert.Equal(await cli.GetAsync(key), String + String); + var ms = new MemoryStream(); + await cli.GetAsync(key, ms); + Assert.Equal(Encoding.UTF8.GetString(ms.ToArray()), String + String); + ms.Close(); + + key = "TestAppend_bytes"; + await cli.SetAsync(key, Bytes); + await cli.AppendAsync(key, Bytes); + Assert.Equal(Convert.ToBase64String(cli.Get(key)), Convert.ToBase64String(Bytes.Concat(Bytes).ToArray())); + } + + [Fact] + async public Task BitCountAsync() + { + await cli.DelAsync("TestBitCount"); + var key = "TestBitCount"; + await cli.SetBitAsync(key, 100, true); + await cli.SetBitAsync(key, 90, true); + await cli.SetBitAsync(key, 80, true); + Assert.Equal(3, await cli.BitCountAsync(key, 0, 101)); + Assert.Equal(3, await cli.BitCountAsync(key, 0, 100)); + Assert.Equal(3, await cli.BitCountAsync(key, 0, 99)); + Assert.Equal(3, await cli.BitCountAsync(key, 0, 60)); + } + + [Fact] + async public Task BitOpAsync() + { + await cli.DelAsync("BitOp1", "BitOp2"); + await cli.SetBitAsync("BitOp1", 100, true); + await cli.SetBitAsync("BitOp2", 100, true); + var r1 = await cli.BitOpAsync(BitOpOperation.and, "BitOp3", "BitOp1", "BitOp2"); + } + + [Fact] + async public Task BitPosAsync() + { + await cli.DelAsync("BitPos1"); + await cli.SetBitAsync("BitPos1", 100, true); + Assert.Equal(100, await cli.BitPosAsync("BitPos1", true)); + Assert.Equal(-1, await cli.BitPosAsync("BitPos1", true, 1, 100)); + } + + [Fact] + async public Task DecrAsync() + { + var key = Guid.NewGuid().ToString(); + Assert.Equal(-1, await cli.DecrAsync(key)); + } + + [Fact] + async public Task DecrByAsync() + { + var key = Guid.NewGuid().ToString(); + Assert.Equal(-10, await cli.DecrByAsync(key, 10)); + } + + [Fact] + async public Task GetAsync() + { + var key = "TestGet_null"; + await cli.SetAsync(key, Null); + Assert.Equal((await cli.GetAsync(key))?.ToString() ?? "", Null?.ToString() ?? ""); + + key = "TestGet_string"; + await cli.SetAsync(key, String); + Assert.Equal(await cli.GetAsync(key), String); + + key = "TestGet_bytes"; + await cli.SetAsync(key, Bytes); + Assert.Equal(cli.Get(key), Bytes); + + key = "TestGet_class"; + await cli.SetAsync(key, Class); + Assert.Equal(JsonConvert.SerializeObject(cli.Get(key)), JsonConvert.SerializeObject(Class)); + + key = "TestGet_classArray"; + await cli.SetAsync(key, new[] { Class, Class }); + Assert.Equal(2, cli.Get(key)?.Length); + Assert.Equal(JsonConvert.SerializeObject(cli.Get(key)?.First()), JsonConvert.SerializeObject(Class)); + Assert.Equal(JsonConvert.SerializeObject(cli.Get(key)?.Last()), JsonConvert.SerializeObject(Class)); + } + + [Fact] + async public Task GetBitAsync() + { + await cli.DelAsync("GetBit1"); + await cli.SetBitAsync("GetBit1", 100, true); + Assert.False(await cli.GetBitAsync("GetBit1", 10)); + Assert.True(await cli.GetBitAsync("GetBit1", 100)); + } + + [Fact] + async public Task GetRangeAsync() + { + var key = "TestGetRange_null"; + await cli.SetAsync(key, Null); + Assert.Equal("", await cli.GetRangeAsync(key, 10, 20)); + + key = "TestGetRange_string"; + await cli.SetAsync(key, "abcdefg"); + Assert.Equal("cde", await cli.GetRangeAsync(key, 2, 4)); + Assert.Equal("abcdefg", await cli.GetRangeAsync(key, 0, -1)); + + key = "TestGetRange_bytes"; + await cli.SetAsync(key, Bytes); + Assert.Equal(Bytes.AsSpan(2, 3).ToArray(), cli.GetRange(key, 2, 4)); + Assert.Equal(Bytes, cli.GetRange(key, 0, -1)); + } + + [Fact] + async public Task GetSetAsync() + { + await cli.DelAsync("GetSet1"); + Assert.Null(await cli.GetSetAsync("GetSet1", "123456")); + Assert.Equal("123456", await cli.GetSetAsync("GetSet1", "123456789")); + } + + [Fact] + async public Task IncrAsync() + { + var key = Guid.NewGuid().ToString(); + Assert.Equal(1, await cli.IncrAsync(key)); + } + + [Fact] + async public Task IncrByAsync() + { + var key = Guid.NewGuid().ToString(); + Assert.Equal(10, await cli.IncrByAsync(key, 10)); + } + + [Fact] + async public Task IncrByFloatAsync() + { + var key = Guid.NewGuid().ToString(); + Assert.Equal(10.1m, await cli.IncrByFloatAsync(key, 10.1m)); + } + + [Fact] + async public Task MGetAsync() + { + await cli.SetAsync("TestMGet_null1", Null); + await cli.SetAsync("TestMGet_string1", String); + await cli.SetAsync("TestMGet_bytes1", Bytes); + await cli.SetAsync("TestMGet_class1", Class); + await cli.SetAsync("TestMGet_null2", Null); + await cli.SetAsync("TestMGet_string2", String); + await cli.SetAsync("TestMGet_bytes2", Bytes); + await cli.SetAsync("TestMGet_class2", Class); + await cli.SetAsync("TestMGet_null3", Null); + await cli.SetAsync("TestMGet_string3", String); + await cli.SetAsync("TestMGet_bytes3", Bytes); + await cli.SetAsync("TestMGet_class3", Class); + + Assert.Equal(4, (await cli.MGetAsync("TestMGet_null1", "TestMGet_string1", "TestMGet_bytes1", "TestMGet_class1")).Length); + Assert.Equal("", (await cli.MGetAsync("TestMGet_null1", "TestMGet_string1", "TestMGet_bytes1", "TestMGet_class1"))[0]); + Assert.Equal(String, (await cli.MGetAsync("TestMGet_null1", "TestMGet_string1", "TestMGet_bytes1", "TestMGet_class1"))[1]); + Assert.Equal(Encoding.UTF8.GetString(Bytes), (await cli.MGetAsync("TestMGet_null1", "TestMGet_string1", "TestMGet_bytes1", "TestMGet_class1"))[2]); + Assert.Equal(JsonConvert.SerializeObject(Class), (await cli.MGetAsync("TestMGet_null1", "TestMGet_string1", "TestMGet_bytes1", "TestMGet_class1"))[3]); + + Assert.Equal(4, cli.MGet("TestMGet_null1", "TestMGet_string1", "TestMGet_bytes1", "TestMGet_class1").Length); + Assert.Equal(new byte[0], cli.MGet("TestMGet_null1", "TestMGet_string1", "TestMGet_bytes1", "TestMGet_class1")[0]); + Assert.Equal(Encoding.UTF8.GetBytes(String), cli.MGet("TestMGet_null1", "TestMGet_string1", "TestMGet_bytes1", "TestMGet_class1")[1]); + Assert.Equal(Bytes, cli.MGet("TestMGet_null1", "TestMGet_string1", "TestMGet_bytes1", "TestMGet_class1")[2]); + Assert.Equal(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(Class)), cli.MGet("TestMGet_null1", "TestMGet_string1", "TestMGet_bytes1", "TestMGet_class1")[3]); + + Assert.Equal(3, cli.MGet("TestMGet_class1", "TestMGet_class2", "TestMGet_class3").Length); + Assert.Equal(JsonConvert.SerializeObject(Class), JsonConvert.SerializeObject(cli.MGet("TestMGet_class1", "TestMGet_class2", "TestMGet_class3")[0])); + Assert.Equal(JsonConvert.SerializeObject(Class), JsonConvert.SerializeObject(cli.MGet("TestMGet_class1", "TestMGet_class2", "TestMGet_class3")[1])); + Assert.Equal(JsonConvert.SerializeObject(Class), JsonConvert.SerializeObject(cli.MGet("TestMGet_class1", "TestMGet_class2", "TestMGet_class3")[2])); + } + + [Fact] + async public Task MSetAsync() + { + await cli.DelAsync("TestMSet_null1", "TestMSet_string1", "TestMSet_bytes1", "TestMSet_class1"); + await cli.MSetAsync(new Dictionary { ["TestMSet_null1"] = Null, ["TestMSet_string1"] = String, ["TestMSet_bytes1"] = Bytes, ["TestMSet_class1"] = Class }); + Assert.Equal("", await cli.GetAsync("TestMSet_null1")); + Assert.Equal(String, await cli.GetAsync("TestMSet_string1")); + Assert.Equal(Bytes, cli.Get("TestMSet_bytes1")); + Assert.Equal(JsonConvert.SerializeObject(Class), JsonConvert.SerializeObject(cli.Get("TestMSet_class1"))); + + await cli.DelAsync("TestMSet_null1", "TestMSet_string1", "TestMSet_bytes1", "TestMSet_class1"); + await cli.MSetAsync("TestMSet_null1", Null, "TestMSet_string1", String, "TestMSet_bytes1", Bytes, "TestMSet_class1", Class); + Assert.Equal("", await cli.GetAsync("TestMSet_null1")); + Assert.Equal(String, await cli.GetAsync("TestMSet_string1")); + Assert.Equal(Bytes, cli.Get("TestMSet_bytes1")); + Assert.Equal(JsonConvert.SerializeObject(Class), JsonConvert.SerializeObject(cli.Get("TestMSet_class1"))); + } + + [Fact] + async public Task MSetNxAsync() + { + await cli.DelAsync("TestMSetNx_null", "TestMSetNx_string", "TestMSetNx_bytes", "TestMSetNx_class", "abctest", + "TestMSetNx_null1", "TestMSetNx_string1", "TestMSetNx_bytes1", "TestMSetNx_class1"); + + Assert.True(await cli.MSetNxAsync(new Dictionary { ["TestMSetNx_null"] = Null })); + Assert.False(await cli.MSetNxAsync(new Dictionary { ["TestMSetNx_null"] = Null })); + Assert.Equal("", await cli.GetAsync("TestMSetNx_null")); + + Assert.True(await cli.MSetNxAsync(new Dictionary { ["TestMSetNx_string"] = String })); + Assert.False(await cli.MSetNxAsync(new Dictionary { ["TestMSetNx_string"] = String })); + Assert.Equal(String, await cli.GetAsync("TestMSetNx_string")); + + Assert.True(await cli.MSetNxAsync(new Dictionary { ["TestMSetNx_bytes"] = Bytes })); + Assert.False(await cli.MSetNxAsync(new Dictionary { ["TestMSetNx_bytes"] = Bytes })); + Assert.Equal(Bytes, cli.Get("TestMSetNx_bytes")); + + Assert.True(await cli.MSetNxAsync(new Dictionary { ["TestMSetNx_class"] = Class })); + Assert.False(await cli.MSetNxAsync(new Dictionary { ["TestMSetNx_class"] = Class })); + Assert.Equal(JsonConvert.SerializeObject(Class), JsonConvert.SerializeObject(cli.Get("TestMSetNx_class"))); + + await cli.SetAsync("abctest", 1); + Assert.False(await cli.MSetNxAsync(new Dictionary { ["abctest"] = 2, ["TestMSetNx_null1"] = Null, ["TestMSetNx_string1"] = String, ["TestMSetNx_bytes1"] = Bytes, ["TestMSetNx_class1"] = Class })); + Assert.True(await cli.MSetNxAsync(new Dictionary { ["TestMSetNx_null1"] = Null, ["TestMSetNx_string1"] = String, ["TestMSetNx_bytes1"] = Bytes, ["TestMSetNx_class1"] = Class })); + Assert.Equal(1, cli.Get("abctest")); + Assert.Equal("", await cli.GetAsync("TestMSetNx_null1")); + Assert.Equal(String, await cli.GetAsync("TestMSetNx_string1")); + Assert.Equal(Bytes, cli.Get("TestMSetNx_bytes1")); + Assert.Equal(JsonConvert.SerializeObject(Class), JsonConvert.SerializeObject(cli.Get("TestMSetNx_class1"))); + + await cli.DelAsync("TestMSetNx_null", "TestMSetNx_string", "TestMSetNx_bytes", "TestMSetNx_class"); + await cli.MSetNxAsync("TestMSetNx_null1", Null, "TestMSetNx_string1", String, "TestMSetNx_bytes1", Bytes, "TestMSetNx_class1", Class); + Assert.Equal("", await cli.GetAsync("TestMSetNx_null1")); + Assert.Equal(String, await cli.GetAsync("TestMSetNx_string1")); + Assert.Equal(Bytes, cli.Get("TestMSetNx_bytes1")); + Assert.Equal(JsonConvert.SerializeObject(Class), JsonConvert.SerializeObject(cli.Get("TestMSetNx_class1"))); + } + + [Fact] + async public Task PSetNxAsync() + { + await cli.PSetExAsync("TestSetNx_null", 10000, Null); + Assert.Equal("", await cli.GetAsync("TestSetNx_null")); + + await cli.PSetExAsync("TestSetNx_string", 10000, String); + Assert.Equal(String, await cli.GetAsync("TestSetNx_string")); + + await cli.PSetExAsync("TestSetNx_bytes", 10000, Bytes); + Assert.Equal(Bytes, cli.Get("TestSetNx_bytes")); + + await cli.PSetExAsync("TestSetNx_class", 10000, Class); + Assert.Equal(JsonConvert.SerializeObject(Class), JsonConvert.SerializeObject(cli.Get("TestSetNx_class"))); + } + + [Fact] + async public Task SetAsync() + { + await cli.DelAsync("TestSetNx_null", "TestSetNx_string", "TestSetNx_bytes", "TestSetNx_class"); + await cli.SetAsync("TestSet_null", Null); + Assert.Equal("", await cli.GetAsync("TestSet_null")); + + await cli.SetAsync("TestSet_string", String); + Assert.Equal(String, await cli.GetAsync("TestSet_string")); + + await cli.SetAsync("TestSet_bytes", Bytes); + Assert.Equal(Bytes, cli.Get("TestSet_bytes")); + + await cli.SetAsync("TestSet_class", Class); + Assert.Equal(JsonConvert.SerializeObject(Class), JsonConvert.SerializeObject(cli.Get("TestSet_class"))); + + await cli.DelAsync("TestSetNx_null", "TestSetNx_string", "TestSetNx_bytes", "TestSetNx_class"); + await cli.SetAsync("TestSet_null", Null, 10); + Assert.Equal("", await cli.GetAsync("TestSet_null")); + + await cli.SetAsync("TestSet_string", String, 10); + Assert.Equal(String, await cli.GetAsync("TestSet_string")); + + await cli.SetAsync("TestSet_bytes", Bytes, 10); + Assert.Equal(Bytes, cli.Get("TestSet_bytes")); + + await cli.SetAsync("TestSet_class", Class, 10); + Assert.Equal(JsonConvert.SerializeObject(Class), JsonConvert.SerializeObject(cli.Get("TestSet_class"))); + + await cli.DelAsync("TestSetNx_null", "TestSetNx_string", "TestSetNx_bytes", "TestSetNx_class"); + await cli.SetAsync("TestSet_null", Null, true); + Assert.Equal("", await cli.GetAsync("TestSet_null")); + + await cli.SetAsync("TestSet_string", String, true); + Assert.Equal(String, await cli.GetAsync("TestSet_string")); + + await cli.SetAsync("TestSet_bytes", Bytes, true); + Assert.Equal(Bytes, cli.Get("TestSet_bytes")); + + await cli.SetAsync("TestSet_class", Class, true); + Assert.Equal(JsonConvert.SerializeObject(Class), JsonConvert.SerializeObject(cli.Get("TestSet_class"))); + } + + [Fact] + async public Task SetNxAsync() + { + await cli.DelAsync("TestSetNx_null", "TestSetNx_string", "TestSetNx_bytes", "TestSetNx_class"); + Assert.True(await cli.SetNxAsync("TestSetNx_null", Null)); + Assert.False(await cli.SetNxAsync("TestSetNx_null", Null)); + Assert.Equal("", await cli.GetAsync("TestSetNx_null")); + + Assert.True(await cli.SetNxAsync("TestSetNx_string", String)); + Assert.False(await cli.SetNxAsync("TestSetNx_string", String)); + Assert.Equal(String, await cli.GetAsync("TestSetNx_string")); + + Assert.True(await cli.SetNxAsync("TestSetNx_bytes", Bytes)); + Assert.False(await cli.SetNxAsync("TestSetNx_bytes", Bytes)); + Assert.Equal(Bytes, cli.Get("TestSetNx_bytes")); + + Assert.True(await cli.SetNxAsync("TestSetNx_class", Class)); + Assert.False(await cli.SetNxAsync("TestSetNx_class", Class)); + Assert.Equal(JsonConvert.SerializeObject(Class), JsonConvert.SerializeObject(cli.Get("TestSetNx_class"))); + + await cli.DelAsync("TestSetNx_null", "TestSetNx_string", "TestSetNx_bytes", "TestSetNx_class"); + Assert.True(await cli.SetNxAsync("TestSetNx_null", Null, 10)); + Assert.False(await cli.SetNxAsync("TestSetNx_null", Null, 10)); + Assert.Equal("", await cli.GetAsync("TestSetNx_null")); + + Assert.True(await cli.SetNxAsync("TestSetNx_string", String, 10)); + Assert.False(await cli.SetNxAsync("TestSetNx_string", String, 10)); + Assert.Equal(String, await cli.GetAsync("TestSetNx_string")); + + Assert.True(await cli.SetNxAsync("TestSetNx_bytes", Bytes, 10)); + Assert.False(await cli.SetNxAsync("TestSetNx_bytes", Bytes, 10)); + Assert.Equal(Bytes, cli.Get("TestSetNx_bytes")); + + Assert.True(await cli.SetNxAsync("TestSetNx_class", Class, 10)); + Assert.False(await cli.SetNxAsync("TestSetNx_class", Class, 10)); + Assert.Equal(JsonConvert.SerializeObject(Class), JsonConvert.SerializeObject(cli.Get("TestSetNx_class"))); + } + + [Fact] + async public Task SetXxAsync() + { + await cli.DelAsync("TestSetXx_null"); + Assert.False(await cli.SetXxAsync("TestSetXx_null", Null, 10)); + await cli.SetAsync("TestSetXx_null", 1, true); + Assert.True(await cli.SetXxAsync("TestSetXx_null", Null, 10)); + Assert.Equal("", await cli.GetAsync("TestSetXx_null")); + + await cli.DelAsync("TestSetXx_string"); + Assert.False(await cli.SetXxAsync("TestSetXx_string", String, 10)); + await cli.SetAsync("TestSetXx_string", 1, true); + Assert.True(await cli.SetXxAsync("TestSetXx_string", String, 10)); + Assert.Equal(String, await cli.GetAsync("TestSetXx_string")); + + await cli.DelAsync("TestSetXx_bytes"); + Assert.False(await cli.SetXxAsync("TestSetXx_bytes", Bytes, 10)); + await cli.SetAsync("TestSetXx_bytes", 1, true); + Assert.True(await cli.SetXxAsync("TestSetXx_bytes", Bytes, 10)); + Assert.Equal(Bytes, cli.Get("TestSetXx_bytes")); + + await cli.DelAsync("TestSetXx_class"); + Assert.False(await cli.SetXxAsync("TestSetXx_class", Class, 10)); + await cli.SetAsync("TestSetXx_class", 1, true); + Assert.True(await cli.SetXxAsync("TestSetXx_class", Class, 10)); + Assert.Equal(JsonConvert.SerializeObject(Class), JsonConvert.SerializeObject(cli.Get("TestSetXx_class"))); + + await cli.DelAsync("TestSetXx_null"); + Assert.False(await cli.SetXxAsync("TestSetXx_null", Null, true)); + await cli.SetAsync("TestSetXx_null", 1, true); + Assert.True(await cli.SetXxAsync("TestSetXx_null", Null, true)); + Assert.Equal("", await cli.GetAsync("TestSetXx_null")); + + await cli.DelAsync("TestSetXx_string"); + Assert.False(await cli.SetXxAsync("TestSetXx_string", String, true)); + await cli.SetAsync("TestSetXx_string", 1, true); + Assert.True(await cli.SetXxAsync("TestSetXx_string", String, true)); + Assert.Equal(String, await cli.GetAsync("TestSetXx_string")); + + await cli.DelAsync("TestSetXx_bytes"); + Assert.False(await cli.SetXxAsync("TestSetXx_bytes", Bytes, true)); + await cli.SetAsync("TestSetXx_bytes", 1, true); + Assert.True(await cli.SetXxAsync("TestSetXx_bytes", Bytes, true)); + Assert.Equal(Bytes, cli.Get("TestSetXx_bytes")); + + await cli.DelAsync("TestSetXx_class"); + Assert.False(await cli.SetXxAsync("TestSetXx_class", Class, true)); + await cli.SetAsync("TestSetXx_class", 1, true); + Assert.True(await cli.SetXxAsync("TestSetXx_class", Class, true)); + Assert.Equal(JsonConvert.SerializeObject(Class), JsonConvert.SerializeObject(cli.Get("TestSetXx_class"))); + } + + [Fact] + async public Task SetBitAsync() + { + await cli.DelAsync("SetBit1"); + await cli.SetBitAsync("SetBit1", 100, true); + Assert.False(await cli.GetBitAsync("SetBit1", 10)); + Assert.True(await cli.GetBitAsync("SetBit1", 100)); + } + + [Fact] + async public Task SetExAsync() + { + await cli.DelAsync("TestSetEx_null", "TestSetEx_string", "TestSetEx_bytes", "TestSetEx_class"); + await cli.SetExAsync("TestSetEx_null", 10, Null); + Assert.Equal("", await cli.GetAsync("TestSetEx_null")); + + await cli.SetExAsync("TestSetEx_string", 10, String); + Assert.Equal(String, await cli.GetAsync("TestSetEx_string")); + + await cli.SetExAsync("TestSetEx_bytes", 10, Bytes); + Assert.Equal(Bytes, cli.Get("TestSetEx_bytes")); + + await cli.SetExAsync("TestSetEx_class", 10, Class); + Assert.Equal(JsonConvert.SerializeObject(Class), JsonConvert.SerializeObject(cli.Get("TestSetEx_class"))); + } + + [Fact] + async public Task SetRangeAsync() + { + var key = "TestSetRange_null"; + await cli.SetAsync(key, Null); + await cli.SetRangeAsync(key, 10, String); + Assert.Equal(String, await cli.GetRangeAsync(key, 10, -1)); + + key = "TestSetRange_string"; + await cli.SetAsync(key, "abcdefg"); + await cli.SetRangeAsync(key, 2, "yyy"); + Assert.Equal("yyy", await cli.GetRangeAsync(key, 2, 4)); + + key = "TestSetRange_bytes"; + await cli.SetAsync(key, Bytes); + await cli.SetRangeAsync(key, 2, Bytes); + Assert.Equal(Bytes, cli.GetRange(key, 2, Bytes.Length + 2)); + } + + [Fact] + async public Task StrLenAsync() + { + var key = "TestStrLen_null"; + await cli.SetAsync(key, Null); + Assert.Equal(0, await cli.StrLenAsync(key)); + + key = "TestStrLen_string"; + await cli.SetAsync(key, "abcdefg"); + Assert.Equal(7, await cli.StrLenAsync(key)); + + key = "TestStrLen_string"; + await cli.SetAsync(key, String); + Assert.Equal(15, await cli.StrLenAsync(key)); + + key = "TestStrLen_bytes"; + await cli.SetAsync(key, Bytes); + Assert.Equal(Bytes.Length, await cli.StrLenAsync(key)); + } + } +}