中间件知识点-微服务中间件(nacos)一

概览

springcloud是集合了多种微服务技术的一站式解决方案,俗称微服务全家桶。

Spring Cloud Alibaba 也是一套微服务解决方案

springcloud alibaba和springcloud区别(使用的组件有些不一样)
注册/发现中心组件不一样,服务间调用不一样:
springcloud alibaba使用nacos作为注册/发现中心,使用dubbo来进行服务间调用。
springcloud官方使用springcloud zookeeper、Eureka等来作为注册中心,使用OpenFeign来进行服务间调用。

springboot:springboot是一个基于spring的提供了快速开发应用的框架,包含了四大组件:1.auto-configuration组件;2.starter组件;3.springboot cli组件;4.actuator组件。
Spring Boot 提供了自动化配置的机制,可以根据项目的依赖和环境自动配置应用程序,从而减少开发者的配置工作量。
Spring Boot 提供了丰富的起步依赖(Starter POMs)库,这些起步依赖预先集成了常用的库和框架,比如数据库访问、安全认证、Web 开发等,开发者可以直接引入这些起步依赖来快速构建特定类型的应用。
Actuator 是 Spring Boot 提供的一个功能强大的监控和管理工具,可以监控应用程序的健康状况、性能指标和运行状态等。

Spring Cloud Alibaba 扩展了 Spring Cloud,并提供了与阿里巴巴相关的分布式解决方案,而这些解决方案都是构建在 Spring Boot 的基础之上的。

springcloud alibaba、springcloud、springboot及其中各组件版本关系
https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E

Nacos

基本架构及概念
在这里插入图片描述主要包含Config Service(配置中心核心服务,对应配置管理)和Naming Service(服务注册中心核心服务,对应服务管理)
基本概念:.
1.服务 (Service)
服务是指一个或一组软件功能(例如特定信息的检索或一组操作的执行),其目的是不同的客户端可以为不同的目的重用(例如通过跨进程的网络调用)。Nacos 支持主流的服务生态,如 KubernetesService、gRPC|Dubbo RPC Service 或者 Spring Cloud RESTful Service.
2.服务注册中心 (Service Registry)
服务注册中心,它是服务,其实例及元数据的数据库。服务实例在启动时注册到服务注册表,并在关闭时注销。服务和路由器的客户端查询服务注册表以查找服务的可用实例。服务注册中心可能会调用服务实例的健康检查 API 来验证它是否能够处理请求。
3.服务元数据 (Service Metadata)
服务元数据是指包括服务端点(endpoints)、服务标签、服务版本号、服务实例权重、路由规则、安全策略等描述服务的数据
4.服务提供方 (Service Provider)
是指提供可复用和可调用服务的应用方
5.服务消费方 (Service Consumer)
是指会发起对某个服务调用的应用方
6.配置 (Configuration)
在系统开发过程中通常会将一些需要变更的参数、变量等从代码中分离出来独立管理,以独立的配置文件的形式存在。目的是让静态的系统工件或者交付物(如 WAR,JAR 包等)更好地和实际的物理运行环境进行适配。配置管理一般包含在系统部署的过程中,由系统管理员或者运维人员完成这个步骤。配置变更是调整系统运行时的行为的有效手段之一。
7.配置管理 (Configuration Management)
在数据中心中,系统中所有配置的编辑、存储、分发、变更管理、历史版本管理、变更审计等所有与配置相关的活动统称为配置管理。
8.名字服务 (Naming Service)
提供分布式系统中所有对象(Object)、实体(Entity)的“名字”到关联的元数据之间的映射管理服务,例如 ServiceName -> Endpoints Info, Distributed Lock Name -> Lock Owner/Status Info, DNS Domain Name -> IP List, 服务发现和 DNS 就是名字服务的2大场景。
9.配置服务 (Configuration Service)
在服务或者应用运行过程中,提供动态配置或者元数据以及配置管理的服务提供者。

OpenApi用于通过接口方式来进行服务注册/发现。

1.注册中心:
是什么:服务注册中心,它是服务及其实例和元数据的数据库。
做什么:服务注册中心的作用就是服务注册与发现。

Nacos 注册中心架构图
在这里插入图片描述
架构原理:
1、微服务系统在启动时将自己注册到服务注册中心,同时外发布 Http 接口供其它系统调用(一般都是基于SpringMVC)
2、服务消费者基于 Feign 调用服务提供者对外发布的接口,先对调用的本地接口加上注解@FeignClient,Feign会针对加了该注解的接口生成动态代理,服务消费者针对 Feign 生成的动态代理去调用方法时,会在底层生成Http协议格式的请求,类似 /stock/deduct?productId=100
3、Feign最终会调用Ribbon从本地的Nacos注册表的缓存里根据服务名取出服务提供在机器的列表,然后进行负载均衡并选择一台机器出来,对选出来的机器IP和端口拼接之前生成的url请求,生成调用的Http接口地址

