在开发使用Redis的组件时,我发现它是一个很好的模式,可以为该组件使用的所有键添加前缀,这样它就不会干扰其他组件.
例子:
管理用户的组件可能使用前缀为的键,user:
管理日志的组件可能使用前缀为的键log:
.
在多租户系统中,我希望每个客户在Redis中使用单独的密钥空间,以确保他们的数据不会干扰.然后,前缀将类似于customer:
与特定客户相关的所有密钥.
使用Redis对我来说仍然是新的东西.我对这种分区模式的第一个想法是为每个分区使用单独的数据库标识符.但是,这似乎是一个坏主意,因为数据库的数量有限,似乎是一个即将被弃用的功能.
另一种方法是让每个组件获得一个IDatabase
实例,并RedisKey
使用它来为所有键添加前缀.(我正在使用StackExchange.Redis)
我一直在寻找一个IDatabase
自动为所有键添加前缀的包装器,以便组件可以按IDatabase
原样使用接口,而不必担心其键空间.我没找到任何东西.
所以我的问题是:在StackExchange Redis上使用分区键空间的推荐方法是什么?
我现在正在考虑实现我自己的IDatabase
包装器,它将为所有键添加前缀.我认为大多数方法只是将它们的调用转发给内部IDatabase
实例.但是,某些方法需要更多工作:例如SORT和RANDOMKEY.
我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
在传递它们之前将键空间前缀添加到任何参数.
的CreateBatch
和CreateTransaction
的方法简单地创建用于这些接口包装,但具有相同的基包装类(如最包裹方法由定义IDatabaseAsync
).
在KeyRandomAsync
与KeyRandom
不支持的方法.调用将抛出一个NotSupportedException
.对我来说这不是一个问题,引用@Marc Gravell:
我无法想到实现这一目标的任何理智方式,但我怀疑NotSupportedException("指定键前缀时不支持RANDOMKEY")完全合理(这不是常用的命令)
我还没有实现ScriptEvaluate
,ScriptEvaluateAsync
因为我不清楚如何处理RedisResult
返回值.这些方法的输入参数接受RedisKey
哪些应该加前缀,但脚本本身可以返回键,在这种情况下,我认为(最有意义的是)取消修正这些键.目前,这些方法将抛出NotImplementedException
......
排序方式(Sort
,SortAsync
,SortAndStore
和SortAndStoreAsync
)有特殊处理by
和get
参数.这些都是正常的前缀,除非它们具有以下特殊值之一:nosort
for 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
类的内部.