Spring Cloud Alibaba 读书笔记_4:服务注册与发现

服务注册与发现

在微服务架构下,一个业务会被拆分成多个微服务,各个服务之间相互通信完成整体功能。为了避免单点故障,微服务都会采取集群方式的高可用部署,服务消费者要去调用多个服务提供者组成的集群。服务提供者集群中若存在节点宕机、下线等情况,服务消费者的本地配置中药同步删除该节点的相关配置信息。此时,需要引入服务注册中心,主要负责:服务地址的管理、服务注册、服务动态感知

Alibaba Nacos

Nacos 用于解决微服务中的统一配置、服务注册与发现等问题。提供了一组简单易用的特征集,帮助开发者快速实现动态服务发现、服务配置、服务元数据及流量管理。其关键特性包括:【 通过nacos-1.1.4进行说明 】
对于服务注册及,提供的对外服务接口地址为:nacos/v1/ns/instance,对应源码:com.alibaba.nacos.naming.controllers.InstanceController

  • 服务注册:register()
@CanDistro
@PostMapping
public String register(HttpServletRequest request) throws Exception {
    
    

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

    serviceManager.registerInstance(namespaceId, serviceName, parseInstance(request));
    return "ok";
}

定位:registerInstance() 方法,主要进行了下述操作:

  • 创建一个空服务,初始化一个serviceMap【本质上为:ConcurrentHashMap
  • serviceMap中根虎namespaceIdserviceName获取一个服务对象
  • 调用 addInstance() 添加 服务实例
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
    
    
    createEmptyService(namespaceId, serviceName, instance.isEphemeral());
    Service service = getService(namespaceId, serviceName);
    if (service == null) {
    
    
        throw new NacosException(NacosException.INVALID_PARAM,
                                 "service not found, namespace: " + namespaceId + ", service: " + serviceName);
    }
    addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}

定位:createEmptyService() ,即:createServiceIfAbsent() 方法,主要实现了:

  • 通过namespaceIdserviceName从缓存中获取Service实例若实例为空,则创建并保存至缓存中
public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster) throws NacosException {
    
    
    Service service = getService(namespaceId, serviceName);
    if (service == null) {
    
    

        Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName);
        service = new Service();
        service.setName(serviceName);
        service.setNamespaceId(namespaceId);
        service.setGroupName(NamingUtils.getGroupName(serviceName));
        // now validate the service. if failed, exception will be thrown
        service.setLastModifiedMillis(System.currentTimeMillis());
        service.recalculateChecksum();
        if (cluster != null) {
    
    
            cluster.setService(service);
            service.getClusterMap().put(cluster.getName(), cluster);
        }
        service.validate();

        putServiceAndInit(service);
        if (!local) {
    
    
            addOrReplaceService(service);
        }
    }
}

让我们把目光聚集到:putServiceAndInit() 方法上,该方法中实现了:

  • 通过putService方法将服务缓存到内存
  • 通过service.init() 建立心跳检测机制
  • consistencyService.listen 实现数据一致性监听
private void putServiceAndInit(Service service) throws NacosException {
    
    
    // 将服务缓存到内存,将service保存到serviceMap中
    putService(service);
    // 建立服务下包含实例心跳检测机制,每5s发送一次心跳包,不断检测当前服务下所有实例的状态,若请求超时,表明服务不健康,触发服务变更事件
    service.init();
    // 实现数据一致性监听
    consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service);
    consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service);
    Loggers.SRV_LOG.info("[NEW-SERVICE] {}", service.toJSON());
}
  • 服务地址的管理查询:list(),解析请求参数,通过doSrvIPXT返回服务列表数据
@GetMapping("/list")
public JSONObject list(HttpServletRequest request) throws Exception {
    
    

    String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID,
                                           Constants.DEFAULT_NAMESPACE_ID);
    String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
    String agent = WebUtils.getUserAgent(request);
    String clusters = WebUtils.optional(request, "clusters", StringUtils.EMPTY);
    String clientIP = WebUtils.optional(request, "clientIP", StringUtils.EMPTY);
    Integer udpPort = Integer.parseInt(WebUtils.optional(request, "udpPort", "0"));
    String env = WebUtils.optional(request, "env", StringUtils.EMPTY);
    boolean isCheck = Boolean.parseBoolean(WebUtils.optional(request, "isCheck", "false"));
    String app = WebUtils.optional(request, "app", StringUtils.EMPTY);
    String tenant = WebUtils.optional(request, "tid", StringUtils.EMPTY);
    boolean healthyOnly = Boolean.parseBoolean(WebUtils.optional(request, "healthyOnly", "false"));
    // 主要实现方法
    return doSrvIPXT(namespaceId, serviceName, agent, clusters, clientIP, udpPort, env, isCheck, app, tenant,
                     healthyOnly);
}

