黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理

教程链接:https://www.bilibili.com/video/BV1LQ4y127n4
黑马的资料下载链接:https://pan.baidu.com/s/1zRmwSvSvoDkWh0-MynwERA&pwd=1234

目录

        • 认识微服务
          • 单体架构
          • 分布式架构
          • 微服务
          • 微服务结构
          • 微服务技术对比
          • SpringCloud
          • 总结
        • 服务拆分及远程调用
          • 服务拆分注意事项
          • 服务拆分示例
          • 实现服务远程调用
          • 提供者与消费者
        • Eureka 注册中心
          • 服务调用出现的问题
          • Eureka 的结构和作用
          • 动手实践
            • 搭建 EurekaServer
            • 注册 user-service 和 order-service
            • 在 order-service 完成服务拉取
        • Ribbon 负载均衡
          • 负载均衡流程
          • 负载均衡策略
        • Nacos 注册中心
          • 安装
          • 服务注册到 Nacos
          • Nacos 服务分级存储模型
          • 服务集群属性
          • NacosRule 负载均衡
          • 权重配置
          • 环境隔离 - namespace
          • Nacos 与 Eureka 的区别
        • Nacos 配置管理
          • 统一配置管理
          • 配置热更新
          • 配置共享
          • 配置共享优先级
          • 搭建 Nacos 集群
        • http 客户端 Feign
          • Feign 替代 RestTemplate
          • Feign 自定义配置
          • Feign 性能优化
          • Feign 最佳实践
            • 继承
            • 抽取
        • Gateway 服务网关
          • gateway 快速入门
          • 路由断言工厂
          • 路由过滤器
          • 全局过滤器
          • 过滤器执行顺序
          • 跨域问题处理

认识微服务
单体架构

单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署。
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第1张图片
单体架构的优缺点如下:
优点:
架构简单
部署成本低
缺点:
耦合度高(维护困难、升级困难)

分布式架构

分布式架构:根据业务功能对系统做拆分,每个业务功能模块作为独立项目开发,称为一个服务。黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第2张图片
分布式架构的优缺点:
优点:
降低服务耦合
有利于服务升级和拓展
缺点:
服务调用关系错综复杂

分布式架构虽然降低了服务耦合,但是服务拆分时也有很多问题需要思考 :
❶ 服务拆分的粒度如何界定❔
❷ 服务集群地址如何维护❔
❸ 服务的调用关系如何管理❔
❹ 服务健康状态如何感知❔

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

微服务

微服务是一种经过良好架构设计 的 分布式 架构方案,微服务架构特征 :
单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责
自治:团队独立、技术独立、数据独立,独立部署和交付
面向服务:服务提供统一标准的接口,与语言和技术无关
隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第3张图片

微服务结构

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第4张图片

微服务技术对比

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第5张图片
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第6张图片

SpringCloud

SpringCloud 是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud。
SpringCloud 集成了各种微服务功能组件,并 基于 SpringBoot 实现了这些组件的自动装配,从而提供了良好的开箱即用体验。
其中常见的组件包括:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第7张图片
另外,SpringCloud 底层是依赖于 SpringBoot 的,并且有版本的兼容关系,如下:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第8张图片
我们课堂学习的版本是 Hoxton.SR10,因此对应的 SpringBoot 版本是 2.3.x 版本

总结

单体架构:简单方便,高度耦合,扩展性差,适合小型项目。例如:学生管理系统
分布式架构:松耦合,扩展性好,但架构复杂,难度大。适合大型互联网项目,例如:京东、淘宝
微服务:一种良好的分布式架构方案
❶ 优点:拆分粒度更小、服务更独立、耦合度更低
❷ 缺点:架构非常复杂,运维、监控、部署难度提高
SpringCloud 是微服务架构的一站式解决方案,集成了各种优秀微服务功能组件

服务拆分及远程调用
服务拆分注意事项

不同微服务,不要重复开发相同业务
微服务数据独立,不要访问其它微服务的数据库
微服务可以将自己的业务暴露为接口供其它微服务调用
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第9张图片

服务拆分示例

以课前资料中的微服务 cloud-demo 为例,其结构如下:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第10张图片
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第11张图片
导入后,会在 IDEA 右下角出现弹窗:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第12张图片
会出现这样的菜单:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第13张图片
cloud-demo:父工程,管理依赖
order-service:订单微服务,负责订单相关业务
user-service:用户微服务,负责用户相关业务
要求:
订单微服务和用户微服务都必须有各自的数据库,相互独立
将课前资料提供的 cloud-order.sqlcloud-user.sql 导入到 mysql 中:
请添加图片描述
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第14张图片
请添加图片描述
请添加图片描述
订单服务和用户服务都对外暴露 Restful 的接口
在 order-service 服务中,有一个根据 id 查询订单的接口:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第15张图片
根据 id 查询订单,返回值是 Order 对象,如图:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第16张图片
其中的 user 为 null。
在 user-service 中有一个根据 id 查询用户的接口:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第17张图片
查询的结果如图:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第18张图片

订单服务如果需要查询用户信息,只能调用用户服务的 Restful 接口,不能查询用户数据库

实现服务远程调用

需求:根据订单 id 查询订单的同时,把订单所属的用户信息一起返回
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第19张图片

