波斯马BOSSMA Information Technology

NServiceKit.Redis不支持SCAN的解决方法

发布时间:2015年1月10日 / 分类:DOTNET, Redis / 14,994 次浏览 / 评论

NServiceKit.Redis这个名字可能听说过的不多,但是在.NET平台使用redis的人一般都接触过ServiceStack.Redis,ServiceStack.Redis从v4开始收费了,NServiceKit.Redis是这个程序的免费开源版本,其源码与ServiceStack.Redis v3基本相同,但是它们好像都不支持SCAN命令(也许是我没发现),尝试用执行Lua Script的ExecLuaAsList等方法也不能返回两个值,所以只好修改了源代码实现这个功能。

SCAN介绍

SCAN cursor [MATCH pattern] [COUNT count]

支持三个参数:

cursor ?类似于数据库查询中的游标,表示开始扫描的位置

MATACH 表示key的匹配模式,全部就是*

COUNT 表示要扫描的数目,返回的数据条数

SCAN执行后每次都返回一个新的cursor,然后用cursor执行下一次SCAN,直到cursor等于0,表示完整迭代完毕。

迭代过程示例:

20150110153119

NServiceKit.Redis修改

在NServiceKit.Redis内部其实已经支持返回多个值,RedisClient中的Slowlog就是一个例子,其内部数据结构是一个嵌套数组,方便组装多级多种不同的返回值。

打开NServiceKit.Redis项目中的RedisNativeClient_Utils.cs文件,找到方法:SendExpectDeeplyNestedMultiData:

 protected object[] SendExpectDeeplyNestedMultiData(params byte[][] cmdWithBinaryArgs)
        {
            // 发送要执行的SCAN命令
            if (!SendCommand(cmdWithBinaryArgs))
                throw CreateConnectionError();

            if (Pipeline != null)
            {
                throw new NotSupportedException("Pipeline is not supported.");
            }
            // 读取返回的嵌套数据
            return ReadDeeplyNestedMultiData();
        }

其中又调用了ReadDeeplyNestedMultiData:

        private object[] ReadDeeplyNestedMultiData()
        {
            return (object[])ReadDeeplyNestedMultiDataItem();
        }

其中又调用了ReadDeeplyNestedMultiDataItem,这是个关键的方法:

private object ReadDeeplyNestedMultiDataItem()
        {
            int c = SafeReadByte(); // 读取数据类型
            if (c == -1)
                throw CreateResponseError("No more data");

            var s = ReadLine();
            Log("R: {0}", s);

            // 根据不同的数据类型处理不同
            switch (c)
            {
                case '$': // $开头后跟数据的字节长度,按照这个长度读取数据
                    return ParseSingleLine(string.Concat(char.ToString((char)c), s));

                case '-':
                    throw CreateResponseError(s.StartsWith("ERR") ? s.Substring(4) : s);

                case '*': // *开头后跟数据条数,从下一行开始是相关数据
                    int count;
                    if (int.TryParse(s, out count))
                    {
                        var array = new object[count];
                        for (int i = 0; i < count; i++)
                        {
                            // 如果数据有多级,通过递归方式读取出来
                            array[i] = ReadDeeplyNestedMultiDataItem();
                        }

                        return array;
                    }
                    break;

                default:
                    return s;
            }

            throw CreateResponseError("Unknown reply on multi-request: " + c + s);
        }

通过上边这个方法SCAN命令的返回值就全取到了,下一步就要利用这个方法来实现SCAN命令。

1、修改NServiceKit.Interfaces项目中的IRedisClient接口,增加一个ScanKeys方法。

string[] ScanKeys(ref long cursor, string pattern = null, long? count = null);

三个参数对应SCAN命令的三个参数,cursor是ref的,可以返回下一步迭代的cursor。

返回值是查询到的key数组。

2、修改NServiceKit.Redis项目中的RedisNativeClient类,增加一个Scan方法。

 public object[] Scan(long cursor, string pattern = null, long? count = null)
        {
            if (pattern == null)
                pattern = "*";

            if (count == null)
                count = 10;

            return SendExpectDeeplyNestedMultiData(Commands.Scan, cursor.ToUtf8Bytes(), "MATCH".ToUtf8Bytes(), pattern.ToUtf8Bytes(), "COUNT".ToUtf8Bytes(), count.Value.ToUtf8Bytes());
        }

这是一个底层方法,对返回值不做加工,其中调用了前边提到的SendExpectDeeplyNestedMultiData方法。

NServiceKit.Redis中本没有Scan命令,这里还在Commands类中增加了一个字段Scan:

public readonly static byte[] Scan = "SCAN".ToUtf8Bytes();

3、修改NServiceKit.Redis项目中的RedisClient类,增加一个ScanKeys方法,实现IRedisClient接口中新增的ScanKeys:

public string[] ScanKeys(ref long cursor, string pattern = null, long? count = null)
        {
            var result = base.Scan(cursor, pattern, count);

            if (result.Length <= 0)
            {
                cursor = 0;
                return null;
            }

            // 返回的游标
            cursor = long.Parse(Encoding.UTF8.GetString((byte[])result[0]));

            // 返回的Keys
            if (result.Length > 1)
            {
                var items = (object[])result[1];

                string[] keys = new string[items.Length];
                for (int i = 0; i < items.Length; i++)
                {
                    keys[i] = Encoding.UTF8.GetString((byte[])items[i]);
                }

                return keys;
            }
            return null;
        }

到此NServiceKit.Redis就支持SCAN命令了。

有兴趣的可以改一下试试,或者直接下载我编译的dll:NServiceKit.Redis

本博客所有文章如无特别注明均为原创。
复制或转载请以超链接形式注明转自波斯马,原文地址《NServiceKit.Redis不支持SCAN的解决方法

关键字:

建议订阅本站,及时阅读最新文章!
【上一篇】 【下一篇】

发表评论