学习说明:
Spring Cloud Alibaba核心组件 Nacos(服务注册与发现和分布式配置中心)、Sentinel(服务保护框架)、Seata(分布式事务解决框架)、阿里云OSS、Alibaba Cloud SchedulerX等。
注意事项:
本知识点需要有一定的SpringBoot基础知识,如果对SpringBoot不了解的话、可以先学习SpringBoot基础内容。
备注:此笔记来源于蚂蚁课堂(余老师)

1 微服务架构演变过程

传统单体架构——分布式架构——SOA面向服务架构——微服务架构模式

1.1 传统架构

  1. 传统的架构,也就是为单点应用,也就是大家在早期所学习的JavaEE知识SSH或者SSM架构模式,会采用分层架构模式:数据库访问层、业务逻辑层、控制层,从前端到后台所有的代码都是一个开发者去完成。<br /> 该架构模式没有对我们业务逻辑代码实现拆分,所有的代码都写入到同一个工程中里面,适合于小公司开发团队或者个人开发。<br />com.orange.controler---springmvc 视图层 jsp/ftl<br />com.orange.service---业务逻辑层<br />com.orange.dao---数据库访问层
  2. 将项目的代码都放入到同一个项目,部署在同一个Tomat中。<br />该架构模式存在哪些优缺点:<br />优点:开发简单、运维简单<br />缺点:该架构模式没有对我们的业务逻辑实现拆分,所有的代码都写入到同一个项目中,<br />只适合小团队或者个人形式开发,不适合团队模式协同工作开发<br /> 这种架构模式最大的缺点,如果该系统一个模块出现不可用、会导致整个系统无法使用。<br />应用场景:政府项目、管理系统、crmoa适合于个人小团队开发。

1.2 分布式架构

    分布式架构模式是基于传统的架构模式演变过来,将传统的单点项目根据业务模块实现拆分、会拆分为会员系统、订单系统、支付系统、秒杀系统等。 从而降低我们项目的耦合度,这种架构模式开始慢慢的适合于互联网公司开发团队。<br />        如果项目团队人数较多需要进行项目拆分。需要把单体项目不同的系统。大型公司和大型开发团队多用这种团队开发的模式。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22535347/1654675240447-3f0c0613-07ff-4791-811b-b53b2491496c.png#clientId=u2f5878da-c443-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=334&id=u37edb1c1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=418&originWidth=692&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1159393&status=done&style=none&taskId=u303b5cb5-3d19-473a-9b3d-89c342f792e&title=&width=553.6)<br />        不同的系统如何连成一块呢?需要通过域名跳转。<br />会员系统:memner.orange.com<br />支付系统:  pay.orange.com<br />命名系统化:包含服务和视图层

1.3 SOA面向服务架构

    不同系统间的会话是如何进行绑定的呢?需要用到SSO单点登入系统。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22535347/1654676821956-38f9ec69-d4fd-4f79-9c6a-83f07a008d50.png#clientId=u2f5878da-c443-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=322&id=u5a3a46b4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=403&originWidth=692&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1117793&status=done&style=none&taskId=ub72d8f01-0f94-45b9-b0d7-51225a96872&title=&width=553.6)<br />        SOA架构模式也称作为:面向服务架构模式、俗称面向与接口开发,将共同存在的业务逻辑抽取成一个共同的服务,提供给其他的服务接口实现调用、服务与服务之间通讯采用rpc远程调用技术。

通过SSO系统能解决代码冗余的问题。
服务:只是有接口没有控制层没有视图层
com.orange.service
com.orange.dao
image.png
这种模式叫作SOA面向业务逻辑的开发。
SOA架构模式特点:
1. SOA架构通讯中,采用XML方式实现通讯、在高并发下通讯过程中协议存在非常大冗余性,所以在最后微服务架构模式中使用JSON格式替代了XML。
2. SOA架构模式实现方案为Web Service或者是ESB企业服务总线底层通讯协议SOAP协议(Http+XML)实现传输。
image.png
传统政府、银行项目还是保留的在使用Web Service
互联网公司肯定采用http+json形式实现运输
ESB企业服务总线
解决多系统之间跨语言通讯,数据协议的转换,提供可靠消息传输。

1.4 微服务架构

1.4.1 微服务架构产生的原因

    微服务架构基于SOA架构演变过来的<br />在传统的Web Service架构中有如下问题:<br />1. 依赖中心化服务发现机制<br />2. 使用Soap通讯协议,通常使用XML格式来序列化通讯数据,xml格式非常喜欢重,比较占宽带传输。<br />3. 服务化管理和治理设施不完善

    微服务架构模式<br />SOA架构模式存在哪些缺点:<br />1、采用SOAP协议实现通讯,xml传输非常重,效率比较低。<br />2、服务化管理和治理设施不够完善<br />3、依赖与中心服务发现机制<br />4、不适合于前后分离架构模式<br />        前端分离技术就是对我们控制层和业务层逻辑实现区分,前端控制可以采用vue调用我们后端接口(http+json)

1.4.2 微服务架构基本概念

    微服务架构模式是从SOA架构模式演变过来,比SOA架构模式粒度更加精细,让专业的人去做专业的事情(专注),目的是提高效率,每个服务与服务之间互不影响,微服务架构中每个服务必须独立部署、互不影响,微服务架构模式体现轻巧、轻量级、适合于互联网公司开发模式。<br />        微服务架构倡导应用程序设计程多个独立、可配置、可运行和可微服务的子服务。服务与服务通讯协议采用Http协议,使用restful风格API形式来进行通讯,数据交换格式轻量级json格式通讯,整个传输过程中,采用二进制,所以http协议可以跨语言平台,并且可以和其他不同的语言进行相互的通讯,所以很多开放平台都采用http协议接口。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22535347/1654679428925-a1b1706c-8e5f-4ab2-9780-0060fc65943c.png#clientId=u2f5878da-c443-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=300&id=u580bc86c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=375&originWidth=389&originalType=binary&ratio=1&rotation=0&showTitle=false&size=584901&status=done&style=none&taskId=u940154fb-2f79-40ba-afe8-d2a6ca0319d&title=&width=311.2)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22535347/1654679437762-8c1ae73c-1e32-49c6-85b8-1578953cf43c.png#clientId=u2f5878da-c443-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=200&id=ub8fa2c76&margin=%5Bobject%20Object%5D&name=image.png&originHeight=250&originWidth=358&originalType=binary&ratio=1&rotation=0&showTitle=false&size=358905&status=done&style=none&taskId=udafcb0b7-59a3-4b94-9fb8-6ee9709681e&title=&width=286.4)

1.4.3 微服务架构与SOA架构的不同

1.微服务架构基于 SOA架构 演变过来,继承 SOA架构的优点,在微服务架构中去除 SOA 架构中的 ESB 企业服务总线,采用 http+json(restful)进行传输。
2.微服务架构比 SOA 架构粒度会更加精细,让专业的人去做专业的事情(专注),目的提高效率,每个服务于服务之间互不影响,微服务架构中,每个服务必须独立部署,微服务架构更加轻巧,轻量级。
3.SOA 架构中可能数据库存储会发生共享,微服务强调独每个服务都是单独数据库,保证每个服务于服务之间互不影响。
4.项目体现特征微服务架构比 SOA 架构更加适合与互联网公司敏捷开发、快速迭代版本,因为粒度非常精细。

    ESB企业服务总线:解决多系统之间跨语言无法实现通讯的问题,对我们数据协议实现转换,可以提供可靠的消息传输,第三方框架实现。<br />        一般情况下都是采用Http+JSON格式传输,所以没有必要使用ESB企业服务总线。

1.4.4 微服务架构会产生那些问题

分布式事务解决方案(rabbitmq/rocketmq/lcn(已经淘汰)/ Seata)
分布式任务调度平台(XXL-Job、阿里Scheduler)
分布式日志采集系统ELJ+Kafka
分布式服务注册中心eureka、Zookeeper、consule、nacos等。
分布式服务追踪与调用链Zipkin等。

非常重要的概念:独立部署、可配置、动态化

1.4.5 为什么我们要使用SpringCloud

    SpringCloud并不是rpc远程调用框架,而是一套全家桶的微服务解决框架,理念就是解决我们在微服务架构中遇到的任何问题。<br />服务治理:eureka<br />分布式配置:config<br />客户端调用工具rest/feign客户端 rpc远程调用

说明:阿里巴巴、腾讯、百度
注意:大家如果去一些比较大型的互联网公司中,整个公司内部实现rpc通讯的框架、服务助治理都是内部自己研发

Rpc远程调用框架有哪些?
Httpclient、dubbo、feign、grpc、基于netty手写rpc

1.4.6 SpringCloud第一代与第二代的区别

image.png
SpringCloud第一代:
SpringCloud Config 分布式配置中心
SpringCloud Netflix 核心组件
Eureka:服务治理
Hystrix:服务保护框架
Ribbon:客户端负载均衡器
Feign:基于ribbon和hystrix的声明式服务调用组件
Zuul: 网关组件,提供智能路由、访问过滤等功能。

    SpringCloud第二代(自己研发)和优秀的组件组合:<br />Spring Cloud Gateway 网关<br />Spring Cloud Loadbalancer 客户端负载均衡器<br />Spring Cloud r4j(Resilience4J) 服务保护