因此,我们需要在 order-service 中向 user-service 发起一个 http 的请求,调用 http://localhost:8081/user/{userId} 这个接口。
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第20张图片
注册一个 RestTemplate 的实例到 Spring 容器
首先,我们在 order-service 服务中的 OrderApplication 启动类中,注册 RestTemplate 实例:

    /**
     * 创建 RestTemplate 并注入 Spring 容器
     * @return
     */
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第21张图片
修改 order-service 服务中的 OrderService 类中的 queryOrderById 方法,根据 Order 对象中的 userId 查询 User
将查询的 User 填充到 Order 对象,一起返回

    	@Autowired
    	private RestTemplate restTemplate;
    	
    	// 2.利用 RestTemplate 发送 http 请求,查询用户
        // 2.1 url 路径
        String url = "http://localhost:8081/user/" + order.getUserId();
        // 2.2 发送 http 请求,实现远程调用
        User user = restTemplate.getForObject(url, User.class);
        // 2.3 封装 user 到 order
        order.setUser(user);

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第22张图片
重启测试:
请添加图片描述
成功啦

提供者与消费者

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第23张图片
在服务调用关系中,会有两个不同的角色:
服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)
服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)
但是,服务提供者与服务消费者的角色并不是绝对的,而是 相对于业务而言
如果服务 A 调用了服务 B,而服务 B 又调用了服务 C,服务 B 的角色是什么?
对于 A 调用 B 的业务而言:A 是服务消费者,B 是服务提供者
对于 B 调用 C 的业务而言:B 是服务消费者,C 是服务提供者
因此,服务 B 既可以是服务提供者,也可以是服务消费者。

Eureka 注册中心
服务调用出现的问题

假如我们的服务提供者 user-service 部署了多个实例,如图:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第24张图片
order-service 在发起远程调用的时候,该如何得知 user-service 实例的 ip 地址和端口
有多个 user-service 实例地址,order-service 调用时该如何选择
order-service 如何得知某个 user-service 实例是否依然健康,是不是已经宕机

Eureka 的结构和作用

这些问题都需要利用 SpringCloud 中的注册中心来解决,其中最广为人知的注册中心就是 Eureka,其结构如下:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第25张图片
Q1: order-service 如何得知 user-service 实例地址❓️
获取地址信息的流程如下
user-service 服务实例启动后,将自己的信息注册到 eureka-server(Eureka 服务端)。这个叫 服务注册
eureka-server 保存服务名称到服务实例地址列表的映射关系
order-service 根据服务名称,拉取实例地址列表。这个叫 服务发现 或 服务拉取

Q2:order-service 如何从多个 user-service 实例中选择具体的实例❓️
order-service 从实例列表中利用 负载均衡算法 选中一个实例地址
向该实例地址发起远程调用

Q3:order-service 如何得知某个 user-service 实例是否依然健康,是不是已经宕机❓️
user-service 会每隔一段时间(默认30秒)向 eureka-server 发起请求,报告自己状态,称为心跳
当超过一定时间没有发送心跳时 ,eureka-server 会认为微服务实例故障,将该实例从服务列表中剔除
order-service 拉取服务时,就能将故障实例排除了

注意:一个微服务,既可以是服务提供者,又可以是服务消费者,因此 eureka 将服务注册、服务发现等功能统一封装到了 eureka-client 端

动手实践

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第26张图片

搭建 EurekaServer

搭建 EurekaServer 服务步骤如下:
在 cloud-demo 父工程下,创建一个子模块 eureka-server:
请添加图片描述
在 eureka-server 中引入 SpringCloud 为 eureka 提供的 starter 依赖:

        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
        dependency>

编写启动类
给 eureka-server 服务编写一个启动类 EurekaApplication,一定要添加一个 @EnableEurekaServer 注解,开启 eureka 的注册中心功能:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第27张图片

@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
@EnableEurekaServer
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }
}

编写配置文件 application.yml
请添加图片描述

server:
  port: 10086
spring:
  application:
    name: eureka-server      # eureka 的服务名称
eureka:
  client:
    service-url:            # eureka 的地址信息
      defaultZone: http://127.0.0.1:10086/eureka

启动 eureka-server
请添加图片描述
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第28张图片
注册到 Eureka 的实例:
请添加图片描述

注册 user-service 和 order-service

在 user-service 和 order-service 的 pom 文件中,引入下面的 eureka-client 依赖:

        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>

在 user-service 和 order-service 中,修改 application.yml 文件,添加服务名称、eureka 地址:

spring:
  application:
    name: userservice
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
spring:
  application:
    name: orderservice
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

刷新 eureka 主页
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第29张图片
为了演示一个服务有多个实例的场景,我们添加一个 SpringBoot 的启动配置,再启动一个 user-service。
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第30张图片
-Dserver.port=8082
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第31张图片
启动 UserApplication2
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第32张图片
请添加图片描述

在 order-service 完成服务拉取

服务拉取是基于服务名称获取服务列表,然后再对服务列表做负载均衡:
修改 OrderService 代码,修改访问的 url 路径,用服务名代替 ip、端口:

        // 2.1 url 路径
        String url = "http://userservice/user/" + order.getUserId();

请添加图片描述
在 order-service 的 OrderApplication 中,给 RestTemplate 这个 Bean 添加一个 @LoadBalanced 注解:
请添加图片描述
重启 orderApplication,访问
http://localhost:8080/order/101请添加图片描述
http://localhost:8080/order/102
请添加图片描述

Ribbon 负载均衡

SpringCloud 底层其实是利用了一个名为 Ribbon 的组件,来实现负载均衡功能的。

