【自研网关系列】整合 Nacos 服务注册与服务订阅的实现

🌈Yu-Gateway:基于 Netty 与原生 Java 实现,使用 Nacos 作为注册与配置中心。项目实现多种过滤器,包含路由、负载均衡、鉴权、灰度发布等过滤器。

🌈项目代码地址:GitHub - YYYUUU42/YuGateway-master

如果该项目对你有帮助,可以在 github 上点个 ⭐ 喔 🥰🥰

🌈自研网关系列:可以点开专栏,参看完整的文档

目录

1. 什么是 Nacos

2. 为什么选择 Nacos

3. Nacos 的几个重要概念

4. 服务中心具体实现流程

1. 什么是 Nacos

Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它是阿里巴巴开源的一个项目,专门用于微服务架构的服务治理。Nacos帮助实现了微服务架构中的服务自动发现、服务配置管理、服务元数据及流量管理等核心功能。以下是Nacos的一些主要功能:

  1. 服务发现和服务健康监测:Nacos支持基于DNS或HTTP的服务发现机制,能够实现云端服务的自动注册与发现。它还提供服务健康监测,确保请求仅被发送到健康的主机。
  2. 动态配置服务:动态管理所有服务的配置信息,支持配置自动更新,减少了服务配置变更带来的管理工作与更新延迟。
  3. 动态DNS服务:利用DNS服务,管理云服务的域名解析,实现服务的动态路由和负载均衡。
  4. 服务及其元数据管理:Nacos能够管理服务的元数据信息,如权重、区域、版本等,为服务路由、负载均衡提供依据。
  5. 支持AP和CP模式的服务:根据CAP理论,Nacos支持在AP(可用性和分区容错性)和CP(一致性和分区容错性)模式之间的切换,以满足不同场景的需求。

简而言之,作为目前最火热的注册中心和配置中心,Nacos提供了许多强大的功能,无论是可视化的Web网页方便操作,还是说开箱即用的特性,开源的代码和活跃的社区,以及CP和AP双支持的特性,都是我选择Nacos作为我项目注册中心和配置中心的原因。

2. 为什么选择 Nacos

  1. Nacos 提供了从微服务平台建设的视角管理数据中心的所有服务及元数据,Nacos将服务细粒度的划分为了各自实例,并且我们可以管理这些实例的信息
  2. Nacos 支持基于DNS和基于RPC的服务发现,这也就意味着提供较强的服务发现的选择能力
  3. Ncos 提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求,也就是安全
  4. 动态配置服务可以以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置

3. Nacos 的几个重要概念

  1. 服务(Service):在Nacos中,服务是指一个或多个相同功能的实例集合,服务通常对应于一个微服务或一个应用。
  2. 服务实例(Instance):服务实例是指运行服务的最小单位,通常是一个服务的单个运行节点。
  3. 配置管理(Configuration Management):配置管理允许您集中存储和管理在分布式系统环境中使用的所有配置。
  4. 服务注册(Service Registry):服务注册是指服务实例启动时,将自己的网络地址(如IP+Port)注册到Nacos中的过程。
  5. 服务发现(Service Discovery):服务发现是指消费者从Nacos获取服务实例信息的过程,以便进行网络调用。
  6. 命名空间(Namespace):用于实现环境隔离,不同的命名空间下可以有相同名称的服务。
  7. 数据持久化:Nacos支持数据的持久化存储,它可以将服务信息和配置信息持久化到外部存储(如MySQL数据库)中。
  8. 分组(Group):用于进一步进行环境隔离。

4. 服务中心具体实现流程

public static void main(String[] args) {
    //加载网关核心静态配置
    Config config = ConfigLoader.getInstance().load(args);

    //启动容器
    Container container = new Container(config);
    container.start();

    //连接注册中心
    final RegisterCenter registerCenter = registerAndSubscribe(config);
    
    //服务停机
    //收到 kill 信号时触发,服务停机
    Runtime.getRuntime().addShutdownHook(new Thread() {

        /**
			 * 服务停机
			 */
        @Override
        public void run() {
            registerCenter.deregister(buildGatewayServiceDefinition(config),
                                      buildGatewayServiceInstance(config));
            container.shutdown();
        }
    });
}

启动类中核心的是里面的 final RegisterCenter registerCenter = registerAndSubscribe(config);

这个方法就是服务注册和订阅服务变更信息通知, 用 spi 方式实现服务注册

完整代码:

private static RegisterCenter registerAndSubscribe(Config config) {
	ServiceLoader<RegisterCenter> serviceLoader = ServiceLoader.load(RegisterCenter.class);
	final RegisterCenter registerCenter = serviceLoader.findFirst().orElseThrow(() -> {
		log.error("not found RegisterCenter impl");
		return new RuntimeException("not found RegisterCenter impl");
	});
	registerCenter.init(config.getRegistryAddress(), config.getEnv());
	//构造网关服务定义和服务实例
	ServiceDefinition serviceDefinition = buildGatewayServiceDefinition(config);
	ServiceInstance serviceInstance = buildGatewayServiceInstance(config);
	//注册
	registerCenter.register(serviceDefinition, serviceInstance);
	//订阅
	registerCenter.subscribeAllServices(new RegisterCenterListener() {
		@Override
		public void onChange(ServiceDefinition serviceDefinition, Set<ServiceInstance> serviceInstanceSet) {
			log.info("refresh service and instance: {} {}", serviceDefinition.getUniqueId(), JSON.toJSON(serviceInstanceSet));
			DynamicConfigManager manager = DynamicConfigManager.getInstance();
			//将这次变更事件影响之后的服务实例再次添加到对应的服务实例集合
			manager.putServiceInstance(serviceDefinition.getUniqueId(), serviceInstanceSet);
			//修改发生对应的服务定义
			manager.putServiceDefinition(serviceDefinition.getUniqueId(), serviceDefinition);
		}
	});
	return registerCenter;
}