核心功能
1.服务注册
Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如ip地址、端口等信息。Nacos Server接收到注册请求后,就会把这些元数据信息存储在一个双层的内存Map中。
2.服务心跳(客户端发送心跳告诉服务端,以防被剔除)
在服务注册后,Nacos Client会维护一个定时心跳来持续通知Nacos Server,说明服务一直处于可用状态,防止被剔除。默认5s发送一次心跳。
3.服务同步(集群间互相同步服务实例)
Nacos Server集群之间会互相同步服务实例,用来保证服务信息的一致性。
4.服务发现
服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给Nacos Server,获取上面注册的服务清单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存
Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存
5.服务健康检查(服务端定时检测,超过一定时间没收到的实例将被提出)
Nacos Server会开启一个定时任务用来检查注册服务实例的健康情况,对于超过15s没有收到客户端心跳的实例会将它的healthy属性置为false(客户端服务发现时不会发现),如果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新注册)

注册中心对比
一致性:
nacos:CP+AP
zookeeper:CP
eureka:AP

环境搭建
1.单机模式
2.集群模式
service-addr:127.0.0.1:8848,xxx.x.x.x:8848
一般不这样直接设置ip来配置,一般配合nginx来使用

Nacos注册中心常见配置
1.服务分级存储模型
a.服务-集群-实例的三层模型
b.一个服务可以分为多个集群,每个集群可分为多个实例在这里插入图片描述2.服务逻辑隔离(实现的是逻辑上的隔离)
a.Nacos 数据模型 Key 由三元组唯一确定:Namespace、Group、Service/DataId
b.Namespace默认命名空间名称是public、其命名空间id为空。默认分组为DEFAULT_GROUP
c.Namespace 隔离设计
Namespace常用场景之一是不同环境的隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。
不同的服务可以归类到同一分组,group也可以起到服务隔离的作用。

3.临时实例和持久化实例
a.临时实例使用客户端上报模式,而持久化实例使用服务端反向探测模式。临时实例需要能够自动摘除不健康实例,而且无需持久化存储实例。持久化实例使用服务端探测的健康检查方式,因为客户端不会上报心跳, 所以不能自动摘除下线的实例。
b.持久化实例
例如数据库、缓存等,这些往往不能上报心跳,这种类型的服务在注册时,就需要作为持久化实例注册。
c.临时实例
而上层的业务服务,例如 微服务或者 Dubbo 服务,服务的 Provider 端支持添加汇报心跳的逻辑,此时就可以使用动态服务的注册方式。
总结:
所以Nacos从大到小是这么划分的:Namespace、Group、Service/DataId、集群、实例

2.配置中心
1.配置优先级
Spring Cloud Alibaba Nacos Config 目前提供了三种配置能力从 Nacos 拉取相关的配置。
A: 通过 spring.cloud.nacos.config.shared-configs 支持多个共享 Data Id 的配置
B: 通过 spring.cloud.nacos.config.ext-config[n].data-id 的方式支持多个扩展Data Id 的配置。扩展配置可以让一个应用支持多个配置。比如可以将一个应用中使用的各个中间件配置放到不用的配置文件中。
C: 通过内部相关规则(应用名、应用名+ Profile )自动生成相关的 Data Id 配置
当三种方式共同使用时,他们的一个优先级关系是:本地文件配置<A < B < C

nacos配置优先级高于本地配置,nacos配置中,共享配置优先级最低,其次是扩展配置。最高是应用名/应用名+ Profile的配置文件。
nacos远程配置是否生效,还得看是否支持动态刷新

@Value属性需要加上@RefreshScope注解才会被感知到刷新。

2.通过自定义扩展的 Data Id 配置,既可以解决多个应用间配置共享的问题,又可以支持一个应用有多个配置文件。
支持共享的 DataId:
spring.cloud.nacos.config.sharedConfigs[0].data‐id= common.yaml
# 支持一个应用多个 DataId 的配置
9 spring.cloud.nacos.config.extensionConfigs[0].data‐id=ext‐config‐
common01.properties
10 spring.cloud.nacos.config.extensionConfigs[0].group=REFRESH_GROUP
11 spring.cloud.nacos.config.extensionConfigs[0].refresh=true
12
13 spring.cloud.nacos.config.extensionConfigs[1].data‐id=ext‐config‐common0
2.properties
14 spring.cloud.nacos.config.extensionConfigs[1].group=REFRESH_GROUP
15 spring.cloud.nacos.config.extensionConfigs[1].refresh=true

refresh=true表示支持动态刷新

3.当利用@RefreshScope刷新配置后会导致定时任务失效
原因:@RefreshScope修饰的bean的属性发生变更后,bean会从缓存中清除。此时没有这个bean,定时任务当然也就不生效了。
@RefreshScope的bean会被放在缓存池中
如果再次去获取bean的时候,因为这时bean又会被重新创建,此时定时任务会再次被创建(在ScheduledAnonotationBeanPostProcessor后置处理器创建),此时定时任务又会重新生效。
定时任务在什么时候被解析为定时任务?有个ScheduledAnonotationBeanPostProcessor后置处理器,在这个后置处理器里执行定时任务。
所以,最好将定时任务放在非 @RefreshScope Bean 中。