负载均衡流程

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第33张图片
为什么我们只输入了 service 名称就可以访问了呢❓️之前还要获取 ip 和端口。
显然有人帮 我们根据 service 名称,获取到了服务实例的 ip 和端口。它就是 LoadBalancerInterceptor,这个类会对 RestTemplate 的请求进行拦截,然后从 Eureka 根据服务 id 获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务 id。
我们进行源码跟踪 ️‍♀:
① LoadBalancerIntercepor
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第34张图片
可以看到这里的 intercept 方法,拦截了用户的 HttpRequest 请求,然后做了几件事:
request.getURI():获取请求 uri,本例中就是 http://user-service/user/8
originalUri.getHost():获取 uri 路径的主机名,其实就是服务 id,user-service
this.loadBalancer.execute():处理服务 id,和用户请求。
这里的 this.loadBalancerLoadBalancerClient 类型,我们继续跟入 。
② LoadBalancerClient
继续跟入 execute 方法:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第35张图片
代码是这样的:
getLoadBalancer(serviceId):根据服务 id 获取 ILoadBalancer,而 ILoadBalancer 会拿着服务 ideureka 中获取服务列表并保存起来。
getServer(loadBalancer):利用内置的负载均衡算法,从服务列表中选择一个。本例中,可以看到获取了 8082 端口的服务
放行后,再次访问并跟踪,发现获取的是 8081:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第36张图片
果然实现了负载均衡 。
负载均衡策略 IRule
在刚才的代码中,可以看到获取服务使通过一个 getServer 方法来做负载均衡:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第37张图片
我们继续跟入:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第38张图片
继续跟踪源码 chooseServer 方法,发现这么一段代码:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第39张图片
我们看看这个 rule 是谁️‍♀:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第40张图片
这里的 rule 默认值是一个 RoundRobinRule,看类的介绍:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第41张图片
这不就是轮询的意思嘛。
到这里,整个负载均衡的流程我们就清楚了。
⑤ 总结
SpringCloudRibbon 的底层采用了一个拦截器,拦截了 RestTemplate 发出的请求,对地址做了修改。用一幅图来总结 一下:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第42张图片
基本流程如下:
拦截我们的 RestTemplate 请求 http://userservice/user/1
RibbonLoadBalancerClient 会从请求 url 中获取服务名称,也就是 user-service
DynamicServerListLoadBalancer 根据 user-service 到 eureka 拉取服务列表
eureka 返回列表,localhost:8081、localhost:8082
IRule 利用内置负载均衡规则,从列表中选择一个,例如 localhost:8081
RibbonLoadBalancerClient 修改请求地址,用 localhost:8081 替代 userservice,得到 http://localhost:8081/user/1,发起真实请求

负载均衡策略

负载均衡的规则都定义在 IRule 接口中,而 IRule 有很多不同的实现类:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第43张图片
不同规则的含义如下:

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

默认的实现就是 ZoneAvoidanceRule,是一种轮询方案
自定义负载均衡策略
通过定义 IRule 实现可以修改负载均衡规则,有两种方式:
❶ 代码方式(针对所有微服务):在 order-service 中的 OrderApplication 类中,定义一个新的 IRule:

@Bean
public IRule randomRule(){
    return new RandomRule();
}

❷ 配置文件方式(只针对某个微服务):在 order-service 的 application.yml 文件中,添加新的配置也可以修改规则:

userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则 

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

ribbon:
    eager-load:
      enabled: true         # 开启饥饿加载
      clients: userservice  # 指定饥饿加载的服务名称
Nacos 注册中心

Nacos 是阿里巴巴的产品,现在是 SpringCloud 中的一个组件。相比 Eureka 功能更加丰富,在国内受欢迎程度较高。

安装

解压
将这个包解压到任意非中文目录下,如图:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第44张图片
目录说明:
❶ bin:启动脚本
❷ conf:配置文件
端口配置
默认端口为 8848:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第45张图片

启动
进入 bin 目录:
请添加图片描述
使用记事本打开 startup.cmd 文件将 set MODE=“cluster” 改为 set MODE=“standalone”
将模型属性:集群改为单机
双击 startup.cmd 即可
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第46张图片
访问
在浏览器输入地址:http://127.0.0.1:8848/nacos 即可:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第47张图片
账号和密码均为 nacos, 进入后:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第48张图片

服务注册到 Nacos

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

            
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-alibaba-dependenciesartifactId>
                <version>2.2.6.RELEASEversion>
                <type>pomtype>
                <scope>importscope>
            dependency>

注释掉 order-service 和 user-service 中的 eureka 依赖
添加 Nacos 的客户端依赖

<dependency>
    <groupId>com.alibaba.cloudgroupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>

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

spring:
  cloud:
    nacos:
      server-addr: localhost:8848

重启微服务
请添加图片描述
请添加图片描述

Nacos 服务分级存储模型

一个 服务 可以有多个 实例,例如我们的 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 是 服务,一个 服务 可以包含多个 集群,如杭州、上海,每个 集群 下可以有多个 实例,形成分级模型,如图:黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第49张图片
微服务互相访问时,应该尽可能访问同集群实例,因为本地访问速度更快。当本集群内不可用时,才访问其它集群。例如:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第50张图片
杭州机房内的 order-service 应该优先访问同机房的 user-service。

服务集群属性

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

spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ 										# 集群名称

user-service:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第51张图片
order-service:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第52张图片

NacosRule 负载均衡

默认的 ZoneAvoidanceRule 并不能实现根据同集群优先来实现负载均衡。
因此 Nacos 中提供了一个 NacosRule 的实现,可以 优先从同集群中挑选实例
修改 order-service 的 application.yml 文件,修改负载均衡规则:

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

重启 orderApplication:
order-service 发起远程调用时优先访问 8081
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第53张图片
但是把 8081 停掉 …
请添加图片描述
请添加图片描述
NacosRule 负载均衡策略:
❶ 优先选择同集群服务实例列表
❷ 本地集群找不到提供者,才会到其他集群寻找,并且会报警告⚠️
❸ 确定了可用实例列表后,再采取随机负载均衡挑选实例

权重配置