Spring Cloud Alibaba Nacos 服务注册
Spring Cloud Alibaba Nacos 分布式配置中心
Spring Cloud Alibaba Sentinel服务保护
SpringCloud Alibaba Seata分布式事务解决框架
Alibaba Cloud OSS 阿里云存储
Alibaba Cloud SchedulerX 分布式任务调度平台
Alibaba Cloud SMS 分布式短信系统

1.4.7 为什么Alibaba要推出SpringCloud组件

    目的就是为了对阿里云的产品实现扩展。<br />**SpringCloud与Spring Cloud Alibaba的区别**<br />Spring Cloud Alibaba实际上对我们的SpringCloud实现了拓展组件能够完美整合到SpringCloud rpc远程调用整合。<br />1、nacos分布式注册中心,分布式配置中心SpringCloudEureka+Config组合<br />2、目的是为了推广阿里云产品,如果使用了Spring Cloud Alibaba建议最好使用Alibaba Mq rocketmq<br />分布式任务调度

2 服务注册与发现nacos

2.1 Nacos产生的背景

Nacos分布式注册与发现功能|分布式配置中心
产生背景rpc远程调用中,服务的url的治理
Rpc的远程调用框架 HttpClient、gprc、dubbo、rest、openfeign等。

传统的rpc远程调用中存在哪些问题
1、超时的问题
2、安全的问题
3、服务与服务之间URL地址管理
在我们微服务架构通讯,服务之间依赖关系非常大,如果通过传统的方式管理我们服务的url地址的情况下,一旦地址发生变化的情况下,还需要人工修改rpc远程调用地址。
每个服务的url管理地址发出复杂,所以这是我们采用服务url治理技术,可以实现对我们整个实现动态服务注册与发现、本地负载均衡、容错等。
image.png

2.2 服务治理基本的概念

服务治理概念:
在RPC远程调用过程中,服务与服务之间依赖关系非常大,服务Url地址管理非常复杂,所以这时候需要对我们服务的url实现治理,通过服务治理可以实现服务注册与发现、负载均衡、容错等。
image.png
rpc远程调用中,地址中 域名和端口号/调用的方法名称:
域名和端口号/调用的方法名称
192.168.212.110:8080/getUser

把每个服务器地址信息和端口人工存放到数据库表中
Id serviced ip 端口号
Mayikt-member 192.168… 8082
Mayikt-member 192.168… 8081
基于数据库形式实现服务url治理
缺点:维护成本非常高、没有完全绝对实现动态智能

思考是否有更好的方案? 微服务中的注册中心
整个微服务架构中最为核心的肯定是注册中心。

注册中心:实际就是存放我们的服务的地址信息,能够实现动态感知。
注册中心:Dubbo依赖Zookeeper、Eureka、Consul、Nacos、Redis、数据库
image.png

2.3 服务注册中心的概念

    每次调用该服务如果地址直接写死的话,一旦接口发生变化的情况下,这时候需要重新发布版本才可以该接口调用地址,所以需要一个注册中心统一管理我们的服务注册与发现。<br />        注册中心:我们的服务注册到我们注册中心,key为服务名称、value为该服务调用地址,该类型为集合类型。Eureka、consul、zookeeper、nacos等。<br />        服务注册:我们生产者项目启动的时候,会将当前服务自己的信息地址注册到注册中心。<br />        服务发现: 消费者从我们的注册中心上获取生产者调用的地址(集合),在使用负载均衡的策略获取集群中某个地址实现本地rpc远程调用。

2.4 微服务调用接口常用名词

面试的过程:Nacos与Eureka区别、Eureka与Zookeeper
整个微服务的注册中心实现原理
生产者:提供接口被其他服务调用
消费者:调用生产者接口实现消费
服务注册:将当前服务地址注册到
服务发现:
image.png
服务注册原理实现:
1、生产者启动的时候key=服务站的名称 value ip 和端口号 注册到我们的微服务注册中心上。
Mayikt-member 192.168.212.110:8080
Mayikt-member 192.168.212.110:8081

2、注册存放服务地址列表类型:key唯一,列表是list集合。
May
{
Mayikt-member:[“192.168.212.110.8080”]
}

3、我们的消费者从我们注册中心上根据服务名称查询服务地址列表(集合)
Mayikt-member===[“192.168.212.110.8080” “192.168.212.110.8081”]

4、消费者获取到集群列表之后,采用负载均衡器选择一个地址实现rpc远程调用

2.5 Nacos的基本的介绍

Nacos可以实现分布式服务注册与发现/分布式配置中心框架。
官网的介绍: https://nacos.io/zh-cn/docs/what-is-nacos.html

2.6 Nacos的环境的准备

Nacos可以在linux/windows/Mac版本上都可以安装
具体安装教程地址:https://nacos.io/zh-cn/docs/quick-start.html

手动实现服务注册与发现
1.实现服务注册
发送post请求:
http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=nacos.naming.serviceName&ip=20.18.7.10&port=8080
2.实现服务发现
http://127.0.0.1:8848/nacos/v1/ns/instance/list?serviceName=nacos.naming.serviceName
详细步骤操作:https://nacos.io/zh-cn/docs/quick-start.html
image.png

2.7 Nacos整合SpringCloud

2.7.1 Maven依赖信息

<?xml version="1.0" encoding="UTF-8"?>
<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>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.8.RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>orange-Order</artifactId>


    <dependencies>
        <!--  springboot 整合web组件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>0.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>

创建工程,需要选择1.8
image.png
之后创建生产者,其中版本最好一致,否则会产生报错现象。
image.png
如何实现服务的注册?
image.png
接口完成后,对会员进行注册。
拿到配置文件,对项目命名
image.png
再进行代码测试,自动实现对服务进行注册
image.png
image.png

2.7.2 会员服务(生产者)

服务接口

@RestController
@CrossOrigin
public class MemberService {
    @Value("${server.port}")
    private String serverPort;

    /**
     * 会员服务提供的接口
     *
     * @param userId
     * @return
     */
    @RequestMapping("/getUser")
    public String getUser(Integer userId) {
        return "会员服务:" + serverPort;
    }
}

配置文件

application.yml文件

spring:
  cloud:
    nacos:
      discovery:
        ###服务注册地址
        server-addr: 127.0.0.1:8848
  application:
    name: mayikt-member
server:
  port: 8081

2.7.3 订单服务(消费者)

订单调用会员服务

@RestController
public class OrderService {

    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private DiscoveryClient discoveryClient;
    @Autowired
    private LoadBalancer loadBalancer;

    /**
     * 订单调用会员服务
     *
     * @return
     */
//    @RequestMapping("/orderToMember")
//    public String orderToMember() {
//        // 从注册中心上获取该注册服务列表
//        List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances("mayikt-member");
//        ServiceInstance serviceInstance = serviceInstanceList.get(0);
//        URI rpcMemberUrl = serviceInstance.getUri();
//        // 使用本地rest形式实现rpc调用
//        String result = restTemplate.getForObject(rpcMemberUrl + "/getUser", String.class);
//        return "订单调用会员获取结果:" + result;
//    }
    @RequestMapping("/orderToMember")
    public String orderToMember() {
        // 从注册中心上获取该注册服务列表
        List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances("mayikt-member");
        ServiceInstance serviceInstance = loadBalancer.getSingleAddres(serviceInstanceList);
        URI rpcMemberUrl = serviceInstance.getUri();
        // 使用本地rest形式实现rpc调用
        String result = restTemplate.getForObject(rpcMemberUrl + "/getUser", String.class);
        return "订单调用会员获取结果:" + result;
    }
}

新建项目之后:
第一步,根据服务名称从注册中心获取集群列表地址
第二步,根据列表任意选择一个实现本地rpc调用rest
image.pngimage.png
注意:RestTeanmlate它不是SpringCloud写的,本身Spring支持Http协议调用 实现Http 调用
image.png

负载均衡算法

public interface LoadBalancer {


    /**
     * 根据多个不同的地址 返回单个调用rpc地址
     *
     * @param serviceInstances
     * @return
     */
    ServiceInstance getSingleAddres(List<ServiceInstance> serviceInstances);
}

@Component
public class RotationLoadBalancer implements LoadBalancer {
    private AtomicInteger atomicInteger = new AtomicInteger(0);

    @Override
    public ServiceInstance getSingleAddres(List<ServiceInstance> serviceInstances) {
        int index = atomicInteger.incrementAndGet() % 2;
        ServiceInstance serviceInstance = serviceInstances.get(index);
        return serviceInstance;
    }
}

Rpc远程调用设计到本地负载均衡算法

1、从注册中心获取服务集群的列表
2、从列表选择一个 负载均衡算法有哪些
A、一致性hash计算
B、轮训、权重
C、随机

采用什么设计模式 策略模式

假设我们两台服务器集群
访问次数%集群size

1%2=1
2%2=0
3%2=1
4%2=0

1%1=0
Spring Cloud Alibaba 学习笔记 - 图18

2.7.4 Nacos与其他注册对比分析

访问之后在 服务列表中发现有两个集群。下线原理是在它的集合中移除
image.png
通过算法实时,实现中心获取地址。
image.png
想要了解手写RPC负载均衡五种算法。轮训权重需要对数据实现重新排序。一致性hash,随机数从集合中随机寻找
image.png
故障转移:集合中取下一个值
编程思维能力就是 举一反三

2.8 Nacos的集群部署

