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?
- Como Eureka, como o centro de registro, consegue leitura e escrita simultâneas altas?
- Qual é o problema com ap e cp de Nacos
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:
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:
- Primeiro, naocs copia o mapa da lista de registro na memória como map1
- Em seguida, adicione a chave de registro sincronizada pelo cliente ao map1
- Depois de processar todas as chaves, copie map1 para o mapa da lista de registro na memória
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.
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
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:
- Somente quando o número especificado de chaves é acumulado, a sincronização em lote é iniciada
- 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ê.