定制软件开发Eureka注册中心、Ribbon负载均衡与Nacos以及http客户端Feign

文章目录


定制软件开发本博客是基于观看b定制软件开发站里视频做的一个学习记录,定制软件开发存有不足的地方,敬请见谅。

定制软件开发服务架构演变

单体架构

  • 单体架构:定制软件开发将业务的所有功能集中定制软件开发在一个项目中开发,定制软件开发打成一个包部署。
  • 优点:架构简单、定制软件开发部署成本低
  • 缺点:耦合度高

定制软件开发定制软件开发分布式架构

  • 分布式架构:定制软件开发根据业务功能对系统做拆分,定制软件开发每个业务功能模块作为定制软件开发独立项目开发,定制软件开发称为一个服务。
  • 优点:定制软件开发降低服务耦合,定制软件开发有利于服务升级和拓展
  • 缺点:定制软件开发服务调用关系错综复杂

定制软件开发分布式架构虽然降低了服务耦合,定制软件开发但是服务拆分时也有很定制软件开发多问题需要思考:

  • 定制软件开发服务拆分的粒度如何界定?
  • 定制软件开发服务之间如何调用?
  • 定制软件开发服务的调用关系如何管理?

定制软件开发人们需要制定一套行之定制软件开发有效的标准来约束分布式架构。

微服务

定制软件开发微服务是一种经过良好定制软件开发架构设计的分定制软件开发布式架构方案,定制软件开发微服务架构特征:

  • 单一职责:定制软件开发微服务拆分粒度更小,定制软件开发每一个服务都对应唯一定制软件开发的业务能力,定制软件开发做到单一职责,定制软件开发避免重复业务开发
  • 面向服务:定制软件开发微服务对外暴露业务接口
  • 自治:团队独立、技术独立、数据独立、部署独立
  • 隔离性强:定制软件开发服务调用做好隔离、容错、降级,定制软件开发避免出现级联问题

定制软件开发微服务的上述特性其实定制软件开发是在给分布式架构制定一个标准,定制软件开发进一步降低服务之间的耦合度,定制软件开发提供服务的独立性和灵活性。定制软件开发做到高内聚,低耦合。

因此,定制软件开发可以认为微服务是一种定制软件开发经过良好架构设计的分布式架构方案 。

其中在 Java 定制软件开发领域最引人注目的就是 SpringCloud 定制软件开发提供的方案了。

SpringCloud

SpringCloud 定制软件开发是目前国内使用最广泛定制软件开发的微服务框架。官网地址:https://spring.io/projects/spring-cloud。

SpringCloud 定制软件开发集成了各种微服务功能组件,并基于 SpringBoot 定制软件开发实现了这些组件的自动装配,定制软件开发从而提供了良好的开箱即用体验。

定制软件开发其中常见的组件包括:


定制软件开发微服务远程调用方式

  • 基于RestTemplate发起的http定制软件开发请求实现远程调用
  • http定制软件开发请求做远程调用是与语言无关的调用,只要知道对方的ip、端口、接口路径、请求参数即可。

服务调用关系

  • 服务提供者:暴露接口给其它微服务调用
  • 服务消费者:调用其它微服务提供的接口
  • 提供者与消费者角色其实是相对的
  • 一个服务可以同时是服务提供者和服务消费者

Eureka


order-service 如何得知 user-service 实例地址?

  • user-service 服务实例启动后,将自己的信息注册到 eureka-server(Eureka服务端),叫做服务注册
  • eureka-server 保存服务名称到服务实例地址列表的映射关系
  • order-service根据服务名称,拉取实例地址列表,这个叫服务发现或服务拉取

order-service 如何从多个 user-service 实例中选择具体的实例?

  • order-service从实例列表中利用负载均衡算法选中一个实例地址,向该实例地址发起远程调用

