Tópico de microsserviços | A essência do design do código-fonte da Naocs está aqui, dê a você a chance de rasgar o entrevistador

Como o Nacos lida com leitura e escrita simultâneas?

Eu frequentemente li o código-fonte recentemente e descobri que a maioria dos frameworks usa a ideia de usar COW ao resolver leitura e escrita simultâneas; o
nacos não é exceção.

solução

Suponha que criemos um mapa para armazenar dados simultâneos. Vamos primeiro ver quais problemas ocorrerão ao ler e gravar a partir deste mapa em um cenário simultâneo:

Insira a descrição da imagem aqui

Gravar em um mapa muito grande será demorado, fazendo com que outros threads esperem muito pelas operações de leitura e gravação neste mapa;

Então, como isso é resolvido em nacos?

Na verdade, a ideia de processamento de nacos é muito simples, vou resumir brevemente e, em seguida, seguir o código-fonte, mostrar a você como escrever código:

  1. Primeiro, naocs copia o mapa da lista de registro na memória como map1
  2. Em seguida, adicione a chave de registro sincronizada pelo cliente ao map1
  3. Depois de processar todas as chaves, copie map1 para o mapa da lista de registro na memória

Insira a descrição da imagem aqui

Rastreamento de código fonte

Ao ler o código-fonte, descobri uma maneira do Nacos atualizar a lista de registro:
com.alibaba.nacos.naming.core.Cluster.updateIPs()

  public void updateIPs(List<Instance> ips, boolean ephemeral) {
// 首先判断是需要更新临时注册列表还是持久化的注册列表(这个会在后面讲解ap/cp提到)
        Set<Instance> toUpdateInstances = ephemeral ? ephemeralInstances : persistentInstances;
// 创建一个map,来保存内存中的注册列表
        HashMap<String, Instance> oldIPMap = new HashMap<>(toUpdateInstances.size());
// 遍历注册列表,依次添加到副本中
        for (Instance ip : toUpdateInstances) {
            oldIPMap.put(ip.getDatumKey(), ip);
        }

// 省略处理key的过程
        toUpdateInstances = new HashSet<>(ips);
// 将更新后的注册列表 重新复制到内存注册列表中
        if (ephemeral) {
            ephemeralInstances = toUpdateInstances;
        } else {
            persistentInstances = toUpdateInstances;
        }
    }

Como Eureka, como o centro de registro, consegue leitura e escrita simultâneas altas?

No eureka, uma estrutura de cache multinível é usada para resolver o problema de leitura e gravação simultâneas altas.
Eureka criará uma lista de registro somente leitura e uma lista de registro de leitura e gravação:
se o cliente iniciar o registro ou sair, o eureka primeiro atualizará o conteúdo da lista de registro mais recente para a lista de registro de leitura e gravação e criará uma quando o eureka iniciar. , sincronize regularmente o conteúdo da lista de registro de leitura e gravação com a lista de registro somente leitura.Quando o cliente executa a descoberta de serviço, ele obtém a lista de serviço disponível da lista de registro somente leitura.

Insira a descrição da imagem aqui

Qual é o problema com ap e cp de Nacos

Ao aprender estruturas relacionadas distribuídas, não podemos prescindir da teoria CAP, então não vou apresentar a teoria CAP muito aqui; o que
faz os desenvolvedores se perguntarem por que os nacos podem oferecer suporte a ap e cp, o que muitas vezes está no processo de entrevista será perguntado. Acredito que depois de ler este artigo, você deve conseguir rasgar o entrevistador com as mãos.

Prefácio

No nacos, ap e cp são refletidos principalmente na implementação de como sincronizar as informações de registro para outros nós do cluster no cluster; o
nacos usa o valor do campo efêmero para determinar se deve usar a sincronização ap ou a sincronização cp. O método ap padrão para o registro de sincronização em formação.
Ao ler o código-fonte, podemos encontrar esse código. Como encontrar esse código será explicado no artigo sobre interpretação do código-fonte nacos:
com.alibaba.nacos.naming.core.ServiceManager.addInstance()

    public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips) throws NacosException {
        // 生成服务的key
        String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
        // 获取服务
        Service service = getService(namespaceId, serviceName);
        // 使用同步锁处理
        synchronized (service) {
            List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);

            Instances instances = new Instances();
            instances.setInstanceList(instanceList);
            // 调用consistencyService.put 处理同步过来的服务
            consistencyService.put(key, instances);
        }
    }