不同版本nacos区别
Nacos 1.x 中持久化及非持久化的属性是作为实例的㇐个元数据进行存储和识别。
Nacos 2.x 中继续沿用了持久化及非持久化的设定,但是有了㇐些调整。在 Nacos2.0 中将是否持久化的数据抽象至服务级别, 且不再允许㇐个服务同时存在持久化实例和非持久化实例,实例的持久化属性继承自服务的持久化属性。

nacos 2.1.0服务注册/发现底层是用过grpc来调用的,nacos1.4.1底层是通过http来调用的。

nacos其实就是一个springboot的web应用

Nacos核心功能源码架构图
略。

A.nacos源码分析(注册中心)
Nacos1.4服务注册表结构是一个双重Map:Map<namespace, Map<group::serviceName, Service>>
在这里插入图片描述
然后每个Service右包含了多个集群,每个集群又包含了多个实例。
比如Bj集群,Gz集群,每个集群包含多个实例。
如果有Bj集群,Gz集群,表示有2个nacos。

nacos是AP还是CP架构取决于是否是临时实例 ephemeral(是否是临时实例,默认为true) 默认是临时实例。临时实例的话是AP架构,持久实例的话是CP架构。
CP:我们服务可以不能用,但必须要保证数据的一致性。
AP:数据可以短暂不一致,但最终是需要一致的,无论如何都要保证服务的可用。
如何保证cp,ap,下面有讲。

nacos集群才有AP、CP架构说法。即有多个nacos

nacos ap、cp架构体现在哪?(体现在持久化上)
1.(同上)nacos的CP架构主要体现在其对服务的持久化方法上。nacos支持两种服务持久化方式:持久化和非持久化。其中,持久化方式是指服务实例注册后,即使注册中心宕机,服务实例信息仍然可以恢复;非持久化方式是指服务实例注册后,如果注册中心宕机,服务实例信息将会丢失。而nacos的CP架构体现在其支持的持久化方式上,即nacos可以通过改变服务的持久化方法来实现CP架构。具体来说,当服务实例注册时,如果将其设置为临时(ephemeral)类型,则表示该服务实例是非持久化的,即nacos采用AP架构;如果将其设置为持久化(persistent)类型,则表示该服务实例是持久化的,即nacos采用CP架构。因此,nacos的CP架构体现在其对服务的持久化方式上。
2.所以AP架构有euraka,nacos ap,CP架构有zookeeper、nacos cp。
3.集群才会有ap、cp架构
AP架构集群没有主从节点概念,每个节点都是对等的。 CP架构才有主从节点概念。
4.集群环境AP架构下,只有一个节点会开启心跳任务,其只会选取一个节点来做心跳任务。

源码可以看到Service使用Map存储每个集群,每个集群的实例分为了临时实例集和持久实例集,
分别使用Set存储。
Map<String,Cluster> clusterMap=new HashMap();
Cluster{
Set persistentInstance=new HaseSet<>();
Set ephemeralInstance=new HaseSet<>();
}

A.1 nacos注册源码分析(包含服务端和客户端)
主要在nacos-core、nacos-naming、nacos-client包
A.实例同步存放,异步注册,可加快注册速度:
客户端服务向nacos注册时采用的是:服务端先将客户端服务实例先放入阻塞队列,在通过线程不断循环从阻塞队列中获取实例进行真正注册。即同步存放服务实例,异步注册实例。
nacos注册表如何防止多节点读写并发冲突?(非加锁实现)
1.使用Copy On Write,但不是使用CopyOnWriteList结构,而是通过复制一个HashMap副本(复制粒度为Cluster)。先创建副本、然后修改副本、然后更新注册表。
2.读的时候读取原值。写的时候只操作副本。
只有一个线程负责写,所以写的时候不会有并发情况。
3.一个线程进行写,会使性能降低,但可以忽略不记。
4.虽说创建了新的HashMap,但是是直接将toUpdateInstances的实例直接赋值给了HashMap而不是重新创建实例,这样的话岂不是还是直接操作的原集合的实例吗?
(只有在需要修改的时候才创建实例,这样可以减少实例的创建)
其实不会直接对实例的属性进行修改,如果修改会重新new一个实例,然后删除并替换掉原实例。
如果删除,则直接删除实例。
在这里插入图片描述B.服务变动:如果有服务变动,服务端会推送给客户端。通过udp协议发送给客户端,所以可能会丢包。
C.nacos-naming的作用是提供服务注册和发现的功能,相当于nacos服务端。同时也会对nacos集群进行管理。

