基于方法名称和参数值的缓存键构造

本文关键字:参数 缓存 于方法 方法 | 更新日期: 2023-09-27 18:19:37

我决定在我们的一个应用程序中实现一个缓存facade,目的是最终减少网络开销并限制数据库点击量。我们使用Castle.Windsor作为IoC Container,并决定使用Interceptors在使用System.Runtime.Caching命名空间的服务层之上添加缓存功能。

目前,我还不能完全弄清楚构建cache key的最佳方法是什么。目标是区分不同的方法,并包括传递的参数值,这意味着这两个方法调用应该缓存在两个不同的键下:

IEnumerable<MyObject> GetMyObjectByParam(56); // key1
IEnumerable<MyObject> GetMyObjectByParam(23); // key2

目前,我可以看到两种可能的实现方式:

选项1:程序集|类|方法返回类型|方法名称|参数类型|参数哈希代码

"MyAssembly.MyClass IEnumerable<MyObject> GetMyObjectByParam(long) { 56 }";

选项2:MD5或SHA-256基于方法的完全限定名称和传递的参数值计算哈希

string key = new SHA256Managed().ComputeHash(name + args).ToString();

我考虑的是第一个选项,因为第二个选项需要更多的处理时间——另一方面,第二个选择强制所有生成的密钥的"长度"完全相同。

假设第一个选项将为使用复杂参数类型的方法生成一个唯一的键是否安全?或者可能有一种完全不同的方式来做这件事?

我们将非常感谢您的帮助和意见!

基于方法名称和参数值的缓存键构造

基于我在这里和这里找到的一些非常有用的链接,我决定或多或少地实现它,如下所示:

public sealed class CacheKey : IEquatable<CacheKey>
{
    private readonly Type reflectedType;
    private readonly Type returnType;
    private readonly string name;
    private readonly Type[] parameterTypes;
    private readonly object[] arguments;
    public User(Type reflectedType, Type returnType, string name, 
        Type[] parameterTypes, object[] arguments)
    {
        // check for null, incorrect values etc.
        this.reflectedType = reflectedType;
        this.returnType = returnType;
        this.name = name;
        this.parameterTypes = parameterTypes;
        this.arguments = arguments;
    }
    public override bool Equals(object obj)
    {
        return Equals(obj as CacheKey);
    }
    public bool Equals(CacheKey other)
    {
        if (other == null)
        {
            return false;
        }
        for (int i = 0; i < parameterTypes.Count; i++)
        {
            if (!parameterTypes[i].Equals(other.parameterTypes[i]))
            {
                return false;
            }
        }
        for (int i = 0; i < arguments.Count; i++)
        {
            if (!arguments[i].Equals(other.arguments[i]))
            {
                return false;
            }
        }
        return reflectedType.Equals(other.reflectedType) &&
           returnType.Equals(other.returnType) &&
           name.Equals(other.name);
    }
    private override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;
            hash = hash * 31 + reflectedType.GetHashCode();
            hash = hash * 31 + returnType.GetHashCode();
            hash = hash * 31 + name.GetHashCode();
            for (int i = 0; i < parameterTypes.Count; i++)
            {
                hash = hash * 31 + parameterTypes[i].GetHashCode();
            }
            for (int i = 0; i < arguments.Count; i++)
            {
                hash = hash * 31 + arguments[i].GetHashCode();
            }
            return hash;
        }
    }
}

基本上,这只是一个一般的想法——上面的代码可以很容易地用一个Fields集合重写为更通用的版本——同样的规则必须应用于集合的每个元素。我可以分享完整的代码。

您似乎跳过了一个选项,即对字符串使用.NET内置的GetHashCode()函数。我很确定,这就是C#字典中以String作为<TKey>的幕后情况(我提到这一点是因为你已经用dictionary标记了这个问题)。我不确定.NET dictionary类与您提到的Castle.Windsorsystem.runtime.caching接口之间的关系。

您不想使用GetHashCode作为哈希键的原因是,MicroSoft特别拒绝在没有警告的情况下在不同版本之间更改该功能(如中提供更独特或执行速度更快的功能)。如果这个缓存将严格存在于内存中,那么这就不成问题,因为升级.NET框架需要重新启动应用程序,擦除缓存。

为了澄清,仅使用连接字符串(选项1)就应该足够唯一。看起来您已经添加了所有可能的内容来唯一限定您的方法。

如果您最终将MD5或Sha256的字符串输入到字典密钥中,程序可能会在幕后重新散列该字符串。我已经有一段时间没有读过Dictionary类的内部工作了。如果将其保留为Dictionary<String, IEnumerable<MyObject>>(而不是使用int返回值作为键自己对字符串调用GetHashCode()),那么字典应该处理哈希代码本身的冲突。

还要注意(至少根据我机器上运行的基准程序),MD5比SHA1快10%左右,是SHA256的两倍。String.GetHashCode()的速度大约是MD5的20倍(它在加密方面不安全)。针对相同的100000个随机生成的长度在32到1024个字符之间的字符串,对计算哈希的总时间进行了测试。但是,不管确切的数字是多少,使用加密安全的哈希函数作为密钥只会减慢程序的速度。

如果你愿意的话,我可以发布我的比较源代码。