Spring Cloud Alibaba--Nacos学习笔记

写在前面,学习Spring Cloud Alibaba的组件 一定要确定好组件的版本对应情况,关于如何选择正确的组件的版本 在官方指定的wiki上已经给出了确切的说明 https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
  说明 这是一篇会长期更新的学习笔记 会根据博主个人不断学习,关于原理、使用、以及在实际应用中遇到的问题,以及这些问题的解决方法, 都会好好记录的一篇博客,所以一开始会显得内容有些苍白,后面会一点一点充实起来😊。
        嗯,如果有很想快速解决,但是搜索了很多帖子也没有找到解决方式的问题,可以在Naocs的社区提一个issue,会有工程师帮你看你的问题,也可以参考下别的童鞋类似问题来解决 ,一起交流解决问题。目前这是博主在使用nacos过程中解决问题最快的方式 。参考地址:https://github.com/alibaba/nacos/issues/

一. 关于Spring Cloud Nacos-Server的安装

关于Spring Cloud Alibaba Nacos-Server的安装,官网上的安装方式参考 https://nacos.io/zh-cn/docs/quick-start.html 
根据官网的推荐,以及博主后续还会选择使用Spring Boot 6.x 以上的的东西来学习Spring Cloud Alibaba的其他组件,所以博主安装的版本是 nacos-server:v2.0.4 附上博主安装过程的参考。
    由于安装Nacos需要一些基础环境,比如JDK和Maven的安装,所以博主直接选择了在Docker内安装Nacos-Server v2.0.4 ,并进行挂载,免去了安装其他的内容的时间。
        首先就是获取nacos-server的发行包 https://github.com/alibaba/nacos/releases 在这里可以选择需要的nacos-server版本,在docker 中,就是获取nacos-server的镜像。首先就是可以docker hub中查看下有没有需要的nacos-server版本,docker  hub的地址https://hub.docker.com/  然后在docker hub 的搜索nacos 选择需要的nacos tag 比如博主使用的就是 https://hub.docker.com/r/nacos/nacos-server/tags,
        
  直接复制右侧的命令,就可以得到指定版本的的nacos-server镜像了。
docker pull nacos/nacos-server:v2.0.4
   获取好镜像后由于博主是希望以后看日志或者修改配置方便,选择把nacos的配置以及日志挂载到容器外文件夹,操作流程,先试运行nacos-server, 然后复制配置文件到自定义的文件夹。
docker run --name nacos-test -d -p 8848:8848 -p 9848-9849:9848-9849  nacos/nacos-server:v2.0.4

docker cp test:/home/nacos/logs/ /docker-mount/docker-nacos-2.0.4/
docker cp test:/home/nacos/conf/ /docker-mount/docker-nacos-2.0.4/
  复制好配置文件后,再一次停止运行nacos-server容器,并删除临时容器。
docker ps                  // 查看当前正在运行的容器
docker ps -a               // 查看容器列表(所有)
docker stop 17e96d2d8a06   // 暂停运行容器
docker rm -f 17e96d2d8a06  // 删除容器

   接下来就是创建容器,博主这里选择直接在创建的时候直接执行一些配置 
docker run -d \
-e MODE=standalone \
-e SPRING_DATASOURCE_PLATFORM=mysql \
-e MYSQL_SERVICE_HOST=127.0.0.1 \
-e MYSQL_SERVICE_PORT=3307 \
-e MYSQL_SERVICE_USER=nacos \
-e MYSQL_SERVICE_PASSWORD=nacos \
-e MYSQL_SERVICE_DB_NAME=nacos \
-e TIME_ZONE='Asia/Shanghai' \
-v /docker-mount/docker-nacos-2.0.4/logs:/home/nacos/logs \
-v /docker-mount/docker-nacos-2.0.4/conf:/home/nacos/conf \
-p 8848:8848 \
-p 9848-9849:9848-9849 \
--name nacos \
--restart=always \
nacos/nacos-server:v2.0.4


docker ps                // 查看正在运行的容器
  至此nacos-server已经在docker中运行成功,接下来就可以在nacos console中体验nacos的功能啦  例如博主的console访问地址就是 http://localhost:8848/nacos/#/login 初始账号密码都是 nacos/nacos。

二. Nacos作为配置中心

1.为什么会需要配置中心?