A.2 nacos心跳机制源码分析(包含服务端和客户端、包含nacos集群场景,针对AP架构/临时实例)
如果是临时实例,就会发送心跳进行检测。每5s发送一次心跳。
客户端向服务端发送心跳是通过嵌套发送心跳的方式来不断向服务端发送心跳的,而服务端检测心跳是通过定时器来检测心跳的。
服务端会判断如果超过15s没收到客户端心跳,则会将客户端实例health设置为false,如果超过30s没收到,则会将服务实例移除。
zookeeper通过长连接tcp协议来发送心跳,而nacos通过http协议来发送心跳,所以nacos没zookeeper耗性能,相对轻量很多。相对来说,zookeeper比nacos更加及时。
在nacos集群中,nacos会为每个注册的服务创建一个Service,每一个Service中都会创建一个ClientBeatCheckTask心跳任务。每个Service在执行心跳检查任务的时候,会进行判断,判断当前是否为执行心跳任务的节点(通过getDistroMapper().responsible(service.getName())这块进行判断),如果是则继续执行心跳检查任务,如果否则跳过。是的话,心跳任务中会拿到nacos集群所有的nacos节点的对应该Service的实例进行心跳检查。即nacos在集群架构下只允许在一台nacos节点上执行心跳检查任务。
服务实例新增判断机制,通过另外一个机制去判断,调用另外其他server节点同步接口同步节点信息。

如果是持久实例呢?
对于持久实例,在 Nacos 中并不需要发送心跳信号来进行健康检测。持久实例在注册到 Nacos 后会一直保留在注册表中,直到主动注销或者因其他原因被移除。持久实例的存在不依赖于定期发送心跳信号,而是通过注册和注销的方式来管理其生命周期。这种机制适用于那些不需要频繁更新状态的长期运行的实例,比如数据库、消息队列等服务。

A.3 nacos集群节点状态同步机制、服务实例节点状态变动同步机制(针对AP架构)
(源码在nacos-naming)
如果心跳节点宕机了怎么办,nacos server会有状态同步机制,即定时向集群其它server发送心跳
除了有判断server节点宕机状态机制(ServerStatusReporter.init()),还会有判断服务实例节点健康状态变更机制(ServerManager.init()),及时同步Service实例的信息。

A.4 nacos集群新增节点的数据同步机制(针对AP架构)
(源码在nacos-naming)
服务实例新增判断机制,通过另外一个机制去判断,调用另外其他server节点同步接口同步节点信息,包括拉取Service实例信息等。
新server节点只会从另外server节点中的一台机器去获取service节点数据(为了确保新节点能够快速获取到集群中的数据,同时也减少了网络通信的开销),此时(AP架构)可能获取的service数据不一定会和所有server节点一致,这就是AP架构的特点,但最终都会一致。即AP架构只能确保数据最终一致性(通过定时任务、心跳等来实现)。

AP、CP
(上面讲的都是AP架构,下面讲CP架构的实现)
A.分布式系统肯定优先保证P,多数时候是在C和A之间做权衡选择
B.CAP:C:一致性A:可用性P:分区容错性
1.P(一定要满足)
一般nacos集群中,各nacos节点是需要同步数据的。如果节点之间由于网络原因无法通信了,我们称之为分区。
容错性表示,即使分区了,nacos集群也能对外提供服务。
2.C(用在持久实例中)
C会涉及到Raft协议,通过raft协议实现。所以A不会涉及到raft协议。

CP架构涉及raft协议(类似zab),讲之前看下zookeeper源码,看下zab选主机制。

为什么要使用Raft协议?
因为持久实例需要将实例信息持久化到文件中,这样的话就需要保证nacos各节点持久化实例信息文件的数据一致性。如何保证?这就用到了Raft协议来保证。
Raft/ZAB协议通过两阶段提交来保证数据一致性。

zab和raft区别
1.zab每个人都需要发起投票,拉票来选自己成为leader。而raft不需要全部,raft协议每个节点发起投票前都需要睡眠随机且不同的时间(一般150ms-300ms),谁先醒来,谁先拉票选举,先到先得,如果同时醒来票数相同会进入第二次选举。
2.zab投票的时候,每个节点都会先投自己一票,而raft协议,只有发起选举的候选者节点会为自己投票,其他节点在收到候选者的请求时,会根据当前情况投票给某个候选者。
总结来说,ZAB 和 Raft 在选举过程中有明显的区别:ZAB 的每个节点都可以成为候选者并为自己投票,Raft 则通过随机化的超时机制来决定何时发起选举,并且只有发起选举的节点会为自己投票。

zab和raft一样
1.所有的修改操作只能先通过leader,再传递给其它从节点
2.写入数据同步的时候都是会进行两阶段提交,第一阶段由主节点将数据提交到本地日志文件,然后广播给从节点,待超过半数节点也将数据提交到本地日志并ack确认后,主节点再将数据提交到内存(第二节点)、然后再由主节点广播给其它从节点,让从节点将数据提交到内存。
3.zab和raft一样,票数超过半数的成为leader。
这里判断待超过半数节点将数据提交到本地并确认,就执行下一步。那么剩余未提交并确认的节点该怎么保证数据最终写入成功?这里我们是通过选举完后,选举完后,主节点会周期性的发起心跳包,给其它节点同步信息来实现的。

选举完后,主节点会周期性的发起心跳包,给其它节点同步信息。

