Arquitetura da Internet Java - Como projetar função de limitação de corrente da API de interface de serviço

O surgimento do conceito de API é anterior ao nascimento do computador pessoal, quanto mais ao nascimento da rede. Nos primórdios do processamento de dados públicos, para que um aplicativo interaja com outros sistemas, os desenvolvedores começaram a projetar "pontos de acesso" acessíveis ao público e claramente descritos. Já naquela época, essa abordagem como diretriz já era o conceito principal de desenvolvimento de software. No entanto, não foi até o surgimento dos sistemas distribuídos e até mesmo o advento da rede que esses conceitos básicos tiveram seus efeitos importantes e surpreendentes.

Quando olhamos para trás na história da API, um dos estágios é muito importante. Foi por volta de 2000, e SOA (Arquitetura Orientada a Serviços) estava em desenvolvimento. Uma forma de API nasceu em aplicativos corporativos. Como uma das grandes práticas de SOA, essa forma de API saiu do campo de aplicativos corporativos e encontrou um solo mais fértil no mundo da tecnologia inovadora.

Hoje, podemos encontrar inúmeras razões, de uma perspectiva técnica, para explicar por que as APIs da web podem ser bem-sucedidas em vários tipos e tamanhos de empresas e são até muito bem-vindas por agências governamentais. Mas, na realidade, a tecnologia não é tudo. O sucesso da API da web também se deve a muitos outros fatores. A maioria desses fatores não são tão atraentes, portanto, precisamos estudar a história com cuidado e, após observação cuidadosa, podemos descobrir por que os pioneiros da API da web podem ter sucesso.

Hoje, ainda temos que aprender as melhores práticas dos últimos dez anos. Ao conduzir pesquisas sobre pioneiros que forneceram APIs com sucesso, incluindo Amazon, Salesforce, Ebay e Twitter, não podemos ignorar nenhum detalhe importante. Você sabe, a maioria das APIs que eles fornecem ainda estão em execução.

1. Descrição da cena

Muitas pessoas que fazem interfaces de serviço encontram esses cenários mais ou menos, devido à capacidade de carga limitada do sistema de aplicativos de negócios, a fim de evitar que solicitações inesperadas sobrecarreguem o sistema e arrastem para baixo o sistema de aplicativos de negócios.

Ou seja, como controlar o fluxo diante de um grande fluxo?

A estratégia de controle de tráfego da interface de serviço: desvio, degradação, limitação de corrente, etc. Este artigo discute a estratégia de limite inferior. Embora a frequência de acesso e a simultaneidade da interface de serviço sejam reduzidas, elas são trocadas pela alta disponibilidade da interface de serviço e do sistema de aplicativos de negócios.

Estratégias de limitação atuais comumente usadas em cenários reais:

Limite de corrente de front-end Nginx

Limite o fluxo no nível Nginx de acordo com certas regras, como número da conta, IP, lógica de chamada do sistema, etc.

Limite atual do sistema de aplicativos de negócios

1. Limite atual do cliente

2. Limite de corrente do servidor

Limite atual do banco de dados

Área da linha vermelha, tente proteger o banco de dados

2. Algoritmos de limitação de corrente comumente usados

Os algoritmos de limitação atuais comumente usados ​​são: algoritmo de balde de construção e algoritmo de balde de token. Este artigo não especifica os princípios dos dois algoritmos em detalhes, e os princípios serão explicados no próximo artigo.

1. Algoritmo de balde com vazamento

O algoritmo Leaky Bucket é muito simples. A água (solicitação) entra primeiro no balde furado, e o balde vaza em uma determinada velocidade (a interface tem uma taxa de resposta). Quando a água flui muito rápido, ela transborda diretamente ( a frequência de acesso excede a resposta da interface). Taxa) e, em seguida, rejeitar a solicitação, pode ser visto que o algoritmo do balde furado pode limitar à força a taxa de transmissão de dados.

O diagrama esquemático é o seguinte:

Arquitetura da Internet Java - Como projetar função de limitação de corrente da API de interface de serviço

Pode-se observar que existem duas variáveis, uma é o tamanho do balde, que suporta quanta água pode ser armazenada quando o tráfego aumenta repentinamente (estouro), e a outra é o tamanho do furo do balde (taxa).

Como a taxa de vazamento do balde furado é um parâmetro fixo, mesmo se não houver conflito de recursos na rede (não ocorre congestionamento), o algoritmo do balde furado não pode fazer o fluxo estourar para a taxa da porta. Portanto, o algoritmo do balde furado é não falta eficiência em termos de características de tráfego.

2. Algoritmo de token bucket

O algoritmo Token Bucket (Token Bucket) e o algoritmo Leaky Bucket têm o mesmo efeito, mas na direção oposta, que é mais fácil de entender. Conforme o tempo passa, o sistema irá para o bucket em um intervalo de tempo constante 1 / QPS (se QPS = 100, o intervalo é de 10 ms) Adicionar token nele (imagine que há uma torneira que está constantemente adicionando água ao vazamento). Se o balde estiver cheio, não será adicionado mais nada. Quando uma nova solicitação chegar, um token será removido e, se não houver um token disponível, ele será bloqueado ou negação de serviço.