2.简单的使用Alibaba Nacos配置中心来体验 如何从配置中心获取配置 以及 如何动态刷新配置后如何生效。
    首先呢就是先上一个Spring Cloud Alibaba Nacos使用的demo程序,还是先来说明下博主选择的框架版本,这个很重要 🙃,博主学习的方向是和Spring Cloud进行整合
         Spring Boot Version                               2.6.11 
         Spring Cloud Alibaba Version                2021.0.4.0
         Spring Cloud Alibaba Nacos Version     2.0.4
         Spring Cloud Version                             2021.0.4
   官网提供的和Spring Cloud 整合Nacos的参考:https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html  个人感觉可以完全参考该示例来体验。
    说明:后续博主会更新一下初次使用Nacos Config 遇到的一些坑。 暂时不更新这部分内容。

3.nacos作为配置中心的原理 
    这个部分会从两个方向来说明,“如何从配置中心获取配置” 以及“动态刷新配置后如何让生效” Spring Cloud 的配置管理的原理。
    首先先大致说明“从配置中心获取配置的”原理,
        在spring-cloud-context模块内部的META-INF/spring.factories 有一个BootStrapApplicationListener的类(这个类实现了ApplicationListener接口),这个类BootStrapApplicationListener主要是用于监听ApplicationEnvironmentPreparedEvent事件(Envirment刚创建,ApplicationContext未创建的时候就会触发这个事件),收到该事件之后就会进入到BootStrap阶段,在BootStrap阶段就会构造ApplicationContext,这个ApplicationContext加载配置的过程就是基于bootstrap.properties或者bootstrap.yml文件去加载文件,在加载的过程中,Spring Cloud 就有一套机制来构造数据源PropertySource(是基于一个PropertySourceLocator接口的定义),从而从配置中心加载配置。
        从源码的角度来分析下整个配置中心获取的配置的流程,先以spring-cloud-context模块说起,在spring-cloud-context模块内部META-INF/spring.factories有一个BootstrapApplicationListener这个类,这个类的主要作用就是监听ApplicationEnvironmentPrepareEvent事件,所以在这个类里主要是关注两个方法,第一个方法就是onApplicationEvent(ApplicationEnvironmentPrepareEvent event) ,在这个方法中首先是需要找到已经构造好的bootstrap ApplicationContext,没有找到就会调用第二个方法 bootStrapServiceContext 去构造bootstrap ApplicationContext,在构造的过程中会注册一个BootstrapImportSelectorConfiguration配置类,这个类非常的关键,在这个类中它会引入BootstrapImportSelector类,在BootstrapImportSelector类中会调用 selectImports方法,那么这个它会使用工厂模式找出以key = org.springframework.cloud.bootstrap.BootstrapConfiguration 对应的配置类,这个配置类里就有一个PropertySourceBootstrapConfiguration类,这个类中的一个方法initialize()方法,在这个方法中就会遍历所有的PropertySourceLocator列表,会调用它的locate方法获取PropertySource,然后将这些数据源添加到bootstrap ApplicationContext的Environment里。我们就获取到了配置。
      同样的,如果配置中心使用的是Nacos-server,同样会在spring-cloud-aibaba-nacos-config模块META-INF/spring.factories加载 key = org.springframwork.cloud.bootstrap.BootstrapConfiguration对应的配置类,只不过此时加载的配置类是NacosConfigBootstrapConfiguration配置类,这个配置类就在内部就会构造NacosPropertySourceLocator类,在这个类中调用locate() 方法,这个方法就是从nacos配置中心获取配置。加载共享 加载扩展 加载应用配置等。
      
这里面就涉及到几个核心的类 BootstrapApplicationListenner BootstrapImportSelectorConfiguration  BootstrapImportSelector NacosPropertySourceLocator 关于核心类的源码暂时先放一下 😊

     然后在来说明"动态刷新配置"原理
         先介绍一下整个动态刷新的代码流程,在用户修改配置的时候,会有一个监听器RefreshEventListener类去监听RefreshEvent事件,在这个监听器类的内部会先调用一个handler的方法,这个方法内部会去调用ContextRefresher类的refresh方法,在这个方法内部,首先会调用refreshEnvironment 方法,在refreshEnvironment内部,首先会对动态刷新前后的配置项进行比较,然后会触发EnvironmentChangeEvent事件,最终返回一个返回一个keys的集合。执行完refreshEnvironment方法之后,就会RefreshScop类的refreshAll()方法,这个方法首先会调用父类(GenericScope)的destory方法,目的就是销毁所有被@RefreshScope修饰的类,destory调用成功,就会触发RefreshScopeRefreshedEvent事件。销毁的后再次获取就需要重新解析,也就是重新获取配置。

     做两个扩展点:
         @ConfigurationProperties,那么这个注解是如何实现动态刷新的呢?
             这个注解的刷新是和EnvironmentChangeEvent事件有关,EnvironmentChangeEvent触发的时候,会带上配置动态修改的具体的key集合,ConfigurationPropertiesRebinder,在这个类中就维护了一个属性ConfigurationPropertiesBeans 这bean就是被@ConfigurationProperties注解修饰的bean,内部有一个方法rebind,这个方法的作用就是重新绑定@ConfigurationProperties修饰的bean,那解析出来的属性必然也是最新的。
         另一个扩展点
             EurekaDiscoveryClientConfiguration自动化配置类里一个内部监听了RefreshScope-->RefreshedEvent事件,当配置动态刷新的时候会下线应用对应的实例,然后重新上线,这里就会认为Eureka的配置进行了修改,所以需要重新注册。

          简单总结整个动态刷新配置的流程就是
                修改配置 --->  触发一个RefreshEvent事件 ---> 调用ContextRefresh中的refresh方法(在这个方法内部会重新构造一个ApplicationContext,从而可以使应用重新加载配置,本地和配置中心的配置中心的配置均能加载,加载完成之后,就会新旧配置就会做一个比较) ---> 在调用ContextRefresh中也会触发RefreshScope的RefreshAll() 方法,这个方法内部会在Spring 上下文里销毁所有@RefreshScope注解修饰的类,销毁后,下次在获取就会重新在构造一遍。
   