上面讲的是raft协议,下面讲的是nacos自己实现的raft协议。有点不一样。
nacos不是直接使用raft协议来实现分布式一致性,而是实现了自己的一套类似raft的协议

nacos 类raft协议解析-leader选举
(类raft协议表示不完全等于raft协议)
源码在RaftCore类的new MasterElection()中。
发送http接口给其它节点,叫其它节点投票,然后收集他们的投票信息,如果判断他们的投票信息大于半数选自己,则自己主动成为leader。

nacos 类raft协议解析-选举完后主节点的心跳机制
用于向从节点同步服务实例信息
源码在RaftCore类的new HeartBeat()中。

nacos 类raft协议解析-数据一致性
(类raft协议表示不完全等于raft协议)
1.如果不是leader,则将实例信息转发给leader
2.如果是leader,则将实例保存到本地文件(日志文件),然后立即刷新内存(不是写数据库,配置信息才是写数据库),然后一次性同步给其它节点。这点是和raft协议不一样的地方。
a.raft协议是这样做的:先将实例信息保存到本地日志文件后,然后同步给其它节点,然后才刷新内存,然后再同步其它节点,这是和raft不一样的地方,nacos实现了简化。
jraft完整意义上实现了raft协议,到时nacos会替换成这个。
nacos类raft协议是这样的:把实例保存到本地后就立即刷新缓存,然后再一次性同步给其他节点。
b.半数写入通过CountDownLatch实现。
如果某一个节点未同步成功,会通过心跳同步。
3.一次性同步给其它节点,调用其它nacos节点接口
4.如果某一个从节点未成功同步信息,leader会通过心跳同步。(源码在RaftCore类的new HeartBeat()中)
5.接收leader请求的controller
如果不是leader发送的,则抛异常

3.A
使用的是Distro协议,来实现多nacos节点数据同步

C.分布式一致性(即C)、具体一点就是保证各nacos节点数据,比如service数据一致性

D.nacos ap架构使用的是Distro协议,cp架构使用的是类raft协议。service临时实例使用的是AP架构,持久实例使用的是CP架构
在这里插入图片描述
E.nacos AP架构用的比较多,性能高点 CAP不能同时满足

Nacos 2.X
A.与nacos1.4主要区别在:
grpc长连接、注册表结构、事件驱动、服务订阅(服务发现时会根据需求是否进行订阅,基于长连接)
grpc并非nacos2.0核心
grpc底层服务端用到netty

1.nacos1.4客户端调用服务端是通过http,会用到controller。而nacos2.0开始用的是grpc,那就不用到controller。那么是怎么调用的呢?使用netty长连接
grpc源码剖析
略。
grpc基于netty、dubbo也基于netty,所以只了解netty即可。

grpc只有临时实例才使用,持久实例还是用http协议
在这里插入图片描述2.nacos1.4注册表结构为(nacos服务注册逻辑相关):
Map<namespace,Map< group::serviceName,Service>>
nacos2.0注册表结构为:
Map<Service,Set>
1个服务对应多个clientId,表示注册某个服务的所有实例。
存放某个服务的注册者都有哪些人(哪些节点)。
即Service存放服务,Set存放服务实例的客户端id集合。
通过clientId,可以间接找到grpc netty服务端持有的客户端的socketChannel。
通过clientId可以找到serviceinstance。
服务提供者和服务订阅者都有该结构属性。

nacos1.4注册表结构为Map<namespace,Map< group::serviceName,Service>>
nacos2.0注册表结构为Map<Service,Set> 那么nacos2.0是怎么区分集群的?
在Nacos 2.0中,服务(Service)下没有直接包含多个集群,而是每个服务(Service)包含了多个实例,每个实例对应一个客户端ID(clientId),代表了该服务的一个实例。
在Nacos 2.0中,集群(Cluster)的概念仍然存在,但是它并不直接体现在注册表的结构中。Nacos 2.0所有集群的实例都存放在同一个注册表中,由同一个Map去统一管理,统一管理并不会影响每个集群的隔离性。集群的隔离性,依然能够通过给实例指定集群名称名称来实现,nacos2.0提供了方法获取某个集群下的实例。
在Nacos 2.0中,集群信息通常存储在服务的元数据中,而不是直接体现在注册表的结构中。当你注册一个服务时,可以在服务的元数据中指定集群名称。

Nacos 2.0中,区分集群的代码逻辑是哪些,可以通过哪些方式来获取不同集群的实例信息?
在Nacos 2.0中,要区分集群的代码逻辑通常涉及以下几个方面:
1.服务注册时指定集群名称:当你注册一个服务时,可以在服务的元数据中指定集群名称。这通常通过Nacos客户端的API来实现。例如,在Java中,你可以使用Nacos Client SDK,通过registerService方法来注册服务,并在Service对象中设置clusterName属性来指定集群名称。
2.服务发现时根据集群名称过滤实例列表:当你进行服务发现时,可以根据指定的集群名称来过滤实例列表,以获取特定集群的服务实例。这通常通过Nacos客户端的API来实现。例如,在Java中,你可以使用Nacos Client SDK,通过selectInstances方法来选择特定集群的服务实例。