Nacos 和信心帮助我们做的事情注册中心、分布式配置中心

注册中心 没有必要将数据持久化到数据库中,可以持久化到本都的硬盘。
分布式配置中心 默认是将数据持久化到本地嵌入式的数据库改为持久化到myql中

伪集群:
127.0.0..1:8848
127.0.0..1:8849
127.0.0..1:8850

注意事项:Nacos在不同版本下运行集群是不一样
在linux版本中运行的时候默认是集群模式,如果需要改为单机启动修改配置
注意:
1. nacos在windows版本下运行默认是单机版本 需要指定startup.cmd -m cluster
2. nacos在linux版本下运行默认是集群版本 如果想连接单机版本 startup.cmd –m standalone
image.png
注意事项:集群的ip地址不能采用127.0.0.1

分布式情况下集群保证数据的一致性集群的算法
ZAB(Zookeeper)核心原理两阶段是提交协议2PC、Paxos、nacos数据一致性协议raft协议采用心跳形式

Nacos的数据持久化

    数据持久化<br />默认的情况下,分布式配置中心的数据存放到本地data目录下,但是这种情况如果nacos集群的话无法保证数据的同步性。

在0.7版本之前,在单机模式时nacos使用嵌入式数据库实现数据的存储,不方便观察数据存储的基本情况。在0.7版本增加了支持mysql数据源能力,具体的操作步骤:

1、安装数据库,版本要求:5.6。5+
2、初始化mysql数据库,数据库初始化文件:nacos-mysql.sql
3、修改conf/application.properties文件,增加支持mysql数据源配置(目前只支持mysql),添加mysql数据源的url、用户名和密码。

相当于配置文件数据源,统一放到数据库中。
image.png

2.9 Nacos与Eureka区别

1、Eureka采用ap模式形式实现注册中心
2、Nacos默认采用AP模式。在1.0版本之后采用ap+cp模式混合实现注册中心。

Eureka与Nacos底层实现集群协议那些区别
1、去中心化对等。
2、Raft协议实现集群产生领导角色。

Raft到底是什么问题 分布式一致性协议的算法

分布式系统一致性算法应用于系统软件实现集群保持每个节点数据的同步性
保持我们的集群中每个节点的数据的一致性的问题,专业的术语分布式一致性的算法。

场景:Redis集群、nacos集群、mongdb集群等

Nacos与Eureka区别
最大区别:Nacos支持两种模式CP/Ap模式 从Nacos1.0版本开始 注意模式就是Ap模式

注册有哪些?
Eueeka、Nacos、consul、Zookepper
核心:
1、Eureka与Zookeeper实现注册的区别
2、Eureka与Nacos实现注册区别
CAP定律概念:
一致性(C):在分布式系统中,如果服务器是计群的情况下,每个节点同一时刻查询的数据必须保持一致性的问题。
可用性(A):集群节点中,部分的节点出现了故障后仍然可以使用
分区容错性(P):在分布式系统中网络存在脑裂的问题,部分的Server与整个集群失去联系

采用:
Cp情况下 虽然我们服务不能用,但是必须要保证数据的一致性
Ap情况下 可以短暂保证数据不一致性,但是最终可以一致性,不管怎么样,要能够保证我们的服务可用

大多的注册中心都是Ap
image.png

Zab协议集群原理
我们在分布式系统,存在多个系统之间的集群保持数据一致性,采用CP一致性算法保持数据的一致性问题。
Zookeeper基于ZAP协议实现保持每个节点的数据同步的问题,中心化思想集群模式;
分为领导和跟随角色。
image.png
在程序中如何成为某个节点能力比较强:
对每个节点配置一个myid或者serverid还有数值越大表示能力越强 或者随机时间

整个集群中为了保持数据的一致性的问题,必须满足大多数情况>n/2+1 可运行的节点环境下才可以使用

ZAP的协议实现原理事通过比较myid myid谁最大谁就是为可能是领导角色,只要满足过半的机制就可以成为领导角色,后来启动的节点不会参与选举的。
image.png
Zab协议如何保持数据的一致性问题
所有写的请求统一交给我们的领导角色实现,领导角色写完数据之后,领导角色再将数据同步给每个节点。

注意:数据之间同步采用2pc两个阶段提交协议。
选举过程:

先去比较zxid zxid谁最大谁就是为领导角色;
如果zxid相等的情况下,myid谁最大谁就为领导角色;
image.png
Raft协议选举的基本概念
在Raft协议算法中分为角色|名词:
1、状态:分为三种 跟随者、竞选者(候选人)、领导角色
2、大多数:>=n/2+1 3/2=1+1=2
3、任期:每次选举一个新的领导角色 任期都会 实现增加
注意:任何算法都来源于生活
4、竞选者谁的票数最多,谁就是为领导角色

存在的疑问:
多个竞选者,产生的票数都一样,这到底谁是领导角色,服务器集群是偶数的情况下

3 客户端负载均衡器Ribbon

3.1 SpringCloud负载均衡器说明

    在SpringCloud第一代中使用Ribbon、SpringCloud第二代中直接采用自研发loadbalancer即可,默认使用的Ribbon。<br />使用方式非常简单:
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

3.2 LoadBalancerClient负载均衡器

@RequestMapping("/loadBalancerClient")
public Object loadBalancerClient() {
    return loadBalancerClient.choose("orange-Member");
}

底层默认原理是调用ribbon的实现客户端负载均衡器。
image.png

3.3 本地负载均衡与Nginx 的区别

3.3.1 本地负载均衡

    本地负载均衡器基本的概念:我们的消费者服务从我们的注册中心获取到集群地址列表,缓存到本地,让后本地采用负载均衡策略(轮训、随机、权重等),实现本地的rpc远程的。

    本地负载均衡器有哪些呢:自己写、ribbon SpringleCloud第一代中  loadbalancerSpringCloud自己研发。<br />如何选择ribbon还是loadbalancer<br />SpringCloud Rest或者Openfeign都是默认支持ribbon。

3.3.2 本地负载均衡器与Nginx 的区别

    Nginx是客户端所有的请求统一都交给我们的Nginx处理,让后在由Nginx实现负载均衡转发,属于服务器端负载均衡器。<br />        本地负载均衡器是从注册中心获取到集群地址列表,本地实现负载均衡算法,既本地负载均衡器。<br />应用场景:<br />        Nginx属于服务器负载均衡,应用于Tomcat/Jetty服务器等,而我们的本地负载均衡器,应用于在微服务架构中rpc框架中,rest、openfeign、dubbo。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22535347/1654845581262-a3a2a997-3582-46e2-b034-b2681c3fd347.png#clientId=uf2f7a881-044e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=190&id=u56126ca4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=237&originWidth=669&originalType=binary&ratio=1&rotation=0&showTitle=false&size=127459&status=done&style=none&taskId=uc2604065-6949-432f-950c-f8d9a086e76&title=&width=535.2)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22535347/1654845591803-954f9572-c6be-4cd9-99e2-8542180b5a70.png#clientId=uf2f7a881-044e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=166&id=ud2ac4ef3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=208&originWidth=753&originalType=binary&ratio=1&rotation=0&showTitle=false&size=84214&status=done&style=none&taskId=u47ece915-6201-4ff3-95e2-56f7a1c6796&title=&width=602.4)<br />SpringCloud rest或者openfeign客户端默认都是ribbon实现调用<br />@loadbalancer实际上是我们的springcloud自己写的。<br />loadbalancerClientSpringCloud根据服务id获取负载均衡器rpc地址。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22535347/1654845727381-5ef73393-ac64-4105-b961-2350401ca7a8.png#clientId=uf2f7a881-044e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=350&id=u9ea3449c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=438&originWidth=886&originalType=binary&ratio=1&rotation=0&showTitle=false&size=320947&status=done&style=none&taskId=u612d2c25-13e6-49ec-b821-1dbe98a2a87&title=&width=708.8)<br />搜索 maven仓库,对比分析<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22535347/1654845823471-39d34b41-e17a-4826-be67-f93ee062a1e4.png#clientId=uf2f7a881-044e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=690&id=u047b83c0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=863&originWidth=1526&originalType=binary&ratio=1&rotation=0&showTitle=false&size=123061&status=done&style=none&taskId=u7c6d4511-4c39-4bb6-9dbf-d07a2b41d0b&title=&width=1220.8)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22535347/1654845942621-02cd7e62-cbd8-49e8-906c-a437afdcf02e.png#clientId=uf2f7a881-044e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=710&id=u2642bba9&margin=%5Bobject%20Object%5D&name=image.png&originHeight=888&originWidth=1497&originalType=binary&ratio=1&rotation=0&showTitle=false&size=128039&status=done&style=none&taskId=ub500dc4f-640a-4158-b81b-483afaa18c3&title=&width=1197.6)

4 OpenFeign客户端

OpenFeign是一个Web声明式的Http客户端调用工具,提供接口和注解形式调用。
SpringCloud第一代采用feign第二代采用openfeign
openfeign客户端作用:是一个Web声明式的Http客户端远程调用工具,底层是封装HttpClient技术。
Openfeign属于SpringCloud自己研发,而feign是netflix代码写法几乎是没有任何变化。

注意:feign客户端调用的事项:如果请求参数没有加上注解的话,默认采用post请求发送。
Openfeign默认是支持负载均衡,ribbon。
image.png
Nacos服务注册 服务名称是否 有下划线?
在微服务架构中服务的名称命名不能够有下划线。

