作为从事五年阿里程序员的我来说,阿里的面试题再熟悉不过了,每一年里有两个月时间我都会在面试考场面试各种人才,有很多跳槽的也有很多校招的学生小白,底标准本科,高的则达到高校硕士博士学位。其中,阿里八位面试官在第三层面试的题目中就有最为重要的一道题:Nacos为什么这么强?他的实现原理是什么?很多人都被问住了。很多人都疏忽这一道看似简单又不简单的题目。这道题很重要!这道题几个大名鼎鼎的大厂面试必问的题目。
需要大厂1000道面试笔记的朋友可以 点赞+转发+关注!后台私信回复【444】即可免费获取完整资料!
Java微服务新生代之Nacos
- 关于 Nacos /nɑ:kəʊs/
- 为什么是 Nacos
- Nacos 实战
- 单机部署
- 准备环境
- 集群部署
- 添加集群配置文件
- 配置 MySQL 数据库
- 实现服务的注册与发现
- Rest 服务的注册与发现
- 服务提供者创建
- 服务消费者创建
- Feign 方式消费 Rest 服务
- Dubbo 服务的注册与发现
- 服务生产者
- 服务消费者
- 结语
- 代码示例
Nacos
Nacos 第一次映入眼帘时,是在18年dubbo的一次线下沙龙中,那时还是v0.1.0版本;而如今,nacos已经经历过了可生产环境使用的 v0.8.0 Pre-GA版,到了v0.9.0版本。下面,就一起来简单了解一下nacos。
nacos是什么?
Nacos 是一个集服务动态发现、服务配置、服务元数据及流量管理于一体的管理中心,能帮助我们更好的发现、配置和管理微服务。
Nacos架构
- Provider APP:服务提供者
- Consumer APP:服务消费者
- Name Server:通过VIP(Virtual IP)或DNS的方式实现Nacos高可用集群的服务路由
- Nacos Server:Nacos服务提供者,里面包含的Open API是功能访问入口,Conig Service、Naming Service 是Nacos提供的配置服务、命名服务模块。Consitency Protocol是一致性协议,用来实现Nacos集群节点的数据同步,这里使用的是Raft算法(Etcd、Redis哨兵选举)
- Nacos Console:控制台
注册中心的原理
- 服务实例在启动时注册到服务注册表,并在关闭时注销
- 服务消费者查询服务注册表,获得可用实例
- 服务注册中心需要调用服务实例的健康检查API来验证它是否能够处理请求
SpringCloud完成注册的时机
在Spring-Cloud-Common包中有一个类org.springframework.cloud. client.serviceregistry .ServiceRegistry ,它是Spring Cloud提供的服务注册的标准。集成到Spring Cloud中实现服务注册的组件,都会实现该接口。
该接口有一个实现类是NacoServiceRegistry。
SpringCloud集成Nacos的实现过程:
在spring-clou-commons包的META-INF/spring.factories中包含自动装配的配置信息如下:
其中AutoServiceRegistrationAutoConfiguration就是服务注册相关的配置类:
在AutoServiceRegistrationAutoConfiguration配置类中,可以看到注入了一个AutoServiceRegistration实例,该类的关系图如下所示。
可以看出, AbstractAutoServiceRegistration抽象类实现了该接口,并且最重要的是NacosAutoServiceRegistration继承了AbstractAutoServiceRegistration。
看到EventListener我们就应该知道,Nacos是通过Spring的事件机制继承到SpringCloud中去的。
AbstractAutoServiceRegistration实现了onApplicationEvent抽象方法,并且监听WebServerInitializedEvent事件(当Webserver初始化完成之后) , 调用this.bind ( event )方法。
最终会调用NacosServiceREgistry.register()方法进行服务注册。
NacosServiceRegistry的实现
在NacosServiceRegistry.registry方法中,调用了Nacos Client SDK中的namingService.registerInstance完成服务的注册。
跟踪NacosNamingService的registerInstance()方法:
- 通过beatReactor.addBeatInfo()创建心跳信息实现健康检测, Nacos Server必须要确保注册的服务实例是健康的,而心跳检测就是服务健康检测的手段。
- serverProxy.registerService()实现服务注册
心跳机制:
从上述代码看,所谓心跳机制就是客户端通过schedule定时向服务端发送一个数据包 ,然后启动-个线程不断检测服务端的回应,如果在设定时间内没有收到服务端的回应,则认为服务器出现了故障。Nacos服务端会根据客户端的心跳包不断更新服务的状态。
注册原理:
Nacos提供了SDK和Open API两种形式来实现服务注册。
Open API:
SDK:
这两种形式本质都一样,底层都是基于HTTP协议完成请求的。所以注册服务就是发送一个HTTP请求:
对于nacos服务端,对外提供的服务接口请求地址为nacos/v1/ns/instance,实现代码咋nacos-naming模块下的InstanceController类中:
- 从请求参数汇总获得serviceName(服务名)和namespaceId(命名空间Id)
- 调用registerInstance注册实例
- 创建一个控服务(在Nacos控制台“服务列表”中展示的服务信息),实际上是初始化一个serviceMap,它是一个ConcurrentHashMap集合
- getService,从serviceMap中根据namespaceId和serviceName得到一个服务对象
- 调用addInstance添加服务实例
- 根据namespaceId、serviceName从缓存中获取Service实例
- 如果Service实例为空,则创建并保存到缓存中
- 通过putService()方法将服务缓存到内存
- service.init()建立心跳机制
- consistencyService.listen实现数据一致性监听
service.init ( )方法的如下图所示,它主要通过定时任务不断检测当前服务下所有实例最后发送心跳包的时间。如果超时,则设置healthy为false表示服务不健康,并且发送服务变更事件。
在这里请大家思考一一个问题,服务实例的最后心跳包更新时间是谁来触发的?实际上前面有讲到, Nacos客户端注册服务的同时也建立了心跳机制。
putService方法,它的功能是将Service保存到serviceMap中:
继续调用addInstance方法把当前注册的服务实例保存到Service中:
总结:
- Nacos客户端通过Open API的形式发送服务注册请求
- Nacos服务端收到请求后,做以下三件事:
- 构建一个Service对象保存到ConcurrentHashMap集合中使用定时任务对当前服务下的所有实例建立心跳检测机制基于数据一致性协议服务数据进行同步
服务提供者地址查询
Open API:
SDK:
InstanceController中的list方法:
- 解析请求参数
- 通过doSrvIPXT返回服务列表数据
- 根据namespaceId、serviceName获得Service实例
- 从Service实例中基于srvIPs得到所有服务提供者实例
- 遍历组装JSON字符串并返回
Nacos服务地址动态感知原理
可以通过subscribe方法来实现监听,其中serviceName表示服务名、EventListener表示监听到的事件:
具体调用方式如下:
或者调用selectInstance方法,如果将subscribe属性设置为true,会自动注册监听:
Nacos中引入的概念
- 客户端发起时间订阅后,在HostReactor中有一个UpdateTask线程,每10s发送一次Pull请求,获得服务端最新的地址列表
- 对于服务端,它和服务提供者的实例之间维持了心跳检测,一旦服务提供者出现异常,则会发送一个Push消息给Nacos客户端,也就是服务端消费者
- 服务消费者收到请求之后,使用HostReactor中提供的processServiceJSON解析消息,并更新本地服务地址列表
Nacos 支持几乎所有主流类型的“服务”的发现、配置和管理,如:
- Kubernetes Service
- gRPC & Dubbo RPC Service
- Spring Cloud RESTful Service
Nacos 可与spring,springboot,springcloud,dubbo,docke, k8s一起使用。
详细使用教程可以参考官网资料:
- Nacos Spring
- Nacos Spring Boot
- Nacos Spring Cloud
- Nacos Docker
- Nacos Dubbo
- Nacos K8s
Nacos关键特性
- 服务发现和服务健康监测
- 动态配置服务
- 动态 DNS 服务
- 服务及其元数据管理
Nacos中引入的概念
- 地域 (Region)
- 物理的数据中心,资源创建成功后不能更换。
- 可用区(Available Zone)
- 同一地域内,电力和网络互相独立的物理区域。同一可用区内,实例的网络延迟较低。
- 接入点(Endpoint)
- 地域的某个服务的入口域名。
- 命名空间(Namespace)
- 用于进行租户粒度的配置隔离。不同的命名空间下,可以存在相同的 Group 或 Data ID 的配置。Namespace 的常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。
- 配置 (Configuration)
- 在系统开发过程中,开发者通常会将一些需要变更的参数、变量等从代码中分离出来独立管理,以独立的配置文件的形式存在。目的是让静态的系统工件或者交付物(如 WAR,JAR 包等)更好地和实际的物理运行环境进行适配。配置管理一般包含在系统部署的过程中,由系统管理员或者运维人员完成。配置变更是调整系统运行时的行为的有效手段。
- 配置管理 (Configuration Management)
- 系统配置的编辑、存储、分发、变更管理、历史版本管理、变更审计等所有与配置相关的活动。
- 配置项 (Configuration Item)
- 一个具体的可配置的参数与其值域,通常以 param-key=param-value 的形式存在。例如我们常配置系统的日志输出级别(logLevel=INFO|WARN|ERROR) 就是一个配置项。
- 配置集 (Configuration Set)
- 一组相关或者不相关的配置项的集合称为配置集。在系统中,一个配置文件通常就是一个配置集,包含了系统各个方面的配置。例如,一个配置集可能包含了数据源、线程池、日志级别等配置项。
- 配置集 ID(Data ID)
- Nacos 中的某个配置集的 ID。配置集 ID 是组织划分配置的维度之一。Data ID 通常用于组织划分系统的配置集。一个系统或者应用可以包含多个配置集,每个配置集都可以被一个有意义的名称标识。Data ID 通常采用类 Java 包(如 com.taobao.tc.refund.log.level)的命名规则保证全局唯一性。此命名规则非强制。
- 配置分组(Group)
- Nacos 中的一组配置集,是组织配置的维度之一。通过一个有意义的字符串(如 Buy 或 Trade )对配置集进行分组,从而区分 Data ID 相同的配置集。当您在 Nacos 上创建一个配置时,如果未填写配置分组的名称,则配置分组的名称默认采用 DEFAULT_GROUP 。配置分组的常见场景:不同的应用或组件使用了相同的配置类型,如 database_url 配置和 MQ_topic 配置。
- 配置快照 (Configuration Snapshot)
- Nacos 的客户端 SDK 会在本地生成配置的快照。当客户端无法连接到 Nacos Server 时,可以使用配置快照显示系统的整体容灾能力。配置快照类似于 Git 中的本地 commit,也类似于缓存,会在适当的时机更新,但是并没有缓存过期(expiration)的概念。
- 服务(Service)
- 通过预定义接口网络访问的提供给客户端的软件功能。
- 服务名(Service Name)
- 服务提供的标识,通过该标识可以唯一确定其指代的服务。
- 服务注册中心(Service Registry)
- 存储服务实例和服务负载均衡策略的数据库。
- 服务发现(Service Discovery)
- 在计算机网络上,(通常使用服务名)对服务下的实例的地址和元数据进行探测,并以预先定义的接口提供给客户端进行查询。
- 元信息(Metadata)
- Nacos数据(如配置和服务)描述信息,如服务版本、权重、容灾策略、负载均衡策略、鉴权配置、各种自定义标签 (label),从作用范围来看,分为服务级别的元信息、集群的元信息及实例的元信息。
- 应用(Application)
- 用于标识服务提供方的服务的属性。
- 服务分组(Service Group)
- 不同的服务可以归类到同一分组。
- 虚拟集群(Virtual Cluster)
- 同一个服务下的所有服务实例组成一个默认集群, 集群可以被进一步按需求划分,划分的单位可以是虚拟集群。
- 实例(Instance)
- 提供一个或多个服务的具有可访问网络地址(IP:Port)的进程。
- 权重(Weight)
- 实例级别的配置。权重为浮点数。权重越大,分配给该实例的流量越大。
- 健康检查(Health Check)
- 以指定方式检查服务下挂载的实例 (Instance) 的健康度,从而确认该实例 (Instance) 是否能提供服务。根据检查结果,实例 (Instance) 会被判断为健康或不健康。对服务发起解析请求时,不健康的实例 (Instance) 不会返回给客户端。
- 健康保护阈值(Protect Threshold)
- 为了防止因过多实例 (Instance) 不健康导致流量全部流向健康实例 (Instance) ,继而造成流量压力把健康 健康实例 (Instance) 压垮并形成雪崩效应,应将健康保护阈值定义为一个 0 到 1 之间的浮点数。当域名健康实例 (Instance) 占总服务实例 (Instance) 的比例小于该值时,无论实例 (Instance) 是否健康,都会将这个实例 (Instance) 返回给客户端。这样做虽然损失了一部分流量,但是保证了集群的剩余健康实例 (Instance) 能正常工作。
关于 Nacos 名字:前四个字母分别为 Naming 和 Configuration 的前两个字母,最后的 s 为Service。
本文主要内容涉及如下:
- Nacos 基本介绍
- 为什么使用 Nacos
- 最新版本 Nacos 与 Rest/Dubbo 服务的注册与发现集成
关于 Nacos /nɑ:kəʊs/
一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
上面这句话摘自 Nacos 官方首页,是对 Nacos 整体功能的总结。
简单来说 Nacos 就是注册中心 + 配置中心的组合,提供简单易用的特性集,帮助我们解决微服务开发必会涉及到的服务注册与发现,服务配置,服务管理等问题。Nacos 还是 Spring Cloud Alibaba 组件之一,负责服务注册与发现。
首先来看下官方对 Nacos 的特性描述:
- 服务发现和服务健康监测
- 动态配置服务
- 动态 DNS 服务
- 服务及其元数据管理
- 不断新增…
可以发现所有特性都离不开服务这一词,服务 (Service) 是 Nacos 世界中的一等公民,说明了服务是 Nacos 中最主要的角色。
为什么是 Nacos
现在的微服务生态中,已经有很多服务注册与发现的开源组件,如 Eurka,ZooKeeper,Consul,为什么还要用 Nacos 呢,我们看下这些框架的简单对比:
据说 Nacos 在阿里巴巴内部有超过 10 万的实例运行,已经过了类似双十一等各种大型流量的考验。
相比之下,目前的 Nacos 无论是部署,还是使用上都简单上手,更重要的是文档资料齐全,社区活跃度高。
并且 Nacos 与目前主流的开源生态都提供了很好的支持:
- Nacos 是专为 Dubbo 而生的注册中心与配置中心
- Nacos 会完全兼容 Spring Cloud
- Nacos 支持 Service Mesh 集成,Kubernetes 集成
除此之外,阿里巴巴正在通过 Dubbo + Nacos 以及一系列开源项目打造服务发现、服务及流量管理、服务共享平台,未来还在不断地发展和演进,相信未来会有更多的地方上使用 Nacos。
Nacos 实战
单机部署
准备环境
Nacos 依赖 Java 环境来运行,并且需要对 Nacos 代码构建生成可执行程序时,还要有 Maven 环境,所以部署前需要保证环境要求:
- 64 bit OS,支持 Linux/Unix/Mac/Windows,推荐选用 Linux/Unix/Mac。
- 64 bit JDK 1.8+
- Maven 3.2.x+
集群部署
相比 Nacos 简单的单机部署,集群部署方式稍微麻烦一些,跟着官方文档走还是有点小坑,还需要自己额外的调整。 为了用于生产环境,必须确保 Nacos 的高可用,所以还是有必要实践下集群部署的操作。
准备环境跟单机部署相同,额外的要求就是 Nacos 需要 3 个或 3 个以上 Nacos 节点构成集群,并且使用 MySQL 作为数据源,主要用于服务配置的数据持久化。
我们先看下官方推荐的集群部署架构图,通过域名方式反向代理如 Nginx 来负载多个 Nacos 节点 IP,外部客户端直接通过域名访问就可,不仅可读性好,而且更换 IP 方便,最为推荐采用。
添加集群配置文件
在每个 Nacos 节点的conf目录下,添加配置文件 cluster.conf,可以参考相同目录下的 cluster.conf.example 文件,每行配置一个节点的 IP 和端口,如 ip:port
注意:配置文件中不能使用 127.0.0.1 或者localhost ,需要真实 IP 或者域名,否则启动后服务无法注册到该集群节点上
配置 MySQL 数据库
Nacos 推荐生产环境中数据库使用建议至少主备模式,或者采用高可用数据库。
这里为了简化只采用了一个数据库。首先新建一个名为 nacos_config 的数据库,使用提供的 sql 语句源文件 导入初始数据。
然后在每个 Nacos 节点的配置文件 conf/application.properties 里添加数据库连接配置
最后以集群模式分别启动每个节点,并且默认为后台启动,启动信息需要从 logs/logs/start.out日志文件中获取。
当日志文件最后出现 Nacos started successfully in cluster mode. 一行时,即说明集群模式下 Nacos 启动成功。这时,我们也可以通过登录任一个 Nacos 控制台的集群管理界面看到节点的信息。
可以从上面看到,集群下的 Nacos 节点状态分为 FOLLOWER ,LEADER 两种,跟我们熟悉的主从架构相似。
到这里,我们集群方式的搭建也完成了。接下我们就来看下如何使用 Nacos 进行服务注册和发现吧。
实现服务的注册与发现
Rest 服务的注册与发现
服务提供者创建
创建一个子项目工程名为 rest-provider 的服务提供者项目,pom.xml
注意这里的 spring-cloud-starter-alibaba-nacos-discovery 版本为 0.9.0,采用的是 Nacos 1.0.0 版本的客户端,而对应 Spring Boot 版本需要为 2.1.x.RELEASE 版本
因此我们使用 Spring Cloud Alibaba 最新版本 0.9.0.RELEASE,对应 Spring Cloud Greenwich 版本,父 Maven 项目 POM 配置需要添加如下依赖管理
依赖添加之后,在引导类中实现一个 Rest 请求方法 /echo,并且通过 @EnableDiscoveryClient 注解表明是一个 Nacos 客户端,而该注解是 Spring Cloud 提供的原生注解,就算切换成 Eureka 作为注册中心也是用这个注解。
修改子项目配置文件 application.properties
Nacos Starter 更多配置项信息可以参考 Spring Cloud Alibaba Nacos Discovery
然后通过引导类 RestProviderApplication 运行项目,登录 Nacos Server 控制台,查看服务列表即可看到服务提供者 rest-provider 已经注册成功。
服务消费者创建
同样的方式我们创建一个服务消费者项目 rest-consumer,pom.xml 依赖和 application.properties 配置与 provider-consumer 一样, 这里我们显式地使用 LoadBalanceClient API 和 RestTemplate 结合的方式来消费服务。
- 首先在引导类中添加 @EnableDiscoveryClient
- 再创建一个名为 RestConsumerConfiguration 的 Java 配置类,注入 RestTemplate。
- 创建一个名为 RestConsumerController 测试用的 Controller,提供一个请求方法 echo
- 启动 rest-cosumer 工程后,就可以在 Nacos Server 控制台的服务列表里看到多了一个名为 rest-consumer 的服务。
使用上看是不是很简单呢,其实和 Eureka 没有太大差别,除了这种方式消费 Rest 服务外,Nacos 也支持 Feign 方式,我们来看下这又是如何使用的吧。
Feign 方式消费 Rest 服务
首先需要引入 Feign 客户端依赖
然后引导类添加注解 @EnableFeignClients 启用 Feign 组件功能,编写一个 EchoService 接口,用于调用远程服务
Get 请求方式的接口方法参数不能必须使用 @RequestParam 参数指定请求参数名称,否则 Feign 都会通过 POST 方式调用,得到状态码 405 的错误。
注解 @FeignClient 指明了调用的服务提供方名称,echo 方法通过 Spring MVC 提供的注解就可以跟服务提供者的 Rest 接口关联,执行时就会调用对应请求方法。
最后我们只需要编写一个注入 EchoService 控制器 RestConsumerFeignController 类,提供一个接口调用接口。
Dubbo 服务的注册与发现
接下来,我们看下 Dubbo 怎么使用 Nacos 作为服务注册中心,进行注册与发现的,其实只要用过 ZooKeeper 方式的就会发现很简单。
首先我们创建一个子工程 dubbo-serivce,POM 文件添加依赖
- 这里我们按照官方推荐使用 2.6.5 版本的 Dubbo 集成 Nacos。
- dubbo-registry-nacos 是 Dubbo 使用 Nacos 作为注册中心的关键依赖,当前版本为 0.0.1。
接着定义服务接口 DemoService
服务生产者
还是服务生产者先来,添加一个类 DemoServiceImpl 实现服务接口
然后以 XML 配置方式配置 Dubbo,只需要在 dubbo:registry元素上配置 Nacos 服务端地址即可。
最后用简单类BasicProvider的 main 方法直接启动服务生产者,保持进程常驻。
正常启动后我们直接在 Nacos 控制台的服务列表里就可以,Dubbo 生产者服务已经成功注册到 Nacos 上了,可以通过详情看到服务的具体数据,比如服务端口,方法之类。
有了服务生产者,就来看下怎么去通过 Nacos 调用 Dubbo 服务。
首先,通过 XML 配置方式注入用 Dubbo 服务对象 DemoService, 在 dubbo:registry元素上配置 Nacos 服务端地址。
然后编写简单类 BasicConsumer ,在 main 方法中加载 Spring 容器,获取 DemoService 对象,直接进行 RCP 调用。
运行之后,我们可以从 IDE 控制台上看到不断有日志输出,说明了通过 Nacos 注册中心的Dubbo 服务调用成功了。
结语
本文主要学习了解 Nacos,以及使用 Nacos 的服务注册与发现功能,如何与 Rest 服务,Dubbo 服务进行集成使用,整体上简单易用,有兴趣的朋友也尝试用下吧。需要java大厂1000道面试笔记的可以 点赞+转发+关注 后台私信回复【444】即可免费获取资料。
版权声明:内容来源于互联网和用户投稿 如有侵权请联系删除