Análisis del código fuente del proceso general de registro de Nacos

prefacio

Ya sea que use spring boot para integrar nacos o use spring cloud alibaba para integrar o use nacos solo. Al final, el método específico para registrar u obtener instancias de servicio es el paquete nacos-client.nacos-api solo define la interfaz, y la operación específica se completa con nacos-client. Los nacos integrados en nuestro proyecto desempeñan el papel del cliente en todo el acceso a nacos, y el almacenamiento y mantenimiento de todos los datos se logra mediante un servidor nacos separado. El cliente y el servidor nacos se comunican a través de http. Este artículo solo presenta el análisis del código fuente de todo el proceso de registro de nacos.

1. Implementación del cliente

1. Objeto de entrada de registro de cliente

Hay una clase NacosNamingService en el directorio de nombres en el paquete nacos-client , que implementa la interfaz NamingService en nacos-api. Los métodos principales incluyeron:

  • public NacosNamingService(String serverList) ##Constructor, pase la dirección del servidor nacos
  • public void registerInstance(String serviceName, String ip, int port) ## Registrar servicio
  • public void deregisterInstance(String serviceName, String ip, int port) ## Cancelar registro
  • …otros se explican en capítulos posteriores

2. ¿Qué hace el constructor?

/** 构造函数 */
public NacosNamingService(String serverList) {
    
    

    // nacos server地址
    this.serverList = serverList;
    // 初始化主要是读取系统的环境变量,读取namespace、日志名称、日志等级、缓存地址等信息
    init();
    // 订阅服务,当服务实例有变化时会触发通知
    eventDispatcher = new EventDispatcher();
    // 具体的与远程nacos server通讯的类
    serverProxy = new NamingProxy(namespace, endpoint, serverList);
    // 心跳服务,客户端要定时给nacos server发送心跳包
    beatReactor = new BeatReactor(serverProxy);
    // 更新服务的,获取服务的实例后,可以订阅服务,会定时拉取最新的实例列表
    hostReactor = new HostReactor(eventDispatcher, serverProxy, cacheDir);
}

3. Servicio de registro

Hay tres métodos de registro, principalmente sobrecargados, y las diferentes versiones pueden ser ligeramente diferentes, pero generalmente son las mismas.

/** 注册实例 */
/** 当前看的这个版本可能相对旧一点,最新的版本是还有一个参数,是否添加心跳的参数,可以选择是否对当前的实例一直与nacos服务端进行心跳检测 */
@Override
public void registerInstance(String serviceName, Instance instance) throws NacosException {
    
    
    
    // 创建添加心跳的对象
    BeatInfo beatInfo = new BeatInfo();
    beatInfo.setServiceName(serviceName);
    beatInfo.setIp(instance.getIp());
    beatInfo.setPort(instance.getPort());
    beatInfo.setCluster(instance.getClusterName());
    beatInfo.setWeight(instance.getWeight());
    beatInfo.setMetadata(instance.getMetadata());
    beatInfo.setScheduled(false);
    // 添加心跳
    beatReactor.addBeatInfo(serviceName, beatInfo);
    // 具体实现注册
    serverProxy.registerService(serviceName, instance);
}

4. Realización del latido del corazón

El siguiente es el objeto que implementa el latido. Su principio principal es iniciar un hilo y solicitar periódicamente la interfaz del servidor nacos a través del objeto NamingProxy durante la inicialización y enviar solicitudes http

estructura de clase

class BeatProcessor implements Runnable {
    
    

        @Override
        public void run() {
    
    
            try {
    
    
                for (Map.Entry<String, BeatInfo> entry : dom2Beat.entrySet()) {
    
    
                    BeatInfo beatInfo = entry.getValue();
                    // 如果是健康的则不进行发送心跳
                    if (beatInfo.isScheduled()) {
    
    
                        continue;
                    }
                    // 如果不是健康的则会触发发送心跳
                    beatInfo.setScheduled(true);
                    executorService.schedule(new BeatTask(beatInfo), 0, TimeUnit.MILLISECONDS);
                }
            } catch (Exception e) {
    
    
                LogUtils.LOG.error("CLIENT-BEAT", "Exception while scheduling beat.", e);
            }
        }
    }


class BeatTask implements Runnable {
    
    

    BeatInfo beatInfo;

    public BeatTask(BeatInfo beatInfo) {
    
    
        this.beatInfo = beatInfo;
    }

    @Override
    public void run() {
    
    
        // serverProxy就是NamingProxy
        long result = serverProxy.sendBeat(beatInfo);
        // 发送心跳后,当前的服务服务实例则置为不健康状态,这样才能触发下一次心跳发送
        beatInfo.setScheduled(false);
        if (result > 0) {
    
    
            clientBeatInterval = result;
        }
    }
}

5. Instancia del servicio de registro

NamingProxy contiene la lógica de registro. El registro aquí es en realidad relativamente simple. Principalmente encapsula los datos de la instancia de servicio actual y luego los solicita al servidor de nacos.

public void registerService(String serviceName, Instance instance) throws NacosException {
    
    

    LogUtils.LOG.info("REGISTER-SERVICE", "{} registering service {} with instance: {}",
        namespaceId, serviceName, instance);

    final Map<String, String> params = new HashMap<String, String>(8);
    params.put(Constants.REQUEST_PARAM_NAMESPACE_ID, namespaceId);
    params.put("ip", instance.getIp());
    params.put("port", String.valueOf(instance.getPort()));
    params.put("weight", String.valueOf(instance.getWeight()));
    params.put("enable", String.valueOf(instance.isEnabled()));
    params.put("healthy", String.valueOf(instance.isHealthy()));
    params.put("metadata", JSON.toJSONString(instance.getMetadata()));
    params.put("serviceName", serviceName);
    params.put("clusterName", instance.getClusterName());

    //通用的发送请求
    reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST);

}