nacos2.0注册表结构总结:
a.服务端会为每个客户端(可以是同一个服务不同的实例,即集群的实例)建一个Client对象,每个Client对象包含了一个<Service,Instance> 的map(代码结构为ConcurrentHashMap<Map,InstancePublishInfo> publishers),属性名为publishers,map存放当前实例的服务名和实例对象(Instance即这个客户端所对应的服务实例),即这个map只包含一条信息?(对于微服务启动注册的时候,map一般只会存储一个Service和一个实例)。服务发现(即获取某个服务的所有节点/实例信息)的时候会在服务注册表这个map中get(“serviceName”)获取这个服务的所有注册的节点id(clientId),拿到clientId后,就通过clientId去获取Client对象,再根据对象的<Service,Instance>map,获取对象的的实例/节点信息,统一返回的消费者/订阅者。
b.2.0服务注册表结构为啥要比1.4搞的那么复杂?而且获取也复杂。
其实获取也并不复杂,看下面,当获取服务的实例之后,会将服务和其所有实例缓存到serviceDataIndexes中,只有新增或删除该服务下节点,下次就会直接从缓存中获取,不用每次都从注册表中获取。
如果把所有信息都存放在Map<namespace,Map< group::serviceName,Service>>中,使用了两层map,数据变更的时候就没那么方便,可能存在数据冲突问题,1.4的解决办法是写时复制,但性能就会有点降低。而2.0拆分之后,最多就一层map,粒度更小,且不会有冲突,因为使用的concurrentHashMap,另外不用写时复制了,性能更高。
c.即1.4注册表更新的时候使用的是写时复制。而2.0简化了Map结构,少了一层map,同时使用了ConcurrentHashMap,无需考虑并发修改冲突。

对于微服务启动注册的时候,map一般只会存储一个Service和一个实例:
在这里插入图片描述

3.事件驱动(nacos服务变动逻辑有关)
(添加事件驱动机制,类似eventbus,使用观察者模式,服务端在收到客户端注册/订阅请求后,会封装成事件进行发送,事件订阅者也为服务端。事件订阅者收到事件后再进行相应处理,并将数据推送同步给客户端)
(事件驱动更复杂,加入了队列进行解耦。而观察者模式算是低耦合,但不是完全解耦)
a.即事件发布者和订阅者都为服务端。
b.ServiceChangeEvent和ServiceSubscribedEvent的区别:
ServiceChangeEvent表示注册服务有变动,会推送节点信息给所有订阅了该服务的订阅者。
而ServiceSubscribedEvent表示某个订阅者需要获取/订阅某个服务节点信息,只会推送节点信息给某个订阅者,不会推送给所有订阅者。
c.当服务端收到订阅/服务变更事件的时候,会封装成一个个PushDelayTask,添加到NacosDelayTaskExecuteEngine引擎中,然后由引擎统一处理。
d.引擎会将数据推送同步给相关客户端,这里会拿到服务对应的订阅者列表Set,然后遍历订阅者列表进行推送。
e.服务端判活逻辑(非客户端发送心跳逻辑,1.4只有客户端心跳逻辑,没有服务端判活逻辑,因为1.4用的是http。nacos两个都有)
服务端会开始定时任务(internal为3s),如果超过20s没有给服务端发送心跳的客户端,服务端会发请求探活,如果失败则剔除服务。
asyncRequest 向客户端发送请求,探活,如果有onResponse返回说明客户端还处于连接状态。
由于2.0使用长连接,asyncRequest是很快的。
在这里插入图片描述f.服务变更集群同步(只讲AP架构,CP架构没讲)(Nacos 2.X AP架构.png)
即一个nacos server节点信息变更后需要同步节点信息给其它的nacos server节点。
AP
AP架构,会涉及到DistroDelayTaskProcessor
通过调用DistroDelayTaskProcessor.handlerClientSyncData()来进行同步
nacos集群中,当客户端服务变更后,其连接的nacos server节点感知到后,会通知其它server节点,其它节点收到信息后,会发送ClientRegisterServiceEvent事件并进行注册表节点信息的变更。其他server节点注册表信息变更执行逻辑同样需要涉及到封装ServiceChangeEvent和ServiceSubscribedEvent事件进行信息处理。
g.Client对象和Client连接Socket是不一样的。nacos集群中都会存储Client对象,但Client的socket对象只会存在一个nacos节点中。这一点要记住。
h.事件驱动模型和事件监听器(观察者模式)区别?
事件驱动模型和观察者模式都有添加订阅者/观察者(监听器)概念
等。区别是事件驱动模型逻辑更加复杂,结构更加复杂,有阻塞队列,发布事件的时候会往队列里放,会进行异步处理,如果队列满了,直接同步交给订阅者处理。事件驱动模型看实现类DefaultPublisher。
事件驱动模型使用了队列算是完全解耦,而事件监听器(如ApplicationEventListener)算是松耦合。