5 分布式配置中心

Nacos是阿里巴巴开源开源框架 注册中心 | 分布式配置中心
替代之前学习Eureka和SPringConfig|携程阿波罗
IP:端口号/调用方法加名称+参数
192.168.212.110:8080/getUser

分布式配置中心产生的背景?

在项目中定义配置文件,最大的缺陷?
如果在生成环境正在运行的时候突然需要修改配置文件的话,必须重启我们的服务器。

分布式配置中心的框架有哪些:
携程的阿波罗、Nacos(属于轻量级)、SpringCloud Config(没有界面)、携程的阿波罗(属于比较重的分布式配置)/disConfig等。

轻量级与重量级分别表示什么意思?
轻量级:部署、架构设计原理都比较简单,学习成本也是比较低:
重量级:部署、架构设计、体量都是非常大,学习成本是比较高。
如何判断配置文件是否发生变化采用 版本 | MD5

分布式配置中心实现原理:
1、本地应用读取我们云端分布式配置中心文件(第一次建立长连接)
2、本地应用读取到配置文件之后,本地jvm和硬盘中都会缓存一份。
3、本地应用与分布式配置中心服务器端一致保持长连接。
4、当我们的配置文件发生变化(MD5|版本号)实现区分,将变化的结果通知给我们的本地应用实时的刷新我们的配置文件。
完全百分百实现动态化修改我们的配置文件。

注意:Nacos分布式配置中心和注册中心都部署在同一个应用,就是一个单体的应用。
image.png

5.1 分布式配置中心的作用

分布式配置中心可以实现不需要重启我们的服务器,动态的修改我们的配置文件内容,
常见的配置中心有携程的阿波罗、SpringCloud Config、Nacos轻量级的配置中心等。

5.2 基于Nacos实现分布式配置中心

5.2.1 服务器端

在Naocs平台中创建配置文件 名称(默认为服务器名称)-版本.properties|yaml;
image.png

5.2.2 客户端

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>0.2.2.RELEASE</version>
        </dependency>

创建一个bootstrap.yml

spring:
  application:
    ###服务的名称
    name: meitemayikt-nacos-client
  cloud:
    nacos:
      discovery:
        ###nacos注册地址
        server-addr: 127.0.0.1:8848
        enabled: true
      config:
        ###配置中心连接地址
        server-addr: 127.0.0.1:8848
        ###分组
        group: DEFAULT_GROUP
        ###类型
        file-extension: yaml
@RestController
@SpringBootApplication
@RefreshScope
public class NacosController {
    @Value("${orange.name}")
    private String userName;

    @RequestMapping("/getConfig")
    public String getConfig() {
        return userName;
    }

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

可以实现动态实现@RefreshScope;可以对配置内容进行监听,察觉到内容被编辑之后会立刻刷新,而不用重启服务器。

注意:连接nacos分布式配置中心一定采用bootstrap形式优先加载 否则可能会报错。

bootstrap.yml 用于应用程序上下文的引导阶段。
application.yml 由父Spring ApplicationContext加载。

5.2.3 多版本控制

分别在nacos服务器端创建
orange-nacos-client-dev.yaml
orange-nacos-client-prd.yaml
客户端指定读取版本
image.png

5.2.4 数据持久化

image.png
找到配置文件config:创建数据库表mysql,然后连接到properties.
image.png
第一步:新建数据库
image.png
然后将sql文档里面的内容复制在查询框里面执行一下。这几张表就有了。
image.png
插入一个用户信息:
image.png
执行结果:
image.png
最后,连接数据库:
image.png
登录测试:
image.png
image.png
可以看到:切换数据源之后,之前的数据都看不到了。可以再次新建配置:
image.png
点击发布之后,配置的内容自动保存在数据库了。
image.png
注意:
默认的情况下,分布式配置中心的数据存放到本地data目录下,但是这种情况如果nacos集群的话无法保证数据的同步性。

在0.7版本之前,在单机模式时nacos使用嵌入式数据库实现数据的存储,不方便观察数据存储的基本情况。0.7版本增加了支持mysql数据源能力,具体的操作步骤:

1.安装数据库,版本要求:5.6.5+
2.初始化mysql数据库,数据库初始化文件:nacos-mysql.sql
3.修改conf/application.properties文件,增加支持mysql数据源配置(目前只支持mysql),添加mysql数据源的url、用户名和密码。

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
db.user=root
db.password=root

摘自官网: https://nacos.io/zh-cn/docs/deployment.html

6 基于Nacos集群部署方案

image.png

6.1 相关集群配置

创建cluster文件夹
—-nacos-server-8848
—-nacos-server-8849
—-nacos-server-8850

cluster.conf
###ip和端口号
127.0.0.1:8848
127.0.0.1:8849
127.0.0.1:8850

Nginx相关配置
客户端连接

spring:
  application:
    ###服务的名称
    name: meitemayikt-nacos-client
  cloud:
    nacos:
      discovery:
        ###nacos注册地址
        server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850
        enabled: true
      config:
        ###配置中心连接地址
        server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850
        ###分组
        group: DEFAULT_GROUP
        ###类型
        file-extension: yaml

注意:
1. nacos在windows版本下运行默认是单机版本 需要指定startup.cmd -m cluster
2. nacos在linux版本下运行默认是集群版本 如果想连接单机版本 startup.cmd –m standalone
image.png
Running in cluster mode

6.1.1 CAP定律

     这个定理的内容是指的是在一个分布式系统中、Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。

一致性(C):在分布式系统中,如果服务器集群,每个节点在同时刻访问必须要保持数据的一致性。
可用性(A):集群节点中,部分节点出现故障后任然可以使用 (高可用)
分区容错性(P):在分布式系统中网络会存在脑裂的问题,部分Server与整个集群失去节点联系,无法组成一个群体。
只有在CP和AP选择一个平衡点

6.1.2 Eureka与Zookeeper区别

相同点、不同点、中心思想
相同点:都可以实现分布式服务注册中心
不同点:Zookeeper采用CP保证数据的一致性的问题,原理采用Zab原子广播协议,当我们的zk领导因为某种原因宕机的情况下,会自动触发重新选一个新的领导角色,整个选举的过程为了保证数据的一致性问题,在选举的过程中整个zk环境是不可使用的可短暂可能无法使用到zk。意味着微服务采用该模式情况下,可能无法实现通讯(本地有缓存除外)
注意:可运行的节点必须满足过半机制,整个zk采用使用。

Eureka采用ap的设计理念架构注册中心,完全去中心化思想,也就是没有主从之分
每个节点都是均等,采用相互注册的原理,你中有我我中有你,只要最后有一个eureka节点存在就可以保证整个微服务可以实现通讯。

我们在使用注册中心,可用性在优先级最高,可以读取数据短暂不一致性,但是至少要能够保证注册中心可用性。

中心化必须围绕一个领导角色作为核心,选举领导和跟随者角色
去中心化每个角色都是均等
image.png

6.1.3 分布式系统一致性算法

   分布式事务性一致性框架与分布式系统一致性算法有哪些?<br />分布式事务一致性框架:核心解决我们在实际系统中产生夸事物导致分布式事务问题。<br />核心靠的是最终一致性:rocketmq事务消息、rabbitmq补单、lcn、SeaTac等。<br />分布式系统一致性算法:解决我们系统之间集群之后每个节点保持数据的一致性<br />有哪些:raft(nacos)、zab(zookeeper)、paxos等。

   如何保持数据的一致性的问题?<br />所有写的请求统一交给我们的领导角色实现,领导角色写完数据之后,领导角同步给每个节点。<br />注意:数据之间同步采用2pc两阶段提交协议。

   在我们分布系统中,存在多个系统之间实现数据的集群,采用CP一致性算法保证每个节点数据的一致性的问题。<br />比如Eureka、Zookeeper、Nacos实现集群都必须保证每个节点数据同步性的问题。<br />Zookeeper基于ZAP协议实现保证每个节点数据同步的问题,中心化思想集群模式。分为领导和跟随者角色。<br />Eureka基于AP模式实现注册中心,去中心化的思想、每个节点都是对等的,采用你中有我我中有你的形式实现注册中心。

常见分布式一致性算法:
1、ZAP协议(底层就是基于Paxos实现), 核心底层基于2PC两阶段提交协议实现。
2、Nacos中集群保证一致性算法采ratf协议模式,采用心跳机制实现选举的。

  ** Ratf整个底层实现原理:**<br />在Raft协议算法中分为角色|名词:<br />1、状态:分为三种,跟随者、竞选者(候选人)、领导角色。<br />跟随者:只有投票权限,不能参与竞选。<br />竞选者(候选人):被投票,有可能成为领导者的角色。<br />领导角色:只有唯一的一个。<br />2、大多数: >n/2+1,n表示集群总数的节点。<br />3、任期:每次选举一个新的领导角色,任期都会增加。<br />注意:任何的算法都是来源于生活。<br />4、竞选者谁的票数最多,谁就成为领导角色。

