Estratégia de expiração da chave Redis, princípio de julgamento e implementação de algoritmo

Estratégia de expiração da chave Redis e princípio de julgamento

Defina o tempo de expiração da chave

Você pode definir o tempo de expiração ao definir uma chave, a sintaxe é :, SET key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]as duas opções para o tempo de expiração são as seguintes:

  • Segundos EX: Defina o tempo de expiração da chave, em segundos.
  • Milissegundos PX: Defina o tempo de expiração da chave em milissegundos.
127.0.0.1:6379> set name morris ex 5
OK

Você pode usar o comando expire para definir individualmente o tempo de expiração da chave, a sintaxe EXPIRE key seconds::

127.0.0.1:6379> expire name 5
(integer) 1

Você pode usar ttl para ver o tempo de expiração da chave em segundos e pttl para ver o tempo de expiração da chave em milissegundos:

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 uma chave expirada ou inexistente, ttl retorna -2. Para uma chave normal, ttl retorna -1. Para uma chave com um tempo de expiração definido, ttl retorna o número de segundos restantes para expirar.

Você pode usar o comando persist para remover o limite de tempo limite e torná-lo uma chave 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

ponto importante:

  • Se a chave existir, o uso do comando set sobrescreverá o tempo de expiração, o que significa que será definido como uma nova chave.
  • Se a chave for modificada pelo comando renomear, o período de tempo limite será transferido para a nova chave.
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

Como eliminar chaves expiradas

Depois que a chave expira no Redis, há duas maneiras de eliminá-la: passiva e ativa.

Maneira ativa

Quando a chave excede o tempo de expiração, a chave não será excluída imediatamente. Ela será apagada apenas quando del, set, getset forem executados na chave expirada, o que significa que todas as operações para alterar o valor da chave irão acionar a ação de exclusão. Quando o cliente Ao tentar acessá-lo, a chave será descoberta e expirará ativamente.

Forma passiva

O método ativo não é suficiente. Como algumas chaves expiradas nunca serão acessadas, elas nunca expirarão. O Redis fornece um método passivo. O método passivo detecta periodicamente as chaves expiradas e as exclui. As operações específicas são as seguintes:

  1. Extraia aleatoriamente 20 chaves a cada 100 ms para detecção de expiração.
  2. Exclua todas as chaves expiradas entre as 20 chaves.
  3. Se a proporção de chaves expiradas for maior que 25%, repita a etapa 1.

O método passivo usa um algoritmo de probabilidade para amostrar chaves aleatoriamente, o que significa que a qualquer momento, até 1/4 das chaves expiradas serão apagadas.

Configurar memória máxima

Você pode /etc/redis/6379.conflimitar o tamanho da memória do redis no arquivo de configuração:

maxmemory <bytes>

Definir maxmemory como 0 significa que não há limite de memória.

Estratégia de reciclagem

Quando o tamanho do limite de memória especificado é atingido, você precisa escolher um comportamento diferente, ou seja, uma estratégia para recuperar alguns dados antigos de forma que o limite de memória possa ser evitado ao adicionar dados.

A maxmemory-policyestratégia de reciclagem específica pode ser configurada por meio de parâmetros, e as estratégias de reciclagem com suporte são as seguintes:

  • noeviction: não reciclar, retornar um erro diretamente para o comando de gravação ou operações de leitura.Se você usar o redis como banco de dados, deverá usar esta estratégia de reciclagem, que é usada por padrão.
  • Volatile-lru: Entre as chaves com tempo de expiração, tente recuperar a chave menos usada recentemente.
  • allkeys-lru: Entre todas as chaves, tente recuperar a chave menos usada recentemente.
  • Volatile-lfu: Entre as chaves com tempo de expiração, tente recuperar a chave menos usada.
  • allkeys-lfu: Entre todas as chaves, tente recuperar a chave menos usada.
  • allkeys-random: recuperação aleatória entre todas as chaves.
  • volatile-random: recupera as chaves aleatoriamente com o tempo de expiração.
  • volatile-ttl: Entre as chaves com tempo de expiração, a chave com o menor tempo de vida (TTL) é a preferida.

Se os pré-requisitos para reciclagem não forem atendidos, as estratégias volatile-lru, volatile-random e volatile-ttl serão semelhantes às de noeviction.

Algoritmo LRU aproximado

O algoritmo LRU no Redis não é uma implementação completa, o que significa que o Redis não pode selecionar a chave que não foi acessada por mais tempo para reciclagem, porque o uso do algoritmo LRU real requer a varredura de todas as chaves, o que levará muito tempo, o que é semelhante ao redis. O design de alto desempenho vai contra a intenção original.

Ao contrário, o Redis usa um algoritmo semelhante ao LRU, amostrando um pequeno número de chaves e, em seguida, selecionando as chaves que não foram acessadas por mais tempo para reciclagem. O Redis fornece os seguintes parâmetros para ajustar o número de amostras verificadas cada vez que é coletado para atingir a precisão do algoritmo de ajuste:

maxmemory-samples 5

Implementação de algoritmo

LRU

LRU (O algoritmo menos usado recentemente, o algoritmo menos usado recentemente): se um dado não foi acessado no período de tempo mais recente, pode-se considerar que é improvável que ele seja acessado no futuro. Portanto, quando o espaço está cheio, os dados que não foram acessados ​​por mais tempo são eliminados primeiro.

Realização: Pode ser realizada com uma lista duplamente vinculada (LinkedList) + tabela hash (HashMap) (a lista vinculada é usada para indicar a localização, e a tabela hash é usada para armazenar e pesquisar).

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 pode ser usado diretamente em Java para alcançar:

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 (Least Frequently Used, o algoritmo menos frequentemente usado): Se um dado raramente é acessado no período recente, pode-se considerar que é improvável que ele seja acessado no futuro. Portanto, quando o espaço está cheio, os dados usados ​​com menos frequência são eliminados primeiro.

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

Usar LinkedList requer que todas as chaves sejam classificadas completamente, e a complexidade de tempo é O (n).

Considerando que o LFU eliminará os dados acessados ​​com menos frequência, precisamos de um método adequado para manter a frequência de acesso aos dados em ordem de magnitude. O algoritmo LFU pode ser essencialmente considerado como um problema K top (K = 1), ou seja, o elemento com a menor frequência é selecionado. Portanto, podemos facilmente pensar em usar um heap binário para selecionar o elemento com a menor frequência. Esta implementação é mais eficiente. A estratégia de implementação é pequena pilha superior + tabela de hash.

Usando um heap binário para encontrar a menor de todas as chaves, a complexidade de tempo é 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();
        }
    }
}

Acho que você gosta

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