order-service 如何得知某个 user-service 实例是否依然健康,是不是已经宕机?

  • user-service 会每隔一段时间(默认30秒)向 eureka-server 发起请求,报告自己状态,称为心跳
  • 当超过一定时间没有发送心跳时,eureka-server 会认为微服务实例故障,将该实例从服务列表中剔除
  • order-service 拉取服务时,就能将故障实例排除了

小结

在Eureka架构中,微服务角色有两类:- EurekaServer:服务端,注册中心 -- 记录服务信息 -- 心跳监控- EurekaClient:客户端 -- Provider:服务提供者,例如案例中的 user-service  --- 注册自己的信息到EurekaServer  --- 每隔30秒向EurekaServer发送心跳 -- consumer:服务消费者,例如案例中的 order-service  --- 根据服务名称从EurekaServer拉取服务列表  --- 基于服务列表做负载均衡,选中一个微服务后发起远程调用
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

接下来我们动手实践的步骤包括:

搭建注册中心

搭建EurekaServer
创建项目,引入spring-cloud-starter-netflix-eureka-server的依赖

<dependency>    <groupId>org.springframework.cloud</groupId>    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency>
  • 1
  • 2
  • 3
  • 4

编写主启动类,添加@EnableEurekaServer注解,开启 eureka 的注册中心功能

