Geralmente, existem três maneiras de implementar bloqueios distribuídos: 1. Bloqueio de banco de dados otimista; 2. Lock distribuído com base em redis; 3. Lock distribuído com base no Zookeeper. Este blog apresentará o segundo método, que é para implementar o bloqueio distribuído com base no Redis. Embora existam vários blogs na Internet que introduzem a implementação de bloqueios distribuídos de Redis, sua implementação tem vários problemas. Para evitar crianças enganosas, este blog introduzirá em detalhes como implementar corretamente os bloqueios distribuídos Redis.
confiabilidade
Primeiro, para garantir que os bloqueios distribuídos estejam disponíveis, devemos pelo menos garantir que a implementação do bloqueio atenda às quatro condições a seguir ao mesmo tempo:
Exclusão mútua. A qualquer momento, apenas um cliente pode manter o bloqueio.
Nenhum impasse ocorre. Mesmo que um cliente trava durante o período de retenção de bloqueio sem desbloqueá -lo ativamente, ele pode garantir que outros clientes possam adicionar bloqueios.
Tolerante a falhas. Enquanto a maioria dos nós Redis estiver em execução normalmente, o cliente pode travar e desbloquear.
A pessoa que amarrou o sino deve ser desamarrada. O bloqueio e o desbloqueio deve ser o mesmo cliente, e o cliente não pode desamarrar as fechaduras adicionadas por outros.
Implementação de código
Dependências de componentes
Primeiro, precisamos apresentar os componentes de código aberto JEDIS através do MAVEN e adicionar o código a seguir ao arquivo pom.xml:
<Depencency> <voupid> redis.clients </frugid> <stifactId> jedis </sutifactId> <versão> 2.9.0 </version> </dependency>
Código de bloqueio
Postura correta
A conversa é barata, mostre -me o código. Primeiro mostre o código e depois explique por que isso é implementado:
classe pública Redistool {private Static final String Lock_success = "OK"; String final estática privada set_if_Not_Exist = "nx"; String final estática privada set_with_expire_time) é obtido com êxito*/public estático booleano trygetDistributedLock (Jedis Jedis, String LockKey, String requestId, int expireTime) {string resultado = jedis.Set (LockKey, requestId, set_if_not_exist, set_with_expire, result);Como você pode ver, apenas adicionamos uma única linha de código: jedis.set (stringKey, stringValue, stringnxxx, stringExpx, inttime). Este método set () possui cinco parâmetros formais no total:
O primeiro é uma chave, usamos a chave como bloqueio, porque a chave é única.
O segundo é o valor. O que estamos passando é o requestId. Muitos sapatos infantis podem não entender. Não é suficiente ter uma chave como bloqueio? Por que ainda precisamos usar o valor? O motivo é que, quando conversamos sobre confiabilidade acima, a trava distribuída deve atender à quarta condição para descompactar a campainha. Ao atribuir o valor ao RequestId, saberemos qual solicitação foi adicionada ao bloqueio e haverá uma base para desbloquear. O requestId pode ser gerado usando o método uuid.randomuuid (). ToString ().
O terceiro é NXXX. Preenchemos este parâmetro NX, que significa setifnotexista, ou seja, quando a chave não existe, executamos a operação definida; Se a chave já existir, nenhuma operação será realizada;
O quarto é o EXPX. Passamos neste parâmetro PX, o que significa que queremos adicionar uma configuração expirada a essa tecla. O tempo específico é determinado pelo quinto parâmetro.
O quinto é o momento, que ecoa o quarto parâmetro e representa o tempo de expiração da chave.
Em geral, a execução do método set () acima levará apenas a dois resultados: 1. Atualmente, não existe bloqueio (a chave não existe), a operação de bloqueio é executada e o bloqueio é definido para ser válido para o bloqueio e o valor representa o cliente bloqueado. 2. Já existe um bloqueio e nenhuma operação é feita.
Se você tiver cuidado, descobrirá que nosso código de bloqueio atende às três condições descritas em nossa confiabilidade. Primeiro de tudo, set () adiciona parâmetros NX, que podem garantir que, se uma chave já existir, a função não será chamada com sucesso, ou seja, apenas um cliente poderá manter o bloqueio para satisfazer o mutex. Em segundo lugar, desde que definimos um tempo de vencimento para o bloqueio, mesmo que o suporte do bloqueio trava na falha subsequente e não desbloqueie, o bloqueio será automaticamente desbloqueado porque atingiu o tempo de expiração (ou seja, a chave é excluída) e não haverá um impasse. Por fim, como atribuímos valor ao RequestId, que representa a identidade da solicitação de cliente bloqueada, o cliente pode verificar se é o mesmo cliente ao desbloquear. Como consideramos apenas cenários de implantação independentes de Redis, não consideraremos a tolerância de falhas por enquanto.
Exemplo de erro 1
Um exemplo de erro comum é usar a combinação de jedis.setnx () e jedis.expire () para obter bloqueio. O código é o seguinte:
public static void WHRINGGETLOCK1 (JEDIS JEDIS, String LockKey, String requestId, int expireTime) {Longo resultado = jedis.setnx (LockKey, requestId); if (resultado == 1) {// em o programa de repente aqui, o tempo de expiração não pode ser definido, e um deadlock ocorrerá;A função do método setNx () é setifnotexista, e o método Expire () é adicionar um tempo de expiração ao bloqueio. À primeira vista, parece ser o mesmo que o método Set () anterior. No entanto, como esses são dois comandos Redis, eles não são atômicos. Se o programa travar de repente após a execução do setNx (), o bloqueio não define o tempo de expiração. Então um impasse ocorrerá. A razão pela qual algumas pessoas implementam isso na Internet é que a versão inferior dos JEDIS não suporta o método do conjunto de vários parâmetros.
Exemplo de erro 2
Este exemplo de erro é mais difícil de encontrar problemas e a implementação também é mais complicada. Idéia de implementação: use o comando jedis.setNx () para implementar o travamento, onde a chave é o bloqueio e o valor é o tempo de expiração do bloqueio. Processo de execução: 1. Tente adicionar um bloqueio através do método setNx (). Se o bloqueio atual não existir, o bloqueio será devolvido com sucesso. 2. Se o bloqueio já existir, adquira o tempo de expiração da trava e compare -o com o horário atual. Se o bloqueio expirou, defina um novo tempo de expiração e retorne o bloqueio é adicionado com sucesso. O código é o seguinte:
public estático booleano WHRINGGETLOCK2 (JEDIS JEDIS, String LockKey, int expirETime) {long expira = System.currenttimemillis () + expireTime; string expiresstr = string.valueof (expire); // Se o bloqueio atual não existe, o bloqueio é realizado com sucessão, se é retronomado. Se o bloqueio existir, o tempo de expiração do bloqueio é obtido string currentValuester = jedis.get (LockKey); if (currentValuestest! = Null && long.parselong (currentValuest) <System.CurrentTimElis ()) {// O bloqueio expirou, obtenha o tempo de expiração do tempo anterior e define o tempo anterior e o bloqueio anterior) {// o bloqueio expirado, obtenha o tempo de expiração do tempo anterior e define o tempo anterior e define o tempo de expiração e define o tempo de expiração e define o tempo anterior e define o tempo anterior e define o tempo e o tempo de expiração, o tempo de expiração do tempo de expiração do tempo e o tempo de expiração do bloqueio e o tempo de expiração do bloqueio e o tempo de expiração do tempo do bloqueio do tempo e do bloqueio. expiresstr); if (OldValuEST! = NULL && OldValuest.Equals (currentValuEstr)) {// Considerando o caso de simultaneidade de vários thread, apenas se o valor de definição de um encadeamento for o mesmo que o valor atual, ele tiver o direito de retornar false;Então, qual é o problema com este código? 1. Como o cliente gera o próprio tempo de expiração, é necessário forçar o tempo de cada cliente a ser sincronizado sob a abordagem distribuída. 2. Quando o bloqueio expirar, se vários clientes executarem o método jedis.getSet () ao mesmo tempo, embora apenas um cliente possa bloquear o final, o tempo de expiração do bloqueio desse cliente pode ser substituído por outros clientes. 3. O bloqueio não possui o logotipo do proprietário, ou seja, qualquer cliente pode desbloqueá -lo.
Código de desbloqueio
Postura correta
Vamos mostrar o código primeiro e depois explicar lentamente por que isso é implementado:
public class Redistool {private Static final Long Release_Success = 1L;/*** Libere o bloqueio distribuído* @param jedis redis cliente* @param bloqueio de bloqueio* @param request request Id* @returgn se o lançamento foi bem -sucedido*/public static boolean relefredist distributedlock (jedis jedis, string.blockl)/stringId)/public static boolean lançado Keys [1]) == argv [1], em seguida, retorne redis.call ('del', chaves [1]) mais retorna 0 final "; resultado do objeto = jedis.eval (script, collection.singletonList (LockKey), coleta.singletonList (requestId); se release_success.equals) {resultado true;Como você pode ver, precisamos apenas de duas linhas de código para desbloqueá -lo! Na primeira linha de código, escrevemos um código de script Lua simples. A última vez que vimos essa linguagem de programação foi em "Hacker and Painter", mas não esperávamos que fosse usado desta vez. Na segunda linha de código, passamos o código Lua para o método jedis.eval () e atribuímos as teclas de parâmetro [1] ao LockKey e Argv [1] para solicitar. O método Eval () é entregar o código Lua ao servidor Redis para execução.
Então, qual é a função deste código Lua? De fato, é muito simples. Primeiro, obtenha o valor correspondente ao bloqueio, verifique se é igual ao requestId e, se for igual, exclua o bloqueio (desbloqueio). Então, por que usar o idioma Lua para implementá -lo? Porque é necessário garantir que as operações acima sejam atômicas. Para quais problemas atomicidade trará, você pode ler [Código de desbloqueio - Exemplo de erro 2]. Então, por que posso executar o método Eval () garante atomicidade, que se origina das características do Redis. Aqui está uma explicação parcial do comando de avaliação no site oficial:
Simplificando, quando o comando avaliar executar o código LUA, o código Lua será executado como um comando, e o Redis não executará outros comandos até que o comando avaliação seja executado.
Exemplo de erro 1
O código de desbloqueio mais comum é usar diretamente o método jedis.del () para excluir o bloqueio. Esse método de desbloquear diretamente sem primeiro julgar o proprietário do bloqueio fará com que qualquer cliente desbloqueie a qualquer momento, mesmo que o bloqueio não seja o seu.
public static void WHREDRELEASELOCK1 (JEDIS JEDIS, String LockKey) {Jedis.del (LockKey); }Exemplo de erro 2
À primeira vista, esse código de desbloqueio está bem. Eu até quase o implementei assim antes, o que é semelhante à postura correta. A única diferença é que ele é dividido em dois comandos para executar. O código é o seguinte:
public static void WHREDRELEASELOCK2 (Jedis Jedis, String LockKey, String requestId) {// Determine se o bloqueio e o desbloqueio são o mesmo cliente se (requestId.equals (Jedis.get (LockKey)))) {// Se esse bloqueio não for de repente deste cliente, será um dos comentários)) {//Quanto aos comentários do código, o problema é que, se o método jedis.del () for chamado, o bloqueio será desbloqueado quando não pertencer ao cliente atual. Então, existe realmente esse cenário? A resposta é sim. Por exemplo, o cliente a bloqueios e, após um período de tempo, o cliente é desbloqueado. Antes de executar o Jedis.del (), a fechadura expira repentinamente. Nesse momento, o Client B tenta bloquear com sucesso e, em seguida, o cliente A executa o método del () e, em seguida, o bloqueio do cliente B é desbloqueado.
Resumir
Este artigo apresenta principalmente como implementar corretamente o Redis Distributed Lock usando o código Java. Dois exemplos de erro clássicos são dados para bloqueio e desbloqueio. De fato, não é difícil implementar bloqueios distribuídos através do Redis, desde que garantisse atender às quatro condições em confiabilidade.
Em que cenário são fechados distribuídos principalmente? Onde a sincronização é necessária, por exemplo, a inserção de uma peça de dados exige verificação com antecedência se o banco de dados possui dados semelhantes. Quando várias solicitações são inseridas ao mesmo tempo, pode -se determinar que o banco de dados não possui dados semelhantes e todos eles podem ser adicionados. No momento, é necessário processamento síncrono, mas a tabela de travamento direta de banco de dados é muito demorada, para que a trava distribuída Redis seja usada. Ao mesmo tempo, apenas um thread pode executar a operação de inserção de dados e outros threads estão esperando.
O exposto acima é todo o conteúdo deste artigo sobre o idioma Java que descreve a implementação correta de bloqueios distribuídos Redis. Espero que seja útil para todos. Amigos interessados podem continuar se referindo a outros tópicos relacionados neste site. Se houver alguma falha, deixe uma mensagem para apontá -la. Obrigado amigos pelo seu apoio para este site!