Arquitetura da Internet Java - Como projetar função de limitação de corrente da API de interface de serviço

Outra vantagem do balde de tokens é que ele pode facilmente alterar a velocidade. Assim que a taxa precisar ser aumentada, a taxa de tokens colocados no balde será aumentada conforme necessário. Geralmente, um determinado número de tokens será adicionado ao balde regularmente (por exemplo, 100 milissegundos), alguns algoritmos variantes calculam o número de tokens que devem ser aumentados em tempo real.

Terceiro, a realização com base na função Redis

Ideia de design simples: presumindo que um usuário (julgado pelo IP) não pode acessar uma determinada interface de serviço mais de 10 vezes por minuto, podemos criar uma chave no Redis e, neste momento, definimos o tempo de expiração da chave para 60 segundos, cada O acesso do usuário a essa interface de serviço aumentará o valor da chave em 1 e, quando o valor da chave aumentar para 10 em 60 segundos, o acesso à interface de serviço será proibido. Ainda é necessário adicionar intervalos de tempo de acesso em determinados cenários.

1) Use o comando incr do Redis para usar o contador como um script Lua

corrente local

current = redis.call ("incr", KEYS [1])

se tonumber (atual) == 1 então

redis.call ("expirar", KEYS [1], 1)

fim

O script Lua é executado no Redis para garantir a atomicidade das operações incr e expire.

2) Use a estrutura de lista de Reids em vez do comando incr

FUNCTION LIMIT_API_CALL (ip)

atual = LLEN (ip)

SE a corrente> 10 ENTÃO

ERROR "muitos pedidos por segundo"

SENÃO

SE EXISTE (ip) == FALSE

MULTI

RPUSH (ip, ip)

EXPIRAR (ip, 1)

EXEC

SENÃO

RPUSHX (ip, ip)

FIM

PERFORM_API_CALL ()

FIM

O Limite de Taxa usa listas Redis como contêineres. LLEN é usado para verificar o número de visitas. Uma coisa contém os comandos RPUSH e EXPIRE. É usado para criar uma lista e definir um tempo de expiração quando a contagem é executada pela primeira vez.

RPUSHX realiza uma operação de aumento na operação de contagem subsequente.

Quarto, com base na implementação do algoritmo de token bucket

O algoritmo token bucket pode bem suportar mudanças repentinas na quantidade de tráfego, ou seja, o pico do número de token buckets completos.

import java.io.BufferedWriter;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.OutputStreamWriter;

import java.util.Random;

import java.util.concurrent.ArrayBlockingQueue;

import java.util.concurrent.Executors;

import java.util.concurrent.ScheduledExecutorService;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.locks.ReentrantLock;

import com.google.common.base.Preconditions;

import com.netease.datastream.util.framework.LifeCycle;

