Введение в алгоритм LRU
Полное название алгоритма LRU — «Наименее недавно использовавшиеся» — это алгоритм, проверяющий наименее недавно использовавшиеся данные. Этот алгоритм часто используется в стратегиях ликвидации памяти для перемещения редко используемых данных из памяти, освобождая место для недавно использованных «горячих данных».
Когда я впервые столкнулся с этим алгоритмом, я забыл, был ли он в классе операционной системы или в классе принципов компоновки компьютеров, он также широко используется в Redis, Guava и других инструментах и даже является одной из основных идей. Если вам в будущем понадобится спроектировать собственную систему, даже если вы не будете реализовывать этот алгоритм самостоятельно, идея LRU по-прежнему очень важна.
Алгоритм очень прост, вам нужно только отсортировать все данные по времени использования, а когда вам нужно отфильтровать данные LRU, вы можете выбрать более низкий ранг.
Реализация алгоритма
LRU в Redis
Объем данных в Redis обычно огромен, и если каждый раз сортировать полный объем данных, это неизбежно скажется на пропускной способности сервиса. Поэтому, когда Redis удаляет некоторые ключи в LRU, он использует выборку и вычисляет приблизительный LRU, поэтому локальные данные LRU исключаются.
Стратегия устранения памяти Redis
maxmemory-policy
Настройте дополнительные параметры:
-
noeviction
: Не устранено, команда записи вернет ошибку после превышения лимита памяти (например, OOM, кроме команд del) -
allkeys-lru
: Механизм LRU всех ключей Удалите ключи по принципу LRU наименее недавно использовавшегося во всех ключах, чтобы освободить место -
volatile-lru
: LRU энергозависимого ключа используется только для LRU в пределах диапазона ключа установленного времени истечения (если время истечения установлено, оно не будет устранено) -
allkeys-random
: Все ключи исключаются случайным образом без дискриминации, случайным образом -
volatile-random
: Случайный выбор для изменчивого ключа Установите случайный только в пределах диапазона ключа времени истечения срока действия. -
volatile-ttl
: исключение TTL для энергозависимых ключей Удаление приоритета в соответствии с ключом с наименьшим значением TTL
Эффекты Redis LRU
Вверху слева — теоретический эффект LRU; вверху справа — приблизительный LRU в Redis3.0 (выборочное значение 10); внизу слева — приблизительный LRU в Redis2.8 (выборочное значение 5); внизу справа — приблизительный LRU в Redis3.0 (выборочное значение 5). ))
светло-серый - устранено; серый - не устранено; зеленый - вновь записано
Дополнительные инструкции:
- Удаление памяти будет выполняться только при достижении установленного порога использования памяти.
-
maxmemory-samples
Конфигурация представляет значение выборки, количество выборок, собираемых при каждом его удалении - значение выборки равно 10, что означает, что 10 ключей берутся из ключей, определенных в настройках для расчета LRU, и ключ, удаляющий LRU. - Алгоритм в Redis 3.0 создает «пул кандидатов», который повышает эффективность и точность алгоритма по сравнению с 2.8, поскольку диапазон уменьшается.
в заключении:
- Redis3.0 повышает точность LRU за счет увеличения пула кандидатов, и эффект лучше, чем 2,8.
- Чем выше значение выборки, тем ближе результат к теоретическому LRU (но чем выше значение выборки, тем ниже эффективность).
- Достаточно точна почти частота дискретизации 5. Конечно, использование 10 в основном близко к теоретическому результату LRU, но теряет эффективность.
Идеи реализации LRU в Java
Согласно алгоритму LRU, реализация на Java требует выполнения следующих условий:
- Базовые данные используют двусвязный список, который удобно удалять в любом месте связанного списка и добавлять в конец связанного списка.
- Это более трудоемко использовать односвязный список, конечно, также трудоемко использовать такие структуры, как массивы
- Конечно, двусвязный список также вызывает затруднения при поиске, но в сочетании с HashMap можно использовать следующее:
- Связанный список необходимо отсортировать в порядке доступа (использования)
- После того, как объем данных превысит определенный порог, наименее использовавшиеся данные необходимо удалить.
Простая реализация LRUCache на Java
Для приведенных выше идей реализацииjava.util.LinkedHashMap
99% из них реализованы, поэтому реализовать LRUCache напрямую на основе LinkedHashMap очень просто.
Что LinkedHashMap прокладывает путь для LRUCache
- Конструктор предоставляет
accessOrder
Параметр, при включении которого метод get будет иметь дополнительные операции, чтобы убедиться, что порядок связанного списка находится в порядке, обратном порядку доступа. - Базовая структура использует двусвязный список для поиска функций, которые могут использовать HashMap.
- Переопределяет родительский класс HashMap
newNode
Методы иnewTreeNode
метод, эти два метода используются только для создания Node в HashMap, в то время как в LinkedHashMap не только создается Node, но и помещается Node в конец связанного списка - Родительский класс HashMap предоставляет 3 метода void Hook, которые ничего не делают:
-
afterNodeRemoval
Родительский класс вызывается после удаления элемента, существующего в коллекции. -
afterNodeInsertion
Родительский класс вызывается после ввода, вычисления, слияния -
afterNodeAccess
Родительский класс будет вызываться после замены, вычисления, слияния и других значений замены, LinkedHashMap будет вызываться, когда accessOrder включен в get,Суть в том, что он будет вызываться, когда будет операция над данными
-
- LinkedHashMap по существу повторно использует большинство функций HashMap, включая базовый Node
[], поэтому он может поддерживать функции исходного HashMap. - Но LinkedHashMap реализует 3 метода Hook родительского класса HashMap:
-
afterNodeRemoval
Реализовать операцию удаления связанного списка -
afterNodeInsertion
Операция вставки связанного списка не реализована, но добавлен новый метод Hookboolean removeEldestEntry
, когда этот метод Hook возвращает значение true, удалите узел в начале связанного списка. -
afterNodeAccess
Как упоминалось выше, после включения accessOrder управляемый узел будет помещен в конец связанного списка, чтобы убедиться, что связанный список расположен в порядке, обратном порядку доступа.
-
- Последние три метода используются для построения двусвязного списка, а LinkedHashMap также охватывает три метода родительского класса:
-
newNode
При создании узла добавьте узел в конец связанного списка. -
newTreeNode
При создании TreeNode добавьте узел в конец связанного списка. -
get
Когда функция get завершена, если accessOrder включен, будет вызываться afterNodeAccess для перемещения узла в конец связанного списка. обложкаnewNode
иnewTreeNode
метод, вызываемый в методе putnewNode
иnewTreeNode
Метод также реализует операцию вставки связанного списка.
-
Таким образом, мы можем понять, почему LinkedHashMap может легко реализовать LRUCache.
- Унаследовав родительский класс HashMap, он имеет функцию HashMap, поэтому временная сложность поиска узла составляет O(1), плюс связанный список является двунаправленным, удалить любой узел в связанном списке очень просто
- С помощью 3 методов Hook, предоставляемых HashMap и охватывающих 2 метода создания Node, реализуется добавление и удаление собственного связанного списка, а построение собственного связанного списка гарантированно выполняется правильно, не затрагивая исходную функцию Array; этот процесс на самом деле. Исходные функции улучшены с помощью метода Hook, потому что метод Hook фактически используется для создания узлов в исходном HashMap.
- предоставить атрибуты
accessOrder
и понялafterNodeAccess
метод, поэтому самые последние использованные или недавно вставленные данные могут быть помещены в конец связанного списка в соответствии с порядком доступа или операции, а данные, которые давно не использовались, ближе к началу связанного list, понимая, что весь связанный список отсортирован согласно требованиям LRU - Предоставляет метод Hook
boolean removeEldestEntry
, когда этот метод возвращает true, будет удален узел заголовка, то есть тот узел, который должен быть устранен в LRU, но реализация этого метода в LinkedHashMap всегда будет возвращать false
До сих пор реализация LRUCache проста: реализуйте этоremoveEldestEntry
Метод Hook устанавливает порог для LinkedHashMap, и при превышении порога будет выполняться устранение LRU.
Реализация кода Java, которую можно увидеть повсюду в Интернете
// 继承LinkedHashMap
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int MAX_CACHE_SIZE;
public LRUCache(int cacheSize) {
// 使用构造方法 public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
// initialCapacity、loadFactor都不重要
// accessOrder要设置为true,按访问排序
super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
MAX_CACHE_SIZE = cacheSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
// 超过阈值时返回true,进行LRU淘汰
return size() > MAX_CACHE_SIZE;
}
}
То, что кажется решенным с помощью нескольких строк кода, на самом деле является лишь верхушкой айсберга.
использованная литература
Использование Redis в качестве кэша LRU — Redis
Эта статья адаптирована измой блог, добро пожаловать в гости!