  ** **存在的**疑问**:<br />多个竞选者,产生的票数都完全一样,这到底谁是领导角色?<br />注意当我们的集群节点总数,如果是奇数情况下,就算遇到了该问题也不用担心。<br />当我们的节点是偶数的情况下,可能会存在该问题,如果两个竞选者获取的票数相等的情况下,开始重置竞选的超时时间,一直到谁的票数最多谁就为领导。

选举过程是怎样的呢:
先去比较zxid,zxid谁最大谁就是为领导角色;
如果zxid相等的情况下,myid谁最大谁就为领导角色;

image.png
image.png
默认情况下选举的过程:
1、默认的情况下每个节点都是跟随者角色
2、每个节点会随机生成一个选举的超时时间,例如大概是100-300ms,在这个超时时间范围内必须要等待。
3、超时时间过后,当前节点的状态由跟随者变为竞选者状态,会给其他的节点发出选举的投票通知,只要该竞选者有超过半数以上即可选为领导角色。
核心的设计原理:谁超时时间最短,谁就有非常大的概率为领导角色。

  ** **那么在随机数有可能一样的情况下,如何选举呢?<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22535347/1655358259828-da57af75-7bd6-427e-a281-76d075d9904b.png#clientId=u5584c3c0-293d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=81&id=ubf8b39a0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=101&originWidth=692&originalType=binary&ratio=1&rotation=0&showTitle=false&size=280206&status=done&style=none&taskId=u05df2140-7b6b-41ee-a9bf-e855c4ae7ab&title=&width=553.6)<br />1、如果所有的节点的超时随机数都是一样的情况下,当前投票全部作废,重新进入随机生成超时时间。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22535347/1655358295231-0b330dd1-c215-4392-8ba9-87b0326aa19c.png#clientId=u5584c3c0-293d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=58&id=uab23058e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=72&originWidth=692&originalType=binary&ratio=1&rotation=0&showTitle=false&size=199775&status=done&style=none&taskId=ua38f5358-94fa-4d4e-bab7-0a232bffda5&title=&width=553.6)<br />2、如果有多个节点生成的随机数都是一样的情况下,比较谁的票数最多,谁就是领导。如果票数完全一样的情况,直接作废,重新进入随机生成超时时间。<br />注意:建议集群节点为奇数。偶数节点容易造成死循环。

故障重新实现选举:
1、如果我们跟随者节点不能够及时的收到领导角色消息,那么这时候跟随者就会将当前自己的状态由跟随者变为竞选者状态,会给其他的节点发出选举投票的通知,只要该竞选者有超过半数以上即可选举为领导角色。

数据是如何保持一致性的,类似于ZAP两阶段提交协议
如何实现日志的复制
1、所有的写的请求都是统一的交给我们的领导角色完成,写入该对应的日志,标记该日志为被提交状态。
2、为了提交该日志,领导角色就会将该日志以心跳的形式发送给其他的跟随者节点,只要满足过半的跟随者节点写入该目志,则直接通知其他的跟随者节点同步该数据,这个过程称为日志复制的过程。

7 SpringCloud Gateway

课题内容:
1、Zuul与Gateway有哪些区别
2、Gateway整合Nacos实现服务转发
3、基于Gateway拦截请求参数
4、Gateway集群原理分析

7.1 什么是微服务网关