20 public class TokenBucket implementa LifeCycle {

// O número de tamanhos de balde padrão, ou seja, o fluxo instantâneo máximo é 64M

privado estático final int DEFAULT_BUCKET_SIZE = 1024 * 1024 * 64;

// A unidade de um intervalo é de 1 byte

private int everyTokenSize = 1;

// fluxo máximo instantâneo

private int maxFlowRate;

// fluxo médio

private int avgFlowRate;

// Fila para armazenar o número de depósitos: o pico máximo de tráfego é = everyTokenSize * DEFAULT_BUCKET_SIZE 64M = 1 * 1024 * 1024 * 64

privado ArrayBlockingQueue <Byte> tokenQueue = novo ArrayBlockingQueue <Byte> (DEFAULT_BUCKET_SIZE);

privado ScheduledExecutorService ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor ();

private volatile boolean isStart = false;

bloqueio ReentrantLock privado = novo ReentrantLock (verdadeiro);

byte final estático privado A_CHAR = 'a';

public TokenBucket () {

}

public TokenBucket (int maxFlowRate, int avgFlowRate) {

this.maxFlowRate = maxFlowRate;

this.avgFlowRate = avgFlowRate;

}

public TokenBucket (int everyTokenSize, int maxFlowRate, int avgFlowRate) {

this.everyTokenSize = everyTokenSize;

this.maxFlowRate = maxFlowRate;

this.avgFlowRate = avgFlowRate;

}

public void addTokens (Integer tokenNum) {

// Se o balde estiver cheio, não há mais um novo token

para (int i = 0; i <tokenNum; i ++) {

tokenQueue.offer (Byte.valueOf (A_CHAR));

}

}

public TokenBucket build () {

começar();

devolva isso;

}

/ **

· Obtenha tokens suficientes

·

· @Retorna

· * /

· Public boolean getTokens (byte [] dataSize) {

Preconditions.checkNotNull (dataSize);

Preconditions.checkArgument (isStart, "invoque o método de início primeiro!");

int needTokenNum = dataSize.length / everyTokenSize + 1; // O número de intervalos correspondentes ao tamanho do conteúdo transmitido

bloqueio ReentrantLock final = this.lock;

lock.lock ();

experimentar {

resultado booleano = needTokenNum <= tokenQueue.size (); // Há um número suficiente de baldes

if (! resultado) {

retorna falso;

}

int tokenCount = 0;

para (int i = 0; i <needTokenNum; i ++) {

Pesquisa de byte = tokenQueue.poll ();

if (votação! = nulo) {

tokenCount ++;

}

}

return tokenCount == needTokenNum;

} finalmente {

bloquear desbloquear();

}

}

@Sobrepor

public void start () {

// Inicializa o tamanho da fila do balde

if (maxFlowRate! = 0) {

tokenQueue = novo ArrayBlockingQueue <Byte> (maxFlowRate);

}

// Inicializa o produtor do token

TokenProducer tokenProducer = novo TokenProducer (avgFlowRate, este);

SchedulesExecutorService.scheduleAtFixedRate (tokenProducer, 0, 1, TimeUnit.SECONDS);

isStart = true;

}

@Sobrepor

public void stop () {

isStart = false;

scheduleExecutorService.shutdown ();

}

@Sobrepor

public boolean isStarted () {

return isStart;

}

classe TokenProducer implementa Runnable {

private int avgFlowRate;

TokenBucket tokenBucket privado;

public TokenProducer (int avgFlowRate, TokenBucket tokenBucket) {

this.avgFlowRate = avgFlowRate;

this.tokenBucket = tokenBucket;

}

@Sobrepor

public void run () {

tokenBucket.addTokens (avgFlowRate);

}

}

public static TokenBucket newBuilder () {

retornar novo TokenBucket ();

}

public TokenBucket everyTokenSize (int everyTokenSize) {

this.everyTokenSize = everyTokenSize;

devolva isso;

}

public TokenBucket maxFlowRate (int maxFlowRate) {

this.maxFlowRate = maxFlowRate;

devolva isso;

}

public TokenBucket avgFlowRate (int avgFlowRate) {

this.avgFlowRate = avgFlowRate;

devolva isso;

}

private String stringCopy (String data, int copyNum) {

StringBuilder sbuilder = novo StringBuilder (data.length () * copyNum);

para (int i = 0; i <copyNum; i ++) {

sbuilder.append (dados);

}

return sbuilder.toString ();

}

public static void main (String [] args) lança IOException, InterruptedException {

tokenTest ();

}

private static void arrayTest () {

ArrayBlockingQueue <Integer> tokenQueue = novo ArrayBlockingQueue <Integer> (10);

tokenQueue.offer (1);

tokenQueue.offer (1);

tokenQueue.offer (1);

System.out.println (tokenQueue.size ());

System.out.println (tokenQueue.remainingCapacity ());

}

private static void tokenTest () lança InterruptedException, IOException {

TokenBucket tokenBucket = TokenBucket.newBuilder (). AvgFlowRate (512) .maxFlowRate (1024) .build ();

BufferedWriter bufferedWriter = new BufferedWriter (new OutputStreamWriter (new FileOutputStream ("/ tmp / ds_test")));

String data = "xxxx"; // Quatro bytes

para (int i = 1; i <= 1000; i ++) {

Aleatório aleatório = novo Aleatório ();

int i1 = random.nextInt (100);

tokens booleanos = tokenBucket.getTokens (tokenBucket.stringCopy (dados, i1) .getBytes ());

TimeUnit.MILLISECONDS.sleep (100);

if (tokens) {

bufferedWriter.write ("passagem de token --- índice:" + i1);

System.out.println ("passagem de token --- índice:" + i1);

} senão {

bufferedWriter.write ("token rejuect --- index" + i1);

System.out.println ("rejuvenescer token --- índice" + i1);

}

bufferedWriter.newLine ();

bufferedWriter.flush ();

}

bufferedWriter.close ();

}

}

Resumindo

Neste ponto, a função de limitação atual da API da interface de serviço acabou. Espero que você possa me perdoar pelas deficiências! ! Caso sinta que ganhou algo, pode clicar para acompanhar a coleção e enviar um wave, obrigado pelo seu apoio. (Blow a wave, 233 ~~)

Deixe-me compartilhar algumas experiências de programação com você:

1. Escreva mais código e digite mais código, um bom código e um conhecimento básico sólido devem ser praticados

2. Teste, teste e teste novamente.Se você não testar completamente seu próprio código, você pode desenvolver mais do que apenas código, você pode ser infame.

3. Simplifique a programação, acelere e atraia código.Depois de terminar de codificar, você deve voltar e otimizá-lo. No longo prazo, algumas melhorias aqui ou ali tornarão mais fácil a equipe de suporte posterior.

Acho que você gosta

Origin blog.csdn.net/keepfriend/article/details/113655312
Recomendado
Clasificación