Estrategia de caducidad de claves de Redis, principio de juicio e implementación de algoritmos

Principio de juicio y estrategia de expiración de claves de Redis

Establecer el tiempo de vencimiento de la clave

Puede establecer el tiempo de vencimiento cuando establece una clave, la sintaxis es :, SET key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]las dos opciones para el tiempo de vencimiento son las siguientes:

  • Segundos EX: establezca el tiempo de caducidad de la clave, en segundos.
  • Milisegundos PX: establezca el tiempo de caducidad de la clave en milisegundos.
127.0.0.1:6379> set name morris ex 5
OK

Puede usar el comando expire para establecer individualmente el tiempo de vencimiento de la clave, la sintaxis EXPIRE key seconds:

127.0.0.1:6379> expire name 5
(integer) 1

Puede usar ttl para ver el tiempo de vencimiento de la clave en segundos y pttl para ver el tiempo de vencimiento de la clave en milisegundos:

127.0.0.1:6379> set name morris ex 10
OK
127.0.0.1:6379> ttl name
(integer) 8
127.0.0.1:6379> pttl name
(integer) 5353
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> set name morris
OK
127.0.0.1:6379> ttl name
(integer) -1

Para una clave caducada o una clave que no existe, ttl devuelve -2. Para una clave normal, ttl devuelve -1. Para una clave con un tiempo de caducidad establecido, ttl devuelve el número de segundos que quedan para caducar.

Puede usar el comando persist para eliminar el límite de tiempo de espera y convertirlo en una clave permanente:

127.0.0.1:6379> set name morris ex 10
OK
127.0.0.1:6379> persist name
(integer) 1
127.0.0.1:6379> ttl name
(integer) -1

punto importante:

  • Si la clave existe, el uso del comando set sobrescribirá el tiempo de vencimiento, lo que significa que se establecerá como una nueva clave.
  • Si la clave es modificada por el comando de cambio de nombre, el período de tiempo de espera se transferirá a la nueva clave.
127.0.0.1:6379> set name morris ex 30
OK
127.0.0.1:6379> set name tom
OK
127.0.0.1:6379> ttl name
(integer) -1
127.0.0.1:6379> set name morris ex 30
OK
127.0.0.1:6379> rename name myname
OK
127.0.0.1:6379> ttl myname
(integer) 22

Cómo eliminar claves caducadas

Una vez que la clave caduca en Redis, hay dos formas de eliminar la clave: pasiva y activa.

Forma activa

Cuando la clave excede el tiempo de vencimiento, la clave no se eliminará inmediatamente. Solo se borrará cuando se ejecuten del, set, getset en la clave caducada, lo que significa que todas las operaciones que cambian el valor de la clave activarán la acción de eliminación. Cuando el cliente Al intentar acceder a él, la clave se descubrirá y caducará activamente.

Forma pasiva

El método activo no es suficiente. Debido a que algunas claves caducadas nunca se accederá, nunca caducarán. Redis proporciona un método pasivo. El método pasivo detectará periódicamente claves caducadas y las eliminará. Las operaciones específicas son las siguientes:

  1. Extraiga aleatoriamente 20 claves cada 100 ms para la detección de caducidad.
  2. Elimine todas las claves caducadas entre las 20 claves.
  3. Si la proporción de claves caducadas es superior al 25%, repita el paso 1.

El método pasivo utiliza un algoritmo de probabilidad para muestrear claves al azar, lo que significa que en un momento dado, se borrará hasta 1/4 de las claves caducadas.

Configurar la memoria máxima

Puede /etc/redis/6379.conflimitar el tamaño de la memoria de redis en el archivo de configuración:

maxmemory <bytes>

Establecer maxmemory en 0 significa que no hay límite de memoria.

Estrategia de reciclaje

Cuando se alcanza el tamaño del límite de memoria especificado, debe elegir un comportamiento diferente, es decir, una estrategia para recuperar algunos datos antiguos de modo que se pueda evitar el límite de memoria al agregar datos.

La maxmemory-policyestrategia de reciclaje específica se puede configurar mediante parámetros, y las estrategias de reciclaje admitidas son las siguientes:

  • noeviction: No reciclar, devolver un error directamente al comando de escritura, o leer operaciones. Si usa redis como base de datos, debe usar esta estrategia de reciclaje. Esta estrategia se usa por defecto.
  • Volatile-lru: Entre las claves con tiempo de caducidad, intente recuperar la clave utilizada menos recientemente.
  • allkeys-lru: Entre todas las claves, intente recuperar la clave utilizada menos recientemente.
  • Volatile-lfu: Entre las claves con tiempo de caducidad, intente recuperar la clave menos utilizada.
  • allkeys-lfu: Entre todas las claves, intente recuperar la clave utilizada con menos frecuencia.
  • allkeys-random: Recuperación aleatoria entre todas las claves.
  • volatile-random: Reclama aleatoriamente las claves con tiempo de vencimiento.
  • volatile-ttl: entre las claves con tiempo de vencimiento, se prefiere la clave con el menor tiempo de vida (TTL).