   微服务网关是整个微服务API请求的入口,可以实现过滤Api接口。<br />       作用:可以实现用户的验证登录、解决跨域、日志拦截、权限控制、限流、熔断、负载均衡、黑名单与白名单机制等。<br />       微服务中的架构模式采用前后端分离,前端调用接口地址都能够被抓包分析到。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22535347/1655625190250-98d123dc-f5d1-4491-9d6e-ac683b39375b.png#clientId=uf5c7442f-5d19-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=274&id=u8fb73168&margin=%5Bobject%20Object%5D&name=image.png&originHeight=342&originWidth=692&originalType=binary&ratio=1&rotation=0&showTitle=false&size=948607&status=done&style=none&taskId=uf29dffc2-95df-4832-b3e4-a33e46de4c2&title=&width=553.6)<br />       在微服务中,我们所有的企业入口必须先经过Api网关,经过Api网关转发到真实的服务器中。<br />       如果此时需要添加验证会话信息:<br />       传统的方式我们可以使用过滤器拦截用户会话信息,这个过程所有的服务器都必须写入该验证会话登录的代码。

7.2 过滤器与网关的区别

过滤器适合于单个服务实现过滤请求;
网关拦截整个的微服务实现过滤请求,能够解决整个微服务中冗余代码。
过滤器是局部拦截,网关实现全局拦截。

7.3 Zuul与Gateway有哪些区别

Zuul网关属于netfix公司开源的产品,属于第一代微服务网关
Gateway属于SpringCloud自研发的网关框架,属于第二代微服务网关
相比来说SpringCloudGateway性能比Zuul性能要好:

注意:Zuul网关底层基于Servlet实现的,阻塞式的Api, 不支持长连接。
SpringCloudGateway基于Spring5构建,能够实现响应式非阻塞式的Api,支持长连接,能够更好的整合Spring体系的产品,依赖SpringBoot-WebFux。

7.4 Gateway环境快速搭建

新建一个项目:orange-Gateway
image.png
此时需要引入依赖jar包:

7.4.1.1 Maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<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>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>orange-Gateway</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
</project>

在此依赖上加入一个依赖:

org.springframework.boot
spring-boot-starter-web

此时程序是否会报错呢?我们来接着进行配置,之后看结果

7.4.1.2 application配置

server:
  port: 80
####服务网关名称
spring:
  application:
    name: orange-Gateway
  cloud:
    gateway:
      discovery:
        locator:
          ####开启以服务id去注册中心上获取转发地址
          enabled: true
        ###路由策略
      routes:
      ###路由id
      - id: orange
        ####转发http://www.mayikt.com/
        uri: http://www.mayikt.com/
        ###匹配规则
        predicates:
        - Path=/orange/**
### 127.0.0.1/orange   转发到http://www.mayikt.com/

image.png
在我们需要服务名称获取地址的情况下,这个一定要设置为true。但现在我们并不需要,所以可以不需要它。
关于匹配规则,默认设为false。
网关的服务端口号一般是:80或者443

这个配置所表示的是:
127.0.0.1/orange 转发到http://www.mayikt.com/
uri: http://www.mayikt.com/ 去掉末尾的“/”,uri: http://www.mayikt.com

之后我们操作来运行一下
image.png
此时执行结果如下:
image.png
程序出错了,最主要的原因是gateway不依赖于serverlet,所以我们需要去掉之前添加的依赖。
之后再次运行,执行结果如下:
image.png
与蚂蚁课堂的404页面一致,是因为CSS页面都是静态的,所以为了避免这种错误,我们一般都需要在最后加上“/”。
执行结果:
image.png

7.4.2 Gateway整合Nacos实现服务转发

在微服务网关中怎样转发到真实的地址:
修改接口地址:
uri: http://127.0.0.1:8081/
修改匹配规则:

  • Path=/mamber/**
    此时执行结果如下:

此时执行结果如下:
image.png
可以实现我们的结果,但是并不是我们想要的方法。我们希望根据我们的服务名称查找地址实现调用,此时需要添加如下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.2.RELEASE</version>
</dependency>
重新配置:
uri: lb://orange-Menber
nacos:
discovery:
server-addr: 127.0.0.1:8848
此时启动80服务,执行结果为:页面404
image.png
此时我们需要修改:
image.png
执行结果还是404:
image.png
此时还需要加一个参数,放置在lb下面:
filters:
- StripPrefix=1
指向前截取一位
此时的执行结果为:
image.png
image.png

7.4.2.1 Maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<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>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>orange-Gateway</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>

        <!--<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>-->

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>0.2.2.RELEASE</version>
        </dependency>
    </dependencies>
</project>

7.4.2.2 网关application.yml配置

server:
  port: 80
####服务网关名称
spring:
  application:
    name: orange-Gateway
  cloud:
    gateway:
      discovery:
        locator:
          ####开启以服务id去注册中心上获取转发地址
          enabled: true
        ###路由策略
      routes:
      ###路由id
      - id: orange-Gateway
        ####转发http://www.mayikt.com/
        uri: http://www.mayikt.com/
        ###匹配规则
        predicates:
          - Path=/member/**
      ###路由id
      - id: orange-Menber
        #### 基于lb负载均衡形式转发
        uri: lb://orange-Menber
        filters:
          - StripPrefix=1
        ###匹配规则
        predicates:
          - Path=/orange/**
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
### 127.0.0.1/orange   转发到http://www.mayikt.com/

8 Nginx与网关的区别

微服务网关能够做的事情,Nginx也可以实现。
相同点:都是可以实现对api接口的拦截,负载均衡、反向代理、请求过滤等,可以实现和网关一样的效果。
不同点:
Nginx采用C语言编写的
在微服务领域中,都是自己语言编写的,比如我们使用java构建微服务项目,Gateway就是java语言编写的。
毕竟Gateway属于Java语言编写的, 能够更好对微服务实现扩展功能,相比Nginx如果想实现扩展功能需要结合Nginx+Lua语言等。
Nginx实现负载均衡的原理:属于服务器端负载均衡器。
Gateway实现负载均衡原理:采用本地负载均衡器的形式。

查看SpringCloud Gateway的官网:
https://spring.io/projects/spring-cloud-gateway
根据官网文档,新建一个包filter以及Token类。

8.1 自定义TokenFilter实现参数拦截

@Component
public class TokenFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if (token == null || token.isEmpty()) {
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.BAD_REQUEST);
            String msg = "token not is null ";
            DataBuffer buffer = response.bufferFactory().wrap(msg.getBytes());
            return response.writeWith(Mono.just(buffer));
        }
        // 使用网关过滤
        return chain.filter(exchange);
    }
}

我们需要添加jar包:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
执行结果:
image.png
我们添加一个参数:token=11
image.png

   思考一个问题:如果我们使用网关宕机了,会出现什么情况?也就是说会导致我们整个微服务无法实现通讯。<br />答:网关实现集群,基于Nginx实现即可。<br />网关实现了集群如何访问?<br />使用nginx或者lvs虚拟vip。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22535347/1655638527422-17dfd747-dfca-4878-a2bc-55c81b5031bd.png#clientId=uf5c7442f-5d19-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=254&id=XGqjH&margin=%5Bobject%20Object%5D&name=image.png&originHeight=317&originWidth=270&originalType=binary&ratio=1&rotation=0&showTitle=false&size=343308&status=done&style=none&taskId=u1661b958-3d41-4258-98c6-2fa4d6696a6&title=&width=216)<br />客户端访问lvs虚拟vip,随机绑定一个Nginx,每个Nginx配置一样

8.2 如何保证微服务接口的安全

接口分为内网和外网接口

外网接口 基于OATUH2.0构建开放平台 比如appid、appsocet获取accesstoken调用接口。
内网接口:都是当前内网中实现通讯,相对于来说比较安全的。

  1. 需要保证接口幂等性问题(基于Token)
    2. 接口采用安全加密传输 https协议
    3. 防止数据被篡改md5验证签名
    4. 使用微服务网关实现Api授权认证等、黑名单白名单。
    5. 对我们的接口实现服务的保护 隔离、熔断、降级等等。
    最后使用apiswagger管理我们的微服务接口。

8.3 GateWay如何保证高可用和集群

使用Nginx或者lvs虚拟vip访问增加系统的高可用
image.png
环境配置:
网关1 127.0.0.1:81
网关2 127.0.0.1:82
Nginx服务器 127.0.0.1:80

8.3.1 网关过滤器相关配置

@Value("${server.port}")
private String serverPort;

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    //如何获取参数呢?
    String token = exchange.getRequest().getQueryParams().getFirst("token");
    if (StringUtils.isEmpty(token)) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
        String msg = "token not is null ";
        DataBuffer buffer = response.bufferFactory().wrap(msg.getBytes());
        return response.writeWith(Mono.just(buffer));
    }
    // 在请求头中存放serverPort serverPort
    ServerHttpRequest request = exchange.getRequest().mutate().header("serverPort", serverPort).build();
    return chain.filter(exchange.mutate().request(request).build());
}

8.3.2 Nginx相关配置

upstream mayiktgwadds {
  server 127.0.0.1:81;
  server 127.0.0.1:82;
}

server {
  listen 80;
  server_name  gw.mayikt.com;
  location / {
    proxy_pass http://mayiktgwadds/;
  }
}

会员服务获取端口号:
image.png
执行结果:
image.png
image.png

8.3.3 动态请求参数网关

动态网关:任何配置都实现不用重启网关服务器都可以及时刷新网关配置。
方案:
1.基于数据库形式实现,特别建议,阅读性高
2.基于配置中心实现,不建议使用,需要定义json格式配置,阅读性差

注意:配置中心实现维护性比较差,建议采用数据库形式设计。

基于数据库表形式的设计
网关已经提供了API接口
1、直接新增
2、直接修改

思路:
默认加载时候
1、当我们的网关服务启动的时候,从我们数据库查询网关的配置。
2、将数据库的内容读取到网关内存中

网关配置要更新的时候,需要同步调用

伪代码步骤:
1、更新数据库
2、调用网关api更新

8.3.4 网关服务相关表

CREATE TABLE `mayikt_gateway` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `route_id` varchar(11) DEFAULT NULL,
  `route_name` varchar(255) DEFAULT NULL,
  `route_pattern` varchar(255) DEFAULT NULL,
  `route_type` varchar(255) DEFAULT NULL,
  `route_url` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;

代码实现动态服务网关过程

@Service
public class GatewayService implements ApplicationEventPublisherAware {
    private ApplicationEventPublisher publisher;
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
    @Autowired
    private MayiktGatewayMapper mayiktGateway;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    public void initAllRoute() {
        // 从数据库查询配置的网关配置
        List<GateWayEntity> gateWayEntities = mayiktGateway.gateWayAll();
        for (GateWayEntity gw :
                gateWayEntities) {
            loadRoute(gw);
        }

    }


    public String loadRoute(GateWayEntity gateWayEntity) {
        RouteDefinition definition = new RouteDefinition();
        Map<String, String> predicateParams = new HashMap<>(8);
        PredicateDefinition predicate = new PredicateDefinition();
        FilterDefinition filterDefinition = new FilterDefinition();
        Map<String, String> filterParams = new HashMap<>(8);
        // 如果配置路由type为0的话 则从注册中心获取服务
        URI uri = null;
        if (gateWayEntity.getRouteType().equals("0")) {
            URI  uri = UriComponentsBuilder.fromUriString("lb://" + gateWayEntity.getRouteUrl() + "/").build().toUri();
        } else {
            uri = UriComponentsBuilder.fromHttpUrl(gateWayEntity.getRouteUrl()).build().toUri();
        }
        // 定义的路由唯一的id
        definition.setId(gateWayEntity.getRouteId());
        predicate.setName("Path");
        //路由转发地址
        predicateParams.put("pattern", gateWayEntity.getRoutePattern());
        predicate.setArgs(predicateParams);

        // 名称是固定的, 路径去前缀
        filterDefinition.setName("StripPrefix");
        filterParams.put("_genkey_0", "1");
        filterDefinition.setArgs(filterParams);
        definition.setPredicates(Arrays.asList(predicate));
        definition.setFilters(Arrays.asList(filterDefinition));
        definition.setUri(uri);
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return "success";
    }


}

8.3.5 Maven依赖

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.1.1</version>
</dependency>
<!-- mysql 依赖 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 阿里巴巴数据源 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.0.14</version>
</dependency>
## 127.0.0.1/mayikt  转到到http://www.mayikt.com/
  datasource:
    url: jdbc:mysql://localhost:3306/meite_gateWay?useUnicode=true&characterEncoding=UTF-8
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver

我们刷新一下,传入token,可以看到:
image.png

8.3.6 GateWay的词汇表有那些

路由:是网关基本的模块,分别为id(唯一)、目标uri(真实服务地址)、一组谓词+过滤器一起组合而成,如果谓词匹配成功,则路由匹配成功。
谓词: 匹配Http请求参数
过滤器:对下游的服务器之前和之后实现处理。
1. 匹配时间之后

- id: mayikt
uri: http://www.mayikt.com/
###匹配规则
predicates:
    - After=2017-01-20T17:42:47.789-07:00[America/Denver]

此路由与2017 年 1 月 20 日 17:42 MountainTime(Denver)之后的所有请求相匹配。
2. 匹配对应的host

- id: meite
  uri: http://www.mayikt.com/
  ###匹配规则
  predicates:
    - Host=meite.mayikt.com

访问 mete.mayikt.com 转发到http://www.mayikt.com/
3.权重谓词

- id: weight_high
  uri: http://www.mayikt.com/yushengjun
  predicates:
    - Weight=group1, 2
- id: weight_low
  uri: http://www.mayikt.com
  predicates:
    - Weight=group1, 1

根据权重比例实现转发

- id: weight_order
  uri: lb://meitemayikt-order
  predicates:
    - Weight=group1,2
- id: weight_member
  uri: lb://mayikt-member
  predicates:
    - Weight=group1,1

详细参考:
https://cloud.spring.io/spring-cloud-gateway/reference/html/#gatewayfilter-factories

8.4 GateWay解决跨域的问题

*/
@Component
public class CrossOriginFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        HttpHeaders headers = response.getHeaders();
        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, PUT, OPTIONS, DELETE, PATCH");
        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*");
        headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
        return chain.filter(exchange);

    }
}

8.5 网关GateWay源码分析

  客户端向网关发送Http请求,会到达DispatcherHandler接受请求,匹配到<br />RoutePredicateHandlerMapping。<br />1. 根据RoutePredicateHandlerMapping匹配到具体的路由策略。<br />2. FilteringWebHandler获取的路由的GatewayFilter数组,创建GatewayFilterChain 处理过滤请求<br />3. 执行我们的代理业务逻辑访问。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22535347/1655645513517-a2864c41-47a1-41bc-81f2-e332f5174602.png#clientId=u22899380-08a5-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=351&id=u15cc3c89&margin=%5Bobject%20Object%5D&name=image.png&originHeight=439&originWidth=293&originalType=binary&ratio=1&rotation=0&showTitle=false&size=515855&status=done&style=none&taskId=u17391d78-90ed-488c-833d-36401674acd&title=&width=234.4)<br />常用配置类说明:<br />1. GatewayClassPathWarningAutoConfiguration  检查是否有正确的配置webflux<br />2. GatewayAutoConfiguration 核心配置类<br />3. GatewayLoadBalancerClientAutoConfiguration 负载均衡策略处理<br />4. GatewayRedisAutoConfiguration  Redis+lua整合限流

8.6 常见错误