让我们把目光聚集到:doSrvIPXT() 方法,该方法主要根据namespaceId、serviceName获得Service实例,并基于实例srvIPs得到所有服务提供者的实例信息。

public JSONObject doSrvIPXT(String namespaceId, String serviceName, String agent, String clusters, String clientIP,
                                int udpPort,
                                String env, boolean isCheck, String app, String tid, boolean healthyOnly) throws Exception {
    
    
	ClientInfo clientInfo = new ClientInfo(agent);
	JSONObject result = new JSONObject();
	Service service = serviceManager.getService(namespaceId, serviceName);
	List<Instance> srvedIPs;
    // 获取指定服务下的所有实例IP
	srvedIPs = service.srvIPs(Arrays.asList(StringUtils.split(clusters, ",")));

	Map<Boolean, List<Instance>> ipMap = new HashMap<>(2);
	ipMap.put(Boolean.TRUE, new ArrayList<>());
	ipMap.put(Boolean.FALSE, new ArrayList<>());

	for (Instance ip : srvedIPs) {
    
    
		ipMap.get(ip.isHealthy()).add(ip);
	}
 
	for (Map.Entry<Boolean, List<Instance>> entry : ipMap.entrySet()) {
    
    
		List<Instance> ips = entry.getValue();

		if (healthyOnly && !entry.getKey()) {
    
    
			continue;
		}

		for (Instance instance : ips) {
    
    
			// remove disabled instance:
			if (!instance.isEnabled()) {
    
    
				continue;
			}
			JSONObject ipObj = new JSONObject();
			ipObj.put("ip", instance.getIp());
			ipObj.put("port", instance.getPort());
			// deprecated since nacos 1.0.0:
			ipObj.put("valid", entry.getKey());
			ipObj.put("healthy", entry.getKey());
			ipObj.put("marked", instance.isMarked());
			ipObj.put("instanceId", instance.getInstanceId());
			ipObj.put("metadata", instance.getMetadata());
			ipObj.put("enabled", instance.isEnabled());
			ipObj.put("weight", instance.getWeight());
			ipObj.put("clusterName", instance.getClusterName());
			if (clientInfo.type == ClientInfo.ClientType.JAVA &&
				clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) {
    
    
				ipObj.put("serviceName", instance.getServiceName());
			} else {
    
    
				ipObj.put("serviceName", NamingUtils.getServiceName(instance.getServiceName()));
			}

			ipObj.put("ephemeral", instance.isEphemeral());
			hosts.add(ipObj);

		}
	}
	result.put("hosts", hosts);
	if (clientInfo.type == ClientInfo.ClientType.JAVA &&
		clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) {
    
    
		result.put("dom", serviceName);
	} else {
    
    
		result.put("dom", NamingUtils.getServiceName(serviceName));
	}
	result.put("name", serviceName);
	result.put("cacheMillis", cacheMillis);
	result.put("lastRefTime", System.currentTimeMillis());
	result.put("checksum", service.getChecksum());
	result.put("useSpecifiedURL", false);
	result.put("clusters", clusters);
	result.put("env", env);
	result.put("metadata", service.getMetadata());
	return result;
}
  • 服务动态感知
    • 客户端发起事件订阅后,在Nacos Client 有 HostReactor 类,用于实现服务的动态更新。其包含一个 UpdateTask现场,每10s发送一次pull请求,获得服务端服务地址列表。
    • 对于Naocs Server,它和服务提供者的实例之间维持了心跳检测,一旦服务提供者实例出现异常,则会通知Nacos Client,也就是服务消费者。
    • Nacos Client 服务消费者收到请求之后,使用HostReactor中提供的processServiceJSON解析信息,并及时更新服务端服务地址列表。


Nacos实现架构图

在这里插入图片描述
从架构图中我们可以得知:

  • Provider APP:服务提供者
  • Consumer APP:服务消费者
  • Name Server:通过Vritual IP 或 DNS 方式实现的Nacos高可用集群的服务路由
  • Nacos Server:Nacos 服务提供者,包含Open API 为功能访问入口;Config Service、Naming Service 是Nacos提供的配置、命名服务模块;Consistency Protocol 是一致性协议,用来实现Nacos集群节点的数据同步。
  • Nacos Console:控制台应用

注册中心功能主要体现在:

  • 服务实例在启动时注册到服务注册表,并在关闭时注销;
  • 服务消费者查询服务注册表,获得可用实例;
  • 服务注册中心需要调用服务实例的健康检查API来验证它是否能够处理请求;
    在这里插入图片描述
    具体服务注册与发现案例代码请见Github

猜你喜欢

转载自blog.csdn.net/Nerver_77/article/details/108258357
今日推荐