Estamos inserindo o método consistenteService.put

Insira a descrição da imagem aqui

Ao clicar no método put, você verá três classes de implementação.De acordo com o contexto (ou método de depuração), você pode inferir que a classe de implementação DelegateConsistencyServiceImpl é referenciada aqui.

    @Override
    public void put(String key, Record value) throws NacosException {
        // 进入到这个put方法后,就可以知道应该使用ap方式同步还是cp方式同步
        mapConsistencyService(key).put(key, value);
    }

A partir do método a seguir, você pode julgar se usar ap ou cp para sincronizar as informações de registro por chave, onde a chave é composta pelo campo efêmero;

   private ConsistencyService mapConsistencyService(String key) {
        return KeyBuilder.matchEphemeralKey(key) ? ephemeralConsistencyService : persistentConsistencyService;
    }

Processo de sincronização do modo AP (ephemeralConsistencyService)

O servidor local processa as informações de registro e sincroniza as informações de registro para outros nós

    @Override
    public void put(String key, Record value) throws NacosException {
        // 处理本地注册列表
        onPut(key, value);
        // 添加阻塞任务,同步信息到其他集群节点
        taskDispatcher.addTask(key);
    }

Processar nós registrados locais

Nacos usa a chave como uma tarefa, adiciona-a às tarefas da fila de bloqueio no notifer e usa a execução de thread único. Quando o notifer é inicializado, ele é colocado no pool de threads como um thread (o pool de threads define apenas um thread principal );

Aqui está um ponto para dizer a você: na maioria das estruturas distribuídas, filas de bloqueio de thread único são usadas para lidar com tarefas demoradas. Por um lado, pode resolver o problema de simultaneidade e, por outro lado, pode resolver a gravação - conflito de escrita causado pela simultaneidade.

A principal lógica de processamento no encadeamento é ler ciclicamente o conteúdo da fila de bloqueio e, em seguida, processar as informações de registro e atualizá-las na lista de registro da memória.

Sincronizar informações de registro para outros nós do cluster

O Nacos também armazena a chave de registro como uma tarefa na fila de bloqueio taskShedule em TaskDispatcher e, em seguida, inicia o thread para ler a fila de bloqueio em um loop:

       @Override
        public void run() {

            List<String> keys = new ArrayList<>();
            while (true) {
                    String key = queue.poll(partitionConfig.getTaskDispatchPeriod(),
                        TimeUnit.MILLISECONDS);
                    // 省略判断代码
                    // 添加同步的key
                    keys.add(key);
                    // 计数
                    dataSize++;
                    // 判断同步的key大小是否等于 批量同步设置的限量 或者 判断据上次同步时间 是否大于 配置的间隔周期,如果满足任意一个,则开始同步
                    if (dataSize == partitionConfig.getBatchSyncKeyCount() ||
                        (System.currentTimeMillis() - lastDispatchTime) > partitionConfig.getTaskDispatchPeriod()) {
                        // 遍历所有集群节点,直接调用http进行同步
                        for (Server member : dataSyncer.getServers()) {
                            if (NetUtils.localServer().equals(member.getKey())) {
                                continue;
                            }
                            SyncTask syncTask = new SyncTask();
                            syncTask.setKeys(keys);
                            syncTask.setTargetServer(member.getKey());

                            if (Loggers.DISTRO.isDebugEnabled() && StringUtils.isNotBlank(key)) {
                                Loggers.DISTRO.debug("add sync task: {}", JSON.toJSONString(syncTask));
                            }

                            dataSyncer.submit(syncTask, 0);
                        }
                        // 记录本次同步时间
                        lastDispatchTime = System.currentTimeMillis();
                        // 计数清零
                        dataSize = 0;
                    }
            }
        }
    }