Parameter 0 of method modifyRequestBodyGatewayFilterFactory in org.springframework.cloud.gateway.config.GatewayAutoConfiguration required a bean of type ‘org.springframework.http.codec.ServerCodecConfigurer’ that could not be found.

原因就是
SpringCloud gateway基于webflux实现的,不是基于SpringBoot-web,所以应该删除
SpringBoot-web依赖组件。
SpringCloud gateway 常用名词

Route 路由 路由的id (唯一)、 转发uri (真实服务地址)、过滤器、谓词组成。
谓词 匹配的规则

源码分析:

SpringBoot项目源码的入口
1. GatewayClassPathWarningAutoConfiguration 作用检查是否配置我们webfux依赖。
2. GatewayAutoConfiguration加载了我们Gateway需要的注入的类。
3. GatewayLoadBalancerClientAutoConfiguration 网关需要使用的负载均衡
Lb//mayikt-member// 根据服务名称查找真实地址
4. GatewayRedisAutoConfiguration 网关整合Redis整合Lua实现限流
5. GatewayDiscoveryClientAutoConfiguration 服务注册与发现功能

微服务中跨域的问题 不属于前端解决,jsonp 只能支持get请求。
核心点就是在我们后端。

解决跨域的问题
1. HttpClient转发
2. 使用过滤器允许接口可以跨域响应头设置
3. Jsonp 不支持我们的post 属于前端解决
4. Nginx解决跨域的问题保持我们域名和端口号一致性
5. Nginx也是通过配置文件解决跨域的问题
6. 基于微服务网关解决跨域问题,需要保持域名和端口一致性
7. 使用网关代码允许所有的服务可以跨域的问题
8. 使用SpringBoot注解形式@CrossOrigin

9 SpringCloud Sentinel

9.1 服务保护的基本概念

服务接口保护有哪些方案?
黑名单和白名单、对IP实现限流/熔断机制、服务降级、服务隔离机制

9.1.1 服务限流/熔断

服务限流目的是为了更好的保护我们的服务,在高并发的情况下,如果客户端请求的数量达到一定极限(后台可以配置阈值),请求的数量超出了设置的阈值,开启自我的保护,直接调用我们的服务降级的方法,不会执行业务逻辑操作,直接走本地falback的方法,返回一个友好的提示。

9.1.2 服务降级

在高并发的情况下, 防止用户一直等待,采用限流/熔断方法,使用服务降级的方式返回一个友好的提示给客户端,不会执行业务逻辑请求,直接走本地的fallback的方法。返回一个友好的提示给到客户端。
提示语:当前排队人数过多,稍后重试~

9.1.3 服务的雪崩效应

默认的情况下,Tomcat或者是Jetty服务器只有一个线程池去处理客户端的请求,
这样的话就是在高并发的情况下,如果客户端所有的请求都堆积到同一个服务接口上,
那么就会产生tomcat服务器所有的线程都在处理该接口,可能会导致其他的接口无法访问,短暂没有线程处理。

假设我们的tomcat线程最大的线程数量是为20,这时候客户端如果同时发送100个请求会导致有80个请求暂时无法访问,就会转圈。

image.png

如何去证明我们的tomcat服务器只有一个线程池处理我们所有接口的请求。
打印线程名称 线程名称组合:线程池名称+线程id名称。

服务雪崩解决方案:服务隔离机制。

9.1.4 服务的隔离的机制

服务的隔离机制分为信号量和线程池隔离模式
服务的线程池隔离机制:每个服务接口都有自己独立的线程池,互不影响,缺点就是占用cpu资源非常大。
服务的信号量隔离机制:最多只有一定的阈值线程数处理我们的请求,超过该阈值会拒绝请求。

9.2 Sentinel 与hytrix区别

前哨以流量为切入点,从流量控制,熔断降级,系统负载保护等多个维度保护服务的稳定性。

前哨具有以下特征:
1.丰富的应用场景:前哨兵承接了阿里巴巴近10年的双十一大促流的核心场景,例如秒杀(即突然流量控制在系统容量可以承受的范围),消息削峰填谷,传递流量控制,实时熔断下游不可用应用等。
2.完备的实时监控:Sentinel同时提供实时的监控功能。您可以在控制台中看到接收应用的单台机器秒级数据,甚至500台以下规模的整合的汇总运行情况。
广泛的开源生态:Sentinel提供开箱即用的与其他开源框架/库的集成模块,例如与Spring Cloud,Dubbo,gRPC的整合。您只需要另外的依赖并进行简单的配置即可快速地接入Sentinel。
3.完善的SPI扩展点:Sentinel提供简单易用,完善的SPI扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理,适应动态数据源等。

Sentinel中文文档介绍:
https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
image.png

9.3 Sentinel 实现对Api动态限流

限流配置有两种方案:
1、手动使用代码配置 纯代码/注解的形式
2、Sentinel控制台形式配置
3、默认情况下Sentinel不对数据持久化,需要自己独立持久化。

9.3.1 SpringBoot项目整合Sentinel

实现步骤:
创建流控规则/限流规则,然后再被映射地址去引用

Maven依赖的配置

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel</artifactId>
    <version>0.2.2.RELEASE</version>

</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

9.3.2 手动配置管理Api限流接口

private static final String GETORDER_KEY = "getOrder";


@RequestMapping("/initFlowQpsRule")
public String initFlowQpsRule() {
    List<FlowRule> rules = new ArrayList<FlowRule>();
    FlowRule rule1 = new FlowRule();
    rule1.setResource(GETORDER_KEY);
    // QPS控制在1以内
    rule1.setCount(1);
    // QPS限流
    rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
    rule1.setLimitApp("default");
    rules.add(rule1);
    FlowRuleManager.loadRules(rules);
    return "....限流配置初始化成功..";
}


@RequestMapping("/getOrder")
public String getOrders() {
    Entry entry = null;
    try {
        entry = SphU.entry(GETORDER_KEY);
        // 执行我们服务需要保护的业务逻辑
        return "getOrder接口";
    } catch (Exception e) {
        e.printStackTrace();
        return "该服务接口已经达到上线!";
    } finally {
        // SphU.entry(xxx) 需要与 entry.exit() 成对出现,否则会导致调用链记录异常
        if (entry != null) {
            entry.exit();
        }
    }

}

9.3.4 手动放入到项目启动自动加载

当SpringBoot项目启动成功之后,加载限流规则。

@Component
@Slf4j
public class SentinelApplicationRunner implements ApplicationRunner {
    private static final String GETORDER_KEY = "getOrder";

    @Override
    public void run(ApplicationArguments args) throws Exception {
        List<FlowRule> rules = new ArrayList<FlowRule>();
        FlowRule rule1 = new FlowRule();
        rule1.setResource(GETORDER_KEY);
        // QPS控制在1以内
        rule1.setCount(1);
        // QPS限流
        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule1.setLimitApp("default");
        rules.add(rule1);
        FlowRuleManager.loadRules(rules);
        log.info(">>>限流服务接口配置加载成功>>>");
    }
}

9.3.5 注解形式配置管理Api限流

@SentinelResource value参数:流量规则资源名称、
blockHandler 限流/熔断出现异常执行的方法
Fallback 服务的降级执行的方法

@SentinelResource(value = GETORDER_KEY, blockHandler = "getOrderQpsException")
@RequestMapping("/getOrderAnnotation")
public String getOrderAnnotation() {
    return "getOrder接口";
}

/**
 * 被限流后返回的提示
 *
 * @param e
 * @return
 */
public String getOrderQpsException(BlockException e) {
    e.printStackTrace();
    return "该接口已经被限流啦!";
}

9.3.6 控制台形式管理限流接口

Sentinel dashboard 控制台选择创建流量规则,设置资源名称(服务接口地址)、设置QPS 为1 表示每s最多能够访问1次接口。
image.png

9.3.6.1 Sentinel 环境快速搭建

下载对应Sentinel-Dashboard
https://github.com/alibaba/Sentinel/releases/tag/1.7.1 运行即可。
运行执行命令
java -Dserver.port=8718 -Dcsp.sentinel.dashboard.server=localhost:8718 -Dproject.name=sentinel-dashboard -Dcsp.sentinel.api.port=8719 -jar
8718属于 界面端口号 8719 属于api通讯的端口号

浏览器打开登录
image.png
登录进入:
image.png

/**
 * 被限流后返回的提示
 *
 * @param e
 * @return
 */
public String getOrderQpsException(BlockException e) {
    e.printStackTrace();
    return "该接口已经被限流啦!";
}


@SentinelResource(value = "getOrderDashboard", blockHandler = "getOrderQpsException")
@RequestMapping("/getOrderDashboard")
public String getOrderDashboard() {
    return "getOrderDashboard";
}

9.3.6.2 SpringBoot整合Sentinel仪表盘 配置

spring:
  application:
    ###服务的名称
    name: meitemayikt-order

  cloud:
    nacos:
      discovery:
        ###nacos注册地址
        server-addr: 127.0.0.1:8848
    sentinel:
      transport:
        dashboard: 127.0.0.1:8718
      eager: true

此时控制台多了一个应用
image.png

9.3.6.3 基于并发数量处理限流

image.png
每次最多只会有一个线程处理该业务逻辑,超出该阈值的情况下,直接拒绝访问。

@SentinelResource(value = "getOrderThrad", blockHandler = "getOrderQpsException")
@RequestMapping("/getOrderThrad")
public String getOrderThrad() {
    System.out.println(Thread.currentThread().getName());
    try {
        Thread.sleep(1000);
    } catch (Exception e) {

    }
    return "getOrderThrad";
}

