SpringCloud

发布时间:2024-07-03 10:01

第1章 SpringCloud

  • 掌握架构演进过程
  • 理解微服务拆分流程及远程调用过程
  • 掌握注册中心Eureka的使用
  • 掌握负载均衡Ribbon的使用
  • 能够基于Feign实现服务远程调用

1 服务架构演进

章节知识点

  • 单体架构
  • 分布式架构
  • 微服务架构
  • SpringCloud

过去的互联网:

1:用户量不多
2:并发低
3:数据少

现在的互联网:

1:用户多
2:并发高
3:数据庞大

互联网架构从简到繁的演进经历了单体架构、分布式架构、SOA架构、微服务架构以及最新的service mesh的演进过程。

1.1 单体架构

1)概念

早期互联网产品用户量少,并发量低,数据量小,单个应用服务器可以满足需要,这就是最早互联网架构。我们用一句话总结什么是单体架构:将业务的所有功能集中在一个项目中开发,部署为一个节点。

2)架构图

3)优缺点

#优点:
	1)架构简单
	2)部署成本低
	
#缺点:
	1)耦合度高

1.2 分布式架构

1)概念

根据业务功能对系统进行拆分,每个业务模块称为一个服务。

2)架构图

3)优缺点

#优点
	1)降低服务耦合度
	2)有利于服务升级拓展
	
#缺点
	1)维护成本增加
	2)服务间调用复杂度增加

4)需要解决的问题

1)服务拆分粒度如何?
2)服务之间如何实现调用?
3)服务关系如何管理?

1.3 微服务

1)概念

微服务是系统架构的一种设计风格,将一个原本独立的服务拆分成多个小型服务,每个服务独立运行在在各自的进程中,服务之间通过 HTTP RESTful API 进行通信.每个小型的服务都围绕着系统中的某个耦合度较高的业务进行构建。

#微服务是一种经过良好设计的分布式架构方案,而全球的互联网公司都在积极尝试自己的微服务落地方案。其中在java领域最引人注目的是SpringCloud提供的方案。

2)架构图

3)微服务架构特征

单一职责:微服务拆分粒度更小,每个服务都应对唯一的业务能力,做到单一职责
自治:团队独立、技术独立、数据独立,独立部署和交付
面向服务:服务提供统一标准的接口,与语言无关、与技术无关
隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题

1.4 SpringCloud

  • SpringCloud是目前国内使用最广泛的微服务技术栈。官网地址:https://spring.io/projects/spring-cloud。

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

  • SpringCloud与SpringBoot的版本兼容关系如下:
  • 我们课堂学习的版本是 Hoxton.SR10,因此对应的SpringBoot版本是2.3.x(2.3.8)版本。

1.5 总结

  • 单体架构:简单方便,高度耦合,扩展性差,适合小型项目。例如:学生管理系统,后台管理系统,ERP,OA 中小级企业级应用

  • 分布式架构:松耦合,扩展性好,但架构复杂,难度大。适合大型互联网项目,例如:京东、淘宝

  • 微服务:一种良好的分布式架构方案

  • 优点:拆分粒度更小、服务更独立、耦合度更低

  • 缺点:架构非常复杂,运维、监控、部署难度提高

  • SpringCloud:SpringCloud是微服务架构的一站式解决方案,集成了各种优秀微服务功能组件

2 服务拆分及远程调用[重点掌握]

章节知识点

  • 远程调用案例业务介绍
  • 工程导入
  • 使用RestTemplate实现远程调用
  • 服务提供者、服务消费者概念
  • RestTemplate源码剖析
  • 服务调用出现的问题

案例说明:管理员查询订单详情->根据订单id查询订单的同时,把订单所属的用户信息一起返回,如下图:

2.1 工程导入

  1. SQL导入

资料\工程代码\springcloud-parent\sql脚本中的cloud-order.sqlcloud-user.sql分别导入到两个数据库中。

  1. 工程导入

资料\工程\springcloud-parent导入到IDEA中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JgzxVCJt-1650383592028)(assets/image-20211027154853135.png)]

  1. 修改数据库配置,并测试

查询某用户详情信息:http://localhost:18081/user/1

查询某订单详情信息:http://localhost:18082/order/101

2.2 远程调用

1)RestTemplate介绍

RestTemplate 是spring家族中一款基于http协议的组件(HttpURLConnection),他的作用就是:用来实现基于http的协议方式的服务之间的通信(也就是远程服务调用)。

RestTemplate 采用同步方式执行 HTTP 请求,底层使用 JDK 原生 HttpURLConnection API 。

#概念总结:RestTemplate是spring提供的一个用来模拟浏览器发送请求和接收响应的一个类,它能基于Http协议实现远程调用。

2)注册RestTemplate

