Microsserviço 12
À medida que sistemas torna-se cada vez mais presentes na vida de nossos usuários, precisamos melhorar continuamente a qualidade dos serviços que oferecemos, já que a falha em um sistema pode ter um impacto significativo na vida das pessoas.
Temos cada vez mais a responsabilidade de criar softwares confiáveis, sendo cada vez menos tolerável downtime por conta de uma manutenção. Assim, microsserviço vem sendo adotado por empresas do mundo todo como uma oportunidade de melhorar a resiliência de seus serviços. Entretanto, adotar microsserviço como uma forma de antigir a resiliência é somente parte do processo.
Resiliência
O termo resiliência advêm de uma área mais ampla, a engenharia de resiliência, na qual possui aspectos da resiliência que podemos classificar em quatro conceitos:
- robustez;
- recuperação;
- extensibilidade com elegância;
- adaptabilidade sustentável;
robustez
Com robustez, queremos incluir métodos e processos em nosso software a fim de acomodar problemas inesperados para que, quando esses problemas surgirem, nosso software possam lidar com eles. Além disso, aplicar a robustez exige um conhecimento prévio do contexto de execução a fim de saber lidar com perturbações conhecidas.
O grande desafio é que, aumentando a robustez, também aumentamos a complexidade no sistema, o que, ocasionalmente, pode gerar novos problemas. Assim, qualquer tentativa de aumentar a robustez de um sistema de ser considerada não só por meio de uma análise simples de custo-benefício, mas também avalidando se a complexidade que será introduzida valerá a pena.
recuperação
Como o software se recupera após um distúrbio é essencial para que seja resiliente. Devemos fazer o máximo para nos proteger dos acontecimentos ruins que possam acontecer. Entretanto, à medida que o software aumenta em escala e complexidade, eliminar todos os problemas em potencial se torna insustentável.
Podemos melhorar nossa capacidade de recuperação de um incidente adotando medidas com antecedência, por exemplo, ter backups de prontidão, manual de procedimento em caso de indisponibilidade do sistema, pois, tentar pensar de forma racional sobre o modo de lidar com uma indisponibilidade enquanto ela estiver em andamento será complicado por causa do estresse e o caos inerentes à situação. O ideal é ter um plano de ação definido com antecedência.
extensibilidade com elegância
Recuperação e robustez estão relacionados a lidar com eventos que podemos prever, todavia, existem situações que acontecerão e que são impresíviveis, ou seja, seremos pegos de supresa. Para isso, precisamos estender nosso sistema de modo elegante a fim de lidar com surpresas, e essa é uma consequência de termos pessoas a disposição com as habilidades, experiências e a responsabilidade apropridada para lidar com essas situações quando elas surgirem.
adaptabilidade sustentável
O fato de ainda não termos sofrido um indisponibilidade não significa que ela não possa ocorrer. Precisamos ester constantemente adaptando o que fazemos a fim de garantir que tenhamos resiliência no futuro. Técnicas como a engenharia do caos, se feita de modo correto, pode ser uma ferramenta útil para ajudar a promover uma adaptabilidade sustentável.
Além disso, ter uma cultura na qual as pessoas possam compartilhar informações sem medo de retaliações, é essencial para incentivar o aprendizado na ocorrência de um incidente.
A ideia aqui é tentar descobrir o que não se sabe.
Sabemos que as falhas podem acontecer. Em larga escala, a falha passa a ser uma certeza estatística. Com isso, podemos gastar menos tempo tentando impedir o inevitável, e um pouco mais para lidar com a situação de forma elegante.
Não será possível evitar o fato de que algo pode e vai falhar, mas podemos incluir esse raciocínio em tudo que fizermos e se planejar para as falhas. Dessa forma, podemos fazer avaliações sobre o custo-benefício bem fundamentadas.
Agora, o nível de falhas que podemos tolerar ou a rapidez que o sistema deve ter serão determinados pelos usuários do sistema. Apesar disso, os usuários nem sempre saberão explicar quais são exatamente os requisitos. Entretanto, podemos definir requisitos genéricos, e então substituí-los para casos de uso específicos. Quando se trata de considerar se, e como, escalar os sistemas para lidar melhor com a carga ou com falhas, podemos entender os seguintes requisitos:
- response time/latência: o tempo em que as operações devem demorar. Podemos fazer mas medições com diferentes quantidades de usuários a fim de entender como o aumento da carga impacta no response time. Assim, definir metas para um dado percentil das respostas monitoradas pode ser conveniente. Queremos saber quantos usuários concorrentes esperados que o sistema suporte.
- disponibilidade: perídos e tempos de downtime aceitáveis;
- durabilidade dos dados: nível de perda dos dados, tempo que os dados devem ser mantidos;
Uma parte essencial no desenvolvimento de sistemas resilientes é a capacidade fazer as funcionalidades de degradarem com segurança. A inatividade de um serviço não deve afetar a disponibiilidade de outro serviço. Com isso, precisamos entender o impacto de cada indisponibilidade de serviço e descobrir como as funcionalidades de degradam de forma apropriada.
A definição de qual comportamento tomar em caso de indisponibilidade de uma funcionalidade será definida pelo contexto de negócios. Tecnicamente saberemos o que deve ser feito, mas o negócio decide qual é a atitude que devemos tomar. Basicamente, a pergunta que precisamos fazer é: o que devem acontecer se isso estiver inativo?. Assim, saberemos o que precisa ser feito.
Pensar na importância de cada uma de nossas funcionalidades no que diz respeito aos requisitos multifuncionais, estaremos em uma posição muito melhor para saber o que pode ser feito.
Pior que um serviço não responder, é reponder de forma lenta. Se um serviço está indisponível, conseguimos saber rapidamente. Se estiver apenas lento, o processo de espera pode deixar todo o sistema lento, provocanto contenção de recursos, o que gera a falha em cascata.
Há alguns padrões que podemos usar para garantir que, se algo de errado acontecer, não causará efeitos em cascata. Exemplos são timeouts, bulkheads e circuit breaker.
Com timeouts, queremos definir um tempo de espera para uma chamada para um serviço downstream. Devemos definir um timeout default para toda chamada externa ao processo e observe os tempos de resposta saudáveis, considerado normais para os serviços downstream, isso pode orientar na definição de valores de timeout. Alem isso, como alguns problemas sem serviços downstream pode ser temporários, fazer novas tentativas para uma chamada faz todo sentido, levando em consideração o código HTTP retornado pelo serviço.
Já com bulkhead, é um conceito como uma forma de isolar uma falha, por exemplo, separando responsabilidades entre microsserviços distintos, pois assim reduzimos as chances de uma falha em uma área afetar outra. Ademais, outro ponto de partida pode ser implementar pool de conexões para cada conexão downstream.
Em circuit breaker, análogo ao disjuntores residenciais, queremos não só proteger o consumidor contra um problema downstream, mas também proteger o serviço downstream de receber mais chamadas que possam causar impacto negativo, já que aqui temos o ponto de falhar rápido em chamadas síncronas, permitindo o serviço upstream tomar alguma ação caso receba erro. Já em cenários de chamadas assíncronas, enfileirar as requisições para novas tentativas mais tarde pode ser uma opção. Essa abordagem de circuit breaker, quando temos a possibilidade de acionar manualmente, pode ser muito útil como parte de uma manutenção de rotina. Falhar rápido é sempre melhor do que demorar a falhar.
De modo geral, queremos isolamento entre serviços, para que um não influencie na execução. Podemos ter cenário em que dois serviços têm seu próprio bancos de dados separados logicamente, mas estão na mesma infraestrutura. Uma falha nesse infraestrutura causaria impacto nos dois serviços. O ideal é termos recursos independentes para cada instância, mas devemos considerar o custo-benefício versus o custo e o aumento da complexidade. O custo pode ficar tão alto que torna inviável uma separação física das instâncias.
Idempotência
Operações idempotentes são aquelas em que o resulta não muda após a primeira aplicação, mesmo que a operação seja subsequentemente aplicada várias vezes. Isso é muito útil para repetir o envio de mensagem quando não temos certeza de que foram processadas. Basicamente adicionamos um identificador no envio de todas as mensagens e, caso o serviço ser idempotente, as mesma mensagem enviadas diversas vezes não causará inconsistência.
Teorema CAP
O teorema CAP diz que, em um sistema distribuído, temos 3 itens que podem ser ponderados entre si:
- consistência (consistency);
- disponibilidade (avalilability);
- tolerância à partição (partition tolerance) ou tolerância à partição de rede;
A consistência significa que teremos a mesma resposta se acessarmos vários nós.
A disponibilidade significa que toda requisição terá uma resposta.
A tolerância a partição é a capacidade do sistema lidar com o fato de que a comunicação entre suas partes às vezes não será possível.
estudo de caso
Cenário: 1 load balancer + 2 instâncias em datacenters distintos + 2 bancos de dados distintos com replicação
Um sistema AP é o sistema em que está disponível, atende às requisições, mas os dados estão inconsistentes, pois a replicação do banco de dados não funciona. Esses sistemas também são conhecidos como eventalmente consistentes, já que esperamos que, em algum momento no futuro, todos os nós vejam os dados atualizados, mas isso não acontecerá ao mesmo tempo, por isso precisamos conviver com a possibilidade os usuários verem dados antigos.
Um sistema CP é aquele em que os dados estão consistentes, já que cada nó do banco de dados sabe se os dados que tem é igual à do outro nó do banco de dados, e que atende às requisições. Mas, se os nós dos bancos dados não puderem se conversar, eles não poderão se coordenar para garantir que haja consistência. Assim, a opção será recusarmos a responder à requisição. O desafio aqui é a dificuldade em ter uma consistância distribuída.
Sobre sistema CA, se os sistema não tem tolerância a partição, ele não poderá executar em uma rede, será um sistema funcionando localmente. Logo, sistemas CA não existem em sistemas distribuídos.
De modo geral, sistemas AP acaba sendo uma opção correta em muitas situações.
Engenharia do Caos
Engenharia do caos é a disciplina de fazer experimentos em um sistema a fim de aumentar a confiança na capacidade do sistema de suportar condições turbulentas em produção. Sistema aqui pode ser expandido para além software e hardware, incluíndo pessoas, processos e cultura organizacional.
O precursos da engenharia do caos seria o Game Days, que é basicamente testar o nível de preparação das pessoas para determinados eventos. São planejados com antecedência, mas iniciados de surpresa com exercícios que oferecem uma chance de testar as pessoas e os processos diante de uma situação realista, porém fictícia.
Aplicando em sua forma mais restrita, a engenharia do caos poderia ser uma atividade útil para melhorar a robustez dos sistemas, até que ponto nossos sistemas são capazes de lidar com problemas esperados.
Vale a pena destacar que, sobre a cultura organizacional, a organização não deve ter uma cultura de acusação, ou seja, procurar a pessoa culpada por determinado incidente. Isso acaba criando uma cultura de medo, na qual pessoas não se sentirão dispostas a se manifestar quando houver algo errado. O resultado é que perdemos a capacidade de aprender com os erros e ficamos suscetíveis à ocorrência dos mesmos problemas novamente. Ter a cultura onde as pessoas fiquem seguras para admitir quando cometem erros é essencial para criar uma cultura de aprendizado, o que acaba contribuíndo para criar softwares mais robustos, além de criar um local que seja mais satisfatório de trabalhar.
O foco deve ser procurar a causa-raiz.
ferramentas
- chaos toolkit
- realiably
- gremlin