在分布式系统中,服务的数量可能会非常多,而且服务的状态(例如,服务的上线、下线、扩容、缩容)可能会频繁变化。如果每个服务都需要主动去查询其他服务的状态,那么将会产生大量的网络请求,这对系统的性能和稳定性都是一个很大的挑战。

为了解决这个问题,我们通常会使用服务注册中心,让每个服务在启动时都向注册中心注册自己的信息(例如,服务的地址、端口等)。然后,其他的服务就可以从注册中心订阅这些信息。当服务的状态发生变化时,注册中心会通知所有订阅了该服务的其他服务,这样就可以避免了大量的网络请求。

registerAndSubscribe 方法就是用来完成这个功能的。它首先通过SPI方式加载 RegisterCenter 实现类,然后初始化,构造网关服务定义和服务实例,注册到注册中心,并订阅所有服务的变更信息。这样,当服务的状态发生变化时,服务就可以立即得到通知,从而做出相应的处理。

其中重要的是ServiceLoader.load(RegisterCenter.class) 这段代码

ServiceLoader 是 Java 中用于加载服务提供者的工具,通常用于实现服务提供者框架。它的作用是查找和加载指定接口或抽象类的服务提供者实现类,这些实现类在运行时动态注册到系统中,以便其他组件或应用程序可以使用它们的功能。

就是查找 META-INF/services 下的接口

这个文件里面定义了实现类的位置 com.yu.gateway.config.center.nacos.impl.NacosConfigCenter

从而得到了一个ServiceLoader<RegisterCenter>

再通过serviceLoader.findFirst() 得到 RegisterCenter 对象,紧接对 RegisterCenter 进行初始化

然后构造网关服务定义和服务实例,进行注册

registerCenter.register(serviceDefinition, serviceInstance);

服务注册完整代码

public void register(ServiceDefinition serviceDefinition, ServiceInstance serviceInstance) {
	try {
		//构造 nacos 实例信息
		Instance instance = new Instance();
		instance.setInstanceId(serviceInstance.getServiceInstanceId());
		instance.setIp(serviceInstance.getIp());
		instance.setPort(serviceInstance.getPort());
		//实例信息可以放入到metadata中
		instance.setMetadata(Map.of(GatewayConst.META_DATA_KEY, JSON.toJSONString(serviceInstance)));
		//注册
		namingService.registerInstance(serviceDefinition.getServiceId(), env, instance);
		//更新服务定义
		namingMaintainService.updateService(serviceDefinition.getServiceId(), env, 0,
				Map.of(GatewayConst.META_DATA_KEY, JSON.toJSONString(serviceDefinition)));
		log.info("register {} {}", serviceDefinition, serviceInstance);
	} catch (NacosException e) {
		log.error("register {} failed", serviceDefinition, e);
		throw new RuntimeException(e);
	}
}

注册完之后就是订阅

订阅服务完整代码:

private void doSubscribeServices() {
    try {
        // 获取已订阅的服务列表
        //这里其实已经在init的时候初始化过 namingService 了,所以这里可以直接拿到当前服务已经订阅的服务
        Set<String> subscribeServiceSet = namingService.getSubscribeServices().stream().map(ServiceInfo::getName).collect(Collectors.toSet());
        int pageNo = 1;
        int pageSize = 100;
        
        // 分页获取所有服务列表,缓存未订阅的服务信息
        List<String> serviceList = namingService.getServicesOfServer(pageNo, pageSize, env).getData();
        while (CollectionUtils.isNotEmpty(serviceList)) {
            log.info("service list size {}", serviceList.size());
            for (String service : serviceList) {
                //判断是否已经订阅了当前服务
                if (subscribeServiceSet.contains(service)) {
                    continue;
                }
                
                //nacos 事件监听器 订阅当前服务
                //这里需要自己实现一个 nacos 的事件订阅类 来具体执行订阅执行时的操作
                EventListener eventListener = new NacosRegisterListener();
                
                //当前服务之前不存在 调用监听器方法进行添加处理
                eventListener.onEvent(new NamingEvent(service, null));
                
                // 订阅指定运行环境下对应的服务名,注册中心服务发生变动时调用 onEvent() 方法更新本地缓存的服务信息
                namingService.subscribe(service, env, eventListener);
                log.info("subscribe a service, serviceName {} env{}", service, env);
            }
            //遍历下一页的服务列表
            serviceList = namingService.getServicesOfServer(++pageNo, pageSize, env).getData();
        }
    } catch (NacosException e) {
        throw new RuntimeException(e);
    }
}

可能有新服务加入,所以需要有一个定时任务来检查

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1, new NameThreadFactory(
		"doSubscribeAllServices"));
//循环执行服务发现与订阅操作
scheduledThreadPool.scheduleWithFixedDelay(() -> doSubscribeServices(), 10, 10, TimeUnit.SECONDS);

弄完之后,在nacos中的服务列表看到刚刚注册的服务了

具体流程还是 clone 完整代码,自己 debug 一下最好(顺便点个⭐)

  • 14
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值