O processo de sincronização usando ap é muito simples, mas existem duas idéias de design para resolver o problema de sincronização de chave única:
Se uma nova chave for pressionada, o nacos iniciará uma sincronização, o que causará um desperdício de recursos de rede, porque todo hora Apenas uma chave ou várias chaves são sincronizadas;

Sincronize um pequeno número de soluções principais:
  1. Somente quando o número especificado de chaves é acumulado, a sincronização em lote é iniciada
  2. Como o tempo da última sincronização excede o tempo limite configurado, o número de chaves é ignorado e a sincronização é iniciada diretamente

Processo de sincronização do modo CP (RaftConsistencyServiceImpl)

O modo cp busca a consistência dos dados. Para consistência dos dados, um líder deve ser selecionado. O líder sincronizará primeiro e, em seguida, o líder notificará o seguidor para obter o último nó registrado (ou enviá-lo ativamente para o seguidor)

Nacos usa o protocolo raft para eleger um líder para implementar o modo cp.

Insira também o método put de RaftConsistencyServiceImpl

    @Override
    public void put(String key, Record value) throws NacosException {
        try {
            raftCore.signalPublish(key, value);
        } catch (Exception e) {
            Loggers.RAFT.error("Raft put failed.", e);
            throw new NacosException(NacosException.SERVER_ERROR, "Raft put failed, key:" + key + ", value:" + value, e);
        }
    }

No método raftCore.signalPublish, extraí alguns códigos-chave

// 首先判断当前nacos节点是否是leader,如果不是leader,则获取leader节点的ip,然后将请求转发到leader处理,否则往下走
if (!isLeader()) {
            JSONObject params = new JSONObject();
            params.put("key", key);
            params.put("value", value);
            Map<String, String> parameters = new HashMap<>(1);
            parameters.put("key", key);

            raftProxy.proxyPostLarge(getLeader().ip, API_PUB, params.toJSONString(), parameters);
            return;
        }

// Use o mesmo método de fila para processar a lista de registro local

onPublish(datum, peers.local());

public void onPublish(Datum datum, RaftPeer source) throws Exception {
       
        // 添加同步key任务到阻塞队列中
        notifier.addTask(datum.key, ApplyAction.CHANGE);

        Loggers.RAFT.info("data added/updated, key={}, term={}", datum.key, local.term);
    }

Percorrer todos os nós do cluster e enviar solicitação de sincronização http

 for (final String server : peers.allServersIncludeMyself()) {
                // 如果是leader,则不进行同步
                if (isLeader(server)) {
                    latch.countDown();
                    continue;
                }
                // 组装url 发送同步请求到其它集群节点
                final String url = buildURL(server, API_ON_PUB);
                HttpClient.asyncHttpPostLarge(url, Arrays.asList("key=" + key), content, new AsyncCompletionHandler<Integer>() {
                    @Override
                    public Integer onCompleted(Response response) throws Exception {
                        if (response.getStatusCode() != HttpURLConnection.HTTP_OK) {
                            Loggers.RAFT.warn("[RAFT] failed to publish data to peer, datumId={}, peer={}, http code={}",
                                datum.key, server, response.getStatusCode());
                            return 1;
                        }
                        latch.countDown();
                        return 0;
                    }

                    @Override
                    public STATE onContentWriteCompleted() {
                        return STATE.CONTINUE;
                    }
                });

            }

O processamento de solicitações de sincronização por cada nó do cluster não será muito introduzido aqui, você pode ir e ver você mesmo

Wechat search for a search [Le Zai open talk] Siga o bonito eu, responda [Receber produtos secos], haverá muitos materiais de entrevista e livros de leitura obrigatória para arquitetos esperando por você para escolher, incluindo java básico, java simultaneidade, microsserviços, middleware, etc. Mais informações estão esperando por você.

Acho que você gosta

Origin blog.csdn.net/weixin_34311210/article/details/112741024
Recomendado
Clasificación