Devo dizer que todo desenvolvedor deve entender a consistência do banco de dados

Imagine atribuir um valor a uma variável e, em seguida, lê-lo imediatamente, apenas para descobrir que a gravação agora não funciona de todo. 

x = 42assert(x == 42)  # 抛出异常

Essa situação pode ser encontrada ao usar armazenamento de dados distribuído com garantias de consistência fracas. Você pode perguntar: “Espere, o banco de dados não deveria resolver o problema de consistência para mim?” Após a operação de atualização, os dados reais serão atualizados imediatamente ou vai demorar um pouco, dependendo se o banco de dados oferece essa garantia.

As garantias de consistência fornecidas por alguns bancos de dados são um pouco contra-intuitivas, mas sua finalidade é fornecer alta disponibilidade e alto desempenho. Existem também alguns bancos de dados que permitem que você escolha se deseja melhor desempenho ou garantias mais fortes, como Cosmos DB e Cassandra do Azure. Portanto, você precisa entender os prós e os contras.

 

Anatomia de uma solicitação de banco de dados

Vamos dar uma olhada no que acontece a seguir, quando você envia a solicitação ao banco de dados. Em uma situação ideal, sua solicitação será executada imediatamente:

No entanto, não vivemos em um mundo ideal. Sua solicitação deve ser enviada ao armazenamento de dados, processada e, finalmente, a resposta é enviada para você. Todas essas operações exigem um certo tempo e não podem ser concluídas em um instante:

A melhor garantia que o banco de dados pode fornecer é que a solicitação seja executada em algum ponto entre a chamada e a conclusão. Você pode pensar que isso não é grande coisa. Afinal, você está acostumado com isso ao escrever um aplicativo de thread único. Por exemplo, se você atribuir 1 a x e, em seguida, ler o valor de x, você definitivamente obterá 1, desde que não haja outros threads. Escreva a mesma variável. No entanto, quando você usa o armazenamento de dados para replicar o estado dos dados para vários computadores, a fim de obter alta disponibilidade e escalabilidade, tudo se torna desconhecido. Para entender por que isso acontece, vamos explorar os prós e os contras que os designers de sistema devem pesar ao implementar leituras no modelo simplificado de bancos de dados distribuídos.

Suponha que tenhamos um armazenamento de valor-chave distribuído, que consiste em um conjunto de réplicas. Um líder é selecionado entre as réplicas e este é o único nó que pode aceitar gravações. Depois que o líder recebe a solicitação de gravação, ele gravará dados em outras réplicas de forma assíncrona. Embora todas as réplicas recebam as mesmas atualizações na mesma ordem, elas são recebidas em momentos diferentes.

Sua tarefa é criar uma estratégia para lidar com as solicitações de leitura, o que você deve fazer? Você pode ler dados do líder ou outras réplicas. Se todas as leituras passarem pelo líder, o rendimento se tornará um gargalo e não poderá exceder a quantidade de dados que um único nó pode manipular. Se alguma réplica puder atender à solicitação de leitura, a taxa de transferência poderá ser bastante melhorada, mas, neste caso, o estado do sistema obtido pelos dois clientes (observadores) pode ser inconsistente, porque o líder e a réplica e entre Pode haver atrasos entre as cópias.