实际部署中会出现这样的场景:
服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求。
默认情况下 NacosRule 是同集群内随机挑选,不会考虑机器的性能问题。
因此,Nacos 提供了权重配置来控制访问频率,权重越大则访问频率越高。
在 nacos 控制台,找到 user-service 的实例列表,点击编辑,即可修改权重 (0 - 1)
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第54张图片
即可使 8082 被访问到的概率大大降低。
注意:如果权重修改为 0,则该实例永远不会被访问

环境隔离 - namespace

Nacos 提供了 namespace 来实现 环境隔离 功能。
❶ nacos 中可以有多个 namespace
❷ namespace 下可以有 group、service 等
❸ 不同 namespace 之间相互隔离,例如 不同 namespace 的服务互相不可见
默认的 namespace 为 public:
请添加图片描述
新建命名空间:
请添加图片描述
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第55张图片
请添加图片描述
命名空间 id: d55f5126-b7c9-4876-8308-87f260cfd4e4
修改 order-service 的 application.yml 文件,配置 namespace:

    nacos:
      server-addr: localhost:8848           				# nacos 服务地址
      discovery:
        cluster-name: HZ                    				# 集群名称, HZ 代指杭州
        namespace: d55f5126-b7c9-4876-8308-87f260cfd4e4     # dev 环境

重启 OrderApplication:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第56张图片
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第57张图片
请添加图片描述

Nacos 与 Eureka 的区别

Nacos 的服务实例分为两种类型:
临时实例:如果实例宕机超过一定时间,会从服务列表剔除,默认的类型。
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第58张图片
非临时实例:如果实例宕机,不会从服务列表剔除,也可以叫永久实例。
配置一个服务实例为永久实例:

spring:
  cloud:
    nacos:
      discovery:
        ephemeral: false 					# 设置为非临时实例

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第59张图片
停止服务,仍在服务列表中:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第60张图片
总结:
Nacos 和 Eureka 整体结构类似,服务注册、服务拉取、心跳等待,但是也存在一些差异:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第61张图片
Nacos 与 Eureka 的 共同点
❶ 都支持服务注册和服务拉取
❷ 都支持服务提供者心跳方式做健康检测
Nacos 与 Eureka 的 区别
Nacos 支持服务端主动检测提供者状态:临时实例采用心跳模式 ,非临时实例 采用 主动检测 模式
❷ 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
Nacos 支持服务列表变更的消息推送模式,服务列表更新更及时
❹ Nacos 集群默认采用 AP 方式,当集群中存在非临时实例时,采用 CP 模式;Eureka 采用 AP 方式

Nacos 配置管理
统一配置管理

当微服务部署的实例越来越多,达到数十、数百时,逐个修改微服务配置就会让人抓狂,而且很容易出错。我们需要一种统一配置管理方案,可以集中管理所有实例的配置。
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第62张图片
Nacos 一方面可以将配置集中管理,另一方可以在配置变更时,及时通知微服务,实现配置的热更新。
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第63张图片
在 Nacos 中添加配置文件
请添加图片描述
配置文件的 id : [服务名称]-[profile].[后缀名]
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第64张图片
请添加图片描述
注意:项目的核心配置,需要热更新的配置 才有放到 nacos 管理的必要。基本不会变更的一些配置还是保存在微服务本地比较好。
微服务配置拉取
微服务要拉取 Nacos 中管理的配置,并且与本地的 application.yml 配置合并,才能完成项目启动。
但如果尚未读取 application.yml,又如何得知 Nacos 地址呢?
因此 spring 引入了一种新的配置文件:bootstrap.yaml 文件,会在 application.yml 之前被读取,流程如下:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第65张图片
❶ 引入 nacos-config 依赖
首先,在 user-service 服务中,引入 nacos-config 的客户端依赖:

        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
        dependency>

❷ 添加 bootstrap.yaml
然后,在 user-service 中添加一个 bootstrap.yaml 文件,内容如下(与 Data ID 对应),这个文件是引导文件,优先级高于 application.yml:

spring:
  application:
    name: userservice 					# 服务名称
  profiles:
    active: dev 						# 开发环境,这里是 dev 
  cloud:
    nacos:
      server-addr: localhost:8848 		# Nacos 地址
      config:
        file-extension: yaml			# 文件后缀名

❸ 在 userservice 中将 pattern.dateformat 这个属性注入到 UserController 中进行测试:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第66张图片
重启 UserApplication
请添加图片描述
但是没有实现热更新。

配置热更新

我们最终的目的,是修改 nacos 中的配置后,微服务中无需重启即可让配置生效,也就是 配置热更新
要实现配置热更新,可以使用两种方式:
在 @Value 注入的变量所在类上添加注解 @RefreshScope
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第67张图片

使用 @ConfigurationProperties 注解代替 @Value 注解。 (推荐)
在 user-service 服务中,添加一个类,读取 patterrn.dateformat 属性:

@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
    private String dateformat;
}

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第68张图片

配置共享

其实微服务启动时,会去 Nacos 读取多个配置文件,例如:
[spring.application.name]-[spring.profiles.active].yaml,例如:userservice-dev.yaml
[spring.application.name].yaml,例如:userservice.yaml
[spring.application.name].yaml 不包含环境,因此可以被多个环境共享。
添加一个环境共享配置 userservice.yaml
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第69张图片
在 user-service 服务中,修改 PatternProperties 类,读取新添加的属性:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第70张图片
在 user-service 服务中,修改 UserController,添加一个方法:

    @Autowired
    private PatternProperties properties;

    @GetMapping("prop")
    public PatternProperties properties() {
        return properties;
    }

