Stratégie d'expiration de la clé Redis, principe de jugement et implémentation d'algorithme

Stratégie d'expiration de la clé Redis et principe de jugement

Définir l'heure d'expiration pour la clé

Vous pouvez définir l'heure d'expiration lorsque vous définissez une clé, la syntaxe est la suivante:, SET key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]les deux options de délai d'expiration sont les suivantes:

  • EX secondes: définissez le délai d'expiration de la clé, en secondes.
  • PX millisecondes: définissez le délai d'expiration de la clé en millisecondes.
127.0.0.1:6379> set name morris ex 5
OK

Vous pouvez utiliser la commande expire pour définir individuellement l'heure d'expiration de la clé, la syntaxe EXPIRE key seconds:

127.0.0.1:6379> expire name 5
(integer) 1

Vous pouvez utiliser ttl pour afficher le délai d'expiration de la clé en secondes et pttl pour afficher le délai d'expiration de la clé en millisecondes:

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

Pour une clé expirée ou une clé qui n'existe pas, ttl renvoie -2. Pour une clé normale, ttl renvoie -1. Pour une clé avec un délai d'expiration défini, ttl renvoie le nombre de secondes restantes pour expirer.

Vous pouvez utiliser la commande persist pour supprimer le délai d'expiration et en faire une clé 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

point important:

  • Si la clé existe, l'utilisation de la commande set écrasera l'heure d'expiration, ce qui signifie qu'elle sera définie comme une nouvelle clé.
  • Si la clé est modifiée par la commande renommer, le délai d'expiration sera transféré à la nouvelle clé.
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

Comment éliminer les clés expirées

Une fois la clé expirée dans Redis, il existe deux façons d'éliminer la clé: passive et active.

Voie active

Lorsque la clé dépasse le délai d'expiration, la clé ne sera pas supprimée immédiatement. Elle ne sera effacée que lorsque del, set, getset sont exécutés sur la clé expirée, ce qui signifie que toutes les opérations modifiant la valeur de la clé déclencheront l'action de suppression. Lorsque le client En essayant d'y accéder, la clé sera découverte et expirée activement.

Voie passive

La méthode active ne suffit pas. Étant donné que certaines clés expirées ne seront jamais accessibles, elles n'expireront jamais. Redis fournit une méthode passive. La méthode passive détectera périodiquement les clés expirées et les supprimera. Les opérations spécifiques sont les suivantes:

  1. Extrayez au hasard 20 clés toutes les 100 ms pour la détection d'expiration.
  2. Supprimez toutes les clés expirées parmi les 20 clés.
  3. Si la proportion de clés expirées est supérieure à 25%, répétez l'étape 1.

La méthode passive utilise un algorithme de probabilité pour échantillonner les clés au hasard, ce qui signifie qu'à tout moment, jusqu'à 1/4 des clés expirées seront effacées.

Configurer la mémoire maximale

Vous pouvez /etc/redis/6379.conflimiter la taille de la mémoire de redis dans le fichier de configuration:

maxmemory <bytes>

La définition de maxmemory sur 0 signifie qu'il n'y a pas de limite de mémoire.

Stratégie de recyclage

Lorsque la taille de limite de mémoire spécifiée est atteinte, vous devez choisir un comportement différent, c'est-à-dire une stratégie pour récupérer certaines anciennes données afin que la limite de mémoire puisse être évitée lors de l'ajout de données.

La maxmemory-policystratégie de recyclage spécifique peut être configurée via des paramètres et les stratégies de recyclage prises en charge sont les suivantes:

  • noeviction: ne pas recycler, renvoyer une erreur directement à la commande d'écriture ou aux opérations de lecture. Si vous utilisez redis comme base de données, vous devez utiliser cette stratégie de recyclage. Cette stratégie est utilisée par défaut.
  • Volatile-lru: parmi les clés avec un délai d'expiration, essayez de récupérer la clé la moins récemment utilisée.
  • allkeys-lru: parmi toutes les clés, essayez de récupérer la clé la moins récemment utilisée.
  • Volatile-lfu: Parmi les clés avec un délai d'expiration, essayez de récupérer la clé la moins fréquemment utilisée.
  • allkeys-lfu: parmi toutes les clés, essayez de récupérer la clé la moins fréquemment utilisée.
  • allkeys-random: récupération aléatoire parmi toutes les clés.
  • volatile-random: récupérez aléatoirement les clés avec le délai d'expiration.
  • volatile-ttl: Parmi les clés avec un délai d'expiration, la clé avec la durée de vie la plus courte (TTL) est préférée.

Si les conditions préalables au recyclage ne sont pas remplies, les stratégies volatile-lru, volatile-random et volatile-ttl sont similaires à noeviction.

Algorithme LRU approximatif

L'algorithme LRU de Redis n'est pas une implémentation complète, ce qui signifie que Redis ne peut pas sélectionner la clé qui n'a pas été accédée le plus longtemps pour le recyclage, car l'utilisation du véritable algorithme LRU nécessite de scanner toutes les clés, ce qui fera perdre beaucoup de temps, ce qui est similaire à redis. La conception haute performance va à l'encontre de l'intention initiale.

Au contraire, Redis utilise un algorithme similaire à LRU, en échantillonnant un petit nombre de clés, puis en sélectionnant les clés qui n'ont pas été accédées le plus longtemps pour le recyclage. Redis fournit les paramètres suivants pour ajuster le nombre d'échantillons vérifiés chaque fois qu'il est collecté afin d'obtenir la précision de l'algorithme d'ajustement:

maxmemory-samples 5

Implémentation d'algorithme

LRU

LRU (L'algorithme le moins récemment utilisé, l'algorithme le moins récemment utilisé): si une donnée n'a pas été consultée dans la période la plus récente, on peut considérer qu'elle ne sera probablement pas accessible à l'avenir. Par conséquent, lorsque l'espace est plein, les données qui n'ont pas été consultées depuis le plus longtemps sont éliminées en premier.

Réalisation: Il peut être réalisé avec une double liste chaînée (LinkedList) + table de hachage (HashMap) (la liste chaînée est utilisée pour indiquer l'emplacement, et la table de hachage est utilisée pour stocker et rechercher).

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 peut être utilisé directement en Java pour réaliser:

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 (le moins fréquemment utilisé, l'algorithme le moins fréquemment utilisé): si une donnée est rarement consultée au cours de la période récente, on peut considérer qu'elle est peu susceptible d'y accéder à l'avenir. Par conséquent, lorsque l'espace est plein, les données les moins fréquemment utilisées sont éliminées en premier.

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();
        }
    }
}

L'utilisation de LinkedList nécessite que toutes les clés soient triées complètement et la complexité temporelle est O (n).

Étant donné que LFU éliminera les données les moins fréquemment consultées, nous avons besoin d'une méthode appropriée pour maintenir la fréquence d'accès aux données par ordre de grandeur. L'algorithme LFU peut essentiellement être considéré comme un problème top K (K = 1), c'est-à-dire que l'élément avec la plus petite fréquence est sélectionné. Par conséquent, nous pouvons facilement penser à utiliser un tas binaire pour sélectionner l'élément avec la fréquence la plus petite. Cette implémentation est plus efficace. La stratégie de mise en œuvre est un petit tas supérieur + table de hachage.

En utilisant un tas binaire pour trouver la plus petite de toutes les clés, la complexité temporelle est 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();
        }
    }
}

Je suppose que tu aimes

Origine blog.csdn.net/u022812849/article/details/108596019
conseillé
Classement