Nacos
Nacos 在微服务中实现动态服务发现、配置管理和服务管理平台,微服务中也搭配使用一些其他组件,比如:
- LoadBalancer ==> 实现负载均衡
- openfeign ==> 实现微服务的互相调用
- Sentinel ==> 实现服务熔断与限流
- Seata ==> 实现分布式事务回滚
微服务是指完成一个或一组可以独立出来的功能,使得可以给多个客户端进行重用,Nacos在微服务组件可以做注册中心和配置中心,一般在Nacos中存储一些配置信息和一些数据信息,
注册中心和配置中心的核心原理,信息的同步主要的几种方式:
- push (服务端主动push)
- pull (客户端的轮询), 超时时间比较短
- long pull 长轮询(超时时间比较长)
CAP
C(一致性),A(可用性),P(分区容错)
AP:满足可用性和分区容错
当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。
数据可以短暂不一致,但最终是需要一致的,无论如何都要保证服务的可用。
AP模式为临时实例,默认情况下,启动服务后,每隔5秒会向nacos发送一个"心跳包",这个心跳包中包含了当前服务的基本信息,Nacos收到这个“心跳包”如果发现这个服务的信息不在注册列表中,就进行注册,如果这个服务的信息在注册列表中就表明这个服务还是健康的。
客户端通过心跳上报的方式告知nacos 注册中心健康状态(默认心跳间隔5s,nacos将超过超过15s未收到心跳的实例设置为不健康,超过30s将实例删除)
这种特性适合于需要应对流量突增的场景,服务可以弹性扩容,当流量过去后,服务停掉即可自动注销。
CP:满足一致性和分区容错
当网络分区出现后,为了保证一致性,就必须拒绝请求,否则无法保证一致性。
我们服务可以不能用,但必须要保证数据的一致性。
CP模式为永久实例,启动时向nacos注册,nacos会对这个实例进行持久化处理,nacos主动检查客户端的健康状态(默认时间间隔20s,健康检查失败后会设置为不健康,不会立即删除),优点就是可以实时的监控到实例的健康状态,便于后续的告警和扩容等一系列处理。只有项目的主干业务才会设置为永久实例。
CP和AP模式之间的相互转换
curl -X PUT "$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP"
curl -X PUT "$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=AP"
成功示例:
需配置如下选项指明注册为临时/永久实例
AP模式不支持数据一致性,所以只支持服务注册的临时实例,CP模式支持服务注册的永久实例,满足配置文件的一致性
spring:
cloud:
nacos:
discovery:
# 示例类型(true为临时实例,false为永久实例 ==> CP模式)
ephemeral: true
tip:
- 这个不能随便切,建议保持默认的AP即可。
- 集群环境下所有的服务都要切换
- 可以使用postman模拟,必须使用put请求。用get和post均无效
Nacos做配置中心
配置中心原理解析
Nacos配置中心采用:客户端长轮询的方式
-
Nacos 客户端会循环请求服务端变更的数据,并且超时时间设置为30s,当配置发生变化时,请求的响应会立即返回,否则会一直等到 29.5s+ 之后再返回响应
-
客户端的请求到达服务端后,服务端将该请求加入到一个叫 allSubs 的队列中,等待配置发生变更时 DataChangeTask主动去触发,并将变更后的数据写入响应对象。
-
与此同时服务端也将该请求封装成一个调度任务去执行,等待调度的期间就是等DataChangeTask 主动触发的,如果延迟时间到了 DataChangeTask 还未触发的话,则调度任务开始执行数据变更的检查,然后将检查的结果写入响应对象(基于文件的MD5)
示例
- 依赖:
<!-- nacos配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- cloud新版本默认将bootstrap移除了,所以需要添加如下依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
-
Nacos中创建一个新的命名空间并在该命名空间新建一个配置,以key/value进行存储数据
-
在Bootstrap.yml配置文件中进行配置
spring: application: name: nacos_config_test cloud: nacos: config: # 配置中心地址 server-addr: ${ NACOS_HOST:moonquakes.club}:${ NACOS_PORT:18848} namespace: ${ NACOS_NAMESPACE:d1733b6f-fb2a-4b55-ba16-742ee239be55} group: ${ NACOS_GROUP:group1} # 默认properties file-extension: yml # Nacos共享配置文件 shared-configs[0]: data-id: application.yml group: ${ NACOS_GROUP:group1} refresh: true shared-configs[1]: data-id: redis.yml group: ${ NACOS_GROUP:group1} refresh: true shared-configs[2]: data-id: rabbitmq.yml group: ${ NACOS_GROUP:group1} refresh: true
-
${NACOS_USERNAME:XXX} 这种写法是如果换配置的时候直接在启动的时候直接加参数就可以统一换配置,冒号后面为默认值
-
shared-configs为项目的共享配置,会配置一些统一的一些信息,来达到Nacos配置灵活改变项目统一配置
注:
-
bootstrap.yml文件启动顺序比application.yml配置文件启动顺序高,但是新版本需要添加注解,否则bootstrap.yml的配置不生效,
-
使用@RefreshScope注解可以实现动态刷新:就是nacos中读取配置的时候,修改了配置不用重启项目也能读取到最新的数据
-
配置文件的spring.application.name=XXX,会自动读Nacos中的XXX.yml(默认properties)配置文件
在 Nacos Spring Cloud 中,
dataId
的完整格式如下:${ prefix}-${ spring.profiles.active}.${ file-extension}
-
prefix
默认为spring.application.name
的值,也可以通过配置项spring.cloud.nacos.config.prefix
来配置。 -
spring.profiles.active
即为当前环境对应的 profile。 注意:当spring.profiles.active
为空时,对应的连接符-
也将不存在,dataId 的拼接格式变成${prefix}.${file-extension}
-
file-exetension
为配置内容的数据格式,可以通过配置项spring.cloud.nacos.config.file-extension
来配置。目前只支持properties
和yaml
类型。发现目前<spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>版本的,当你配置了active何extension时,会读取三个文件:1. p r e f i x 、 2. {prefix}、2. prefix、2.{prefix}. f i l e − e x t e n s i o n 、 3. {file-extension}、3. file−extension、3.{prefix}- s p r i n g . p r o f i l e s . a c t i v e . {spring.profiles.active}. spring.profiles.active.{file-extension}
-
Nacos做服务中心
注册中心原理
nacos注册中心采用了 :pull (客户端的轮询)和push (服务端主动push)
-
客户端启动时会将当前服务的信息包含ip、端口号、服务名、集群名等信息封装为一个Instance对象,然后创建一个定时任务,每隔一段时间向Nacos服务器发送PUT请求并携带相关信息。
-
nacos服务器端在接收到心跳请求后,会去检查当前服务列表中有没有该实例,如果没有的话将当前服务实例重新注册,注册完成后立即开启一个异步任务,更新客户端实例的最后心跳时间,如果当前实例是非健康状态则将其改为健康状态。
-
心跳定时任务创建完成后,通过POST请求将当前服务实例信息注册进nacos服务器。
-
nacos服务器端在接收到注册实例请求后,会将请求携带的数据封装为一个Instance对象,然后为这个服务实例创建一个服务Service,一个Service下可能有多个服务实例,服务在Nacos保存到一个ConcurrentHashMap中Map(namespace,Map(group::serviceName, Service)); 。
-
nacos将实例添加到对应服务列表中会根据AP和CP不同的模式,采用不同协议。
- CP模式就是基于Raft协议(通过leader节点将实例数据更新到内存和磁盘文件中,并且通过CountDownLatch实现了一个简单的raft写入数据的逻辑,必须集群半数以上节点写入成功才会给客户端返回成功)
- AP模式基于Distro协议(向任务阻塞队列添加一个本地服务实例改变任务,去更新本地服务列表,然后在遍历集群中所有节点,分别创建数据同步任务放进阻塞队列异步进行集群数据同步,不保证集群节点数据同步完成即可返回)
- nacos在将服务实例更新到服务注册表中时,为了防止并发读写冲突,采用的是写时复制的思想,将原注册表数据拷贝一份,添加完成之后再替换回真正的注册表。
-
nacos在更新完成之后,通过发布服务变化事件,将服务变动通知给客户端,采用的是UDP通信,客户端接收到UDP消息后会返回一个ACK信号,如果一定时间内服务端没有收到ACK信号,还会尝试重发,当超出重发时间后就不在重发。
-
客户端通过定时任务定时从服务端拉取服务数据保存在本地缓存。
服务端在发生心跳检测、服务列表变更或者健康状态改变时会触发推送事件,在推送事件中会基于UDP通信将服务列表推送到客户端,虽然通过UDP通信不能保证消息的可靠抵达,但是由于Nacos客户端会开启定时任务,每隔一段时间更新客户端缓存的服务列表,通过定时轮询更新服务列表做兜底,所以不用担心数据不会更新的情况,这样既保证了实时性,又保证了数据更新的可靠性。
提供方
- 添加服务发现依赖:
<!-- nacos服务注册与发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
-
在bootstrap.yml配置类中
spring: application: name: nacos-stock profiles: # 环境配置 active: ${ SPRING_PROFILES_ACTIVE:dev} cloud: nacos: discovery: # 服务注册地址 server-addr: ${ NACOS_HOST:moonquakes.club}:${ NACOS_PORT:18848} namespace: ${ NACOS_NAMESPACE:d1733b6f-fb2a-4b55-ba16-742ee239be55} group: ${ NACOS_GROUP:group1} server: port: 8080
-
在主启动类上添加@EnableDiscoveryClient注解开启服务注册发现功能,注册服务名为:${spring.application.name}
服务调用(消费方)
- openfeign、loadbalancer依赖:
<!-- nacos服务注册与发现 移除ribbon支持 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 微服务调用openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<!-- 不使用Ribbon 进行客户端负载均衡 -->
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 微服务:负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
调用者需要配置:client.XXXClient、主配置类添加注解:@EnableFeignClients
@FeignClient(value = XXXConstant.SERVICE_NAME) // 被调用微服务的注册服务名
public interface XXXClient {
@GetMapping("/getAll")
public String test(@RequestParam("info") String info); // 变量需要加注解@RequestParam
}
LoadBalancer负载均衡
Spring Cloud LoadBalancer是Spring Cloud官方自己提供的客户端负载均衡器, 用来替代Ribbon。项目使用RestTemplate整合LoadBalance:只需要导入依赖并配合openfeign直接使用轮询进行微服务的调用。
<!-- 提供了RestTemplate支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- nacos服务注册与发现 移除ribbon支持 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- LoadBalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
Loadbalancer默认实现了如下两种负载均衡策略:
- RandomLoadBalancer - 随机分配策略
- (默认) RoundRobinLoadBalancer - 轮询分配策略
- 轮询算法(默认):启动多个服务并配置程序启动参数,默认为轮询算法,依次调用启动的服务,且顺序不变
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N1iL4yZ3-1675824953984)(C:/Users/Administrator/Desktop/imgs/image-20230207162257214.png)]
- 随机轮询算法:需要配置 Loadbalancer实现的RandomLoadBalancer类并在主启动类上添加对应注解
MyLoadBalancerConfig配置类:
@Configuration
public class MyLoadBalancerConfig {
@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
// 随机轮询
return new RandomLoadBalancer(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}
主启动类上添加注解:
@LoadBalancerClients(defaultConfiguration = {
MyLoadBalancerConfig.class})
- 加权轮询:直接在nacos中配置分布式服务的权重,nacos会根据各个服务权重的大小,从大到小依次调用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wVxYLGhU-1675824953985)(C:/Users/Administrator/Desktop/imgs/image-20230207171719284.png)]
- 加权随机:在加权的基础上再配置Loadbalancer实现的随机分配策略类并在主启动类上添加对应注解
openfeign微服务远程调用
- 在使用 Feign 时提供负载均衡的 http 客户端
<!-- 微服务调用openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<!-- 不使用Ribbon 进行客户端负载均衡 -->
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
- 主启动类添加@EnableDiscoveryClient服务发现注解、@EnableFeignClients开启feign调用注解
- 创建一个调用微服务的类并加上@FeignClient注解:
- value值为需要调用微服务的名称
- path值为该微服务的统一前置路径
- contextId以免有不同调用者,不能重名
- fallbackFactory是容错处理,可以自定义每个接口的错误信息