@SpringBootApplication@EnableEurekaServerpublic class EurekaApplication {    public static void main(String[] args) {        SpringApplication.run(EurekaApplication.class, args);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

编写配置文件
编写一个 application.yml 文件,内容如下:

server:  port: 10086spring:  application:    name: eureka-servereureka:  client:    service-url:       defaultZone: http://127.0.0.1:10086/eureka
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

其中 default-zone是因为前面配置类开启了注册中心所需要配置的 eureka 的地址信息,因为 eureka 本身也是一个微服务,这里也要将自己注册进来,当后面 eureka 集群时,这里就可以填写多个,使用 “,” 隔开
启动完成后,访问 http://localhost:10086/

服务注册

将 user-service、order-service 都注册到 eureka

引入 SpringCloud 为 eureka 提供的 starter 依赖,注意这里是用 client

<dependency>    <groupId>org.springframework.cloud</groupId>    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency>
  • 1
  • 2
  • 3
  • 4

在启动类上添加注解:@EnableEurekaClient

在 application.yml 文件,添加下面的配置:

spring:  application:      #name:orderservice    name: userserviceeureka:  client:    service-url:       defaultZone: http:127.0.0.1:10086/eureka
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

3个项目启动后,访问 http://localhost:10086/
我们可以通过 idea 的多实例启动,来查看 Eureka 的集群效果。

4个项目启动后,访问 http://localhost:10086/

小结

服务注册

  • 引入eureka-client依赖
  • 在application.yml中配置eureka地址

无论是消费者还是提供者,引入eureka-client依赖、知道eureka地址后,都可以完成服务注册

服务拉取

服务拉取是基于服务名称获取服务列表,然后在对服务列表做负载均衡

在 order-service 中完成服务拉取,然后通过负载均衡挑选一个服务,实现远程调用

下面我们让 order-service 向 eureka-server 拉取 user-service 的信息,实现服务发现。

首先给 RestTemplate 这个 Bean 添加一个 @LoadBalanced注解,用于开启负载均衡。

@Bean@LoadBalancedpublic RestTemplate restTemplate(){    return new RestTemplate();}
  • 1
  • 2
  • 3
  • 4
  • 5

修改 OrderService 访问的url路径,用服务名代替ip、端口:
spring 会自动帮助我们从 eureka-server 中,根据 userservice 这个服务名称,获取实例列表后去完成负载均衡。

总结:

  1. 搭建EurekaServer

    • 引入eureka-server依赖
    • 添加@EnableEurekaServer注解
    • application.yml中配置eureka地址
  2. 服务注册

    • 引入eureka-client依赖
    • application.yml中配置eureka地址
  3. 服务发现

    • 引入eureka-client依赖
    • application.yml中配置eureka地址
    • RestTemplate添加@LoadBalanced注解
    • 服务提供者的服务名称远程调用

负载均衡

我们添加了 @LoadBalanced 注解,即可实现负载均衡功能,这是什么原理呢?

SpringCloud 底层提供了一个名为 Ribbon 的组件,来实现负载均衡功能。

源码跟踪

为什么我们只输入了 service 名称就可以访问了呢?为什么不需要获取ip和端口,这显然有人帮我们根据 service 名称,获取到了服务实例的ip和端口。它就是LoadBalancerInterceptor这个类会在对 RestTemplate 的请求进行拦截,然后从 Eureka 根据服务 id 获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务 id。

我们进行源码跟踪:

这里的 intercept()方法,拦截了用户的 HttpRequest 请求,然后做了几件事:

  • request.getURI()获取请求uri,即 http://user-service/user/8
  • originalUri.getHost()获取uri路径的主机名,其实就是服务id user-service
  • this.loadBalancer.execute()处理服务id,和用户请求

这里的 this.loadBalancerLoadBalancerClient 类型

继续跟入 execute()方法:

  • getLoadBalancer(serviceId):根据服务id获取 ILoadBalancer,而 ILoadBalancer 会拿着服务 id 去 eureka 中获取服务列表。
  • getServer(loadBalancer):利用内置的负载均衡算法,从服务列表中选择一个。在图中可以看到获取了8082端口的服务

可以看到获取服务时,通过一个 getServer() 方法来做负载均衡:
继续跟入:继续跟踪源码 chooseServer() 方法,发现这么一段代码:
看看这个 rule 是谁:
这里的 rule 默认值是一个 RoundRobinRule ,看类的介绍:
负载均衡默认使用了轮训算法,当然我们也可以自定义

流程总结

SpringCloud Ribbon 底层采用了一个拦截器,拦截了 RestTemplate 发出的请求,对地址做了修改。

基本流程如下:

  • 拦截我们的 RestTemplate 请求 http://userservice/user/1
  • RibbonLoadBalancerClient 会从请求url中获取服务名称,也就是 user-service
  • DynamicServerListLoadBalancer 根据 user-serviceeureka 拉取服务列表
  • eureka 返回列表,localhost:8081、localhost:8082
  • IRule 利用内置负载均衡规则,从列表中选择一个,例如
    localhost:8081
  • RibbonLoadBalancerClient 修改请求地址,用 localhost:8081 替代
    userservice,得到 http://localhost:8081/user/1,发起真实请求

负载均衡的规则都定义在IRule 接口中,而 IRule 有很多不同的实现类:
不同规则的含义如下:

内置负载均衡规则类规则描述
RoundRobinRule简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。
AvailabilityFilteringRule对以下两种服务器进行忽略:(1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule 规则的客户端也会将其忽略。并发连接数的上限,可以由客户端设置。
WeightedResponseTimeRule为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
ZoneAvoidanceRule以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。
BestAvailableRule忽略那些短路的服务器,并选择并发数较低的服务器。
RandomRule随机选择一个可用的服务器。
RetryRule重试机制的选择逻辑

默认的实现就是 ZoneAvoidanceRule,是一种轮询方案。

通过定义IRule实现可以修改负载均衡规则,有两种方式:

  1. 代码方式:在order-service中的OrderApplication类中,定义一个新的IRule
@Beanpublic IRule randomRule(){    return new RandomRule();    }
  • 1
  • 2
  • 3
  • 4
  1. 配置文件方式:在order-serviceapplication.yml文件中,添加新的配置也可以修改规则:
userservice: # 给需要调用的微服务配置负载均衡规则,orderservice服务去调用userservice服务  ribbon:    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则 
  • 1
  • 2
  • 3

注意:一般用默认的负载均衡规则,不做修改。

饥饿加载

当我们启动 orderservice,第一次访问时,时间消耗会大很多,这是因为 Ribbon 懒加载的机制。

Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:

ribbon:  eager-load:    enabled: true    clients: userservice # 项目启动时直接去拉取userservice的集群,多个用","隔开
  • 1
  • 2
  • 3
  • 4

小结

  1. Ribbon负载均衡规则

    • 规则接口是IRule
    • 默认实现是ZoneAvoidanceRule,根据zone选择服务列表,然后轮询
  2. 负载均衡自定义方式

    • 代码方式:配置灵活,但修改时需要重新打包发布
    • 配置方式:直观,方便,无需重新打包发布,但是无法做全局配置
  3. 饥饿加载

    • 开启饥饿加载
    • 指定饥饿加载的微服务名称

Nacos注册中心

SpringCloudAlibaba 推出了一个名为 Nacos 的注册中心,在国外也有大量的使用。

解压启动 Nacos,至于如何安装nacos请看:

startup.cmd -m standalone
  • 1

访问:http://localhost:8848/nacos/

服务注册

这里上来就直接服务注册,很多东西可能有疑惑,其实 Nacos 本身就是一个 SprintBoot 项目,这点你从启动的控制台打印就可以看出来,所以就不再需要去额外搭建一个像 Eureka 的注册中心。

引入依赖

在 cloud-demo 父工程中引入 SpringCloudAlibaba 的依赖:

<dependency>    <groupId>com.alibaba.cloud</groupId>    <artifactId>spring-cloud-alibaba-dependencies</artifactId>    <version>2.2.6.RELEASE</version>    <type>pom</type>    <scope>import</scope></dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

然后在 user-service 和 order-service 中的pom文件中引入 nacos-discovery 依赖,注释掉原有的eureka依赖:

<dependency>    <groupId>com.alibaba.cloud</groupId>    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>
  • 1
  • 2
  • 3
  • 4

配置nacos地址

在 user-service 和 order-service 的 application.yml 中添加 nacos 地址:

spring:  cloud:    nacos:      server-addr: 127.0.0.1:8848 #nacos服务端地址
  • 1
  • 2
  • 3
  • 4

项目重新启动后,可以看到三个服务都被注册进了 Nacos

浏览器访问:http://localhost:8080/order/101,正常访问,同时负载均衡也正常。

分级存储模型

一个服务可以有多个实例,例如我们的 user-service,可以有:

  • 127.0.0.1:8081
  • 127.0.0.1:8082
  • 127.0.0.1:8083

假如这些实例分布于全国各地的不同机房,例如:

  • 127.0.0.1:8081,在上海机房
  • 127.0.0.1:8082,在上海机房
  • 127.0.0.1:8083,在杭州机房

Nacos就将同一机房内的实例,划分为一个集群

服务跨集群调用问题

服务调用尽可能选择本地集群的服务,跨集群调用延迟较高
本地集群不可访问时,再去访问其它集群

配置集群

接下来我们给 user-service 配置集群

修改 user-service 的 application.yml 文件,添加集群配置:

spring:  cloud:    nacos:      server-addr: localhost:8848      discovery:        cluster-name: HZ # 集群名称 HZ杭州
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

重启两个 user-service 实例后,我们再去启动一个上海集群的实例。

-Dserver.port=8083 -Dspring.cloud.nacos.discovery.cluster-name=SH
  • 1


查看 nacos 控制台:

小结

Nacos服务分级存储模型

  • 一级是服务,例如userservice
  • 二级是集群,例如杭州或上海
  • 三级是实例,例如杭州机房的某台部署了userservice的服务器

如何设置实例的集群属性

  • 修改application.yml文件,添加spring.cloud.nacos.discovery.cluster-name属性即可

NacosRule

Ribbon的默认实现 ZoneAvoidanceRule 并不能实现根据同集群优先来实现负载均衡,我们把规则改成 NacosRule 即可。我们是用 orderservice 调用 userservice,所以在 orderservice 配置规则。

@Beanpublic IRule iRule(){    //默认为轮询规则,这里自定义为随机规则    return new NacosRule();}
  • 1
  • 2
  • 3
  • 4
  • 5

另外,你同样可以使用配置的形式来完成,具体参考上面的 Ribbon 栏目。

userservice:  ribbon:    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule #负载均衡规则 
  • 1
  • 2
  • 3

然后,再对 orderservice 配置集群。

spring:  cloud:    nacos:      server-addr: localhost:8848      discovery:        cluster-name: HZ # 集群名称
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

启动了四个服务,分别是:

  • orderservice - HZ
  • userservice - HZ
  • userservice1 - HZ
  • userservice2 - SH

访问地址:http://localhost:8080/order/101

在访问中我们发现,只有同在一个 HZ 集群下的 userservice、userservice1 会被调用,并且是随机的。

我们试着把 userservice、userservice2 停掉。依旧可以访问。

在 userservice2 控制台可以看到发出了一串的警告,因为 orderservice 本身是在 HZ 集群的,这波 HZ 集群没有了 userservice,就会去别的集群找。

权重配置

实际部署中会出现这样的场景:

服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求。但默认情况下 NacosRule 是同集群内随机挑选,不会考虑机器的性能问题。

因此,Nacos 提供了权重配置来控制访问频率,0~1 之间,权重越大则访问频率越高,权重修改为 0,则该实例永远不会被访问。

在 Nacos 控制台,找到 user-service 的实例列表,点击编辑,即可修改权重。


在弹出的编辑窗口,修改权重

另外,在服务升级的时候,有一种较好的方案:我们也可以通过调整权重来进行平滑升级,例如:先把 userservice 权重调节为 0,让用户先流向 userservice2、userservice3,升级 userservice后,再把权重从 0 调到 0.1,让一部分用户先体验,用户体验稳定后就可以往上调权重啦。

环境隔离

Nacos 提供了 namespace 来实现环境隔离功能。

  • Nacos 中可以有多个 namespace
  • namespace 下可以有 group、service
  • 不同 namespace 之间相互隔离,例如不同 namespace 的服务互相不可见

创建namespace

默认情况下,所有 service、data、group 都在同一个 namespace,名为 public(保留空间):
我们可以点击页面新增按钮,添加一个 namespace
然后,填写表单:

就能在页面看到一个新的 namespace

配置namespace

给微服务配置 namespace只能通过修改配置来实现。

例如,修改 order-service 的application.yml文件:

spring:  cloud:    nacos:      server-addr: localhost:8848      discovery:        cluster-name: HZ        namespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f9 # 命名空间ID
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

重启 order-service 后,访问控制台。

public

dev

此时访问 order-service,因为 namespace 不同,会导致找不到 userservice,控制台会报错:

小结

  • Nacos环境隔离
    • 每个namespace都有唯一id
    • 服务设置namespace时要写id而不是名称
    • 不同namespace下的服务互相不可见

nacos注册中心细节分析

临时实例

Nacos 的服务实例分为两种类型:

  • 临时实例:如果实例宕机超过一定时间,会从服务列表剔除,默认的类型。
  • 非临时实例:如果实例宕机,不会从服务列表剔除,也可以叫永久实例。
    配置一个服务实例为永久实例:
spring:  cloud:    nacos:      discovery:        ephemeral: false # 设置为非临时实例
  • 1
  • 2
  • 3
  • 4
  • 5

另外,Nacos 集群默认采用AP方式(可用性),当集群中存在非临时实例时,采用CP模式(一致性);而 Eureka 采用AP方式,不可切换。(这里说的是 CAP 原理,后面会写到)

小结

Nacos与eureka的共同点

  1. 都支持服务注册和服务拉取
  2. 都支持服务提供者心跳方式做健康检测

Nacos与Eureka的区别

  • Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
  • 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
  • Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
  • Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式

Nacos配置中心

Nacos除了可以做注册中心,同样可以做配置管理来使用。

当微服务部署的实例越来越多,达到数十、数百时,逐个修改微服务配置就会让人抓狂,而且很容易出错。我们需要一种统一配置管理方案,可以集中管理所有实例的配置。

Nacos 一方面可以将配置集中管理,另一方可以在配置变更时,及时通知微服务,实现配置的热更新
创建配置
在 Nacos 控制面板中添加配置文件


然后在弹出的表单中,填写配置信息:
注意:项目的核心配置,需要热更新的配置才有放到 nacos 管理的必要。基本不会变更的一些配置(例如数据库连接)还是保存在微服务本地比较好。

拉取配置

首先我们需要了解 Nacos 读取配置文件的环节是在哪一步,在没加入 Nacos 配置之前,获取配置是这样:

加入 Nacos 配置,它的读取是在 application.yml 之前的:

这时候如果把 nacos 地址放在 application.yml 中,显然是不合适的,Nacos 就无法根据地址去获取配置了。

因此,nacos 地址必须放在优先级最高的 bootstrap.yml 文件。

统一配置管理

引入Nacos的配置管理客户端依赖
首先,在 user-service 服务中,引入 nacos-config 的客户端依赖:

<!--nacos配置管理依赖--><dependency>    <groupId>com.alibaba.cloud</groupId>    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

添加 bootstrap.yml
在userservice中的resource目录添加一个bootstrap.yml文件,这个文件是引导文件,优先级高于application.yml:

spring:  application:    name: userservice # 服务名称  profiles:    active: dev #开发环境,这里是dev   cloud:    nacos:      server-addr: localhost:8848 # Nacos地址      config:        file-extension: yaml # 文件后缀名
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

根据 spring.cloud.nacos.server-addr 获取 nacos地址,再根据${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}作为文件id,来读取配置。

在这个例子例中,就是去读取 userservice-dev.yaml


使用代码来验证是否拉取成功

在 user-service 中的 UserController 中添加业务逻辑,读取 pattern.dateformat 配置并使用:

@RestController@RequestMapping("/user")public class UserController {    // 注入nacos中的配置属性       @Value("${pattern.dateformat}")    private String dateformat;     // 编写controller,通过日期格式化器来格式化现在时间并返回    @GetMapping("now")       public String now(){    	return LocalDate.now().format(DateTimeFormatter.ofPattern(dateformat,Locale.CHINA));    	}    // ... 略    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

小结

将配置交给Nacos管理的步骤

  • Nacos中添加配置文件
  • 在微服务中引入nacosconfig依赖
  • 在微服务中添加bootstrap.yml,配置nacos地址、当前环境、服务名称、文件后缀名。这些决定了程序启动时去nacos 读取哪个文件

配`置自动刷新

我们最终的目的,是修改 nacos 中的配置后,微服务中无需重启即可让配置生效,也就是配置热更新
不过需要通过下面两种配置实现:
方式一:在@Value注入的变量所在类上添加注解@RefreshScope

方式二:使用@ConfigurationProperties注解

小结

Nacos配置更改后,微服务可以实现热更新,方式:

  • 通过@Value注解注入,结合@RefreshScope来刷新
  • 通过@ConfigurationProperties注入,自动刷新

注意事项:

  • 不是所有的配置都适合放到配置中心,维护起来比较麻烦
  • 建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义配置

配置共享

其实在服务启动时,nacos 会读取多个配置文件,例如:

  • [spring.application.name]-[spring.profiles.active].yaml,例如:userservice-dev.yaml
  • [spring.application.name].yaml,例如:userservice.yaml
  • 无论profile如何变化,[spring.application.name].yaml这个文件一定会加载,因此多环境共享配置可以写入这个文件
    多种配置的优先级

小结

  • 服务会从nacos读取的配置文件:
    [服务名]-[spring.profile.active].yaml,环境配置
    [服务名].yaml,默认配置,多环境共享
  • 优先级:
    [服务名]-[环境].yaml >[服务名].yaml > 本地配置

案例

添加一个环境共享配置

我们在 nacos 中添加一个 userservice.yaml 文件:
在 user-service 中读取共享配置

在 user-service 服务中,修改 PatternProperties 类,读取新添加的属性

在 user-service 服务中,修改 UserController,添加一个方法:

运行两个 UserApplication,使用不同的profile
修改 UserApplication2 这个启动项,改变其profile值:

这样,UserApplication(8081) 使用的 profile 是 dev,UserApplication2(8082) 使用的 profile 是test

启动 UserApplication 和 UserApplication2

访问地址:http://localhost:8081/user/prop,结果:

访问地址:http://localhost:8082/user/prop,结果:

可以看出来,不管是 dev,还是 test 环境,都读取到了 envSharedValue这个属性的值。

上面的都是同一个微服务下,那么不同微服务之间可以环境共享吗?

通过下面的两种方式来指定:

  • extension-configs
  • shared-configs
spring:   cloud:    nacos:      config:        file-extension: yaml # 文件后缀名        extends-configs: # 多微服务间共享的配置列表          - dataId: common.yaml # 要共享的配置文件id
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
spring:   cloud:    nacos:      config:        file-extension: yaml # 文件后缀名        shared-configs: # 多微服务间共享的配置列表          - dataId: common.yaml # 要共享的配置文件id
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

配置优先级

当 nacos、服务本地同时出现相同属性时,优先级有高低之分。

更细致的配置

Nacos集群搭建

Nacos生产环境下一定要部署为集群状态,其搭建过程会另外进行具体的描述,这里先简单的介绍一下集群的架构。

集群搭建步骤:

  • 搭建MySQL集群并初始化数据库表
  • 下载解压nacos
  • 修改集群配置(节点信息)、数据库配置
  • 分别启动多个nacos节点
  • nginx反向代理

Feign远程调用

我们以前利用 RestTemplate 发起远程调用的代码:

存在代码可读性差,编程体验不统一以及参数复杂URL难以维护的问题。为了解决这一方面的问题,我们引入了基于http客户端的Feign。

Feign是一个声明式的http客户端,官方地址:https://github.com/OpenFeign/feign
其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。

定义和使用Feign客户端

使用Feign的步骤如下:

  1. 引入依赖:
    我们在 order-service 引入 feign 依赖:
<dependency>    <groupId>org.springframework.cloud</groupId>    <artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
  • 1
  • 2
  • 3
  • 4
  1. 在order-service的启动类添加注解开启Feign的功能:
  2. 编写Feign客户端:
@FeignClient("userservice")public interface UserClient {    @GetMapping("/user/{id}")     User findById(@PathVariable("id") Long id);    }
  • 1
  • 2
  • 3
  • 4
  • 5

主要是基于SpringMVC的注解来声明远程调用的信息,比如:

  • 服务名称:userservice——@FeignClient("userservice"):其中参数填写的是微服务名
  • 请求方式:GET——@GetMapping("/user/{id}"):其中参数填写的是请求路径
  • 请求路径:/user/{id}
  • 请求参数:Long id
  • 返回值类型:User
  1. 用Feign客户端代替RestTemplate
@Autowiredprivate UserClient userClient;public Order queryOrderAndUserById(Long orderId) {    // 1.查询订单    Order order = orderMapper.findById(orderId);    //  使用feign远程调用,发起HTTP请求,查询用户    User user = userClient.findById(order.getUserId());    // 3. 将用户信息封装进订单    order.setUser(user);    // 4.返回    return order;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

总结:
Feign的使用步骤

  1. 引入依赖
  2. 添加@EnableFeignClients注解
  3. 编写FeignClient接口
  4. 使用FeignClient中定义的方法代替RestTemplate

自定义配置

Feign 可以支持很多的自定义配置,如下表所示:

类型作用说明
feign.Logger.Level修改日志级别包含四种不同的级别:NONE、BASIC、HEADERS、FULL
feign.codec.Decoder响应结果的解析器http远程调用的结果做解析,例如解析json字符串为java对象
feign.codec.Encoder请求参数编码将请求参数编码,便于通过http请求发送
feign.Contract支持的注解格式默认是SpringMVC的注解
feign.Retryer失败重试机制请求失败的重试机制,默认是没有,不过会使用Ribbon的重试

一般我们需要配置的就是日志级别。

一般情况下,默认值就能满足我们使用,如果要自定义时,只需要创建自定义的 @Bean 覆盖默认 Bean 即可。下面以日志为例来演示如何自定义配置。

基于配置文件修改 feign 的日志级别可以针对单个服务
局部生效

feign:    client:    config:       userservice: # 针对某个微服务的配置        loggerLevel: FULL #  日志级别 
  • 1
  • 2
  • 3
  • 4
  • 5

也可以针对所有服务
全局生效

feign:    client:    config:       default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置        loggerLevel: FULL #  日志级别 
  • 1
  • 2
  • 3
  • 4
  • 5

而日志的级别分为四种:

  • NONE不记录任何日志信息,这是默认值。
  • BASIC仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL记录所有请求和响应的明细,包括头信息、请求体、元数据

也可以基于 Java 代码来修改日志级别,先声明一个类,然后声明一个 Logger.Level的对象

public class DefaultFeignConfiguration  {    @Bean    public Logger.Level feignLogLevel(){        return Logger.Level.BASIC; // 日志级别为BASIC    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果要全局生效,将其放到启动类的 @EnableFeignClients 这个注解中:

@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class) 
  • 1

如果是局部生效,则把它放到对应的 @FeignClient 这个注解中:

@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration .class) 
  • 1

总结:
Feign的日志配置:

  1. 方式一是配置文件feign.client.config.xxx.loggerLevel
    如果xxxdefault则代表全局
    如果xxx服务名称,例如userservice则代表某服务
  2. 方式二是java代码配置Logger.Level这个Bean
    如果在@EnableFeignClients注解声明则代表全局
    如果在@FeignClient注解中声明则代表某服务

性能优化

Feign 底层发起 http 请求,依赖于其它的框架。其底层客户端实现有:

  • URLConnection:默认实现,不支持连接池
  • Apache HttpClient :支持连接池
  • OKHttp:支持连接池

因此提高 Feign 性能的主要手段就是使用连接池代替默认的 URLConnection

另外,日志级别应该尽量用 basic/none,可以有效提高性能。

这里我们用 Apache 的HttpClient来演示连接池。

1、在 order-service 的 pom 文件中引入 HttpClient 依赖

<!--httpClient的依赖 --><dependency>    <groupId>io.github.openfeign</groupId>    <artifactId>feign-httpclient</artifactId></dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

2、配置连接池

order-serviceapplication.yml中添加配置

feign:  client:    config:      default: # default全局的配置        loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息  httpclient:    enabled: true # 开启feign对HttpClient的支持    max-connections: 200 # 最大的连接数    max-connections-per-route: 50 # 每个路径的最大连接数
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

3、在 FeignClientFactoryBean 中的 loadBalance 方法中打断点
Debug 方式启动 order-service 服务,可以看到这里的 client,底层就是 HttpClient

最佳实践

继承方式

方式一(继承):给消费者的FeignClient和提供者的controller定义统一的父接口作为标准。

  • 服务紧耦合
  • 父接口参数列表中的映射不会被继承

抽取方式

方式二(抽取):将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用

实现最佳实践方式二的步骤如下:

  1. 首先创建一个module,命名为feign-api,然后引入feign的starter依赖
<dependency>    <groupId>org.springframework.cloud</groupId>    <artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
  • 1
  • 2
  • 3
  • 4
  1. 将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中

  2. 在order-service中引入feign-api的依赖

<dependency>    <groupId>com.xn2001.feign</groupId>    <artifactId>feign-api</artifactId>    <version>1.0</version></dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  1. 修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包
  2. 重启测试

当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。有两种方式解决:
方式一:指定FeignClient所在包

@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
  • 1

方式二:指定FeignClient字节码

@EnableFeignClients(clients = {UserClient.class})
  • 1
网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发