Si no se cumplen los requisitos previos para el reciclaje, las estrategias volatile-lru, volatile-random y volatile-ttl son similares a no desalojo.

Algoritmo aproximado de LRU

El algoritmo LRU en Redis no es una implementación completa, lo que significa que Redis no puede seleccionar la clave a la que no se ha accedido por más tiempo para reciclar, porque el uso del algoritmo LRU real requiere escanear todas las claves, lo que perderá mucho tiempo, que es similar a redis. El diseño de alto rendimiento va en contra de la intención original.

Por el contrario, Redis utiliza un algoritmo similar a LRU, muestreando una pequeña cantidad de claves y luego seleccionando las claves a las que no se ha accedido por más tiempo para su reciclaje. Redis proporciona los siguientes parámetros para ajustar el número de muestras comprobadas cada vez que se recolecta para lograr la precisión del algoritmo de ajuste:

maxmemory-samples 5

Implementación de algoritmos

LRU

LRU (El menos utilizado recientemente, el algoritmo utilizado menos recientemente): si no se ha accedido a un dato en el período de tiempo más reciente, se puede considerar que es poco probable que se acceda a él en el futuro. Por tanto, cuando el espacio está lleno, primero se eliminan los datos a los que no se ha accedido durante más tiempo.

Realización: Se puede realizar con una lista doblemente enlazada (LinkedList) + tabla hash (HashMap) (la lista enlazada se usa para indicar la ubicación, y la tabla hash se usa para almacenar y buscar).

package com.morris.redis.demo.cache;

import java.util.HashMap;
import java.util.LinkedList;

public class LRUCache<K, V> {
    
    

    private int capacity;

    private int size;

    private LinkedList<K> linkedList = new LinkedList<>();

    private HashMap<K, V> hashMap = new HashMap();

    public LRUCache(int capacity) {
    
    
        this.capacity = capacity;
    }

    public void set(K k, V v) {
    
    

        if(hashMap.containsKey(k)) {
    
     // key存在
            linkedList.remove(k); // 从双向链表中移除
            linkedList.addFirst(k); // 插入到双向链表尾部
            hashMap.put(k, v);
            return;
        }

        // key不存在
        if(size == capacity) {
    
    
            linkedList.removeLast();
            size--;
        }

        linkedList.addFirst(k);
        hashMap.put(k, v);
        size++;
    }

    public V get(K k) {
    
    
        if(hashMap.containsKey(k)) {
    
    
            linkedList.remove(k); // 从双向链表中移除
            linkedList.addFirst(k); // 插入到双向链表尾部
            return hashMap.get(k);
        }
        return null;
    }
}

LinkedHashMap se puede utilizar directamente en Java para lograr:

import java.util.LinkedHashMap;
import java.util.Map;

public class LRUCache2<K, V> extends LinkedHashMap<K, V> {

    private int capacity;

    public LRUCache2(int capacity) {
        super(16, 0.75f, true);
        this.capacity = capacity;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return size() > capacity;
    }

}

LFU

LFU (menos utilizado, el algoritmo menos utilizado): si rara vez se accede a un dato en el período reciente, se puede considerar que es poco probable que se acceda a él en el futuro. Por lo tanto, cuando el espacio está lleno, primero se eliminan los datos utilizados con menor frecuencia.

package com.morris.redis.demo.cache.lfu;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;

public class LFUCache<K, V> {
    
    

    private int capacity;

    private int size;

    public LFUCache(int capacity) {
    
    
        this.capacity = capacity;
    }

    private HashMap<K, Node<K, V>> hashMap = new HashMap<>();

    private LinkedList<Node<K, V>> linkedList = new LinkedList<>();

    public void set(K k, V v) {
    
    

        if(hashMap.containsKey(k)) {
    
     // 存在则更新
            Node<K, V> node = hashMap.get(k);
            node.count = 0; // 增加使用次数
            node.lastTime = System.nanoTime(); // 更新使用时间
            return;
        }

        // 不存在
        if(size == capacity) {
    
    

            // 删除最近最不常用的key
            Collections.sort(linkedList, (k1, k2) -> {
    
    
                // 先比较使用次数
                if(k1.count > k2.count) {
    
    
                    return 1;
                }

                if(k1.count < k2.count) {
    
    
                    return -1;
                }

                // 再比较最后一次使用时间
                if(k1.lastTime > k2.lastTime) {
    
    
                    return 1;
                }

                if(k1.lastTime < k2.lastTime) {
    
    
                    return -1;
                }

                return 0;
            });

            linkedList.removeFirst();
            size--;
        }

        Node<K, V> node = new Node<>(k, v, System.nanoTime());
        hashMap.put(k, node);
        linkedList.addLast(node);
        this.size++;
    }