4.关于配置刷新的存在的问题
       Spring Cloud配置动态刷新本质上是事件的触发让客户端再一次的从配置中心拉取最新的配置,然后配合@RefreshScope重新刷新bean,用来达到动态刷新的目的,这种方式就会带来一些问题 :
          如果使用的是Spring Cloud 提供的Spring Cloud Config Server/Client 组件在分布式环境下需要配合消息总线Spring Cloud Bus(消息总线),来达到多节点配置动态刷新的目的。因此 如果说使用Spring Cloud Config Server/Client组件需要额外加上消息中间件。
         @RefreshScope引起的Spring Bean刷新可能会与其他组件冲突。比如spring-context模块下的scheduling调度功能在Spring Bean被刷新后会失去作用。

 三.Nacos作为服务发现以及注册中心

   关于源码部分的内容:
        首先先介绍几个核心的类:
       Spring Cloud 统一提供了两个类,ServiceInstance 以及Registration 用来抽象实例在各种注册中心的数据模型,ServiceInstance代表的就是客户端从注册中心获取到的实例的数据结构,Registration代表的就是客户端注册到注册中心的实例数据结构。
       ServiceRegistry,主要用于服务信息的注册以及注销。
       AutoServiceRegistration作用的自动完成服务注册过程,对应的实现类NacosAutoServiceRegistration,会在NacosServiceRegistryAutoConfiguration自动化配置类中被构造。
       AbstractAutoServiceRegistration 这个抽象类就实现了AutoServiceRegistration接口,同时也实现了ApplicationListener接口,同时也监听了WebServerInitializeEvent事件。收到事件之后使用ServiceRegistry的register方法去完成服务注册,在应用程序关闭的时候就会触发@PreDestory注解使用ServiceRegistry完成服务注销。整个就是整个Spring Cloud服务注册与注销的时机。

四. Spring Cloud LoadBalancer 负载均衡组件

     写在前面,目前官方推荐使用Spring Cloud LoadBalancer,不过目前 Spring Cloud LoadBalancer 功能和Ribbon相比还是比较弱的,比如Ribbon提供了熔断特性,当一个实例发生3次错误后,就会被熔断一段时间,这段时间内该实例永远不会被选择。所以要 使用的话根据阿里巴巴一位资深的专家的建议,还是使用Spring Cloud Netflix Ribbon组件,预计Spring Cloud LoadBalancer在未来的版本中会逐渐更新更多的功能,并达到可以替换的Ribbon的状态。
1.关于Spring Cloud LoadBalancer负载均衡组件的使用
        就是在pom里面添加依赖 --> spring-cloud-starter-loadbalancer