4.服务订阅
(服务发现会涉及到服务订阅)
比如order服务需要调用到商品服务,则订单服务需要调用服务端的方法取获取,同时会传递是否订阅的参数(默认为true),订阅商品服务事件主要用于比如商品服务实例信息变更了,order服务可以及时的被服务端通知到。所以order服务(订阅者)也有个服务注册表,服务订阅表(订阅者)结构也为<Service,Set>,属性名为subscribeIndexes
1个服务对应多个clientId,表示订阅某个服务的所有实例。
和1.4版本一样,2.0也有个UpdateTask用于定时从服务端获取/同步订阅的服务的信息,也通过方法嵌套调用实现定时服务。和1.4区别是定时间隔不同,1.4是每5s拉取一次,而2.0不是。
但是有了订阅机制之后,这个定时任务就不一定是必须的了。

B.nacos2.0持久实例逻辑和nacos1.4一样

C.naocs2.0在ap架构上和1.0一样,用的也是distro,但在cp架构上有不同,1.4用的是raft,而2.0用的是jraft。

总结:
1.注册中心原理:通过NacosDiscoveryAutoConfiguration配置类,NacosDiscoveryAutoConfiguration实现了ApplicationListener监听器,在监听器register回调方法后进行注册。
2.一般用临时实例。
3.Nacos1.x服务注册表结构是一个双重Map:Map<namespace, Map<group::serviceName, Service>>
然后每个Service右包含了多个集群,每个集群又包含了多个实例。
Cluster中的实例,根据是临时实例,还是持久实例,分别存放在不同的Set中。
nacos2.0注册表结构为:Map<Service,Set>
4.nacos1.x客户端调用http请求将实例信息传给注册中心(服务端),服务端会将请求信息先放到队列中,然后会有个线程(GlobalExecutor)遍历该队列请求进行处理。即异步注册,这样很多客户端注册时,客户端不会阻塞。
nacos2.x客户端临时实例通过grpc协议和服务端通信、持久实例依然通过http协议和服务端通信。
5.namespace可以用于区分dev、prod、个人环境
Group用于区分服务组
6.如果某个实例,超过15秒没有收到实例得心跳,则将它的health设置为false,如果超过30s没收到,设将起实例删除。
7.注册中心实例数据通过http定时拉取+服务变动时服务端udp推送实现数据同步。

nacos源码分析(配置中心)
讲nacos 2.1配置中心,大部分知识nacos1.4同样适用

配置中心的服务流程如下:

  1. 用户在配置中心更新配置信息。
  2. 服务A和服务B及时得到配置更新通知,从配置中心获取配置。
    配置中心就是一种统一管理各种应用配置的基础服务组件。

nacos存储数据的形式是key/value

nacos2.0没有集成dubbo作为rpc。
配置中心传输配置信息的时候2.0采用的grpc长连接传输,1.4采用http协议传输。

dataId 的完整格式
在 Nacos Spring Cloud 中,dataId 的完整格式如下:
prefix−{prefix}-prefix{spring.profiles.active}.${file-extension}
1.prefix 默认为 spring.application.name 的值,也可以通过配置项spring.cloud.nacos.config.prefix来配置。
2.spring.profiles.active 即为当前环境对应的 profile,当 spring.profiles.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}. ${file-extension}
3.file-exetension 为配置内容的数据格式,可以通过配置项spring.cloud.nacos.config.file-extension 来配置。
如bdscs.properties、bdscs-dev.properties

Nacos数据模型
Key 由三元组(Namespace、Group、Service/DataId)唯一确定
1.支持配置的动态更新
2.支持profile粒度的配置
在加载配置的时候,不仅会加载bdscs.properties的配置,还会加载bdscs-dev.properties的配置。即不仅仅加载了以 dataid为 $ {spring.application.name}.$ {file-extension:properties} 为前缀的基础配置,还加载了dataid为$ {spring.application.name}-$ {profile}.$ {file-extension:properties} 的基础配置。但是非active如bdscs-pro.properties不会被加载。
3.支持自定义 namespace 的配置
Namespace 的常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。
默认使用的是Nacos 上 Public 这个namespace。如果需要使用自定义的命名空间,可以通过以下配置来实现:
spring.cloud.nacos.config.namespace=71bb9785‐231f‐4eca‐b4dc‐6be446e12ff8
4.支持自定义 Group 的配置
Group表示配置属于哪个组织。通过一个有意义的字符串(如 Buy 或 Trade )对配置集进行分组。
在没有明确指定${spring.cloud.nacos.config.group} 配置的情况下,默认是DEFAULT_GROUP 。如果需要自定义自己的 Group,可以通过以下配置来实现:
spring.cloud.nacos.config.group=DEVELOP_GROUP
5.支持自定义扩展的 Data Id 配置

nacos配置热更新
Nacos本身并不会热更新,需要进行配置。
即修改了nacos配置属性后,客户端在不重启的情况下不会立即生效。需要进行配置,才会支持热更新。
通过 Spring Cloud 的 @RefreshScope 注解,可以使得配置属性在下一次被使用时自动刷新

