nacos学习笔记
OpenFeign
1.使用规则
- 引入依赖xml
<!-- openfeign 远程调用 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--feign-jackson -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
</dependency>
<!-- nacos服务注册与发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
- 开启注解
@EnableFeignClients
@SpringBootApplication
@EnableFeignClients //扫描和注册feign客户端bean定义
public class MallUserFeignDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MallUserFeignDemoApplication.class, args);
}
}
- 使用@FeignClent,这里注意的是,参数前一定要加注解,否则默认是@RequestBody,多个参数的话一定要加
- openFeign请求接受是String类型,所以用String接受也是可以的。
//FeignConfig局部配置,让指定的微服务生效,在@FeignClient 注解中指定configuration
//@FeignClient(value = "mall-order",path = "/order",configuration = FeignConfig.class)
@FeignClient(value = "mall-order",path = "/order")
public interface OrderFeignService {
/**
* 1. openfeign传递restful参数
* 方法要求:
* 返回值: 要对应
* 方法名:随意
* 参数: 要对应的注解
* 方法上添加springmvc
* @param userId
* @return
*/
@RequestMapping("/findOrderByUserId/{userId}")
public R findOrderByUserId(@PathVariable("userId") Integer userId);
@RequestMapping("/findOrderByUserId/{userId}")
public String findOrderByUserId2(@PathVariable("userId") Integer userId);
}
- 使用
@RestController
@RequestMapping("/user")
public class UserController {
//注入openFeign
@Autowired
OrderFeignService orderFeignService;
@RequestMapping(value = "/findOrderByUserId/{id}")
public R findOrderByUserId(@PathVariable("id") Integer id) {
//openFeign调用 远程调用
R result = orderFeignService.findOrderByUserId(id);
return result;
}
问题
- @EnableFeignClients注解的作用?
- @FeignClient注解的作用?
- 为什么OrderFeignService 可以当做Bean来使用,并且能够调用发送请求,调用其他的服务节点?实现逻辑是什么?
- 为什么实现一个远程调用,需要引入四个pom依赖,这四个依赖是干什么的?
思考:
- 如果你理解了OpenFign源码,是否可以写一个简易的框架?使用mybatis引入使用一下?
2.测试openFign,启动springBoot装配了那些类
springBoot加载了那些路径的Bean
file:/E:/MyPro/spring-boot-2.6.6/spring-boot-project/spring-boot-autoconfigure/out/production/resources/META-INF/spring.factories
file:/E:/MyPro/spring-boot-2.6.6/spring-boot-project/spring-boot/out/production/resources/META-INF/spring.factories
jar:file:/F:/develop/gradle/.gradle/caches/modules-2/files-2.1/org.springframework/spring-beans/5.3.18/3f0ea6598a5a1eae0a672f025a33a0b7e0d6dfd3/spring-beans-5.3.18.jar!/META-INF/spring.factories
nacos应用
1.下载和启动
下载nacos源码 1.4.1
https://github.com/alibaba/nacos/tree/1.4.1
- 怎么找到服务的启动类?
- 通过启动路径,找到启动文件
2. 打开文件,找到启动命令,
4.所以启动路径就是 java -jar xxx/target/naocs-server.jar
那就找到这个路径
5.解压jar包,查看主启动类是什么?主启动类,存在MANIFEST.MF中
6.可以查看主启动类的路径是com.alibaba.nacos.Nacos
,那就可以找到这个类对他进行启动了。
7.也可以在idea中全局搜索nacos-server,
发现主程序类
- 启动Nacos
设置JVM参数单机启动:-Dnacos.standalone=true
2.客户端配置分析
启动一个springBoot项目,添加pom文件
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
yml 配置nacos
**server:
port: 8040
spring:
application:
name: springcloud-nacos #微服务名称
cloud:
nacos:
discovery: #配置nacos注册中心地址
# server-addr: nacos.mall.com:8848
server-addr: localhost:8848
#server-addr: 192.168.65.174:8848,192.168.65.192:8848,192.168.65.204:8848
# namespace: eb7c9ad7-6429-4f79-aa90-e1387586316b
# group: tulingshop
cluster-name: SH
# ephemeral: false
username: nacos
password: nacos
**
启动项目,就能将客户端注册到nacos中。
3.使用restTemplate请求nacos注册节点的服务
- 使用
@LoadBalanced
,才能使用RestTemplate获取到nacos各个服务节点,并且负载均衡请求,但这是为什么尼?
@Configuration
public class RestConfig {
@Bean
@LoadBalanced //必须加@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
controller层
@RequestMapping(value = "/findOrderByUserId/{id}")
public R findOrderByUserId(@PathVariable("id") Integer id) {
log.info("根据userId:"+id+"查询订单信息");
// 方式1:restTemplate调用,url写死
//利用@LoadBalanced,restTemplate需要添加@LoadBalanced注解
String url = "http://mall-order/order/findOrderByUserId/"+id;
System.out.println("url====: " + url);
R result = restTemplate.getForObject(url,R.class);
return result;
}
4. 优先集群请求
- 配置类添加
@LoadBalancerClient
注解,指定需要使用优先请求当前集群的节点服务,指定配置规则
@SpringBootApplication
@LoadBalancerClient(value = "mall-order", configuration = CustomLoadBalancerConfiguration.class)
public class MallUserApplication {
public static void main(String[] args) {
SpringApplication.run(MallUserApplication.class, args);
}
}
配置项目yml
cloud:
nacos:
discovery: #配置nacos注册中心地址
server-addr: localhost:8848
namespace: eb7c9ad7-6429-4f79-aa90-e1387586316b1
cluster-name: SH # 集群编号 SH
username: nacos
password: nacos
- 配置类
public class CustomLoadBalancerConfiguration {
@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory, NacosDiscoveryProperties nacosDiscoveryProperties){
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new NacosLoadBalancer(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class),name,nacosDiscoveryProperties);
}
}
集群核心代码
package com.alibaba.cloud.nacos.loadbalancer;class NacosLoadBalancer{}
//获取请求实例
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> serviceInstances) {
if (serviceInstances.isEmpty()) {
log.warn("No servers available for service: " + this.serviceId);
return new EmptyResponse();
} else {
try {
//获取当前服务节点的clusterName,为SH
String clusterName = this.nacosDiscoveryProperties.getClusterName();
//获取所有的服务节点
List<ServiceInstance> instancesToChoose = serviceInstances;
if (StringUtils.isNotBlank(clusterName)) {
//过滤节点中是SH的服务节点
List<ServiceInstance> sameClusterInstances = (List)serviceInstances.stream().filter((serviceInstance) -> {
String cluster = (String)serviceInstance.getMetadata().get("nacos.cluster");
return StringUtils.equals(cluster, clusterName);
}).collect(Collectors.toList());
//如果存在clusterName为SH的节点,则使用,如果没有则使用所有节点
if (!CollectionUtils.isEmpty(sameClusterInstances)) {
instancesToChoose = sameClusterInstances;
}
} else {
log.warn("A cross-cluster call occurs,name = {}, clusterName = {}, instance = {}", new Object[]{this.serviceId, clusterName, serviceInstances});
}
instancesToChoose = this.filterInstanceByIpType(instancesToChoose);
ServiceInstance instance = NacosBalancer.getHostByRandomWeight3(instancesToChoose);
return new DefaultResponse(instance);
} catch (Exception var5) {
log.warn("NacosLoadBalancer error", var5);
return null;
}
}
}
规则:
- 拿到所有服务节点,和当前节点的cluster-name进行匹配
- 匹配到优先使用集群为SH的节点,否则使用全部节点进行轮询
问题:
- @LoadBalancerClient在SpringIoc的生命周期是什么时候?
- 在请求和什么时候触发进入配置类逻辑?
- LoadBalancerClientFactory 、NacosDiscoveryProperties 是如何成为Bean的?
nacos1.4.1源码分析
- springBoot中,ApplicationListener接口的使用!!
引入nacos依赖,入口spring.factories中,配置了org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.alibaba.cloud.nacos.NacosDiscoveryAutoConfiguration
,其中注册一个NacosAutoServiceRegistration类型的Bean
,这个Bean实现了ApplicationListener
接口,会触发的onApplicationEvent()方法,那那它在springBoot生命周期怎么执行的呢?以ApplicationListener看一下源码。
ApplicationListener使用
问题:
- 实现ApplicationListener接口,在什么时期被调用?
- spring.factories中定义ApplicationListener时期
//1.从spring.factories中拿到SpringApplicationRunListener对象,其中springBoot默认的是EventPublishingRunListener 事件监听发布类
SpringApplicationRunListeners listeners = getRunListeners(args);
//2.EventPublishingRunListener 初始化的时候,会从SrpingApplication获取ApplicationListener类型的对象,添加到initialMulticaster属性中,其中这个
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
// listeners 这个listeners会执行所有spring.factories中ApplicationListener类型的对象,并执行onApplicationEvent()
public void starting(ConfigurableBootstrapContext bootstrapContext) {
this.initialMulticaster
.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}
https://www.processon.com/diagraming/66969ebd95a7657d4670f33a
2. 配置类中定义实现ApplicationListener接口的类
public class ZhqAutoSpringFactories {
@Configuration
public class AutoConfig {
@Bean
public ZhqApplicationListenerBean nacosAutoServiceRegistrationZhq() {
return new ZhqApplicationListenerBean();
}
}
在spring生命周期最后执行
- spring.factories中EnableAutoConfiguration配置实现ApplicationListener接口的Bean
public class ZhqAutoSpringFactories {
@Bean
public ZhqApplicationListenerBean nacosAutoServiceRegistrationZhq() {
return new ZhqApplicationListenerBean();
}
}
和第二步执行流程一样,都是在生命周期完成之后执行。
Spring.factories中AutoConfiguration的使用
客户端源码入口
nacos注册
这里会发送一个url为/nacos/v1/ns/instance
http请求,到nacos服务端
服务端源码入口(健康检查)
1.服务注册的时候会开启一个延时定时任务,进行服务健康检查
初始等待5s,每5s执行一次
执行定时任务的类为ClientBeatCheckTask
中的run方法。
- 系统时间大于实例注册最后一次更新时间15秒,设置成健康下线
- 如果大于30s,则剔除该服务
问题:
- 实例注册的更新时间是怎么同步的呢?
后面的逻辑就将客户端实例instace放到一个阻塞队列中,然后开启一个定时任务,将所有的实例进行注册
问题:
- nacos是怎么解决读写并发问题的?
客户端拉去nacos服务端的信息入口
获取服务端的地址信息之后,缓存到本地,然后使用Rabbion进行轮询调用其他服务的
- 第一次从本地Map中获取服务信息,如果没有则请求nacos获取服务信息
- 下一次请求服务的时候,就从本地Map中获取服务信息,使用Rabbion对相应的服务进行调用、
问题
- 本地Map维护nacos中的服务信息,他是怎么保持更新的呢?
解:本地维护一个定时任务,每1s执行一次,去服务端拉取服务列表,缓存并更新到本地。循环执行这个定时任务,定时任务入口在客户端拉去nacos服务端的信息入口,上述代码中- Ribbion是怎么整合nacos,获取本地拉取的服务,去调取他的服务的?
客户端发送心跳检测
客户端发送/v1/ns/instance/beat
到服务点,进行心跳检测
服务端主动推送
服务端感知到客户端注册成功之后,会发布一个事件,主动的推送注册列表到客户端去。更新客户端的本地注册表信息
udp推送客户端,让客户端更新本地的服务列表
naocs1.4.1集群架构源码分析
1.源码集群启动
- nacos集群需要配置mysql存储,需要先创建一个数据,名字随便取,然后执行
distribution/conf
目录下的 nacos-mysql.sql 脚本,然后修改console\src\main\resources
目录下的 application.properties 文件里的mysql配置
- 配置vm参数,分别
-Dserver.port=8847 -Dnacos.home=E:\project\Study\nacos\nacos-cluster\nacos-8847
-Dserver.port=8848 -Dnacos.home=E:\project\Study\nacos\nacos-cluster\nacos-8848
-Dserver.port=8849 -Dnacos.home=E:\project\Study\nacos\nacos-cluster\nacos-8849
-
分别创建执行的目录
每个指定的文件创建四个文件夹,只有conf
文件夹有数据,其他为空
-
conf
文件夹添加,并指定其他节点的ip和端口地址
2.心跳在集群架构下的设计原理剖析
nacos心跳是,各个客户端实例注册到nacos服务端开启的一个心跳定时任务。每间一段时间检测服务是否下线,但是呢如果是集群的话,他的心跳机制是怎么做的呢?
这里就是用hash算法,对所有的服务取模,只用一个服务进行心跳检测,意思就是说,只允许一台机器上做健康检查任务。那如果说一台机器挂了呢,这就要看这个集群节点状态是怎么同步的问题了~!!!
3.nacos集群节点状态同步
ServerListManager
这个类主要做集群节点状态同步问题的.就是说每隔一段时间,就会向其他服务发送一个/operator/server/status
http请求,让其他的服务节点感知到自己还活着。
对集群所有服务节点发送一个/operator/server/status
http请求
4.naocs集群服务新增数据同步
如果一个实例注册到nacos中,它是怎么做到其他的nacos集群节点是怎么同步的呢?
DistroConsistencyServiceImpl
distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,
globalConfig.getTaskDispatchPeriod() / 2);
public void sync(DistroKey distroKey, DataOperation action, long delay) {
//获取除开自己的其他服务节点,进行同步
for (Member each : memberManager.allMembersWithoutSelf()) {
DistroKey distroKeyWithTarget = new DistroKey(distroKey.getResourceKey(), distroKey.getResourceType(),
each.getAddress());
DistroDelayTask distroDelayTask = new DistroDelayTask(distroKeyWithTarget, action, delay);
distroTaskEngineHolder.getDelayTaskExecuteEngine().addTask(distroKeyWithTarget, distroDelayTask);
if (Loggers.DISTRO.isDebugEnabled()) {
Loggers.DISTRO.debug("[DISTRO-SCHEDULE] {} to {}", distroKey, each.getAddress());
}
}
}
5.naocs集群服务状态变动同步
如果一个客户端服务节点挂掉了,他们的状态的是怎么同步的呢,一个服务节点挂掉了,naocs中被hash取模的那个服务节点就会感知到,然后会发送一个请求,去同步其他的nacos服务节点,进行同步这个客户端实例挂掉的信息
ServiceManager
中init()方法中,
//开启一个定时任务,去发送其他的服务节点,同
GlobalExecutor.scheduleServiceReporter(new ServiceReporter(), 60000, TimeUnit.MILLISECONDS);
向其他服务节点发送一个/service/status
http请求,去同步其他的服务节点