2.从源码的角度来分析下Spring Cloud LoadBalancer实现的原理
          Spring Cloud LoadBalancer的代码是在spring-cloud-commons模块中的,相关的主要的类和接口:
             ServiceInstanceChooser:服务实例选择器,根据服务名获取一个实例(ServiceInstance)。
             LoadBalancerClient:客户端负载均衡,继承了ServiceInstanceChooser,会根据serviceInstance以及request请求信息执行最终的结果。
             BlockingBalanceClient:是Spring Cloud LoadBalancer的LoadBalancerClient默认的实现。
          我们从一个接地气的方式切入到源码的流程中,最原始的获取服务实例的流程是:首先通过DiscoveryClient(eg:discoveryClient.getInstances())获取服务实例(ServiceInstance),然后在通过restTemplate进行调用服务。这是一个比较传统的方式,但是其实这个方式是可以优化的,就是我们可以省略掉通过dicoveyClient.getInstance()这一步骤,使用restemplate通过服务名直接调用服务。那这种通过服务名直接调用服务的实现方式就是只需要在定义的RestTemplate Bean上添加@LoadBalancer注解,就可以基于服务名来进行服务调用了。那么这个@LoadBalancer注解在底层都做了哪些事情呢?
          spring-cloud-commons模块中的 META-INF/spring.factories 文件里存在LoadBalancerAutoConfiguration自动化配置类,根据工厂机制会被ApplicationContext加载,这个配置类内部维护了一个属性List<RestTemplate> restTemplates 这个属性 代表的意思就是获取ApplicationContext中被@LoadBalancer注解修饰的RestTemplate。 然后再这个配置类中还定义了一个 SmartInitializingSingleton Bean  在定义这个bean的过程中存在RestTemplateCustomizer 类的集合。这个集合的作用就是给被@LoadBalancer注解修饰的RestTemplate Bean的定制化。那么这个RestTemplateCustomizer定制做了哪些事情呢?其实在LoadBalancerAutoConfiguration这个类中定义了RestTemplateCustomizer,在定义的这个Bean的方法中,存在一个List<ClientHttpRequestInterceptor> list这个拦截器属性列表,获取这个类型的拦截器属性列表,主要是看下当前实现或者集成这个拦截器的类,然后在这把LoadBalancerInterceptor这个拦截器也加载到这个列表里,所以在LoadBalancerAutoConfiguration类中定义RestTemplateCustomizer 时做的一件重要额事情就是添加了一个LoadBalancerInterceptor拦截器,这个也是一个扩展点。那么接下来我们就看一下LoadBalancerInterceptor这个拦截器类里做了什么?首先来看下它的构造方法,它的构造方法里引入了两个类 一个LoadBalancerClient 以及LoadBalancerRequestFactory,LoadBalancerClient这个类中的方法可以根据负载均衡请求LoadBalancerRequest以及服务名做真正非服务调用,后者可以构造请求的参数。然后在这个LoadBalancerInterceptor类中intercept方法 就是进行服务调用的方法,构造的请求参数服务名使用的是host。然后在看一下LoadBalancerClient 这个方法中有一个requestURI的方法,主要是用于重新构造url,例如会访问的nacos-provider-lb服务,ip地址对应是 192.168.1.100 完整构造为 例如:http://nacos-provider-lb -----> http://192.168.1.100,端口为8080 http://192.168.1.100:8080/  以上就是整个负载均衡的代码的原理了。
      扩展点:Spring Cloud LoadBalancer还提供了一个@LoadBalancerClient注解用于进行自定义的配置操作,可以自定义负载均衡算法,整个注解有3个属性, value name configuration。

3.OpenFeign原理
     回顾一下之前说的使用RestTemplate的构造可以通过@LoadBanlance注解,使其拥有可以通过服务名进行调用的能力,那想象一下这个场景,一个复杂的微服务场景涉及上百个微服务名,如果这个时候还使用RestTemplate发起服务,服务信息的维护就变得非常麻烦。Spring Cloud就提供了另一个Rest客户端OpenFeign,用于解决这个问题,那来看一下OpenFeign的源码,Spring Cloud 是通过@EnableFeignClients注解提供的包名以及@FeignClient注解修饰的接口,来找到所有的接口,基于这些接口构造FeignClientFactoryBean。
    就从@EnableFeignClients注解来看下OpenFeign源码的流程,首先是@EnableFeignClients找到接口,在@EnableFeignClients注解类中引入了 FeignClientRegistrar这个注册类,在这个类中调用了一个方法registerDefaultConfiguration 这个方法主要就是注册一下默认的配置,然后会调用当前类中的registerFeignClients这个方法,会查找所有被@FeignClient注解修饰的接口,然后为它们创建相应的代理对象,在这个方法中还会调用 registerclient 方法 这个方法就是基于@FeignClient注解修饰接口构造FeignClientFactoryBean这个bean,在构造的过程中会调用FeignClientFactoryBean 类中的getObject方法,即调用Targeter类的target方法,在target方法中就会继续调用feign客户端的target方法,即调用build(),在调用build方法会返回一个默认的ReflectiveFeign这个默认的客户端,调用完build 方法后会继续调用ReflectiveFeign的newI.nstance() 方法,所以在FeignClientFactoryBean 构造的过程中实际返回的是一个Proxy。即接口的动态代理,并给予MethodMetadata进行Rest调用。

           


                
         



 
























全部评论

相关推荐

learYuan:🐕看了都摇头
点赞 评论 收藏
分享
04-25 19:29
已编辑
宁波大学 运营
被普调的六边形战士很高大:你我美牛孩
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务