Построение ключа кэша на основе имени метода и значений аргумента - PullRequest
8 голосов
/ 07 января 2012

Я решил внедрить кеширующий фасад в одно из наших приложений - цель состоит в том, чтобы в конечном итоге уменьшить нагрузку на сеть и ограничить количество попаданий в дБ. Мы используем 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();

Я думаю о первом варианте, так как второй требует больше времени на обработку - с другой стороны, второй параметр обеспечивает одинаковую «длину» всех сгенерированных ключей.

Безопасно ли предполагать, что первый вариант будет генерировать уникальный ключ для методов, использующих сложные типы аргументов? Или, может быть, есть совершенно другой способ сделать это?

Помощь и мнение будут высоко оценены!

Ответы [ 2 ]

4 голосов
/ 04 февраля 2012

Основываясь на некоторых очень полезных ссылках, которые я нашел здесь и здесь Я решил реализовать это более или менее следующим образом:

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 - те же правила должны быть применены к каждому элементу коллекции,Я могу поделиться полным кодом.

2 голосов
/ 25 января 2012

Опция, которую вы, похоже, пропустили, - это использование встроенной функции .NET GetHashCode () для строки. Я совершенно уверен, что это то, что происходит за кулисами в словаре C # со строкой как <TKey> (я упоминаю об этом, потому что вы пометили вопрос со словарем). Я не уверен, как класс словаря .NET относится к вашему Castle.Windsor или system.runtime.caching интерфейсу, который вы упомянули.

Причина, по которой вы не хотите использовать GetHashCode в качестве хэш-ключа, заключается в том, что MicroSoft специально отказывается от этой функции, чтобы переключаться между версиями без предупреждения (например, чтобы обеспечить более уникальную или более быструю функцию выполнения). Если этот кэш будет жить исключительно в памяти, то это не проблема, поскольку для обновления .NET Framework потребуется перезапустить приложение, очистив кэш.

Для пояснения, просто использование составной строки (вариант 1) должно быть достаточно уникальным. Похоже, вы добавили все возможное для уникальной квалификации ваших методов.

Если вы в конечном итоге введете строку слов MD5 или Sha256 в ключ словаря, программа в любом случае, вероятно, перефразирует строку за кулисами. Прошло много времени с тех пор, как я прочитал о внутренней работе класса Dictionary. Если вы оставите его как Dictionary<String, IEnumerable<MyObject>> (в отличие от вызова GetHashCode () для самих строк с использованием возвращаемого значения int в качестве ключа), то словарь должен обрабатывать коллизии самого хеш-кода.

Также обратите внимание, что (по крайней мере, согласно тестовой программе, запущенной на моей машине), MD5 примерно на 10% быстрее, чем SHA1, и в два раза быстрее, чем SHA256. String.GetHashCode () примерно в 20 раз быстрее, чем MD5 (он не криптографически безопасен). Были проведены тесты на общее время для вычисления хэшей для тех же 100 000 случайно сгенерированных строк длиной от 32 до 1024 символов. Но независимо от точных чисел, использование криптографически безопасной хеш-функции в качестве ключа только замедлит вашу программу.

Я могу опубликовать исходный код для своих сравнений, если хотите.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...