itheima-orderOrderApp中注册RestTemplate`:

3)远程调用

修改itheima-order中的OrderServiceImplfindById方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lkq1f5QT-1650383592029)(assets/image-20211027154731989.png)]

4)测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9YHxcTbx-1650383592030)(assets/1635929466603.png)]

2.3 服务提供者、服务消费者

服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)

服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)

在上面案例中itheima-order调用了itheima-user提供的接口,所以itheima-order是服务消费者,itheima-user是服务提供者。

2.4 RestTemplate源码剖析

下面是RestTemplate部分源码,我们可以看到执行过程中采用了Http请求。

沿着RestTemplate.doExecute()往下看相关源码:

一直往后跟踪,在SimpleBufferingClientHttpRequest类中的executeInternal方法中,可以发现会调用sun.net.www.protocol.http.HttpURLConnection.connect()实现远程调用:

2.5 服务调用出现的问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sNs8HbvF-1650383592030)(assets/image-20211027154500316.png)]

按照上面调用流程,消费者调用服务者存在很多问题:

1:服务消费者该如何获取服务提供者的地址信息?
2:如果有多个服务提供者,消费者该如何选择?
3:消费者如何得知服务提供者的健康状态?

2.6 总结

  • RestTemplate使用有2个步骤:
    • 1)注册RestTemplate
    • 2)使用restTemplate.getForObject(url,T.class)远程调用
  • RestTemplate底层是封装了Http请求(HttpURLConnection jdk)
  • 服务提供者、服务消费者
    • 服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)
    • 服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)

3 注册中心-Eureka[了解]

章节知识点

  • 注册中心的作用讲解
  • EurekaServer搭建
  • 服务提供者注册
  • 服务消费者注册

3.1 Eureka的作用

Eureka注册中心如何解决上面的问题?

Eureka工作原理

#1:消费者该如何获取服务提供者具体信息?
	服务提供者启动时向eureka注册自己的信息
	eureka保存这些信息
	消费者根据服务名称向eureka拉取提供者信息
	
#2:如果有多个服务提供者,消费者该如何选择?
	服务消费者利用负载均衡算法,从服务列表中挑选一个
	
#3:消费者如何感知服务提供者健康状态?
	服务提供者会每隔30秒向EurekaServer发送心跳请求,报告健康状态
	EurekaServer在90秒内没有接收到某个微服务节点的心跳,EurekaServer将会注销该微服务的节点
	消费者就可以拉取到最新的信息

3.2 Eureka注册中心实战

3.2.1 搭建EurekaServer

搭建EurekaServer服务步骤如下:

1)pom.xml

创建项目itheima-eurekaserver,引入spring-cloud-starter-netflix-eureka-server的依赖:

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

2)启动类

创建启动类com.itheima.EurekaServerApp,代码如下:

package com.itheima;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

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

3)核心配置文件application.yml

server:
  port: 8001    #端口号
spring:
  application:
    name: eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
eureka:
  client:
    register-with-eureka: false   #是否将自己注册到Eureka中
    fetch-registry: false   #是否从eureka中获取服务信息
    service-url:
      defaultZone: http://localhost:8001/eureka

此时我们访问EurekaServer地址http://localhost:8001/,效果如下:

3.2.2 服务提供者注册

将itheima-user服务注册到EurekaServer步骤如下:

1)pom.xml

itheima-user添加如下依赖:


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

2)修改application.yml

修改itheima-userapplication.yml,添加如下配置:

...
eureka:
  client:
    service-url:
      # EurekaServer的地址
      defaultZone: http://localhost:8001/eureka
  instance:
  	#以IP地址注册到服务中心
    prefer-ip-address: true
    #服务向eureka注册时,注册名默认:“IP名:应用名:应用端口名”
    #现在配置:注册名:应用名:端口:项目版本号
    instance-id: ${spring.application.name}:${server.port}:@project.version@

prefer-ip-address:true 效果图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ki5fM1yS-1650383592031)(assets/1636690773904.png)]

prefer-ip-address:flase 效果图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N4H9csP4-1650383592031)(assets/1636690910445.png)]

3)多实例启动

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9XPohZeB-1650383592032)(assets/1635929398644.png)]

分别启动3个服务配置,Eureka(http://localhost:8001/)信息如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VNPIgp8g-1650383592032)(images\1628586175690.png)]

3.3.3 服务消费者注册

itheima-order虽然是消费者,但与itheima-user一样都是eurekaclient端,同样可以实现服务注册:
itheima-order项目引入spring-cloud-starter-netflix-eureka-client的依赖

1)pom.xml

itheima-orderpom.xml中引入如下依赖


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

2)修改application.yml

修改itheima-orderapplication.yml,添加如下配置:

server:
  port: 18082
spring:
  application:
    name: itheima-order
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springcloud-order?characterEncoding=UTF-8&&serverTimezone=GMT
    username: root
    password: 123456
eureka:
  client:
    service-url:
      # EurekaServer的地址
      defaultZone: http://localhost:8001/eureka
  instance:
    prefer-ip-address: true
    instance-id: ${spring.application.name}:${server.port}:@project.version@

3.3.4 远程调用

itheima-order完成服务拉取实现远程调用,服务拉取是基于服务名称获取服务列表,然后在对服务列表做负载均衡。

修改itheima-order的OrderServiceImpl的代码,修改访问的url路径,用服务名代替ip、端口,代码如下:

在itheima-order项目的启动类OrderApplication中的RestTemplate添加负载均衡注解:

在itheima-order工程启动类OrderApp中,开启负载均衡

/***
 * 注册RestTemplate
 */
@Bean
@LoadBalanced//开启负载均衡
public RestTemplate restTemplate(){
    return new RestTemplate();
}

我们访问http://localhost:18082/order/101测试效果如下:

3.3.5 Eureka配置说明(了解)

服务注册时默认使用的是主机名,如果我们想用ip进行注册,可以在客户端(提供者与消费者)中的application.yml添加配置:

eureka:
  client:
    service-url:
      # EurekaServer的地址
      defaultZone: http://localhost:8001/eureka
  instance:
    prefer-ip-address: true
    instance-id: ${spring.application.name}:${server.port}:@project.version@
    lease-renewal-interval-in-seconds: 30 #心跳周期,默认是30秒
    lease-expiration-duration-in-seconds: 90 #心跳失败最长超时间,默认90秒

itheima-eurekaserver 服务端,可以关闭保护机制

eureka:
  ...
  server:
    enable-self-preservation: false # false关闭保护机制。 15分钟内,如果心跳成功率<85%,则启动保护(服务提供者列表不再变化)

3.3.6 总结

  • 搭建EurekaServer

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

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

    • 引入eureka-client依赖
    • 在application.yml中配置eureka地址
    • 给RestTemplate添加@LoadBalanced注解
    • 用服务提供者的服务名称远程调用(由原来的ip:port改服务名(spring.application.name))

4 负载均衡Ribbon

章节知识点

  • Ribbon是什么
  • 负载均衡流程讲解
  • 负载均衡算法学习
  • Ribbon负载均衡使用

Ribbon是什么?

Ribbon是Netflix发布的负载均衡器,有助于控制HTTP客户端行为。为Ribbon配置服务提供者地址列表后,Ribbon就可基于负载均衡算法,自动帮助服务消费者请求。

概念:Ribbon是基于Http协议请求的客户端负载均衡器,能实现很丰富的负载均衡算法。

4.1 负载均衡流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ISbd8Zqj-1650383592033)(images\1628599474712.png)]

负载均衡流程如上图所示:

1:用户发起请求,会先到达itheima-order服务
2:itheima-order服务通过Ribbon负载均衡器从eurekaserver中获取服务列表
3:获取了服务列表后,轮询(负载均衡算法)调用

4.2 负载均衡算法[重点-面试]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4BcSIUwp-1650383592033)(…/…/…/…/…/坚果云共享文件夹/我的坚果云/java study/images/1628599691173.png)]

轮询调用会涉及到很多负载均衡算法,负载均衡算法比较多,关系图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t67wJCaq-1650383592033)(images\1628599720713.png)]

Ribbon的负载均衡算法策略如下表:

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

4.3 Ribbon负载均衡算法使用

Ribbon负载均衡算法的使用有2种方式

  • 代码方式

    • 注册IRule接口的实现类(负载均衡算法):在itheima-order的启动类中添加如下负载均衡注册代码:

      /**
       * 随机负载均衡算法
       * @return
       */
      @Bean
      public IRule randomRule() {
          return new RandomRule();
      }
      
  • 配置方式

    • 为指定服务配置负载均衡算法:在itheima-order的核心配置文件中添加如下配置:

      #注意配置到跟节点
      
      #指定服务使用指定负载均衡算法
      itheima-user:
        ribbon:
          NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #负载均衡规则
      

从懒加载 变为 饥饿加载

Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,在itheima-order的核心配置文件中,添加如下配置开启饥饿加载:

#注意配置到跟节点

#饥饿加载
ribbon:
  eager-load:
    clients: itheima-user #开启饥饿加载 
    enabled: true #指定对user这个服务饥饿加载

4.4. 总结

  • Ribbon负载均衡规则

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

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

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

5 http客户端Feign

章节知识点

  • Feign介绍
  • Feign入门案例学习
  • Feign日志功能、性能优化、最佳实践讲解

5.1 Feign介绍

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

User user = restTemplate.getForObject("http://itheima-user/user/"+orderInfo.getUserId(), User.class);

存在下面的问题:

  • 代码可读性差,编程体验不统一
  • 参数复杂URL难以维护

上面RestTemplate存在的问题可以使用Feign解决,那么什么是Feign?

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X9SQDu18-1650383592034)(images\1628603540688.png)]

5.2 Feign入门案例

定义和使用Feign客户端的步骤如下:

1:引入依赖包 spring-cloud-starter-openfeign
2:添加注解@EnableFeignClients开启Feign功能
3:定义远程调用接口,在接口中指明远程调用的【服务名字】、【方法签名】
4:注入接口,执行远程调用(接口)

1)引入依赖

itheima-order中引入如下依赖:


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

2)开启Feign功能

itheima-order的启动类OrderApplication添加@EnableFeignClients注解开启Feign功能,代码如下:

@SpringBootApplication
@EnableFeignClients
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
    //...其他略
}

3)定义远程调用接口

itheima-order中创建接口UserClient,代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CO0IYucn-1650383592035)(assets/1636707016650.png)]

上图代码如下:在itheima-order工程中添加

package com.itheima.client;
import com.itheima.user.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * order调用user服务(代替了 String url = "http://itheima-user/user/" + orderInfo.getUserId();)
 * 1.接口上使用@FeignClient(value="被调用服务名")
 * 2.定义被调用接口中的方法(基于被调用的controller编写)
 *  2.1 requestMapping中的路径必须是全路径(controller类上的+方法上的)
 *  2.2 使用PathVariable注解,必须取别名
 */
@FeignClient(value = "itheima-user")
public interface UserClient {

    /**
     * 调用用户微服中controller的方法
     */
    @GetMapping(value = "/user/{id}")
    public User one(@PathVariable(value = "id") Long id);
}

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

  • 服务名称:user
  • 请求方式:GET
  • 请求路径:/user/{username}
  • 请求参数:String username
  • 返回值类型:User

4)远程调用

修改itheima-orderOrderServiceImpl.one()方法,执行远程调用,代码如下:

@Autowired
private UserClient userClient;

/**
 * 根据ID查询订单信息
 */
@Override
public OrderInfo findById(Long id) {
    //1.查询订单
    OrderInfo orderInfo = orderDao.selectById(id);
    //2.根据订单查询用户信息->需要调用  【item-user】  服务
    User user = userClient.one(orderInfo.getUserId());
    //3.封装user
    orderInfo.setUser(user);
    //4.返回订单信息
    return orderInfo;
}

5.3 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的重试
NONE:默认的,不显示任何日志
BASIC:仅记录请求方法、URL、响应状态码以及执行时间
HEADERS:除了BASIC中定义的信息以外,还有请求和响应的头信息
FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据

SpringBoot日志配置;

5.3.1 Feign日志配置

要想让Feign日志生效,得结合着SpringBoot的日志配置一起使用

SpringBoot日志配置

logging:
  level:
    # feign 日志以什么级别监控哪个接口
    com.itheima: debug

配置Feign日志有两种方式:

  • 配置文件方式

    • 全局生效

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

      feign:
        client:
          config:
            itheima-user: #指定服务
              loggerLevel: FULL #日志级别
      
  • 代码方式

    • 注册日志级别

      /**
       * 注册日志级别
       * @return
       */
      @Bean
      public Logger.Level feignLogLevel() {
          return Logger.Level.FULL;
      }
      
    • 全局生效

      #如果是全局配置,则把它放到@EnableFeignClients这个注解中
      @EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)
      
    • 局部生效

      #如果是局部配置,则把它放到@FeignClient这个注解中
      @FeignClient(value = "itheima-user",configuration = FeignClientConfiguration.class)
      

5.3.2 Feign性能优化

Feign底层的客户端实现:

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

因此优化Feign的性能主要包括:

  • 使用连接池代替默认的URLConnection
  • 日志级别,最好用basic或none

Feign切换Apache HttpClient步骤如下:

1:引入依赖
2:配置连接池

1)引入依赖

itheima-order中引入如下依赖:


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

2)配置连接池

itheima-order的核心配置文件application.yml中添加如下配置:

feign:
  client:
    config:
      default: #这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
        loggerLevel: BASIC #日志级别
      itheima-user: #指定服务
        loggerLevel: BASIC #日志级别
  httpclient:
    enabled: true #开启feign对HttpClient的支持
    max-connections: 200 #最大的连接数
    max-connections-per-route: 50 #每个路径的最大连接数

5.3.3 Feign最佳实现[重点掌握]

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F8hh6BO2-1650383592036)(assets/1636819395042.png)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5mEX8XQ7-1650383592036)(images\1628608818706.png)]

Feign最佳实现流程如上图所示:

实现最佳实践方式二的步骤如下:
1:创建itheima-api,然后引入feign的starter依赖 itheima-user依赖
2:将itheima-order中编写的UserClient复制到itheima-api项目中
3:在itheima-order中引入itheima-api的依赖
4:重启测试

1)引入依赖

创建itheima-api,然后引入feign的starter依赖 itheima-user依赖

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

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

     <dependency>
         <groupId>com.itheimagroupId>
         <artifactId>itheima-pojoartifactId>
         <version>1.0-SNAPSHOTversion>
     dependency>
dependencies>

2)编写的UserClient

将itheima-order中编写的UserClient复制到itheima-api项目中

package com.itheima.client;

import com.itheima.user.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * order调用user服务(代替了 String url = "http://itheima-user/user/" + orderInfo.getUserId();)
 * 1.接口上使用@FeignClient(value="被调用服务名")
 * 2.定义被调用接口中的方法(基于被调用的controller编写)
 *  2.1 requestMapping中的路径必须是全路径(controller类上的+方法上的)
 *  2.2 使用PathVariable注解,必须取别名
 */
@FeignClient(value = "itheima-user")
public interface UserClient {

    /**
     * 调用用户微服中controller的方法
     */
    @GetMapping(value = "/user/{id}")
    public User one(@PathVariable(value = "id") Long id);
}

3)在itheima-order中引入itheima-api的依赖


<dependency>
    <groupId>com.itheimagroupId>
    <artifactId>itheima-apiartifactId>
    <version>1.0-SNAPSHOTversion>
dependency>

4)当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。

有两种方式解决:
方式一:指定FeignClient所在包

@EnableFeignClients(basePackages = "com.itheima.user.feign")

方式二:指定FeignClient字节码

@EnableFeignClients(clients = {UserClient.class})

5.3.4. 总结

  • Feign的使用步骤

    • 引入依赖
    • 添加@EnableFeignClients注解
    • 编写FeignClient接口
    • 使用FeignClient中定义的方法代替RestTemplate
  • Feign的日志配置:

    • 方式一是配置文件,feign.client.config.xxx.loggerLevel
    • 如果xxx是default则代表全局
    • 如果xxx是服务名称,例如userservice则代表某服务
  • 方式二是java代码配置Logger.Level这个Bean

    • 如果在@EnableFeignClients注解声明则代表全局
    • 如果在@FeignClient注解中声明则代表某服务
  • Feign的优化

    • 日志级别尽量用basic
    • 使用HttpClient或OKHttp代替URLConnection
      • 引入feign-httpClient依赖
      • 配置文件开启httpClient功能,设置连接池参数
  • Feign的最佳实践:

    • 让controller和FeignClient继承同一接口
    • 将FeignClient、POJO、Feign的默认配置都定义到一个项目中,供所有消费者使用

第2章 SpringCloud

今日内容介绍

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g9CkbEz3-1650383592036)(images/1641084401666.png)]

  • SpringCloud Gateway 微服务网关
  • Nacos 服务注册中心
  • Nacos 服务配置中心

1 微服务网关(gateway)

在SpringCloud中网关的实现包括两种:

  • gateway
  • zuul

Zuul是基于Servlet的实现,功能不强,性能较低,是阻塞式 。

SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。

Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。

1.1 为什么需要网关[重点-面试]

Gateway网关是我们服务的守门神,所有微服务的统一入口。

网关的核心功能特性

  • 请求路由
  • 权限控制
  • 限流

权限控制:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。

路由和负载均衡:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。

限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。****

跨域

1.2.gateway快速入门[重点掌握]

下面,我们就演示下网关的基本路由功能。基本步骤如下:

  1. 创建SpringBoot工程gateway,引入网关依赖
  2. 编写启动类
  3. 编写基础配置和路由规则
  4. 启动网关服务进行测试

1)创建微服务网关并引入依赖


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-parentartifactId>
        <groupId>com.itheimagroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>
    <artifactId>itheima-gatewayartifactId>

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

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

2)配置文件application.yml

server:
  # 网关端口
  port: 7001
spring:
  application:
    name: gateway
  cloud:
    gateway:
      routes: # 网关路由配置
        - id: user-service # 路由id,自定义,只要唯一即可
          # uri: http://127.0.0.1:18081 # 路由的目标地址 http就是固定地址
          uri: lb://itheima-user # 路由的目标地址 lb就是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
eureka:
  client:
    service-url:
      # EurekaServer的地址
      defaultZone: http://localhost:8001/eureka
  instance:
    prefer-ip-address: true
    instance-id: ${spring.application.name}:${server.port}:@project.version@
    lease-renewal-interval-in-seconds: 30 #心跳周期,默认是30秒
    lease-expiration-duration-in-seconds: 90 #心跳失败最长超时间,默认90秒

我们将符合Path 规则的一切请求,都代理到 uri参数指定的地址。

本例中,我们将 /user/**开头的请求,代理到lb://itheima-user,lb是负载均衡,根据服务名拉取服务列表,实现负载均衡。

3)创建启动类com.itheima.GatewayApplication

package com.itheima;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GatewayApplication {

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

4)路由测试

重启网关,访问http://localhost:7001/user/1时,符合/user/**规则,请求转发到uri:http://user/user/1,得到了结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6rg9eth7-1650383592037)(images/1636863455306.png)]

思考:

比如所有以/order开始的请求交给itheima-order服务,如何配置itheima-order服务的路由请求?

1.3 断言工厂

我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件

例如Path=/user/**是按照路径匹配,这个规则是由

org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来

处理的,像这样的断言工厂在SpringCloudGateway还有十几个:

名称 说明 示例
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 权重处理

我们只需要掌握Path这种路由工程就可以了。

上面使用案例如下:

spring:
  application:
    name: gateway
  cloud:
    gateway:
      routes: # 网关路由配置
        - id: user-service # 路由id,自定义,只要唯一即可
          # uri: http://127.0.0.1:18081 # 路由的目标地址 http就是固定地址
          uri: lb://user # 路由的目标地址 lb就是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
            # 这里user最好和路径中的路径保持一致,如果不一致,需要配置:
            #filters:
           	# - StripPrefix= 1  见下图
        - id: order-service
          uri: lb://order
          predicates:
            - Path=/order/**
            - After=2020-12-30T23:59:59.789+08:00[Asia/Shanghai]
            - Before=2022-12-30T23:59:59.789+08:00[Asia/Shanghai]
            - Between=2020-12-30T23:59:59.789+08:00[Asia/Shanghai],2022-12-30T23:59:59.789+08:00[Asia/Shanghai]
            - Cookie=uname, itheima
            - Header=X-Request-Id, \d+
            - Host=localhost:7001
            - Method=GET,POST
            - Query=uname,zhangsan
            - RemoteAddr=127.0.0.1/16

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BTntBrX0-1650383592037)(images/image-20220105092125175.png)]

1.4.过滤器工厂

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

1.4.1.路由过滤器的种类

Spring提供了31种不同的路由过滤器工厂。例如:

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

1.4.2.请求头过滤器

下面我们以AddRequestHeader 为例来讲解。

需求:给所有进入itheima-order的请求添加一个请求头:Heima=szheima121 nb!

1)只需要修改gateway服务的application.yml文件,添加路由过滤即可:

spring:
  cloud:
    gateway:
      routes: # 网关路由配置
        - id: order-service # 路由id,自定义,只要唯一即可
          #uri: http://127.0.0.1:18082 # 路由的目标地址 http就是固定地址
          uri: lb://itheima-order # 路由的目标地址 lb就是负载均衡,后面跟服务名称
          filters: # 过滤器
            - AddRequestHeader=Heima,szheima119 nb! # 添加请求头
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/order/** # 这个是按照路径匹配,只要以/user/开头就符合要求

**注意:**当前过滤器写在order-service路由下,因此仅仅对访问order-service的请求有效。

2)修改itheima-order中OrderController

/**
  * 查询订单信息
  */
@GetMapping(value = "/{id}")
public OrderInfo one(@PathVariable(value = "id") Long id, @RequestHeader("Heima") String head){
    System.out.println("*********head********"+head);
    return orderService.findById(id);
}

1.4.3.默认过滤器

1)如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下:

spring:
  cloud:
    gateway:
      default-filters: # 默认过滤项
        - AddRequestHeader= Heima,szheima119 nb!
      routes: # 网关路由配置
        - id: user-service # 路由id,自定义,只要唯一即可
          #uri: http://127.0.0.1:18081 # 路由的目标地址 http就是固定地址
          uri: lb://itheima-user # 路由的目标地址 lb就是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求 
        - id: order-service # 路由id,自定义,只要唯一即可
          #uri: http://127.0.0.1:18082 # 路由的目标地址 http就是固定地址
          uri: lb://itheima-order # 路由的目标地址 lb就是负载均衡,后面跟服务名称
#          filters: # 过滤器
#            - AddRequestHeader=Heima,szheima119 nb! # 添加请求头
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/order/** # 这个是按照路径匹配,只要以/user/开头就符合要求

1.4.4.总结

过滤器的作用是什么?

① 对路由的请求或响应做加工处理,比如添加请求头

② 配置在路由下的过滤器只对当前路由的请求生效

③default-filters的作用是什么? 对所有路由都生效的过滤器

1.5.全局过滤器

上一节学习的过滤器,网关提供了31种,但每一种过滤器的作用都是固定的。如果我们希望拦截请求,做自己的业务逻辑则没办法实现。

1.5.1.全局过滤器作用

全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与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中编写自定义逻辑,可以实现下列功能:

  • 登录状态判断
  • 权限校验
  • 请求限流等

1.5.2.自定义全局过滤器**[重点掌握-探花]**

需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:

  • 参数中是否有authorization,
  • authorization参数值是否为admin

如果同时满足则放行,否则拦截

实现:在gateway中定义一个过滤器:

package com.itheima.gateway.filters;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.Charset;

/**
 * 自定义全局过滤器
 */
@Component
public class AuthorizeFilter implements GlobalFilter {

    /**
     * -参数中是否有authorization,
     * - authorization参数值是否为admin
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpResponse response = exchange.getResponse();
        //1 获取请求头中authorization
        MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams(); 
        //2 校验是否为admin
        String authorization = queryParams.getFirst("authorization");
        //3 如果是则放行
        if("admin".equals(authorization)){
            return chain.filter(exchange);
        }
        //4 如果不是则 禁止访问
        //4.1 设置状态码
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        //4.2 设置请求已经完成
        return response.setComplete();
    }
}

响应提示信息:

response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
return response.writeWith(Flux.just(response.bufferFactory().wrap("请您先登录!".getBytes(Charset.forName("UTF-8")))));

测试:

http://localhost:7001/order/101?authorization=admin

http://localhost:7001/user/1?authorization=admin

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Hk4TwVh-1650383592038)(images/1636990785234.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KqNhijdD-1650383592038)(images/1636990964684.png)]

1.5.3.过滤器执行顺序

请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter

请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器:

排序的规则是什么呢?

  • 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前
  • GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
  • 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
  • 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。

详细内容,可以查看源码:

org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()方法是先加载defaultFilters,然后再加载某个route的filters,然后合并。

org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uwr9fcjs-1650383592039)(images/1636984290899.png)]

1.6.跨域问题

1.6.1.什么是跨域问题

跨域:域名不一致就是跨域,主要包括:

  • 域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
  • 域名相同,端口不同:localhost:8080和localhost8081

跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题

1.6.2.模拟跨域问题

找到资料nginx-1.18.0跨域测试.7z 解压放到没有空格和中文目录下,启动并访问。

可以在浏览器控制台看到下面的错误:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-niPRNgTw-1650383592039)(images/1636986996376.png)]

从localhost:8090访问localhost:7001,端口不同,显然是跨域的请求。

1.6.3.解决跨域问题

在gateway服务的application.yml文件中,添加下面的配置:

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

1.7 小结

1.为什么需要网关?

  • 请求路由
  • 权限控制
  • 限流
  • 统一入口
  • 跨域问题
  • 请求 响应处理

2.掌握gateway网关搭建

会配置网关路由过滤器

会配置网关全局过滤器

会解决跨域问题

2 Nacos 服务注册

国内公司一般都推崇阿里巴巴的技术,比如注册中心,SpringCloudAlibaba也推出了一个名为Nacos的注册中心。

2.1.认识和安装Nacos

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

安装方式可以参考课前资料《Nacos安装指南.md》

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

控制台账号:nacos 密码:nacos

2.2.服务注册到nacos[重点掌握]

Nacos是SpringCloudAlibaba的组件,而SpringCloudAlibaba也遵循SpringCloud中定义的服务注册、服务发现规范。因此使用Nacos和使用Eureka对于微服务来说,并没有太大区别。

主要差异在于:

  • 依赖不同
  • 服务地址不同

1)引入依赖

springcloud-parent父工程的pom文件中的中引入SpringCloudAlibaba的依赖:

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

然后在itheima-user和itheima-order中的pom文件中引入nacos-discovery依赖:

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

注意:不要忘了注释掉eureka的依赖。

2)配置nacos地址

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

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

注意:不要忘了注释掉eureka的地址

3)itheima-order修改OrderController

去掉请求头参数

package com.itheima.controller;

import com.itheima.order.pojo.OrderInfo;
import com.itheima.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(value = "/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    /**
     * 查询订单信息
     */
    @GetMapping(value = "/{id}")
    public OrderInfo one(@PathVariable(value = "id") Long id){
        return orderService.findById(id);
    }
}

4)重启

重启微服务后,登录nacos管理页面,可以看到微服务信息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bWdUTVS1-1650383592040)(images/1636996840889.png)]

5)测试

访问:http://localhost:18082/order/101 http://localhost:18081/user/1

2.3.服务分级存储模型[重点掌握]

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

  • 127.0.0.1:18081
  • 127.0.0.1:28081
  • 127.0.0.1:38081

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

  • 127.0.0.1:18081,在深圳机房
  • 127.0.0.1:28081,在深圳机房
  • 127.0.0.1:38081,在杭州机房

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

也就是说,itheima-user是服务,一个服务可以包含多个集群,如深圳、杭州,每个集群下可以有多个实例,形成分级模型,如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fO4QayGT-1650383592040)(images/1636997762904.png)]

微服务互相访问时,应该尽可能访问同集群实例,因为本地访问速度更快。当本集群内不可用时,才访问其它集群。例如:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wa0eUXgr-1650383592041)(images/1636997936128.png)]

深圳机房内的itheima-order应该优先访问同机房的itheima-user。

2.3.1.给itheima-user配置集群

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

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

重启两个user-service实例后,我们可以在nacos控制台看到下面结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mmp4fgqQ-1650383592041)(images/1636998478729.png)]

我们再次复制一个itheima-user启动配置,添加属性:

-Dserver.port=38081 -Dspring.cloud.nacos.discovery.cluster-name=HZ

配置如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e7ets58M-1650383592042)(images/1636998675210.png)]

启动UserApp3后再次查看nacos控制台:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iV5p2BSA-1650383592042)(images/1636998903744.png)]

2.3.2.同集群优先的负载均衡

默认的ZoneAvoidanceRule并不能实现根据同集群优先来实现负载均衡。

因此Nacos中提供了一个NacosRule的实现,可以优先从同集群中挑选实例。

1)给order-service配置集群信息

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

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

2)修改负载均衡规则

修改itheima-order的application.yml文件,修改负载均衡规则:

itheima-user:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则 
  1. 测试访问:http://localhost:18082/order/101

2.4.权重配置[重点掌握]

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

服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求。

但默认情况下NacosRule是同集群内随机挑选,不会考虑机器的性能问题。

因此,Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高。

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

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

注意:如果权重修改为0,则该实例永远不会被访问

2.5.环境隔离[重点掌握]

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

  • nacos中可以有多个namespace(环境隔离:test dev pro
  • namespace下有group(项目隔离 探花项目 头条项目)、service(实例隔离tanhua-server tanhua-service)等
  • 不同namespace之间相互隔离,例如不同namespace的服务互相不可见

2.5.1.创建namespace

默认情况下,所有service、data、group都在同一个namespace,名为public:

我们可以点击页面新增按钮,添加一个namespace:

然后,填写表单:

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

2.5.2.给微服务配置namespace

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

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

spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: SZ
        namespace: devnamespace # 命名空间,填ID

重启itheima-order后,访问控制台,可以看到下面的结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HtkYMkOW-1650383592043)(images/1631822989268.png)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-50Apeui0-1650383592043)(images/image-20210714000941256.png)]

在itheima-user修改配置:

-Dspring.cloud.nacos.discovery.namespace=devnamespace

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YuAUMgZW-1650383592044)(images/1637003839496.png)]

重启itheima-user后,访问控制台,可以看到下面的结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-reeYmk0K-1650383592044)(images/1637003966048.png)]

此时访问itheima-user,因为namespace相同,找到itheima-user:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rvxaRKGo-1650383592044)(images/1637004043157.png)]

2.6.Nacos与Eureka的区别[重点-面试]

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

  • 临时实例:如果实例宕机超过一定时间,会从服务列表剔除,默认的类型。
  • 非临时实例:如果实例宕机,不会从服务列表剔除,也可以叫永久实例。

配置一个服务实例为永久实例:

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

Nacos和Eureka整体结构类似,服务注册、服务拉取、心跳等待,但是也存在一些差异:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wFPFleZC-1650383592045)(images/1631823118790.png)]

  • Nacos与eureka的共同点
    • 都支持服务注册和服务拉取
    • 都支持服务提供者心跳方式做健康检测
  • Nacos与Eureka的区别
    • Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式,Eureka采用心跳模式,无主动检测模式。
    • 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
    • Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
    • Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式,如果需要在服务级别编辑或者存储配置信息,那么CP是必须的(CAP理论:C一致性,A高可用,P分区容错性)
    • Nacos使用的netty和服务进行连接,属于长连接。eureka使用定时发送和服务进行连接,属于短连接

3.Nacos配置管理

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

3.1.统一配置管理[重点掌握]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IQk8qZjX-1650383592045)(images/image-20210714164426792.png)]

Nacos一方面可以将配置集中管理,另一方可以在配置变更时,及时通知微服务,实现配置的热更新。

3.1.1.在nacos中添加配置文件

如何在nacos中管理配置呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HFRoS8Ei-1650383592045)(images/1631823734187.png)]

然后在弹出的表单中,填写配置信息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-34Ww3q5B-1650383592046)(images/1637002210728.png)]

配置内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DpV4Ebfb-1650383592046)(images/1637032854943.png)]

注意:项目的核心配置,需要热更新的配置才有放到nacos管理的必要。基本不会变更的一些配置还是保存在微服务本地比较好。

3.1.2.微服务从配置中心拉取配置

微服务要拉取nacos中管理的配置,并且与本地的application.yml配置合并,才能完成项目启动。

但如果尚未读取application.yml,又如何得知nacos地址呢?

因此spring引入了一种新的配置文件:bootstrap.yaml文件,会在application.yml之前被读取,流程如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SIP32sQS-1650383592046)(images/L0iFYNF.png)]

1)引入nacos-config依赖

首先,在user中,引入nacos-config的客户端依赖:


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

2)修改application.yaml

将application.yaml 修改为 bootstrap.yaml,并修改配置完整内容如下:

server:
  port: 18081
spring:
  application:
    name: itheima-user
  profiles:
    active: dev #开发环境,这里是dev
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/cloud_user?characterEncoding=UTF8&&serverTimezone=Asia/Shanghai
    username: root
    password: admin
  cloud:
    nacos:
      server-addr: localhost:8848
      config:
        file-extension: yaml # 文件后缀名
        server-addr: localhost:8848 #nacos配置中心地址
        namespace: devnamespace
      discovery:
        cluster-name: SZ
        #ephemeral: false # 设置为非临时实例
        namespace: devnamespace

这里会根据spring.cloud.nacos.server-addr获取nacos地址,再根据

${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}作为文件id,来读取配置itheima-user-dev.yaml

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N6YCnVon-1650383592047)(images/1637032645231.png)]

3)读取nacos配置

在user-service中的UserController中,读取uname配置:

@Value("${user.uname}")
private String uname;

@GetMapping("name")
public String now(){
    return "********"+uname+"*********";
}
  1. 在页面访问,可以看到效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RKYDYfqM-1650383592047)(images/1637032872733.png)]

3.2.配置热更新[重点掌握]

我们最终的目的,是修改nacos中的配置后,微服务中无需重启即可让配置生效,也就是配置热更新

要实现配置热更新,可以使用两种方式:

3.2.1.方式一

在@Value注入的变量所在类上添加注解@RefreshScope:

@RestController
@RequestMapping(value = "/user")
@RefreshScope //刷新配置
public class UserController { 

3.2.2.方式二

使用@ConfigurationProperties注解代替@Value注解(此处需要多加个@Setter )。

在itheima-user服务中处理如下:

@RestController
@RequestMapping(value = "/user")
//@RefreshScope //注释掉
@ConfigurationProperties(prefix = "user") //配置读取注解,并未uname添加set方法
@Setter 
public class UserController {

    @Autowired
    private UserService userService;

    //@Value("${user.uname}")   //去掉@Value("${xx}")
    private String uname;

    @GetMapping("name")
    public String now(){
        return "********"+uname+"*********";
    }
    ........
}

3.3.配置共享[重点掌握]

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

  • [spring.application.name]-[spring.profiles.active].yaml,例如:itheima-user-dev.yaml
  • [spring.application.name].yaml,例如:itheima-user.yaml

[spring.application.name].yaml不包含环境,因此可以被多个环境共享(dev test pro环境)。

下面我们通过案例来测试配置共享

1)添加一个环境共享配置

我们在nacos dev 环境 itheima-user.yaml文件:

用的什么环境就在那个环境中增加

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NjEldmmD-1650383592048)(images/1637038248474.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LgzQwezF-1650383592048)(images/image-20220304170355252.png)]

2)在user-service中读取共享配置

在user-service服务中,修改UserController

@RestController
@RequestMapping(value = "/user")
//@RefreshScope //注释掉
@ConfigurationProperties(prefix = "user")
@Setter
public class UserController {

    @Autowired
    private UserService userService;

    //@Value("${user.uname}")
    private String uname;

    private String sysname;

    @GetMapping("name")
    public String now(){
        return "********"+uname+"*********"+sysname+"*********";
    }
    ............
}

3)运行两个UserApp,使用不同的profile

UserApp(18081)使用的profile是dev,UserApp2(28081)使用的profile是test。

修改UserApp2这个启动项,改变其profile值:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wql0qOJB-1650383592048)(images/1637038773343.png)]

启动UserApp和UserApp2,访问http://localhost:18081/user/name,结果:

访问http://localhost:28081/user/name,结果:

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

4)配置共享的优先级

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

itheima-user-dev.yaml > itheima-user.yaml > application.yml

3.4.搭建Nacos集群

Nacos生产环境下一定要部署为集群状态,部署方式参考课前资料中的文档:

第3章 微服务保护

今日内容介绍

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bAZ60Y8K-1650383592049)(images/1641170823623.png)]

  • 初识Sentinel
  • 流量控制
  • 隔离和降级
  • 授权规则
  • 规则持久化

1.初识Sentinel[重点-面试]

1.1.雪崩问题及解决方案

1.1.1.雪崩问题

微服务中,服务间调用关系错综复杂,一个微服务往往依赖于多个其它微服务。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-etHRWTxJ-1650383592049)(images/1533829099748.png)]

如图,如果服务提供者I发生了故障,当前的应用的部分业务因为依赖于服务I,因此也会被阻塞。此时,其它不依赖于服务I的业务似乎不受影响。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ukPqHcI-1650383592050)(images/1533829198240.png)]

但是,依赖服务I的业务请求被阻塞,用户不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t6psf2Vp-1650383592050)(images/1533829307389.png)]

服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,那么当前服务也就不可用了。

那么,依赖于当前服务的其它服务随着时间的推移,最终也都会变的不可用,形成级联失败,雪崩就发生了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NpL8fyYb-1650383592050)(images/image-20210715172710340.png)]

1.1.2.超时处理

解决雪崩问题的常见方式有四种:

•超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lYFBiPSd-1650383592051)(images/image-20210715172820438.png)]

1.1.3.仓壁模式

方案2:仓壁模式(线程池隔离)

仓壁模式来源于船舱的设计:

船舱都会被隔板分离为多个独立空间,当船体破损时,只会导致部分空间进入,将故障控制在一定范围内,避免整个船体都被淹没。

于此类似,我们可以限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uH3v36Hn-1650383592051)(images/image-20210715173215243.png)]

1.1.4.断路器

断路器模式:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。

断路器会统计访问某个服务的请求数量,异常比例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WaI90x6H-1650383592051)(images/image-20210715173327075.png)]

当发现访问服务D的请求异常比例过高时,认为服务D有导致雪崩的风险,会拦截访问服务D的一切请求,形成熔断:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nEvc4Pop-1650383592052)(images/image-20210715173428073.png)]

1.1.5.限流

流量控制:限制业务访问的QPS(每秒处理请求的多少),避免服务因流量的突增而故障。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G2YP4340-1650383592052)(images/image-20210715173555158.png)]

1.1.6.总结

什么是雪崩问题?

  • 微服务之间相互调用,因为调用链中的一个服务故障,引起整个链路都无法访问的情况。

如何避免因瞬间高并发流量而导致服务故障?

  • 限流是对服务的保护,避免因瞬间高并发流量而导致服务故障,进而避免雪崩。是一种预防措施。

如何避免因服务故障引起的雪崩问题?

  • 超时处理、线程隔离、降级熔断是在部分服务故障时,将故障控制在一定范围,避免雪崩。是一种补救措施。

1.2.服务保护技术对比

在SpringCloud当中支持多种服务保护技术:

  • Netfix Hystrix
  • Sentinel
  • Resilience4J

早期比较流行的是Hystrix框架,但目前国内实用最广泛的还是阿里巴巴的Sentinel框架,这里我们做下对比:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZH3bSEQ9-1650383592052)(images/1637051375716.png)]

1.3.Sentinel介绍和安装

1.3.1.初识Sentinel

Sentinel是阿里巴巴开源的一款微服务流量控制组件。官网地址:https://sentinelguard.io/zh-cn/index.html

Sentinel 具有以下特征:

丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。

完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。

广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。

完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

1.3.2.安装Sentinel

1)下载

sentinel官方提供了UI控制台,方便我们对系统做限流设置。大家可以在GitHub下载。

课前资料也提供了下载好的jar包:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CmvC4C6S-1650383592053)(images/image-20210715174252531.png)]

2)运行

将jar包放到任意非中文目录,执行命令:

java -jar sentinel-dashboard-1.8.1.jar

如果要修改Sentinel的默认端口、账户、密码,可以通过下列配置:

配置项 默认值 说明
server.port 8080 服务端口
sentinel.dashboard.auth.username sentinel 默认用户名
sentinel.dashboard.auth.password sentinel 默认密码

例如,修改端口:

java -Dserver.port=8090 -jar sentinel-dashboard-1.8.1.jar

3)访问

访问http://localhost:8090页面,就可以看到sentinel的控制台了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zl6p6XFO-1650383592053)(images/image-20210715190827846.png)]

需要输入账号和密码,默认都是:sentinel

登录后,发现一片空白,什么都没有:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f9EwLytk-1650383592053)(images/1637225046736.png)]

这是因为我们还没有与微服务整合。

1.4.微服务整合Sentinel

我们在itheima-order中整合sentinel,并连接sentinel的控制台,步骤如下:

1)引入sentinel依赖


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

2)配置控制台

修改application.yaml文件,添加下面内容:

spring:
  cloud: 
    sentinel:
      transport:
        dashboard: localhost:8090

3)访问itheima-order的任意端点

打开浏览器,访问http://localhost:18082/order/101,这样才能触发sentinel的监控。

然后再访问sentinel的控制台,查看效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dJD6uwar-1650383592054)(images/1637057546846.png)]

2.流量控制[重点操作]

雪崩问题虽然有四种方案,但是限流是避免服务因突发的流量而发生故障,是对微服务雪崩问题的预防。我们先学习这种模式。

2.1.簇点链路

当请求进入微服务时,首先会访问DispatcherServlet,然后进入Controller、Service、Mapper,这样的一个调用链就叫做簇点链路。簇点链路中被监控的每一个接口就是一个资源

默认情况下sentinel会监控SpringMVC的每一个端点(Endpoint,也就是controller中的方法),因此SpringMVC的每一个端点(Endpoint)就是调用链路中的一个资源。

例如,我们刚才访问的itheima-order中的OrderController中的端点:/order/{id}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nKy9xh00-1650383592054)(images/1637063254238.png)]

流控、熔断等都是针对簇点链路中的资源来设置的,因此我们可以点击对应资源后面的按钮来设置规则:

  • 流控:流量控制
  • 降级:降级熔断
  • 热点:热点参数限流,是限流的一种
  • 授权:请求的权限控制

2.1.快速入门-直接模式

2.1.1.示例

点击资源/order/{id}后面的流控按钮,就可以弹出表单。

表单中可以填写限流规则,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F1UtsiOP-1650383592054)(images/1637063385702.png)]

其含义是限制 /order/{id}这个资源的单机QPS为2,即每秒只允许2次请求,超出的请求会被拦截并报错。

2.1.2.练习

需求:给 /order/{id}这个资源设置流控规则,QPS不能超过 5,然后测试。

1)首先在sentinel控制台添加限流规则

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JqLRRJQy-1650383592054)(images/1637063536857.png)]

2)利用jmeter测试

如果没有用过jmeter,可以参考课前资料提供的文档《Jmeter快速入门.md》

课前资料提供了编写好的Jmeter测试样例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e8Wny9PK-1650383592055)(images/image-20210715200431615.png)]

打开jmeter,导入课前资料提供的测试样例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hvoJjpKj-1650383592055)(images/image-20210715200537171.png)]

选择:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HjgjBmcd-1650383592055)(images/image-20210715200635414.png)]

20个用户,2秒内运行完,QPS是10,超过了5.

选中流控入门,QPS<5右键运行:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hcSLqOn2-1650383592055)(images/image-20210715200804594.png)]

注意,不要点击菜单中的执行按钮来运行。

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wEuX6Qw2-1650383592056)(images/image-20210715200853671.png)]

可以看到,成功的请求每次只有5个

2.2.流控模式

在添加限流规则时,点击高级选项,可以选择三种流控模式

  • 直接:统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式
  • 关联:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流
  • 链路:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JS6aoN0o-1650383592056)(images/1637227103023.png)]

快速入门测试的就是直接模式。

2.2.1.关联模式

关联模式:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流

配置规则

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BfvfKeHA-1650383592056)(images/image-20210715202540786.png)]

语法说明:当/write资源访问量触发阈值时,就会对/read资源限流,避免影响/write资源。

使用场景:比如用户支付时需要修改订单状态,同时用户要查询订单。查询和修改操作会争抢数据库锁,产生竞争。业务需求是优先支付和更新订单的业务,因此当修改订单业务触发阈值时,需要对查询订单业务限流。

需求说明

  • 在OrderController新建两个端点:/order/query和/order/update,无需实现业务

  • 配置流控规则,当/order/ update资源被访问的QPS超过5时,对/order/query请求限流

1)定义/order/query端点,模拟订单查询

@GetMapping("/query")
public String queryOrder() {
    return "查询订单成功";
}

2)定义/order/update端点,模拟订单更新

@GetMapping("/update")
public String updateOrder() {
    return "更新订单成功";
}

重启服务,查看sentinel控制台的簇点链路:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5p5xOe73-1650383592057)(images/1637067169394.png)]

3)配置流控规则

对哪个端点限流,就点击哪个端点后面的按钮。我们是对订单查询/order/query限流,因此点击它后面的按钮:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6e4H9x1l-1650383592057)(images/image-20210716101934499.png)]

在表单中填写流控规则:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yhkXcn49-1650383592057)(images/1637067347687.png)]

4)在Jmeter测试

选择《流控模式-关联》:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TSpD7vP1-1650383592058)(images/image-20210716102416266.png)]

可以看到1000个用户,100秒,因此QPS为10,超过了我们设定的阈值:5

查看http请求:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tKaTF2na-1650383592058)(images/image-20210716102532554.png)]

请求的目标是/order/update,这样这个断点就会触发阈值。

但限流的目标是/order/query,我们在浏览器访问,可以发现确实被限流了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fALRtOvC-1650383592058)(images/1637067592843.png)]

5)总结

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5cvFyHCu-1650383592058)(images/image-20210716103143002.png)]

2.2.2.链路模式

链路模式:只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值。

配置示例

例如有两条请求链路:

  • /test1 --> /common

  • /test2 --> /common

如果只希望统计从/test2进入到/common的请求,则可以这样配置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xmmqfOE0-1650383592059)(images/image-20210716103536346.png)]

实战案例

需求:有查询订单和创建订单业务,两者都需要查询商品。针对从查询订单进入到查询商品的请求统计,并设置限流。

步骤:

  1. 在OrderService中添加一个queryGoods方法,不用实现业务
  2. 在OrderController中,改造/order/query端点,调用OrderService中的queryGoods方法
  3. 在OrderController中添加一个/order/save的端点,调用OrderService的queryGoods方法
  4. 在OrderService的queryGoods方法添加@SentinelResource注解,配置关闭SpringMVC的资源聚合
  5. 给queryGoods设置限流规则,从/order/query进入queryGoods的方法限制QPS必须小于2

1)添加查询商品方法

在itheima-order服务中,添加一个queryGoods方法:

OrderService接口
/**
  * 查询商品方法
  */
public void queryGoods();

OrderServiceImpl/**
  * 查询商品方法
  */
public void queryGoods(){
    System.out.println("查询商品");
}

2)查询订单时,查询商品

在itheima-order的OrderController中,修改/order/query端点的业务逻辑:

@GetMapping("/query")
public String queryOrder() {
    // 查询商品
    orderService.queryGoods();
    // 查询订单
    System.out.println("查询订单");
    return "查询订单成功";
}

3)新增订单,查询商品

在order-service的OrderController中,修改/order/save端点,模拟新增订单:

@GetMapping("/save")
public String saveOrder() {
    // 查询商品
    orderService.queryGoods();
    // 查询订单
    System.err.println("新增订单");
    return "新增订单成功";
}

4)给查询商品添加资源标记

默认情况下,OrderServiceImpl中的方法是不被Sentinel监控的,需要我们自己通过注解来标记要监控的方法。

给OrderServiceImpl的queryGoods方法添加@SentinelResource注解:

@SentinelResource("goods")
public void queryGoods(){
    System.err.println("查询商品");
}

链路模式中,是对不同来源的两个链路做监控。但是sentinel默认会给进入SpringMVC的所有请求设置同一个root资源,会导致链路模式失效。

我们需要关闭这种对SpringMVC的资源聚合,修改itheima-order服务的application.yml文件:

spring:
  cloud:
    sentinel:
      web-context-unify: false # 关闭context整合

重启服务,访问/order/query和/order/save,可以查看到sentinel的簇点链路规则中,出现了新的资源:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a2IGC3Ht-1650383592059)(images/1637069244914.png)]

5)添加流控规则

点击goods资源后面的流控按钮,在弹出的表单中填写下面信息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nECg0X1Y-1650383592059)(images/1637069568361.png)]

只统计从/order/query进入/goods的资源,QPS阈值为2,超出则被限流。

6)Jmeter测试

选择《流控模式-链路》:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gVJef437-1650383592060)(images/image-20210716105612312.png)]

可以看到这里200个用户,50秒内发完,QPS为4,超过了我们设定的阈值2

一个http请求是访问/order/save:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kh22muNc-1650383592060)(images/image-20210716105812789.png)]

运行的结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s6179LNU-1650383592060)(images/image-20210716110027064.png)]

完全不受影响。

另一个是访问/order/query:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZxsNcQBh-1650383592061)(images/image-20210716105855951.png)]

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kWQmnDhs-1650383592061)(images/image-20210716105956401.png)]

每次只有2个通过。

2.2.3.总结

流控模式有哪些?

•直接:对当前资源限流

•关联:高优先级资源触发阈值,对低优先级资源限流。

•链路:阈值统计时,只统计从指定资源进入当前资源的请求,是对请求来源的限流

2.3.流控效果

在流控的高级选项中,还有一个流控效果选项:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yBPpHrP9-1650383592061)(images/image-20210716110225104.png)]

流控效果是指请求达到流控阈值时应该采取的措施,包括三种:

  • 快速失败:达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。是默认的处理方式。

  • warm up:预热模式,对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。

  • 排队等待:让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长

2.3.1.warm up

阈值一般是一个微服务能承担的最大QPS,但是一个服务刚刚启动时,一切资源尚未初始化(冷启动),如果直接将QPS跑到最大值,可能导致服务瞬间宕机。

warm up也叫预热模式,是应对服务冷启动的一种方案。请求阈值初始值是 maxThreshold(最大阈值) / coldFactor(冷因子),持续指定时长后,逐渐提高到maxThreshold值。而coldFactor的默认值是3.

例如,我设置QPS的maxThreshold为10,预热时间为5秒,那么初始阈值就是 10 / 3 ,也就是3,然后在5秒后逐渐增长到10.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LCWngnA5-1650383592061)(images/image-20210716110629796.png)]

案例

需求:给/order/{id}这个资源设置限流,最大QPS为10,利用warm up效果,预热时长为5秒

1)配置流控规则:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-de9McHbw-1650383592062)(images/1637070723519.png)]

2)Jmeter测试

选择《流控效果,warm up》:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k0U7SEAl-1650383592062)(images/image-20210716111136699.png)]

QPS为10.

刚刚启动时,大部分请求失败,成功的只有3个,说明QPS被限定在3:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z0cogyoV-1650383592062)(images/image-20210716111303701.png)]

随着时间推移,成功比例越来越高:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DGwCYUVV-1650383592062)(images/image-20210716111404717.png)]

到Sentinel控制台查看实时监控:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LCmrk6qX-1650383592063)(images/1637071036729.png)]

2.3.2.排队等待

当请求超过QPS阈值时,快速失败和warm up 会拒绝新的请求并抛出异常。

而排队等待则是让所有请求进入一个队列中,然后按照阈值允许的时间间隔依次执行。后来的请求必须等待前面执行完成,如果请求预期的等待时间超出最大时长,则会被拒绝。

工作原理

例如:QPS = 5,意味着每200ms处理一个队列中的请求;timeout = 2000,意味着预期等待时长超过2000ms的请求会被拒绝并抛出异常。

那什么叫做预期等待时长呢?

比如现在一下子来了12 个请求,因为每200ms执行一个请求,那么:

  • 第6个请求的预期等待时长 = 200 * (6 - 1) = 1000ms
  • 第12个请求的预期等待时长 = 200 * (12-1) = 2200ms

现在,第1秒同时接收到10个请求,但第2秒只有1个请求,此时QPS的曲线这样的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7fgxcqoB-1650383592063)(images/image-20210716113147176.png)]

如果使用队列模式做流控,所有进入的请求都要排队,以固定的200ms的间隔执行,QPS会变的很平滑:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2PSW9Dh9-1650383592064)(images/image-20210716113426524.png)]

平滑的QPS曲线,对于服务器来说是更友好的。

案例

需求:给/order/{id}这个资源设置限流,最大QPS为10,利用排队的流控效果,超时时长设置为5s

1)添加流控规则

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C8fGMsNU-1650383592064)(images/1637072328761.png)]

2)Jmeter测试

选择《流控效果,队列》:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HZqGpW1S-1650383592065)(images/image-20210716114243558.png)]

QPS为15,已经超过了我们设定的10。

如果是之前的 快速失败、warmup模式,超出的请求应该会直接报错。

但是我们看看队列模式的运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h23YIZ1q-1650383592065)(images/image-20210716114429361.png)]

全部都通过了。

再去sentinel查看实时监控的QPS曲线:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Z5SFH2q-1650383592066)(images/image-20210716114522935.png)]

QPS非常平滑,一致保持在10,但是超出的请求没有被拒绝,而是放入队列。因此响应时间(等待时间)会越来越长。

当队列满了以后,才会有部分请求失败:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KqstQlaA-1650383592066)(images/image-20210716114651137.png)]

2.3.3.总结

流控效果有哪些?

  • 快速失败:QPS超过阈值时,拒绝新的请求

  • warm up: QPS超过阈值时,拒绝新的请求;QPS阈值是逐渐提升的,可以避免冷启动时高并发导致服务宕机。

  • 排队等待:请求会进入队列,按照阈值允许的时间间隔依次执行请求;如果请求预期等待时长大于超时时间,直接拒绝

2.4.热点参数限流

之前的限流是统计访问某个资源的所有请求,判断是否超过QPS阈值。而热点参数限流是分别统计参数值相同的请求,判断是否超过QPS阈值。

2.4.1.全局参数限流

例如,一个根据id查询商品的接口:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fp6QCape-1650383592066)(images/image-20210716115014663.png)]

访问/goods/{id}的请求中,id参数值会有变化,热点参数限流会根据参数值分别统计QPS,统计结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ahGFr22G-1650383592067)(images/image-20210716115131463.png)]

当id=1的请求触发阈值被限流时,id值不为1的请求不受影响。

配置示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CDbQ7mNI-1650383592067)(images/image-20210716115232426.png)]

代表的含义是:对hot这个资源的0号参数(第一个参数)做统计,每1秒相同参数值的请求数不能超过5

2.4.2.热点参数限流

案例需求:给/order/{orderId}这个资源添加热点参数限流,规则如下:

•默认的热点参数规则是每1秒请求量不超过2

•给102这个参数设置例外:每1秒请求量不超过4

•给103这个参数设置例外:每1秒请求量不超过10

注意事项:热点参数限流对默认的SpringMVC资源无效,需要利用@SentinelResource注解标记资源

1)标记资源

给itheima-order中的OrderController中的/order/{id}资源添加注解:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AHgwiyBy-1650383592067)(images/1637239535875.png)]

2)热点参数限流规则

访问该接口,可以看到我们标记的hot资源出现了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bzts8CEU-1650383592068)(images/1637074359196.png)]

注意:这里不要点击hot后面的按钮,页面有BUG

点击左侧菜单中热点规则菜单:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MUNRaj18-1650383592068)(images/image-20210716120319009.png)]

点击新增,填写表单:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RDv9D4xd-1650383592068)(images/image-20210716120536714.png)]

3)Jmeter测试

选择《热点参数限流 QPS1》:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vd0haR7P-1650383592069)(images/image-20210716120754527.png)]

这里发起请求的QPS为5.

包含3个http请求:

普通参数,QPS阈值为2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SIBSyRqi-1650383592069)(images/image-20210716120840501.png)]

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lvXmVgT0-1650383592069)(images/image-20210716121105567.png)]

例外项,QPS阈值为4

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8vDCsJpc-1650383592070)(images/image-20210716120900365.png)]

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AZk2etEt-1650383592070)(images/image-20210716121201630.png)]

例外项,QPS阈值为10

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0LqfXuL9-1650383592070)(images/image-20210716120919131.png)]

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GRJ51Nk6-1650383592071)(images/image-20210716121220305.png)]

3.隔离和降级[重点操作]

限流是一种预防措施,虽然限流可以尽量避免因高并发而引起的服务故障,但服务还会因为其它原因而故障。

而要将这些故障控制在一定范围,避免雪崩,就要靠线程隔离(舱壁模式)和熔断降级手段了。

线程隔离之前讲到过:调用者在调用服务提供者时,给每个调用的请求分配独立线程池,出现故障时,最多消耗这个线程池内资源,避免把调用者的所有资源耗尽。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pRc7JUpg-1650383592071)(images/image-20210715173215243.png)]

熔断降级:是在调用方这边加入断路器,统计对服务提供者的调用,如果调用的失败比例过高,则熔断该业务,不允许访问该服务的提供者了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YrnMq0Wr-1650383592071)(images/image-20210715173428073.png)]

可以看到,不管是线程隔离还是熔断降级,都是对客户端(调用方)的保护。需要在调用方 发起远程调用时做线程隔离、或者服务熔断。

而我们的微服务远程调用都是基于Feign来完成的,因此我们需要将Feign与Sentinel整合,在Feign里面实现线程隔离和服务熔断。

3.1.熔断降级

SpringCloud中,微服务调用都是通过Feign来实现的,因此做客户端保护必须整合Feign和Sentinel。

3.1.1.修改配置,开启sentinel功能

修改OrderService的application.yml文件,开启Feign的Sentinel功能:

feign:
  sentinel:
    enabled: true # 开启feign对sentinel的支持

3.1.2.编写失败降级逻辑

业务失败后,不能直接报错,而应该返回用户一个友好提示或者默认结果,这个就是失败降级逻辑。

给FeignClient编写失败后的降级逻辑

①方式一:FallbackClass,无法对远程调用的异常做处理

②方式二:FallbackFactory,可以对远程调用的异常做处理,我们选择这种

这里我们演示方式二的失败降级处理。

1)在feing-api项目中定义类,实现FallbackFactory:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5RLWACNS-1650383592072)(images/1637242862791.png)]

代码:

package com.itheima.client.fallback;

import com.itheima.client.UserClient;
import com.itheima.user.pojo.User;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * 自定义失败降级处理
 */
@Slf4j
@Component
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {

    /**
     * 业务处理
     * @param throwable
     * @return
     */
    @Override
    public UserClient create(Throwable throwable) {
        return new UserClient() {
            @Override
            public User one(Long id) {
                log.error("查询用户异常", throwable);
                User user = new User();
                user.setAddress("xx");
                user.setUsername("yu");
                return user;
            }
        };
    }
}

2)在feing-api项目中的UserClient接口中使用UserClientFallbackFactory:

package com.itheima.client;

import com.itheima.client.fallback.UserClientFallbackFactory;
import com.itheima.user.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * 将String url = "http://itheima-user/user/"+orderInfo.getUserId();
 * 替换成当前接口
 * 1.通过@FeignClient("被调用的服务名称")
 * 2.定义方法 需要跟 被调用的微服务要一样
 *  2.1.路径必须是被调用微服务的完整路径
 *  2.2.参数一定要加别名
 */
@FeignClient(value = "itheima-user",fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {

    /***
     * 根据id查询用户详情
     */
    @GetMapping(value = "/user/{id}")
    public User one(@PathVariable(value = "id") Long id);
}

重启后,访问一次订单查询业务,然后查看sentinel控制台,可以看到新的簇点链路:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-45QfO6JE-1650383592072)(images/1637243095378.png)]

3.1.3.总结

Sentinel支持的雪崩解决方案:

  • 线程隔离(仓壁模式)
  • 降级熔断

Feign整合Sentinel的步骤:

  • 在application.yml中配置:feign.sentienl.enable=true
  • 创建UserClientFallbackFactory实现FallbackFactory
  • 将FallbackFactory配置到FeignClient

3.2.线程隔离(舱壁模式)

3.2.1.线程隔离的实现方式

线程隔离有两种方式实现:

  • 线程池隔离

  • 信号量隔离(Sentinel默认采用)

如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ILPNNpvb-1650383592072)(images/image-20210716123036937.png)]

线程池隔离:给每个服务调用业务分配一个线程池,利用线程池本身实现隔离效果

信号量隔离:不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号量上限时,禁止新的请求。

两者的优缺点:

扇出:请求到A微服务,A微服务依赖N个微服务,请求到A就会扇出N个微服务

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zeP58zbs-1650383592073)(images/image-20210716123240518.png)]

3.2.2.sentinel的线程隔离

用法说明

在添加限流规则时,可以选择两种阈值类型:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bJgkyhki-1650383592073)(images/1637243320525.png)]

  • QPS:就是每秒的请求数,在快速入门中已经演示过

  • 线程数:是该资源能使用用的tomcat线程数的最大值。也就是通过限制线程数量,实现线程隔离(舱壁模式)。

案例需求:给 order-service服务中的UserClient的查询用户接口设置流控规则,线程数不能超过 2。然后利用jemeter测试。

1)配置隔离规则

选择feign接口后面的流控按钮:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-my5XXgnC-1650383592073)(images/1637243486672.png)]

填写表单:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jzP22x0c-1650383592074)(images/1637244456134.png)]

2)Jmeter测试

选择《阈值类型-线程数<2》:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sepmclJp-1650383592074)(images/image-20210716124229894.png)]

一次发生10个请求,有较大概率并发线程数超过2,而超出的请求会走之前定义的失败降级逻辑。

查看运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FGSw7CUH-1650383592074)(images/image-20210716124147820.png)]

发现虽然结果都是通过了,不过部分请求得到的响应是降级返回的null信息。

3.2.3.总结

线程隔离的两种手段是?

  • 信号量隔离

  • 线程池隔离

信号量隔离的特点是?

  • 基于计数器模式,简单,开销小

线程池隔离的特点是?

  • 基于线程池模式,有额外开销,但隔离控制更强

3.3.熔断降级

熔断降级是解决雪崩问题的重要手段。其思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问该服务的请求。

断路器控制熔断和放行是通过状态机来完成的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rB0s9j20-1650383592074)(images/image-20210716130958518.png)]

状态机包括三个状态:

  • closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态
  • open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态5秒后会进入half-open状态
  • half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。
    • 请求成功:则切换到closed状态
    • 请求失败:则切换到open状态

断路器熔断策略有三种:慢调用、异常比例、异常数

3.3.1.慢调用

慢调用:业务的响应时长(RT)大于指定时长的请求认定为慢调用请求。在指定时间内,如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。

例如:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gnuHmVII-1650383592075)(images/image-20210716145934347.png)]

解读:RT超过500ms的调用是慢调用,统计最近10000ms内的请求,如果请求量超过10次,并且慢调用比例不低于0.5,则触发熔断,熔断时长为5秒。然后进入half-open状态,放行一次请求做测试。

案例

需求:给 UserClient的查询用户接口设置降级规则,慢调用的RT阈值为50ms,统计时间为1秒,最小请求数量为5,失败阈值比例为0.4,熔断时长为5

1)设置慢调用

修改user-service中的/user/{id}这个接口的业务。通过休眠模拟一个延迟时间:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9W3Jl9yu-1650383592075)(images/1637245124539.png)]

此时,orderId=101的订单,关联的是id为1的用户,调用时长为60ms:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K4K6G40l-1650383592075)(images/image-20210716150510956.png)]

orderId=102的订单,关联的是id为2的用户,调用时长为非常短;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F7JaWFUi-1650383592076)(images/image-20210716150605208.png)]

2)设置熔断规则

下面,给feign接口设置降级规则:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R1UfRxf1-1650383592076)(images/image-20210716150654094.png)]

规则:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gT7cBSRo-1650383592080)(images/1637246218366.png)]

超过50ms的请求都会被认为是慢请求

3)测试(手速要快)

在浏览器访问:http://localhost:18082/order/101,快速刷新5次,可以发现:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-91TSNYUC-1650383592081)(images/image-20210716150911004.png)]

触发了熔断,请求时长缩短至5ms,快速失败了,并且走降级逻辑,返回的null

在浏览器访问:http://localhost:18082/order/102,竟然也被熔断了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XJBGPtMr-1650383592082)(images/image-20210716151107785.png)]

3.3.2.异常比例、异常数

异常比例或异常数:统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。

例如,一个异常比例设置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e7HZ2K1P-1650383592082)(images/image-20210716131430682.png)]

解读:统计最近1000ms内的请求,如果请求量超过10次,并且异常比例不低于0.4,则触发熔断。

一个异常数设置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qzThehhW-1650383592082)(images/image-20210716131522912.png)]

解读:统计最近1000ms内的请求,如果请求量超过10次,并且异常比例不低于2次,则触发熔断。

案例

需求:给 UserClient的查询用户接口设置降级规则,统计时间为1秒,最小请求数量为5,失败阈值比例为0.4,熔断时长为5s

1)设置异常请求

首先,修改user-service中的/user/{id}这个接口的业务。手动抛出异常,以触发异常比例的熔断:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wLzcBmBL-1650383592083)(images/1637246048801.png)]

也就是说,id 为 2时,就会触发异常

2)设置熔断规则

下面,给feign接口设置降级规则:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8dTGtdg8-1650383592083)(images/image-20210716150654094.png)]

规则:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wlJ6yndZ-1650383592083)(images/1637246261038.png)]

在5次请求中,只要异常比例超过0.4,也就是有2次以上的异常,就会触发熔断。

3)测试(手速要快)

在浏览器快速访问:http://localhost:18082/order/102,快速刷新5次,触发熔断:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xfuBQ1aw-1650383592084)(images/image-20210716151722916.png)]

此时,我们去访问本来应该正常的103:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NsvUtfzQ-1650383592084)(images/image-20210716151844817.png)]

4.授权规则[了解]

授权规则可以对请求方来源做判断和控制。

4.1.授权规则

4.1.1.基本规则

授权规则可以对调用方的来源做控制,有白名单和黑名单两种方式。

  • 白名单:来源(origin)在白名单内的调用者允许访问

  • 黑名单:来源(origin)在黑名单内的调用者不允许访问

点击左侧菜单的授权,可以看到授权规则:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DVH1tBPL-1650383592085)(images/image-20210716152010750.png)]

  • 资源名:就是受保护的资源,例如/order/{id}

  • 流控应用:是来源者的名单,

    • 如果是勾选白名单,则名单中的来源被许可访问。
    • 如果是勾选黑名单,则名单中的来源被禁止访问。

比如:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YhnESv20-1650383592085)(images/image-20210716152349191.png)]

我们允许请求从gateway到order-service,不允许浏览器访问order-service,那么白名单中就要填写网关的来源名称(origin)

4.1.2.如何获取origin

Sentinel是通过RequestOriginParser这个接口的parseOrigin来获取请求的来源的。

public interface RequestOriginParser {
    /**
     * 从请求request对象中获取origin,获取方式自定义
     */
    String parseOrigin(HttpServletRequest request);
}

这个方法的作用就是从request对象中,获取请求者的origin值并返回。

默认情况下,sentinel不管请求者从哪里来,返回值永远是default,也就是说一切请求的来源都被认为是一样的值default。

因此,我们需要自定义这个接口的实现,让不同的请求,返回不同的origin

例如order-service服务中,我们定义一个RequestOriginParser的实现类:

package com.itheima.sentinel;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;

/**
 * 自定义接口的实现,让不同的请求,返回不同的origin
 */
@Component
public class HeaderOriginParser implements RequestOriginParser {

    /**
     * 获取请求头值 为空返回blank
     * @param httpServletRequest
     * @return
     */
    @Override
    public String parseOrigin(HttpServletRequest httpServletRequest) {
        // 1.获取请求头
        String origin = httpServletRequest.getHeader("origin");
        // 2.非空判断
        if(StringUtils.isEmpty(origin)){
            origin = "blank";
        }
        return origin;
    }
}

我们会尝试从request-header中获取origin值。

4.1.3.给网关添加请求头

既然获取请求origin的方式是从reques-header中获取origin值,我们必须让所有从gateway路由到微服务的请求都带上origin头

这个需要利用之前学习的一个GatewayFilter来实现,AddRequestHeaderGatewayFilter。

1)修改gateway服务中的application.yml,添加一个defaultFilter:

spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: SZ #集群设置名称
        namespace: devnamespace #环境隔离 命名空间ID
    gateway:
      default-filters: #全局过滤器配置 针对所有的微服务
        - AddRequestHeader=Heima,shenzhen119 nb!!!
        - AddRequestHeader=origin,gateway
      routes:
       # ...略

从gateway路由的所有请求都会带上origin头,值为gateway。而从其它地方到达微服务的请求则没有这个头。

注意:删除eureka配置

2)添加nacos起步依赖


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

注意:删除eureka依赖

4.1.4.配置授权规则

接下来,我们添加一个授权规则,放行origin值为gateway的请求。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DFO8ZNjG-1650383592085)(images/image-20210716153250134.png)]

配置如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4gQiSC4K-1650383592086)(images/1637247706908.png)]

现在,我们直接跳过网关,访问order-service服务:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NP2hC5Dj-1650383592086)(images/1637247818494.png)]

通过网关访问:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W6IGDAnN-1650383592087)(images/1637247863623.png)]

4.2.自定义异常结果

默认情况下,发生限流、降级、授权拦截时,都会抛出异常到调用方。异常结果都是flow limmiting(限流)。这样不够友好,无法得知是限流还是降级还是授权拦截。

4.2.1.异常类型

而如果要自定义异常时的返回结果,需要实现BlockExceptionHandler接口:

public interface BlockExceptionHandler {
    /**
     * 处理请求被限流、降级、授权拦截时抛出的异常:BlockException
     */
    void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception;
}

这个方法有三个参数:

  • HttpServletRequest request:request对象
  • HttpServletResponse response:response对象
  • BlockException e:被sentinel拦截时抛出的异常

这里的BlockException包含多个不同的子类:

异常 说明
FlowException 限流异常
ParamFlowException 热点参数限流的异常
DegradeException 降级异常
AuthorityException 授权规则异常
SystemBlockException 系统规则异常

4.2.2.自定义异常处理

下面,我们就在order-service定义一个自定义异常处理类:

package com.itheima.sentinel;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        String msg = "未知异常";
        int status = 429;

        if (e instanceof FlowException) {
            msg = "请求被限流了";
        } else if (e instanceof ParamFlowException) {
            msg = "请求被热点参数限流";
        } else if (e instanceof DegradeException) {
            msg = "请求被降级了";
        } else if (e instanceof AuthorityException) {
            msg = "没有权限访问";
            status = 401;
        }

        response.setContentType("application/json;charset=utf-8");
        response.setStatus(status);
        response.getWriter().println("{\"msg\": " + msg + ", \"status\": " + status + "}");
    }
}

重启测试,在不同场景下,会返回不同的异常消息.

限流:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iibATPoX-1650383592087)(images/1637248135372.png)]

授权拦截时:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ax90PjPb-1650383592087)(images/1637248166833.png)]

5.规则持久化[了解]

现在,sentinel的所有规则都是内存存储,重启后所有规则都会丢失。在生产环境下,我们必须确保这些规则的持久化,避免丢失。

5.1.规则管理模式

规则是否能持久化,取决于规则管理模式,sentinel支持三种规则管理模式:

  • 原始模式:Sentinel的默认模式,将规则保存在内存,重启服务会丢失。
  • pull模式
  • push模式

5.1.1.pull模式

pull模式:控制台将配置的规则推送到Sentinel客户端,而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库中查询,更新本地规则。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1iDjGPmJ-1650383592088)(images/image-20210716154155238.png)]

5.1.2.push模式

push模式:控制台将配置规则推送到远程配置中心,例如Nacos。Sentinel客户端监听Nacos,获取配置变更的推送消息,完成本地配置更新。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D7yBvwfp-1650383592088)(images/image-20210716154215456.png)]

5.2.实现push模式

详细步骤可以参考课前资料的《sentinel规则持久化》:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NvfIYqoF-1650383592089)(images/image-20210716154255466.png)]

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号