    public V get(K k) {
    
    
        V v = null;
        if(hashMap.containsKey(k)) {
    
    
            Node<K, V> node = hashMap.get(k);
            node.count++; // 增加使用次数
            node.lastTime = System.nanoTime(); // 更新使用时间
            v = node.v;
        }
        return v;
    }

    public void print() {
    
    
        System.out.println(linkedList);
    }

    private static class Node<K, V> {
    
    
        K k;
        V v;
        int count; // 使用次数
        long lastTime; // 最后一次使用时间

        public Node(K k, V v, long lastTime) {
    
    
            this.k = k;
            this.v = v;
            this.lastTime = lastTime;
        }

        @Override
        public String toString() {
    
    
            final StringBuilder sb = new StringBuilder("Node{");
            sb.append("k=").append(k);
            sb.append(", count=").append(count);
            sb.append(", lastTime=").append(lastTime);
            sb.append('}');
            return sb.toString();
        }
    }
}

El uso de LinkedList requiere que todas las claves se ordenen por completo y la complejidad del tiempo es O (n).

Teniendo en cuenta que LFU eliminará los datos a los que se accede con menor frecuencia, necesitamos un método adecuado para mantener la frecuencia de acceso a los datos en orden de magnitud. El algoritmo LFU se puede considerar esencialmente como un problema de K superior (K = 1), es decir, se selecciona el elemento con la frecuencia más pequeña. Por lo tanto, podemos pensar fácilmente en usar un montón binario para seleccionar el elemento con la frecuencia más pequeña. Esta implementación es más eficiente. La estrategia de implementación es una pequeña tabla hash y de pila superior.

Usando un montón binario para encontrar la más pequeña de todas las claves, la complejidad del tiempo es O (logn).

package com.morris.redis.demo.cache.lfu;

import java.util.HashMap;
import java.util.PriorityQueue;

public class LFUCache2<K, V> {
    
    

    private int capacity;

    private int size;

    public LFUCache2(int capacity) {
    
    
        this.capacity = capacity;
    }

    private HashMap<K, Node<K, V>> hashMap = new HashMap<>();

    private PriorityQueue<Node<K, V>> priorityQueue = new PriorityQueue<>((k1, k2) -> {
    
    
        // 先比较使用次数
        if(k1.count > k2.count) {
    
    
            return 1;
        }

        if(k1.count < k2.count) {
    
    
            return -1;
        }

        // 再比较最后一次使用时间
        if(k1.lastTime > k2.lastTime) {
    
    
            return 1;
        }

        if(k1.lastTime < k2.lastTime) {
    
    
            return -1;
        }

        return 0;
    });

    public void set(K k, V v) {
    
    

        if(hashMap.containsKey(k)) {
    
     // 存在则更新
            Node<K, V> node = hashMap.get(k);
            node.count = 0; // 增加使用次数
            node.lastTime = System.nanoTime(); // 更新使用时间
            return;
        }

        // 不存在
        if(size == capacity) {
    
    

            // 删除最近最不常用的key
            priorityQueue.remove();
            size--;
        }

        Node<K, V> node = new Node<>(k, v, System.nanoTime());
        hashMap.put(k, node);
        priorityQueue.add(node);
        this.size++;
    }

    public V get(K k) {
    
    
        V v = null;
        if(hashMap.containsKey(k)) {
    
    
            Node<K, V> node = hashMap.get(k);
            node.count++; // 增加使用次数
            node.lastTime = System.nanoTime(); // 更新使用时间
            v = node.v;
        }
        return v;
    }

    public void print() {
    
    
        System.out.println(priorityQueue);
    }

    private static class Node<K, V> {
    
    
        K k;
        V v;
        int count; // 使用次数
        long lastTime; // 最后一次使用时间

        public Node(K k, V v, long lastTime) {
    
    
            this.k = k;
            this.v = v;
            this.lastTime = lastTime;
        }

        @Override
        public String toString() {
    
    
            final StringBuilder sb = new StringBuilder("Node{");
            sb.append("k=").append(k);
            sb.append(", count=").append(count);
            sb.append(", lastTime=").append(lastTime);
            sb.append('}');
            return sb.toString();
        }
    }
}

Supongo que te gusta

Origin blog.csdn.net/u022812849/article/details/108596019
Recomendado
Clasificación