StackExchange Redis的分区密钥空间

 霸气的艳子_612 发布于 2022-12-19 18:24

在开发使用Redis的组件时,我发现它是一个很好的模式,可以为该组件使用的所有键添加前缀,这样它就不会干扰其他组件.

例子:

管理用户的组件可能使用前缀为的键,user:管理日志的组件可能使用前缀为的键log:.

在多租户系统中,我希望每个客户在Redis中使用单独的密钥空间,以确保他们的数据不会干扰.然后,前缀将类似于customer::与特定客户相关的所有密钥.

使用Redis对我来说仍然是新的东西.我对这种分区模式的第一个想法是为每个分区使用单独的数据库标识符.但是,这似乎是一个坏主意,因为数据库的数量有限,似乎是一个即将被弃用的功能.

另一种方法是让每个组件获得一个IDatabase实例,并RedisKey使用它来为所有键添加前缀.(我正在使用StackExchange.Redis)

我一直在寻找一个IDatabase自动为所有键添加前缀的包装器,以便组件可以按IDatabase原样使用接口,而不必担心其键空间.我没找到任何东西.

所以我的问题是:在StackExchange Redis上使用分区键空间的推荐方法什么?

我现在正在考虑实现我自己的IDatabase包装器,它将为所有键添加前缀.我认为大多数方法只是将它们的调用转发给内部IDatabase实例.但是,某些方法需要更多工作:例如SORT和RANDOMKEY.

1 个回答
  • IDatabase现在创建了一个提供密钥空间分区的包装器.

    包装器是使用扩展方法创建的 IDatabase

        ConnectionMultiplexer multiplexer = ConnectionMultiplexer.Connect("localhost");
        IDatabase fullDatabase = multiplexer.GetDatabase();
        IDatabase partitioned = fullDatabase.GetKeyspacePartition("my-partition");
    

    几乎所有分区包装器中的方法都具有相同的结构:

    public bool SetAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None)
    {
        return this.Inner.SetAdd(this.ToInner(key), value, flags);
    }
    

    它们只是将调用转发到内部数据库,并RedisKey在传递它们之前将键空间前缀添加到任何参数.

    CreateBatchCreateTransaction的方法简单地创建用于这些接口包装,但具有相同的基包装类(如最包裹方法由定义IDatabaseAsync).

    KeyRandomAsyncKeyRandom不支持的方法.调用将抛出一个NotSupportedException.对我来说这不是一个问题,引用@Marc Gravell:

    我无法想到实现这一目标的任何理智方式,但我怀疑NotSupportedException("指定键前缀时不支持RANDOMKEY")完全合理(这不是常用的命令)

    我还没有实现ScriptEvaluate,ScriptEvaluateAsync因为我不清楚如何处理RedisResult返回值.这些方法的输入参数接受RedisKey哪些应该加前缀,但脚本本身可以返回键,在这种情况下,我认为(最有意义的是)取消修正这些键.目前,这些方法将抛出NotImplementedException......

    排序方式(Sort,SortAsync,SortAndStoreSortAndStoreAsync)有特殊处理byget参数.这些都是正常的前缀,除非它们具有以下特殊值之一:nosortfor by#for get.

    最后,为了允许前缀ITransaction.AddCondition我必须使用一点反射:

    internal static class ConditionHelper
    {
        public static Condition Rewrite(this Condition outer, Func<RedisKey, RedisKey> rewriteFunc)
        {
            ThrowIf.ArgNull(outer, "outer");
            ThrowIf.ArgNull(rewriteFunc, "rewriteFunc");
    
            Type conditionType = outer.GetType();
            object inner = FormatterServices.GetUninitializedObject(conditionType);
    
            foreach (FieldInfo field in conditionType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
            {
                if (field.FieldType == typeof(RedisKey))
                {
                    field.SetValue(inner, rewriteFunc((RedisKey)field.GetValue(outer)));
                }
                else
                {
                    field.SetValue(inner, field.GetValue(outer));
                }
            }
    
            return (Condition)inner;
        }
    }
    

    包装器使用这个帮助器,如下所示:

    internal Condition ToInner(Condition outer)
    {
        if (outer == null)
        {
            return outer;
        }
        else
        {
            return outer.Rewrite(this.ToInner);
        }
    }
    

    有几种其他ToInner方法可以包含不同类型的参数,RedisKey但它们或多或少都会最终调用:

    internal RedisKey ToInner(RedisKey outer)
    {
        return this.Prefix + outer;
    }
    

    我现在已经为此创建了一个pull请求:

    https://github.com/StackExchange/StackExchange.Redis/pull/92

    现在调用扩展方法,WithKeyPrefix并且不再需要用于重写条件的反射黑客,因为新代码可以访问Condition类的内部.

    2022-12-19 18:26 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有