运行两个 UserApplication,使用不同的 profile
修改 UserApplication2 这个启动项,改变其 profile 值:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第71张图片
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第72张图片
这样,UserApplication(8081) 使用的 profile 是 dev,UserApplication2(8082) 使用的 profile 是 test。
启动 UserApplication 和 UserApplication2
访问 http://localhost:8081/user/prop,结果:
请添加图片描述
访问 http://localhost:8082/user/prop,结果:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第73张图片
可以看出来,不管是 dev,还是 test 环境,都读取到了 envSharedValue 这个属性的值。
请添加图片描述
请添加图片描述

配置共享优先级

❶ 本地 application.yml
请添加图片描述
❷ Nacos userservice.yaml
请添加图片描述
❸ Nacos userservice-dev.yaml
请添加图片描述
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第74张图片
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第75张图片

搭建 Nacos 集群

集群结构图
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第76张图片
其中包含 3 个 Nacos 节点,然后一个负载均衡器代理 3 个 Nacos。这里负载均衡器可以使用 Nginx。
我们计划的集群结构:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第77张图片
首先新建一个数据库,命名为 Nacos,而后导入下面的 SQL:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第78张图片

CREATE TABLE `config_info` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `data_id` varchar(255) NOT NULL COMMENT 'data_id',
  `group_id` varchar(255) DEFAULT NULL,
  `content` longtext NOT NULL COMMENT 'content',
  `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  `src_user` text COMMENT 'source user',
  `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
  `app_name` varchar(128) DEFAULT NULL,
  `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
  `c_desc` varchar(256) DEFAULT NULL,
  `c_use` varchar(64) DEFAULT NULL,
  `effect` varchar(64) DEFAULT NULL,
  `type` varchar(64) DEFAULT NULL,
  `c_schema` text,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';

/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = config_info_aggr   */
/******************************************/
CREATE TABLE `config_info_aggr` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `data_id` varchar(255) NOT NULL COMMENT 'data_id',
  `group_id` varchar(255) NOT NULL COMMENT 'group_id',
  `datum_id` varchar(255) NOT NULL COMMENT 'datum_id',
  `content` longtext NOT NULL COMMENT '内容',
  `gmt_modified` datetime NOT NULL COMMENT '修改时间',
  `app_name` varchar(128) DEFAULT NULL,
  `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';


