day104-Princípio de bloqueio distribuído em cache e uso de bloqueio distribuído

1. Princípio

Um princípio básico de bloqueios distribuídos é que quando vários threads em vários serviços acessam um recurso, todos vão para o mesmo lugar para ocupar o poço. Este lugar é o cache, que é compartilhado por todos os serviços. O primeiro thread a acessar irá para o cache. Configuração intermediária

Um cache de valor de chave, por ser uma operação atômica, o cache falha quando o thread subsequente define a mesma chave, realizando assim um bloqueio distribuído

2. Como implementar comandos do Linux

Referência de comando

http://www.redis.cn/commands/set.html

No link acima, podemos ver que quando a opção NX é adicionada à instrução definida, os dados podem ser armazenados em cache apenas quando a chave não existe

(1) Copie quatro links

(2) Envie o mesmo comando para se conectar ao cliente (lembre-se de mudar os privilégios de root primeiro)

(3) Depois de conectar, envie o comando de cache novamente ao mesmo tempo

Conexão 1

As conexões 2, 3 e 4 são todas as interfaces a seguir e todas retornam nulo, que é nulo

Pode-se observar que no modo cache NX, quando o valor da chave é o mesmo, apenas o primeiro cache terá sucesso, sendo  este o princípio básico dos bloqueios distribuídos.

3. Implementação de código e otimização perfeita

A implementação inicial, o código central é o seguinte, quando acessamos um thread (consultamos o banco de dados e salvamos os dados no cache), adicionamos um bloqueio distribuído a ele, ou seja, definimos um cache com chave como bloqueio no modo NX em redis. Quando outros threads acessam, se a configuração do cache falhar, significa que o bloqueio distribuído não foi obtido, e girou, ou seja, chamar este método repetidamente para tentar novamente, é claro, para não tentar muito frequentemente, você pode definir o tempo de sono

    @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
        //先从缓存中尝试获取,若为空则从数据库中获取然后放入缓存
        String catalogJson;
        synchronized (this) {
            ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
            catalogJson = opsForValue.get("catalogJson");
            System.out.println("从缓存内查询...");
            if (StringUtils.isEmpty(catalogJson)) {
                Map<String, List<Catelog2Vo>> catalogJsonFromDb = null;
                try {
                    catalogJsonFromDb = getCatalogJsonFromDbWithRedisLock();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("从数据库内查询...");
                return catalogJsonFromDb;
            }
        }

        Map<String, List<Catelog2Vo>> catalogJsonFromCache = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
        });
        return catalogJsonFromCache;
    }

    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() throws InterruptedException {

        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");

        if(lock){
            Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
            //删除锁
            stringRedisTemplate.delete("lock");
            return dataFromDb;
        }else{
            Thread.sleep(100);
           return getCatalogJsonFromDbWithRedisLock();
        }

    }

Analise os problemas nas implementações acima. Antes de excluir o bloqueio, uma anormalidade, falha de energia ou tempo de inatividade causará um deadlock.

, Portanto, definimos um prazo de validade para a chave (fechadura)

Desta vez, porque configuramos separadamente, se a energia for cortada antes do tempo de expiração ser definido após o bloqueio ser adquirido, isso também causará um deadlock.

Solução: o tempo de expiração do cache e dos dados em cache deve ser definido atomicamente e o redis oferece suporte, portanto, altere-o para o código a seguir

        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111",30,TimeUnit.SECONDS);

Mas ainda há um problema. Quando consultamos o banco de dados e o adicionamos ao cache ou processamos a lógica de negócios por muito tempo, isso excede o tempo de expiração que definimos. Nesse momento, se o bloqueio for excluído após a lógica ser executada, ele será excluído por outros tópicos.

Código do núcleo, o valor é definido como uuid para facilitar a identificação do bloqueio de thread atual ao excluir

    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() throws InterruptedException {

        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111",30,TimeUnit.SECONDS);
        String uuid = UUID.randomUUID().toString();
        if(lock){
            Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
            //删除锁 如果是当前线程的才删除 不然可能删的是线程的锁
            String value = stringRedisTemplate.opsForValue().get("lock");
            if(uuid.equals(value)){
                stringRedisTemplate.delete("lock");
            }
            return dataFromDb;
        }else{
            Thread.sleep(100);
           return getCatalogJsonFromDbWithRedisLock();
        }
    }

Porém, ainda há um problema. Como a comparação entre a obtenção do valor e a exclusão do cache não é uma operação atômica, se houver uma falha de energia após a obtenção do valor e antes de excluir o cache, também fará com que o bloqueio de outros , o que equivale a não estar bloqueado.

Portanto, o código é modificado novamente, usando o método de bloqueio de exclusão de script fornecido pelo redis para garantir a atomicidade

    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() throws InterruptedException {

        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111",300,TimeUnit.SECONDS);
        String uuid = UUID.randomUUID().toString();
        if(lock){
            Map<String, List<Catelog2Vo>> dataFromDb;
            try {
                dataFromDb = getDataFromDb();
            }finally {
                String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                        "then\n" +
                        "    return redis.call(\"del\",KEYS[1])\n" +
                        "else\n" +
                        "    return 0\n" +
                        "end";
                //保证获取value对比与删除缓存是原子操作,这里采用redis提供的执行脚本方式
                Long lock1 = stringRedisTemplate.execute(new DefaultRedisScript<Long>(), Arrays.asList("lock"), uuid);
            }
            return dataFromDb;
        }else{
            Thread.sleep(200);
           return getCatalogJsonFromDbWithRedisLock();
        }
    }

Neste momento, ele evoluiu para a forma final do estágio cinco

Até agora, o bloqueio distribuído foi basicamente implementado e melhorado. Não vou fazer o teste de estresse aqui. O resultado deve ser bem-sucedido.

Devido à repetitividade do código, ele pode ser encapsulado. Claro, outros já o fizeram. Posteriormente, aprenderemos sobre o framework mais profissional de bloqueios distribuídos - Redisson

Links de referência principais para este artigo:

http://www.redis.cn/commands/set.html

 

Acho que você gosta

Origin blog.csdn.net/JavaCoder_juejue/article/details/113777382
Recomendado
Clasificación