6. Solicitud HTTP de envío genérico

El latido mencionado anteriormente en realidad está implementado en el objeto NamingProxy

public long sendBeat(BeatInfo beatInfo) {
    
    
    try {
    
    
        LogUtils.LOG.info("BEAT", "{} sending beat to server: {}", namespaceId, beatInfo.toString());
        // 封装请求参数
        Map<String, String> params = new HashMap<String, String>(4);
        params.put("beat", JSON.toJSONString(beatInfo));
        params.put(Constants.REQUEST_PARAM_NAMESPACE_ID, namespaceId);
        params.put("serviceName", beatInfo.getServiceName());
        // 请求远程接口
        String result = reqAPI(UtilAndComs.NACOS_URL_BASE + "/instance/beat", params, HttpMethod.PUT);
        JSONObject jsonObject = JSON.parseObject(result);

        if (jsonObject != null) {
    
    
            return jsonObject.getLong("clientBeatInterval");
        }
    } catch (Exception e) {
    
    
        LogUtils.LOG.error("CLIENT-BEAT", "failed to send beat: " + JSON.toJSONString(beatInfo), e);
    }
    return 0L;
}

Todas las solicitudes de interfaz se realizan en el método reqAPI

/** 请求远程接口,传入请求接口方法、请求参数、nacos server服务地址,请求方式(post/get) */
public String reqAPI(String api, Map<String, String> params, List<String> servers, String method) {
    
    

    params.put(Constants.REQUEST_PARAM_NAMESPACE_ID, getNamespaceId());

    if (CollectionUtils.isEmpty(servers) && StringUtils.isEmpty(nacosDomain)) {
    
    
        throw new IllegalArgumentException("no server available");
    }

    // 获取nacos服务列表,随机获取地址进行请求
    if (servers != null && !servers.isEmpty()) {
    
    
        Random random = new Random(System.currentTimeMillis());
        int index = random.nextInt(servers.size());
        for (int i = 0; i < servers.size(); i++) {
    
    
            String server = servers.get(index);
            try {
    
    
                return callServer(api, params, server, method);
            } catch (Exception e) {
    
    
                LogUtils.LOG.error("NA", "req api:" + api + " failed, server(" + server, e);
            }
            // 如果随机获取的服务地址没有请求成功,则尝试下一个地址
            index = (index + 1) % servers.size();
        }
        throw new IllegalStateException("failed to req API:" + api + " after all servers(" + servers + ") tried");
    }

    for (int i = 0; i < UtilAndComs.REQUEST_DOMAIN_RETRY_COUNT; i++) {
    
    
        try {
    
    
            return callServer(api, params, nacosDomain);
        } catch (Exception e) {
    
    
            LogUtils.LOG.error("NA", "req api:" + api + " failed, server(" + nacosDomain, e);
        }
    }
    throw new IllegalStateException("failed to req API:/api/" + api + " after all servers(" + servers + ") tried");

}

En segundo lugar, la implementación del servidor.

1. Recibir solicitud de alta de cliente

/**
 * Register new instance.
 *
 * @param request http request
 * @return 'ok' if success
 * @throws Exception any error during register
 */
@CanDistro
@PostMapping("/instance")
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String registerInstance(@RequestParam(defaultValue = "v2", required = false) String ver,
        HttpServletRequest request) throws Exception {
    
    

    final String namespaceId = WebUtils
            .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
    final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
    NamingUtils.checkServiceNameFormat(serviceName);

    final Instance instance = HttpRequestInstanceBuilder.newBuilder()
            .setDefaultInstanceEphemeral(switchDomain.isDefaultInstanceEphemeral()).setRequest(request).build();
    // 选择版本,选择V1还是v2,做了兼容
    getInstanceOperator(ver).registerInstance(namespaceId, serviceName, instance);
    return "ok";
}
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
    
    
    // 检测service是否存在,不存在则进行创建
    createEmptyService(namespaceId, serviceName, instance.isEphemeral());
    // 获取service
    Service service = getService(namespaceId, serviceName);
    // 校验service是否存在,不存在则抛出异常
    checkServiceIsNull(service, namespaceId, serviceName);
    // 将传入的实例对象进行添加操作
    addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}
public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
        throws NacosException {
    
    
    
    String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
    // 获取service
    Service service = getService(namespaceId, serviceName);
    // 防止并发处理
    synchronized (service) {
    
    
        // 将实例添加到目标service的实例列表中,返回添加后的实例列表
        List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);

        Instances instances = new Instances();
        instances.setInstanceList(instanceList);
        //处理一致性的实例数据,保证nacos server集群的数据一致性
        consistencyService.put(key, instances);
    }
}

Implementación

El más familiar aquí es el algoritmo balsa

3. Resumen

El rango de versiones es relativamente grande y los métodos de implementación también son diferentes, pero la dirección general de implementación es esta: se recomienda seguir el código fuente usted mismo.

Supongo que te gusta

Origin blog.csdn.net/weixin_33613947/article/details/121168236
Recomendado
Clasificación