Simplificando, precisamos pesar os prós e os contras entre a consistência do sistema vista pelo observador e o desempenho e alta disponibilidade do sistema. Para entender essa relação, precisamos definir com precisão a consistência. Podemos nos referir ao modelo de consistência (https://jepsen.io/consistency), que define a visualização do status do sistema que o observador do status do sistema pode experimentar.

 

Consistência forte

Se as operações de escrita e leitura do cliente só podem ser enviadas para o líder, parece que cada solicitação é feita atomicamente em um determinado momento, como se houvesse apenas uma cópia dos dados. Não importa quantas réplicas existam, e não importa quão atrasada cada réplica esteja, desde que o cliente consulte diretamente o líder, do seu ponto de vista, há apenas uma cópia de dados. 

Como a solicitação não será atendida imediatamente e apenas um nó fornece o serviço, a solicitação deve ser executada durante o período de chamada e de conclusão. Outra maneira de pensar é que, após a conclusão da solicitação, todos os observadores podem ver seus efeitos colaterais:

Como outros participantes podem ver a solicitação entre a chamada e a conclusão da solicitação, o desempenho em tempo real deve ser garantido. Essa garantia possui um modelo de consistência teórica denominado consistência linear, também conhecida como consistência forte. A consistência linear é a consistência mais forte que o sistema pode fornecer para uma única solicitação de objeto.

E se o cliente enviar uma solicitação de leitura para o líder, mas quando a solicitação chegar, o líder foi abolido, mas o servidor que recebeu a solicitação achar que ainda é o líder, o que devo fazer? Se a solicitação for processada pelo líder anterior, a consistência forte do sistema não pode ser garantida. Para evitar que isso aconteça, o líder hipotético primeiro precisa contatar a maioria das réplicas para confirmar se ele ainda é o líder. Somente quando ele ainda for o líder, ele poderá executar a solicitação e enviar a resposta de volta ao cliente. Esse processo aumenta muito o tempo necessário para a leitura.

 

Consistência sequencial 

Até agora, discutimos a prática de processar leituras em ordem pelo líder. Mas essa abordagem cria um gargalo, o que limita o rendimento do sistema. Mais importante, o líder também precisa entrar em contato com a maioria das réplicas para processar as leituras. Para melhorar o desempenho de leitura, devemos permitir que réplicas processem solicitações.

Embora a réplica fique atrás do líder, ela recebe atualizações na mesma ordem que o líder. Se o cliente A consultar apenas a cópia 1 e o cliente B consultar apenas a cópia 2, os dois clientes verão estados diferentes em pontos diferentes no tempo porque as cópias não estão totalmente sincronizadas:

Neste modelo de consistência, para todos os observadores, as operações ocorrem na mesma ordem, mas quando os efeitos colaterais das operações serão vistos pelos observadores, este modelo não pode fornecer nenhuma garantia em tempo real. Este modelo é chamado de consistência sequencial. A diferença entre consistência sequencial e consistência linear é que a primeira carece de garantias em tempo real.

Uma aplicação simples desse modelo é um sistema produtor / consumidor sincronizado com a fila: o nó produtor é responsável pela gravação na fila e o consumidor é responsável pela leitura. Produtores e consumidores veem a mesma ordem de itens na fila, mas os consumidores ficam atrás dos produtores.

 

Consistência final

Embora tenhamos conseguido melhorar o rendimento de leitura, tivemos que fixar o cliente em uma cópia. O que devemos fazer se a cópia falhar? Para melhorar a disponibilidade de armazenamento, podemos permitir que os clientes consultem qualquer cópia. No entanto, em termos de consistência, esta etapa exige um preço alto. Suponha que haja duas cópias 1 e 2, onde a cópia 2 está atrás da cópia 1. Se o cliente consultar a cópia 1 imediatamente após consultar a cópia 2, ele verá o estado anterior, o que pode ser muito confuso. A única garantia que o cliente tem é que se a gravação do sistema for interrompida, todas as cópias acabarão convergindo para o estado final. Este modelo de consistência é denominado consistência eventual.

É muito difícil construir aplicativos sobre armazenamento de dados eventualmente consistente porque seu comportamento é diferente daquele que você está acostumado a escrever aplicativos de thread único. Qualquer pequeno erro pode se espalhar gradualmente e é difícil depurar e reproduzir. No entanto, nem todos os aplicativos exigem consistência linear, portanto, a consistência eventual também tem alguma utilidade. Você precisa fazer escolhas sábias e considerar cuidadosamente se as garantias fornecidas por seu armazenamento de dados podem atender às necessidades de seu aplicativo. Se você deseja registrar o número de visitas do site, então a consistência eventual será sua primeira escolha, pois não importa se o número retornado pela leitura está um pouco desatualizado. Mas para sistemas de pagamento, uma consistência forte é absolutamente indispensável.

 

Teorema PACELC

Além dos modelos apresentados neste artigo, existem muitos modelos relacionados à consistência. Mas a ideia básica por trás disso é inseparável: quanto mais forte for a garantia de consistência, maior será o tempo de espera para uma única operação e menor será a disponibilidade de armazenamento em caso de falha. Essa relação também é conhecida como teorema PACELC: Ao realizar o particionamento de rede (P) em um sistema de computador distribuído, devemos executar entre disponibilidade (Disponibilidade, A) e consistência (Consistência, C) Escolha, caso contrário (Else ou E), mesmo se o sistema não tiver nenhuma partição, devemos escolher entre latência (Latência, ou L) e consistência (Consistência ou C).

Se você acha que este artigo é útil para você, você pode curtir e segui-lo para apoiá-lo, ou pode seguir minha conta pública. Há mais artigos técnicos sobre produtos secos e informações relacionadas compartilhando sobre ele, todos aprendem e progridem juntos!

 

Acho que você gosta

Origin blog.csdn.net/weixin_50205273/article/details/108597609
Recomendado
Clasificación