注意:如果没有使用@SentinelResource注解的情况下,默认的资源名称为接口路径地址。
image.png
Sentinel限流的规则默认情况下是没有持久化的,如果需要持久化,则采用zk、nacos、携程阿波罗等。
image.png
image.png
在流控规则的阈值类型有2种,分别表达
QPS:接口每秒达到阈值的情况下,自动实现限流。
线程数:接口最多允许多少线程进行处理,如果超出线程数进行限流。
降级规则
image.png
当耗时时间超过了设定的RT值时,则发生熔断机制,降级时间间隔可由时间窗口设定,发生熔断之后,在时间窗口时间内,不能访问该接口。
image.png
例子:
image.png
此时首先访问是正常的,但是接口耗时0.3s
image.png
此时紧接着再次执行:
image.png
而一旦出现熔断情况,在2秒内是无法访问此接口的,一直显示服务降级。

9.4 Sentinel如何保证规则的持久化

默认的情况下Sentinel的规则是存放在内存中,如果Sentinel客户端重启后,Sentinel数据规则可能会丢失。
解决方案:
Sentinel持久化机制支持四种持久化的机制。
1. 本地文件
2. 携程阿波罗
3. Nacos
4. Zookeeper
image.png
image.png
1、订单服务项目启动的时候读取nacos配置规则到内存中
2、Sentinel控制台读取订单服务展示流控规则信息

9.4.1 基于Nacos持久化我们的数据规则

9.4.1.1 Nacos平台中创建我们的流控规则

meitemayikt-order-sentinel
image.png

[
  {
        "resource": "/ getOrderSentinel",
        "limitApp": "default",
        "grade": 1,
        "count": 5,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]

resource:资源名,即限流规则的作用对象
limitApp:流控针对的调用来源,若为 default 则不区分调用来源
grade:限流阈值类型(QPS 或并发线程数);0代表根据并发数量来限流,1代表根据QPS来进行流量控制
count:限流阈值
strategy:调用关系限流策略
controlBehavior:流量控制效果(直接拒绝、Warm Up、匀速排队)
clusterMode:是否为集群模式

@SentinelResource(value = "getOrderSentinel", blockHandler = "getOrderQpsException")
@RequestMapping("/getOrderSentinel")
public String getOrderSentinel() {
    return "getOrderSentinel";
}

9.4.1.2 SpringBoot客户端整合

引入jar包

<!--sentinel 整合nacos -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <version>1.5.2</version>
</dependency>

相关配置

sentinel:
  transport:
    dashboard: 127.0.0.1:8718
  eager: true
  datasource:
    ds:
      nacos:
        ### nacos连接地址
        server-addr: localhost:8848
        ## nacos连接的分组
        group-id: DEFAULT_GROUP
        ###路由存储规则
        rule-type: flow
        ### 读取配置文件的 data-id
        data-id: meitemayikt-order-sentinel
        ###  读取培训文件类型为json
        data-type: json

9.5 SpringCloud网关如何整合sentinel实现限流

查看到sentinel中文社区文档 https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
https://github.com/alibaba/Sentinel/wiki/%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81

9.5.1 相关核心配置

Maven依赖配置

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
            <version>1.6.0</version>
        </dependency>
gateway:
  routes:
    - id: my-member
      uri: lb://meitemayikt-member
      predicates:
        - Path=/meitemayikt-member/**
    - id: mayikt
      uri: http://www.mayikt.com
      predicates:
        - Path=/mayikt/**
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;

import java.util.Collections;
import java.util.List;

@Configuration
public class GatewayConfiguration {

    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        // Register the block exception handler for Spring Cloud Gateway.
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }
}

加载网关流控规则

@Slf4j
@Component
public class SentinelApplicationRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        initGatewayRules();

    }

    /**
     * 配置限流规则
     */
    private void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        rules.add(new GatewayFlowRule("mayikt")
                // 限流阈值
                .setCount(1)
                // 统计时间窗口,单位是秒,默认是 1 秒
                .setIntervalSec(1)
        );
        GatewayRuleManager.loadRules(rules);
    }
}

9.5.2 如何修改限流错误提示

public class JsonSentinelGatewayBlockExceptionHandler implements WebExceptionHandler {
    public JsonSentinelGatewayBlockExceptionHandler(List<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
    }

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        ServerHttpResponse serverHttpResponse = exchange.getResponse();
        serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        byte[] datas = "{\"code\":403,\"msg\":\"API接口被限流\"}".getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas);
        return serverHttpResponse.writeWith(Mono.just(buffer));
    }
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public JsonSentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
    // Register the block exception handler for Spring Cloud Gateway.
    return new JsonSentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}

执行结果:
image.png

9.6 sentinel实现熔断降级

https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7
sentinel支持服务的熔断降级

熔断类似于保险丝,在超出了阈值的情况下,在一定的时间内不会执行业务逻辑,直接执行服务降级的方法。

服务降级:利用本地fallback方法,返回一个有好的提示给客户端,不会真实的去执行业务逻辑。

除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。

9.6.1 降级的策略

1.平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 5 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。
2.异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
3.异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。

9.6.1 平均的响应时间

image.png
如果在1s秒,平均有5个请求的响应时间大于配置的10rt毫秒时间 阈值,则会执行一定时间窗口的熔断和降级。

基于平均相应时间实现降级代码:

@SentinelResource(value = "getOrderDowngradeRtType", fallback = "getOrderDowngradeRtTypeFallback")
@RequestMapping("/getOrderDowngradeRtType")
public String getOrderDowngradeRtType() {
    try {
        Thread.sleep(300);
    } catch (Exception e) {
    }
    return "getOrderDowngradeRtType";
}

public String getOrderDowngradeRtTypeFallback() {
    return "服务降级啦,当前服务器请求次数过多,请稍后重试!";
}

9.6.2 异常的比例

image.png
当我们每秒的请求大于5的时候,会根据一定比例执行我们的熔断降级的策略。

@SentinelResource(value = "getOrderDowngradeErrorType", fallback = "getOrderDowngradeErrorTypeFallback")
@RequestMapping("/getOrderDowngradeErrorType")
public String getOrderDowngradeErrorType(int age) {
    int j = 1 / age;
    return "正常执行我们的业务逻辑";
}

public String getOrderDowngradeErrorTypeFallback(int age) {
    return "服务降级啦,当前服务器请求次数过多,请稍后重试!";
}

9.6.3 异常的次数

image.png
当资源近1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。

9.7 Sentinel实现热点词限流

热点参数限流
https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81

可以根据访问频繁的参数实现限流。

热点参数限流:对接口热词实现限流

9.7.1 fallback与blockHandler的区别

fallback是服务熔断或者业务逻辑出现异常执行的方法(1.6版本以上)
blockHandler 限流出现错误执行的方法。

9.7.2 手动形式创建限流

@RestController
@Slf4j
public class SeckillServiceImpl {

    public SeckillServiceImpl() {
        initSeckillRule();
    }

    /**
     * 秒杀路由资源
     */
    private static final String SEKILL_RULE = "seckill";

    /**
     * 秒杀抢购
     *
     * @return
     */
    @RequestMapping("/seckill")
    public String seckill(Long userId, Long orderId) {
        try {
            Entry entry = SphU.entry(SEKILL_RULE, EntryType.IN, 1, userId);
            return "秒杀成功";
        } catch (Exception e) {
            return "当前用户访问过度频繁,请稍后重试!";
        }
    }
    // seckill?userId=123456&orderId=644064779
    // seckill?userId=123456&orderId=644064779

    private void initSeckillRule() {
        ParamFlowRule rule = new ParamFlowRule(SEKILL_RULE)
                // 对我们秒杀接口第0个参数实现限流
                .setParamIdx(0)
                .setGrade(RuleConstant.FLOW_GRADE_QPS)
                // 每秒QPS最多只有1s
                .setCount(1);
        ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
        log.info(">>>秒杀接口限流策略配置成功<<<");
    }
}

执行结果:
image.png
此时如果频繁访问:
image.png
此时利用这种方式限流是不灵活的。

9.7.3 控制台自定义形式

一、搭建环境:
image.png
配置规则前将jar包导入:
image.png
二、编辑代码

@RequestMapping("/seckill")
@SentinelResource(value = SEKILL_RULE, fallback = "seckillFallback", blockHandler = "seckillBlockHandler")
public String seckill(Long userId, Long orderId) {
    return "秒杀成功";
}

执行结果:
image.png
新增动态规则:
image.png
参数索引表示我们方法传递的第一个参数;注意:该配置在重启之后不会保存。

但此时出现异常会直接显示页面崩溃,用户体验不好,我们可以使用全局捕获异常捕获修改限流出现错误:

@RestControllerAdvice
public class InterfaceExceptionHandler {
    @ResponseBody
    @ExceptionHandler(ParamFlowException.class)
    public String businessInterfaceException(ParamFlowException e) {
        return "您当前访问的频率过高,请稍后重试!";
    }
}

出现异常的执行结果:
image.png
VIP通道设置:调整访问频率即可:
image.png
此时其他窗口还是每秒钟可以访问一次,次数过多会提示“访问频率过高”,但是888窗口是“VIP”,它可以每秒钟访问10次!
image.png