/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = config_info_beta   */
/******************************************/
CREATE TABLE `config_info_beta` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `data_id` varchar(255) NOT NULL COMMENT 'data_id',
  `group_id` varchar(128) NOT NULL COMMENT 'group_id',
  `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
  `content` longtext NOT NULL COMMENT 'content',
  `beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',
  `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  `src_user` text COMMENT 'source user',
  `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
  `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';

/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = config_info_tag   */
/******************************************/
CREATE TABLE `config_info_tag` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `data_id` varchar(255) NOT NULL COMMENT 'data_id',
  `group_id` varchar(128) NOT NULL COMMENT 'group_id',
  `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
  `tag_id` varchar(128) NOT NULL COMMENT 'tag_id',
  `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
  `content` longtext NOT NULL COMMENT 'content',
  `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  `src_user` text COMMENT 'source user',
  `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';

/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = config_tags_relation   */
/******************************************/
CREATE TABLE `config_tags_relation` (
  `id` bigint(20) NOT NULL COMMENT 'id',
  `tag_name` varchar(128) NOT NULL COMMENT 'tag_name',
  `tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',
  `data_id` varchar(255) NOT NULL COMMENT 'data_id',
  `group_id` varchar(128) NOT NULL COMMENT 'group_id',
  `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
  `nid` bigint(20) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`nid`),
  UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
  KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';

/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = group_capacity   */
/******************************************/
CREATE TABLE `group_capacity` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
  `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
  `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
  `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
  `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
  `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
  `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';

/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = his_config_info   */
/******************************************/
CREATE TABLE `his_config_info` (
  `id` bigint(64) unsigned NOT NULL,
  `nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `data_id` varchar(255) NOT NULL,
  `group_id` varchar(128) NOT NULL,
  `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
  `content` longtext NOT NULL,
  `md5` varchar(32) DEFAULT NULL,
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `src_user` text,
  `src_ip` varchar(50) DEFAULT NULL,
  `op_type` char(10) DEFAULT NULL,
  `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
  PRIMARY KEY (`nid`),
  KEY `idx_gmt_create` (`gmt_create`),
  KEY `idx_gmt_modified` (`gmt_modified`),
  KEY `idx_did` (`data_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';


/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = tenant_capacity   */
/******************************************/
CREATE TABLE `tenant_capacity` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
  `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
  `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
  `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
  `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
  `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
  `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';


CREATE TABLE `tenant_info` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `kp` varchar(128) NOT NULL COMMENT 'kp',
  `tenant_id` varchar(128) default '' COMMENT 'tenant_id',
  `tenant_name` varchar(128) default '' COMMENT 'tenant_name',
  `tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',
  `create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',
  `gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
  `gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
  KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';

CREATE TABLE `users` (
	`username` varchar(50) NOT NULL PRIMARY KEY,
	`password` varchar(500) NOT NULL,
	`enabled` boolean NOT NULL
);

CREATE TABLE `roles` (
	`username` varchar(50) NOT NULL,
	`role` varchar(50) NOT NULL,
	UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
);

CREATE TABLE `permissions` (
    `role` varchar(50) NOT NULL,
    `resource` varchar(255) NOT NULL,
    `action` varchar(8) NOT NULL,
    UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
);

INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);

INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第79张图片
配置 Nacos
❶ 进入 Nacos 的 conf 目录,修改配置文件 cluster.conf.example,重命名为 cluster.conf:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第80张图片
❷ 然后添加内容:

127.0.0.1:8845
127.0.0.1.8846
127.0.0.1.8847

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第81张图片
❸ 然后修改 application.properties 文件,添加数据库配置

spring.datasource.platform=mysql

db.num=1

db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=12345678

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第82张图片
启动 Nacos
❶ 将 nacos 文件夹复制三份,分别命名为:nacos1、nacos2、nacos3
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第83张图片
❷ 然后分别修改三个文件夹中的 application.properties,
nacos1:

server.port=8845

nacos2:

server.port=8846

nacos3:

server.port=8847

❸ 然后分别启动三个 nacos 节点:

startup.cmd

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第84张图片

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第85张图片

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第86张图片

配置 Nginx 反向代理
修改 conf/nginx.conf 文件,配置如下:

upstream nacos-cluster {
    server 127.0.0.1:8845;
	server 127.0.0.1:8846;
	server 127.0.0.1:8847;
}

server {
    listen       80;
    server_name  localhost;

    location /nacos {
        proxy_pass http://nacos-cluster;
    }
}

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第87张图片
启动 Nginx (因为我的 80 端口被占用了,所以我用了 81 端口),访问 localhost/nacos:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第88张图片
代码中 application.yml 文件配置如下:

spring:
  cloud:
    nacos:
      server-addr: localhost:80 # Nacos地址

重启两个 user-service:
服务注册成功:
请添加图片描述
新建配置:
config_info 中完成了数据持久化:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第89张图片

http 客户端 Feign

先来看我们以前利用 RestTemplate 发起远程调用的代码:
请添加图片描述
存在下面的问题:
❶ 代码可读性差,编程体验不统一
❷ 参数复杂, URL 难以维护
Feign 是一个声明式的 http 客户端,官方地址:https://github.com/OpenFeign/feign
其作用就是帮助我们优雅的实现 http 请求的发送,解决上面提到的问题。

Feign 替代 RestTemplate

引入依赖:
我们在 order-service 服务的 pom 文件中引入 feign 的依赖:

<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-openfeignartifactId>
dependency>

添加注解
在 order-service 的启动类添加注解开启 Feign 的功能:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第90张图片
编写 Feign 的客户端

@FeignClient("userservice")
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

这个客户端主要是基于 SpringMVC 的注解来声明远程调用的信息,比如:
❶ 服务名称:userservice
❷ 请求方式:GET
❸ 请求路径:/user/{id}
❹ 请求参数:Long id
❺ 返回值类型:User
这样,Feign 就可以帮助我们发送 http 请求,无需自己使用 RestTemplate 来发送了。
修改 order-service 中的 OrderService 类中的 queryOrderById 方法,使用 Feign 客户端代替 RestTemplate:

    	@Autowired
    	private UserClient userClient;
        // 2. 用 Feign 远程调用
        User user = userClient.findById(order.getUserId());

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第91张图片
重启 orderApplication,测试:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第92张图片
并且 Feign 还实现了 Ribbon 负载均衡:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第93张图片
总结 : Feign 使用步骤
❶ 引入依赖
❷ 添加 @EnableFeignClients 注解
❸ 编写 FeignClient 接口
❹ 使用 FeignClient 中定义的方法代替 RestTemplate

Feign 自定义配置

Feign 运行自定义配置来覆盖默认配置,可以修改的配置如下:

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

一般我们需要的配置的是 日志级别
配置 Feign 的日志有两种方式:
配置文件方式
❶ 全局生效:

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

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第94张图片
❷ 局部生效:

feign:  
  client:
    config: 
      userservice: # 针对某个微服务的配置
        loggerLevel: FULL # 日志级别 

而日志的级别分为四种:
NONE:不记录任何日志信息,这是默认值。
BASIC:仅记录请求的方法,URL 以及响应状态码和执行时间
HEADERS:在 BASIC 的基础上,额外记录了请求和响应的头信息
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
Java 代码方式
先声明一个类,然后声明一个 Logger.Level 的对象:

public class DefaultFeignConfiguration  {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.BASIC; // 日志级别为 BASIC
    }
}

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

@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class) 

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

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

请添加图片描述

Feign 性能优化

Feign 底层发起 http 请求,依赖于其它的框架。其底层客户端实现包括:
URLConnection:默认实现,不支持连接池
Apache HttpClient :支持连接池
OKHttp:支持连接池
因此提高 Feign 的性能主要手段就是使用 HttpClient 连接池 代替默认的 URLConnection。
引入依赖
在 order-service 的 pom 文件中引入 Apache 的 HttpClient 依赖:


<dependency>
    <groupId>io.github.openfeigngroupId>
    <artifactId>feign-httpclientartifactId>
dependency>

配置连接池
在 order-service 的 application.yml 中添加配置:

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

接下来,在 FeignClientFactoryBean 中的 loadBalance 方法中打断点:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第95张图片
Debug 方式启动 order-service 服务,可以看到这里的 client,底层就是 Apache HttpClient:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第96张图片
总结 ,Feign 的优化:
日志级别尽量用 basic
使用 HttpClient 或 OKHttp 代替 URLConnection
❶ 引入 feign-httpClient 依赖
❷ 配置文件开启 httpClient 功能,设置连接池参数

Feign 最佳实践

所谓最佳实践,就是使用过程中总结 的经验,最好的一种使用方式。
观察可以发现,Feign 的客户端与服务提供者的 controller 代码非常相似:
Feign 客户端:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第97张图片
UserController:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第98张图片
有没有一种办法简化这种重复的代码编写呢❓

继承

一样的代码可以通过继承来共享:
定义一个 API 接口,利用定义方法,并基于 SpringMVC 注解做声明
Feign 客户端和 Controller 都集成该接口
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第99张图片
优点:
❶ 简单
❷ 实现了代码共享
缺点:
❶ 服务提供方、服务消费方 紧耦合
❷ 参数列表中的注解映射并不会继承,因此 Controller 中必须再次声明方法、参数列表、注解
请添加图片描述

抽取

将 Feign 的 Client 抽取为独立模块,并且把接口有关的 POJO、默认的 Feign 配置都放到这个模块中,提供给所有消费者使用。
例如,将 UserClient、User、Feign 的 默认配置 都抽取到一个 feign-api 包中,所有微服务引用该依赖包,即可直接使用。
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第100张图片
抽取
❶ 首先创建一个 module,命名为 feign-api:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第101张图片
❷ 在 feign-api 中引入 feign 的 starter 依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
        dependency>
    dependencies>

❸ 将 order-service 中编写的 UserClient、User、DefaultFeignConfiguration 都复制到 feign-api 项目中
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第102张图片
在 order-service 中使用 feign-api
❶ 删除 order-service 中的 UserClient、User、DefaultFeignConfiguration 等类或接口
❷ 在 order-service 的 pom 文件中中引入 feign-api 的依赖:

        
        <dependency>
            <groupId>cn.itcast.demogroupId>
            <artifactId>feign-apiartifactId>
            <version>1.0version>
        dependency>

❸ 修改 order-service 中的所有与上述三个组件有关的导包部分,改成导入 feign-api 中的包
重启 OrderApplication 后发现报错:
Field userClient in cn.itcast.order.service.OrderService required a bean of type ‘cn.itcast.feign.clients.UserClient’ that could not be found.
这是因为 UserClient 现在在 cn.itcast.feign.clients 包下,
而 order-service 的 @EnableFeignClients 注解是在 cn.itcast.order 包下,不在同一个包,无法扫描到 UserClient
解决扫描包问题:
❶ 指定 Feign 应该扫描的包:

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

❷ 指定需要加载的 Client 接口(推荐):

@EnableFeignClients(clients = {UserClient.class})
Gateway 服务网关

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第103张图片

网关的 核心功能特性⭐️:
身份认证、权限控制
网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。
请求路由、负载均衡
一切请求都必须先经过网关,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。
请求限流
当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。

在 SpringCloud 中网关的实现包括两种:
❶ SpringCloudGateway
官网:https://spring.io/projects/spring-cloud-gateway
❷ Zuul
Zuul 是基于 Servlet 的实现,属于阻塞式编程。而 SpringCloudGateway 则是基于 Spring5 中提供的 WebFlux,属于响应式编程的实现,具备 ️更好的性能

gateway 快速入门

下面,我们就演示一下网关的基本路由功能。基本步骤如下:
创建 SpringBoot 工程 gateway,引入网关依赖
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第104张图片


<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-gatewayartifactId>
dependency>

<dependency>
    <groupId>com.alibaba.cloudgroupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>

编写启动类

@SpringBootApplication
public class GatewayApplication {

	public static void main(String[] args) {
		SpringApplication.run(GatewayApplication.class, args);
	}
}

编写基础配置和路由规则
创建 application.yml 文件,内容如下:

server:
  port: 10010 # 网关端口
spring:
  application:
    name: gateway # 服务名称
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos地址
    gateway:
      routes: # 网关路由配置
        - id: user-service # 路由 id,自定义,只要唯一即可
          # uri: http://127.0.0.1:8081 # 路由的目标地址 http 就是固定地址
          uri: lb://userservice # 路由的目标地址 lb 就是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/user/** # 这个是按照路径匹配,只要以 /user/ 开头就符合要求

启动网关服务进行测试
重启网关,访问 http://localhost:10010/user/1 时,符合 /user/** 规则,请求转发到 uri:http://userservice/user/1,得到了结果:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第105张图片
整个访问的流程如下:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第106张图片
总结
网关搭建步骤:
创建项目,引入 nacos 服务发现和 gateway 依赖
配置 application.yml,包括服务基本信息、nacos 地址、路由
路由配置包括:
路由 id:路由的唯一标示
路由目标 uri:路由的目标地址,http 代表固定地址,lb 代表根据服务名负载均衡
路由断言 predicates:判断路由的规则
路由过滤器 filters:对请求或响应做处理

路由断言工厂

我们在配置文件中写的断言规则只是字符串,这些字符串会被 Predicate Factory 读取并处理,转变为路由判断的条件
例如 Path=/user/** 是按照路径匹配,这个规则是由 org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的,像这样的断言工厂在 SpringCloudGateway 共有 12 个:

名称 说明 示例
After 是某个时间点后的请求 - After=2037-01-20T17:42:47.789-07:00[America/Denver]
Before 是某个时间点之前的请求 - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]
Between 是某两个时间点之前的请求 - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]
Cookie 请求必须包含某些cookie - Cookie=chocolate, ch.p
Header 请求必须包含某些header - Header=X-Request-Id, \d+
Host 请求必须是访问某个host(域名) - Host=.somehost.org,.anotherhost.org
Method 请求方式必须是指定方式 - Method=GET,POST
Path 请求路径必须符合指定规则 - Path=/red/{segment},/blue/**
Query 请求参数必须包含指定参数 - Query=name, Jack或者- Query=name
RemoteAddr 请求者的ip必须是指定范围 - RemoteAddr=192.168.1.1/24
Weight 权重处理

下面测试一下 After 和 Before :
请添加图片描述
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第107张图片
请添加图片描述
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第108张图片
总结
Predicate Factory 的作用是什么❓️
读取用户定义的断言条件,对请求作出判断
Path=/user/** 是什么含义❓️
按照路径匹配,只要以 /user/ 开头就符合要求

路由过滤器

GatewayFilter 是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
请添加图片描述

https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
Spring 提供了多种不同的路由过滤器工厂。例如:

名称 说明
AddRequestHeader 给当前请求添加一个请求头
RemoveRequestHeader 移除请求中的一个请求头
AddResponseHeader 给响应结果中添加一个响应头
RemoveResponseHeader 从响应结果中移除有一个响应头
RequestRateLimiter 限制请求的流量

下面我们以 AddRequestHeader 为例来讲解:
需求:给所有进入 userservice 的请求添加一个请求头:Truth=itcast is freaking awesome!
实现
只需要修改 gateway 服务的 application.yml 文件,添加路由过滤即可:

spring:
  cloud:
    gateway:
      routes:
      - id: user-service 
        uri: lb://userservice 
        predicates: 
        - Path=/user/** 
        filters: # 过滤器
        - AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头

当前过滤器写在 userservice 路由下,因此仅仅对访问 userservice 的请求有效。
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第109张图片
请添加图片描述
默认过滤器
如果要对所有的路由都生效,则可以将过滤器工厂写到 default 下。格式如下:

spring:
  cloud:
    gateway:
      default-filters: # 默认过滤项
      - AddRequestHeader=Truth, Itcast is freaking awesome! 

总结
过滤器的作用是什么?
❶ 对路由的请求或响应做加工处理,比如添加请求头
❷ 配置在路由下的过滤器 只对当前路由的请求生效
defaultFilters 的作用是什么?
对所有路由都生效的过滤器

全局过滤器

全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与 GatewayFilter 的作用一样。区别在于 GatewayFilter 通过配置定义,处理逻辑是固定的;而 GlobalFilter 的逻辑需要自己写代码实现
定义方式是 实现 GlobalFilter 接口

public interface GlobalFilter {
    /**
     *  处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
     *
     * @param exchange 请求上下文,里面可以获取Request、Response等信息
     * @param chain 用来把请求委托给下一个过滤器 
     * @return {@code Mono} 返回标示当前过滤器业务结束
     */
    Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

在 filter 中编写自定义逻辑,可以实现下列功能:
❶ 登录状态判断
❷ 权限校验
❸ 请求限流
需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
❶ 参数中是否有 authorization
❷ authorization 参数值是否为 admin
如果同时满足则放行,否则拦截
实现
在 gateway 中定义一个过滤器:

//@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1. 获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String, String> queryParams = request.getQueryParams();   
        
        // 2. 获取参数中的 authorization 参数
        String auth = queryParams.getFirst("authorization");
        // 3. 判断参数值是否等于 admin
        if ("admin".equals(auth)) {
            // 4. 是,放行
            return chain.filter(exchange);
        }
        // 5. 否,拦截
        // 5.1 设置状态码为未登录
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        // 5.2 拦截
        return exchange.getResponse().setComplete();
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

重启测试:
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第110张图片
黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理_第111张图片
总结
全局过滤器的作用是什么?
对所有路由都生效 的过滤器,并且可以 自定义处理逻辑
实现全局过滤器的步骤?
❶ 实现 GlobalFilter 接口
❷ 添加 @Order 注解或实现 Ordered 接口
❸ 编写处理逻辑

过滤器执行顺序

请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter
请求路由后,会将 当前路由过滤器 和 DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器:
❶ 当前路由过滤器 和 DefaultFilter 同属 GatewayFilter
请添加图片描述
❷ GlobalFilter 也会被适配成 GatewayFilter
请添加图片描述
排序的规则是什么呢❓️
❶ 每一个过滤器都必须指定一个 int 类型的 order 值order 值越小,优先级越高,执行顺序越靠前
GlobalFilter 通过实现 Ordered 接口,或者添加 @Order 注解来指定 order 值,由我们自己指定
路由过滤器和 defaultFilter 的 order 由 Spring 指定,默认是按照声明顺序从 1 递增
❹ 当过滤器的 order 值一样时,会按照 defaultFilter → \rightarrow 路由过滤器 → \rightarrow GlobalFilter 的顺序 执行。
详细内容,可以查看源码:
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters() 方法是先加载 defaultFilters,然后再加载某个 route 的 filters,然后合并
org.springframework.cloud.gateway.handler.FilteringWebHandler#handle() 方法会加载全局过滤器,与前面的过滤器合并后根据 order 排序,组织过滤器链

跨域问题处理

跨域, 主要包括:
❶ 域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
❷ 域名相同,端口不同:localhost:8080 和 localhost:8081
跨域问题:浏览器 禁止请求的发起者与服务端发生跨域 ajax 请求,请求被浏览器拦截的问题
模拟跨域问题:
将下面前端页面放到 Nginx 服务器 (端口 8090) 中:

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Documenttitle>
head>
<body>
<pre>
spring:
  cloud:
    gateway:
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求
              - "http://localhost:8090"
              - "http://www.leyou.com"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期
pre>
body>
<script src="https://unpkg.com/axios/dist/axios.min.js">script>
<script>
  axios.get("http://localhost:10010/user/1?authorization=admin")
  .then(resp => console.log(resp.data))
  .catch(err => console.log(err))
script>
html>

从 localhost:8090 访问 localhost:10010,端口不同,显然是跨域的请求:
请添加图片描述
解决跨域问题
在 gateway 服务的 application.yml 文件中,添加下面的配置:

spring:
  cloud:
    gateway:
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决 options 请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求 
              - "http://localhost:8090"
            allowedMethods: # 允许的跨域 ajax 的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带 cookie
            maxAge: 360000 # 这次跨域检测的有效期

请添加图片描述

你可能感兴趣的:(Engineering,#,Java,EE,微服务,spring,cloud,java,nacos)