源码
A.客户端
a.获取配置
获取配置的主要方法是 NacosConfigService 类的 getConfig 方法,通常情况下该方法直接从本地文件(快照,非本地配置文件)中取得配置的值,如果本地文件不存在或者内容为空,则再通过grpc从远端拉取配置,并保存到本地快照中。
NacosConfigService类getConig如何被调用?
利用springboot扩展点SPI(ApplicationContextInitializer),开始获取配置为什么要在ApplicationContextInitializer实现了加载获取配置,是因为我们需要在bean创建的时候能获取到配置,而ApplicationContextInitializer实现类是在bean创建前调用的,所以需要在ApplicationContextInitializer实现类中调用。
b.注册监听器
配置中心客户端会通过对配置项注册监听器达到在配置项变更的时候执行回调的功能。
c.CacheData
CacheData 是一个维护配置项以及配置项值的MD5值和其下注册的所有监听器的实例,所有的 CacheData 都保存在 ClientWorker 类中的原子 cacheMap 中。
d.客户端如何感知到配置中心配置文件更新了?
ClientWorker会定时发起rpc调用,不断从nacos服务端获取配置信息md5值来判断配置项是否更新。
同时下面也会讲到,服务端在收到配置发生变更的时候也会通知客户端。
ClientWorker会开启定时任务(10ms执行一次),发起rpc调用,不断获取配置信息的md5值,如果有信息变动,客户端会根据变化的dataId调用nacos config服务端获取配置信息,并更新本地快照。同时回调配置监听器,通知应用配置项变更了。
e.配置变更之后会刷新容器,将旧的配置信息给删除掉,重新加载配置信息。然后调用
this.scope.refreshAll将@RefreshScope的bean从缓存中清掉。
也会重新创建Spring Context容器。所以我们会重新看到Started application in…

B.服务端
客户端通过NacosConfigService.getConfig->getConfigInner->worker.getServerConfig开始调用服务端。worker为ClientWorker。

相应服务端找ConfigQueryRequestHandler类。
只有在nacos启动的时候才会去数据库取数据,其它时候从文件缓存取数据。相关的源码为dump。

DumpService
(服务端在启动或配置发布的时候会调用dump方法加载配置数据到本地缓存文件中,加载的时候会根据情况来决定是全量还是增量加载。全量加载时会进行分页加载)
a.配置dump
1)dump是啥意思?就是服务端启动的时候加载配置信息到本地缓存文件时是全量还是增量加载。(只有在启动的时候才会调用init方法,进而调用dump方法。但不是只有这里会调用dump方法,配置发布的时候也会调用dump方法同步数据库数据到
本地缓存文件,下面配置发布会讲到)
2)服务端启动时就会依赖 DumpService 的 init 方法,从数据库中 load 配置存储在本地磁盘上,并将一些重要的元信息例如 MD5 值缓存在内存中。服务端会根据心跳文件中保存的最后一次心跳时间,来判断到底是从数据库 dump 全量配置数据还是部分增量配置数据(如果机器上次心跳间隔是 6h 以内的话,则全量更新,将本地的文件清掉,重新从数据库中读取数据)。
3)全量获取配置信息时,由于可能数据量太大,内存会撑爆,采用分页下载,这个和我们获取基础数据接口重构的方法差不多,只是这里没有使用多线程。
b.配置发布
发布配置的代码位于 ConfigController#publishConfig中。
1)集群部署,请求一开始也只会打到一台机器,这台机器将配置插入Mysql中进行持久化。服务端并不是针对每次配置查询都去访问 MySQL ,而是会依赖 dump 功能在本地文件中将配置缓存起来。因此当单台机器保存完毕配置之后,需要通知其他机器刷新内存和本地磁盘中的文件内容,因此它会发布一个名为 ConfigDataChangeEvent 的事件,这个事件会通过grpc调用通知所有集群节点(包括自身),触发本地文件和内存的刷新。
2)配置发布流程
保存好配置数据后,接下来主要流程是
1.告诉客户端配置发生变更(告诉前会调用上面说的dump方法,往本地缓存文件同步最新的配置数据,看流程图)。
2.如果是集群,通知集群其它节点配置发生变更。

C.推模式与拉模式
a.nacos客户端对象收到LocalDataChangeEvent事件后,会通过长连接通知真正的naocs客户端节
点说配置信息已经变更(这就是推式/push),然后客户端再取请求服务端获取配置数据(这就是拉模式)。
b.推模式(服务端向客户端推)
c.拉模式(客户端收到服务端配置信息变更通知后,客户端会将拉取服务端配置信息的任务放到阻塞队列中,即listenExecutebell.offer(bellItem),执行器会不断从listenExecutebell获取任务执行,这就是拉模式)

D.综上所知:客户端会通过ClientWorker定时从服务端获取配置md5来判断感知是否有配置项更新。服务端在其客户端对象收到LocalDataChangeEvent事件后,会通过长连接通知真正的naocs客户端节点说配置信息已经变更。

nacos2.1插件化设计:比如用于mysql的配置信息的加解密处理,防止非开发人员看到。基于SPI机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值