写代码步骤

  1. 建 Module
  2. 改 pom
  3. 写 yml
  4. 主启动

Spring Cloud

一.初识微服务

1. 三种架构

单体架构

将业务的所有功能集中在一个项目中开发,打成一个包部署。
20210901083809.webp
优点:架构简单,部署成本低
缺点:耦合度高(维护困难、升级困难),牵一发而动全身

分布式架构

根据业务功能对系统做拆分,每个业务功能模块作为独立项目开发,称为一个服务。
20210901092921.webp
优点:降低服务耦合,有利于服务升级和拓展
缺点:服务调用关系错综复杂
分布式架构虽然降低了服务之间的耦合,但是服务拆分的时候也需要考虑很多问题:

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

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

微服务架构

微服务架构特征:

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

202205162352847.webp
微服务的上述特性其实是在给分布式架构制定一个标准,进一步降低服务之间的耦合度,提供服务的独立性和灵活性。做到高内聚,低耦合。
因此,可以认为微服务是一种经过良好架构设计的分布式架构方案 。
需要学习的微服务知识内容:
20210901092925.webp

2. SpringCloud

技术栈:20210901083717.webp
image.png
技术栈对比:
20210901090726.webp

二.服务注册与发现

1.Eureka

什么是服务治理?

  Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来 **实现服务治理**
  在传统的rpc远程调用框架中,**管理每个服务与服务之间依赖关系比较复杂**,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。

什么是服务注册与发现?

Eureka 采用了 CS 的设计架构,Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka 的客户端连接到 Eureka Server 并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行
在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地 RPC 调用 RPC 远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何 rpc 远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))

说人话就是:假如你要添加一个美女的电话号码的步骤

  1. 将美女的电话号码(服务地址通讯地址)放到通讯录(注册中心)里面,并且备注(别名)(服务注册)
  2. 要和美女聊天就直接在通讯录里面找美女的名字就完事了(调用服务直接找别名)(服务发现)
  3. 当美女某某电话号码更新的时候,需要通知到你,并修改通讯录服务中的号码。

20210901090919.webp

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

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

    • order-service 从实例列表中利用负载均衡算法选中一个实例地址,向该实例地址发起远程调用
  • order-service 如何得知某个 user-service 实例是否依然健康,是不是已经宕机?

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

代码实践

1.搭建注册中心

  1. 改 pom
  • 引入 SpringCloud 为 eureka 提供的 starter 依赖,注意这里是用 server
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
  1. 写 yml

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 7001

eureka:
instance:
hostname: localhost #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  1. 主启动类
  • 在主启动类上添加 @EnableEurekaServer注解,开启 Eureka 的注册中心功能
1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001
{
public static void main(String[] args)
{
SpringApplication.run(EurekaMain7001.class,args);
}
}

2.服务注册

  1. 改 pom
  • 引入 SpringCloud 为 eureka 提供的 starter 依赖,注意这里是用 client
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  1. 写 yml
  • 在原配置的基础上添加以下配置
1
2
3
4
5
6
7
8
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka
  1. 启动类
  • 在主启动类上添加 **@EnableEurekaClient **注解
1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8001
{
public static void main(String[] args)
{
SpringApplication.run(PaymentMain8001.class,args);
}
}

3.服务拉取

pom、yml、启动类配置与服务注册一样
这里需要向 spring 容器中注入一个RestTemplate,这个 Bean 添加一个 @LoadBalanced 注解,用于开启负载均衡,方便后续搭建集群使用。

RestTemplate:
spring 框架提供的 RestTemplate 类可用于在应用中调用 rest 服务,它简化了与 http 服务的通信方式,统一了 RESTful 的标准,封装了 http 链接, 我们只需要传入 url 及返回值类型即可。

1
2
3
4
5
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RestController
@Slf4j
public class OrderController {

// public static final String PAYMENT_URL = "http://localhost:8001";
//这里用在注册中心注册的名字就可以找到对应的微服务
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

@Resource private RestTemplate restTemplate;

@GetMapping("/consumer/payment/create")
public CommonResult<Payment> create(Payment payment) {
return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
}

@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}
}

Eureka 集群搭建

  1. 将 eureka7001 复制一份,成为 7002,并且在配置文件中互相注册,搭建注册中心集群
1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 7001


eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://eureka7002.com:7002/eureka/
1
2
3
4
5
6
7
8
9
10
11
12
13
server:
port: 7002


eureka:
instance:
hostname: eureka7002.com #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://eureka7001.com:7001/eureka/

  1. 将消费端和服务端都注册进两个注册中心中,并且参照 8001 写出一份 8002,形成服务提供集群
1
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  # 集群版
  1. 测试

发现调用服务是 8001,8002 交替出现,轮流被调用,可以说明RestTemplate默认的负载均衡策略为轮询

服务发现

对于注册进 eureka 里面的微服务,可以通过服务发现来获得该服务的信息
@EnableDiscoveryClient //服务发现

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient //服务发现
public class PaymentMain8001
{
public static void main(String[] args)
{
SpringApplication.run(PaymentMain8001.class,args);
}
}

2. Zookeeper 和 Consul

相比 Eureka,这两个注册中心只是引入了各自的依赖和配置文件,其余基本没变,这里当懒狗就不记了。

3.CAP 理论

CAP 理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,
最多只能同时较好的满足两个。

  • CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
  • AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
  1. AP 架构(Eureka)

当网络分区出现后,为了保证可用性,系统 B 可以返回旧值,保证系统的可用性。
结论:违背了一致性 C 的要求,只满足可用性和分区容错,即 AP
image.png

  1. CP 架构(Zookeeper/Consul)

当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性
结论:违背了可用性 A 的要求,只满足一致性和分区容错,即 CP
image.png

三. 服务调用

1.Ribbon 负载均衡

Eureka 依赖中自带了 Ribbon 的依赖
image.png
前面 RestTemplate 的**@LoadBalanced的注解即赋予了其负载均衡功能,默认为轮询**。

1.1 负载均衡策略

20210901091811.webp

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

1.2 自定义策略

一般使用默认的负载均衡策略。
官方文档明确给出了警告:
这个自定义配置类不能放在@ComponentScan 所扫描的当前包下以及子包下,
否则我们自定义的这个配置类就会被所有的 Ribbon 客户端所共享,达不到特殊化定制的目的了。
image.png

1
2
3
4
5
6
7
8
9
@Configuration
public class MySelfRule
{
@Bean
public IRule myRule()
{
return new RandomRule();//定义为随机
}
}

主启动类

1
2
3
4
5
6
7
8
9
10
11
@SpringBootApplication
@EnableEurekaClient
//在启动该微服务的时候就能去加载我们的自定义Ribbon配置类,从而使配置生效,形如:
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration=MySelfRule.class)
public class OrderMain80
{
public static void main(String[] args)
{
SpringApplication.run(OrderMain80.class,args);
}
}

1.3 LB 负载均衡(Load Balance)是什么?

简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的 HA(高可用)。
常见的负载均衡有软件 Nginx,LVS,硬件 F5 等。

1.4 Ribbon 本地负载均衡客户端 VS Nginx 服务端负载均衡区别

Nginx 是服务器负载均衡,客户端所有请求都会交给 nginx,然后由 nginx 实现转发请求。即负载均衡是由服务端实现的。
** Ribbon 本地负载均衡**,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到 JVM 本地,从而在本地实现 RPC 远程服务调用技术。

2.OpenFeign 服务接口调用

使用 OpenFeign 代替 RestTemplate 来调用其他微服务的接口更加符合开发习惯(Controller 调 Service)

2.1 引入 OpenFeign

  1. 改 pom
1
2
3
4
5
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  1. yml 不用改,启动类如下
  • 使用**@EnableFeignClients**来开启
1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableFeignClients
public class OrderFeignMain80
{
public static void main(String[] args)
{
SpringApplication.run(OrderFeignMain80.class,args);
}
}
  1. 业务类
  • 利用**@FeignClient**来使用,这样子调用这个方法就会去找对应服务的该方法
1
2
3
4
5
6
7
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService
{
@GetMapping(value = "/payment/get/{id}")
CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
}
1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class OrderFeignController
{
@Resource
private PaymentFeignService paymentFeignService;

@GetMapping(value = "/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
{
return paymentFeignService.getPaymentById(id);
}
}

image.png

2.2 超时控制

OpenFeign 默认等待 1 秒钟,超过后报错,OpenFeign 默认支持 Ribbon
配置相关参数自定义超时时间

1
2
3
4
5
6
#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000

2.3 日志打印

日志级别:

  • NONE:默认的,不显示任何日志;
  • BASIC:仅记录请求方法、URL、响应状态码及执行时间;
  • HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;
  • FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。

配置日志级别:

1
2
3
4
5
6
7
8
9
@Configuration
public class FeignConfig
{
@Bean
Logger.Level feignLoggerLevel()
{
return Logger.Level.FULL;
}
}
1
2
3
4
logging:
level:
# feign日志以什么级别监控哪个接口
com.atguigu.springcloud.service.PaymentFeignService: debug

四.服务降级

1.重要概念

1.1 服务降级

当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。
我认为服务降级也是一种思想,能够保证在服务器压力剧增不能及时响应时,给予用户友好提示,以免带来不好的体验的思想。

2.2 服务熔断

类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示

2.3 服务限流

秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟 N 个,有序进行

五.服务网关

1. Gateway 网关

1.1 我们为什么选择 gateway?

1.1.1 SpringCloud Gateway 特性

1.2 SpringCloud 和 Zuul 的区别


Zuul 1.x 的模型

** gateway 模型**

1.3.Gateway 的三大核心概念

  1. Route 路由
  • 路由是构建网关的基本模块,它由 ID,目标 URI,一些列的断言和过滤器组成,如果断言为 true 则匹配该路由。
  • 路由转发:
    • 发送请求调用微服务时,负载均衡分派到微服务之前,会经过网关。
    • 具体分派到哪个微服务,要符合网关的路由转发规则,才能转发到指定微服务
  1. Predicate 断言
  • 参考的是 Java8 的 java.util.function.Predicate,开发人员可以匹配 HTTP 请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由。
  1. **Filter 过滤 **
  • 指的是 Spring 框架中 GatewayFliter 的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。 处理请求前和处理请求后都可能有 Filter

总体流程:

  1. 客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。
  2. Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
  3. 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
  4. Filter 在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。

核心逻辑:路由转发+执行过滤器链

1.4 Gateway 的入门配置

  1. 新建 Module,后改 pom
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<dependencies>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--一般基础配置类-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
  1. 写 yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 9527

spring:
application:
name: cloud-gateway

eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
  1. 主启动
1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527
{
public static void main(String[] args)
{
SpringApplication.run(GateWayMain9527.class,args);
}
}

1.4.1 使用

我们不想暴露 8001 端口,所以在 8001 上套一层 9527

  1. 在配置文件中增加网关配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

server:
port: 9527

spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由

- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由

eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka

  1. 在配置类中配置
  • 这里演示的是转发到百度新闻
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Configuration
public class GateWayConfig
{
/**
* 配置了一个id为route-name的路由规则,
* 当访问地址 http://localhost:9527/guonei时会自动转发到地址:http://news.baidu.com/guonei
* @param builder
* @return
*/
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder)
{
RouteLocatorBuilder.Builder routes = builder.routes();

routes.route("path_route_atguigu", r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();

return routes.build();

}
@Bean
public RouteLocator customRouteLocator2(RouteLocatorBuilder builder)
{
RouteLocatorBuilder.Builder routes = builder.routes();
routes.route("path_route_atguigu2", r -> r.path("/guoji").uri("http://news.baidu.com/guoji")).build();
return routes.build();
}
}

1.5 通过微服务名实现动态路由

默认情况下 Gateway 会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能

  1. 配置文件修改
  • 需要注意的是 uri 的协议为 lb,表示启用 Gateway 的负载均衡功能。
  • lb://serviceName 是 spring cloud gateway 在微服务中自动为我们创建的负载均衡 uri
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
server:
port: 9527

spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由

- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由

eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka

1.6 Predicate 的使用

Predicate 就是为了实现一组匹配规则,让请求过来找到对应的 Route 进行处理。

1.7 Filter 的使用

路由过滤器可用于修改进入的 HTTP 请求和返回的 HTTP 响应,路由过滤器只能指定路由进行使用。
Spring Cloud Gateway 内置了多种路由过滤器,他们都由 GatewayFilter 的工厂类来产生.

Spring Cloud Alibaba

官网:https://github.com/alibaba/spring-cloud-alibaba/blob/2.2.x/README-zh.md
主要功能:

  • 服务限流降级:默认支持 WebServlet、WebFlux、OpenFeign、RestTemplate、Spring Cloud Gateway、Zuul、Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
  • 服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
  • 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
  • 消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
  • 分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。
  • 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
  • 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。
  • 阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。

一.Nacos 服务注册和配置中心

1.Nacos 是什么?能干嘛?

一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
Nacos 就是注册中心 + 配置中心的组合,Nacos = Eureka+Config +Bus
可以替代 Eureka 做服务注册中心,替代 Config 做服务配置中心。

2.各个服务中心比较

image.png
image.png
Nacos 提供 AP 和 CP 两个模式。

3.使用 Nacos 作为注册中心

3.1 安装 Nacos

3.2 注册服务

  1. 新建 cloudalibaba-provider-payment9001,并修改 pom
1
2
3
4
5
6
7
8
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>

1
2
3
4
5
6
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

  1. 写 yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server:
port: 9001

spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址

management:
endpoints:
web:
exposure:
include: "*"
  1. 主启动

注意**@EnableDiscoveryClient**注解

1
2
3
4
5
6
7
8
@EnableDiscoveryClient
@SpringBootApplication
public class PaymentMain9001
{
public static void main(String[] args) {
SpringApplication.run(PaymentMain9001.class, args);
}
}
  1. 业务类
1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class PaymentController
{
@Value("${server.port}")
private String serverPort;

@GetMapping(value = "/payment/nacos/{id}")
public String getPayment(@PathVariable("id") Integer id)
{
return "nacos registry, serverPort: "+ serverPort+"\t id"+id;
}
}

这时查看 nacos 控制台,发现已经注册进去了
image.png
参照 9001,写出一个 9002,方便演示负载均衡

3.3 服务拉取

  1. 新建 cloudalibaba-consumer-nacos-order83,修改 pom
1
2
3
4
5
6
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

  1. 写 yml

这里需要配置 消费者将要去访问的微服务名称

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 83

spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848

#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider
  1. 主启动
1
2
3
4
5
6
7
8
9
@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain83
{
public static void main(String[] args)
{
SpringApplication.run(OrderNacosMain83.class,args);
}
}
  1. 业务类

RestTemplate 的注入和前面一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
public class OrderNacosController
{
@Resource
private RestTemplate restTemplate;

@Value("${service-url.nacos-user-service}")
private String serverURL;

@GetMapping("/consumer/payment/nacos/{id}")
public String paymentInfo(@PathVariable("id") Long id)
{
return restTemplate.getForObject(serverURL+"/payment/nacos/"+id,String.class);
}

}

这时访问 http://localhost:83/consumer/payment/nacos/13 ,出现 9001,9002 交替出现,轮询负载均衡成功,因为 Nacos 集成了 ribbon。

4.使用 Nacos 作为配置中心

4.1 基础配置

  1. 新建 cloudalibaba-config-nacos-client3377,并修改 pom
1
2
3
4
5
<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
  1. 写 yml

这里需要写两个 yml,Nacos 同 springcloud-config 一样,在项目初始化时,要保证先从配置中心进行配置拉取
拉取配置之后,才能保证项目的正常启动。
springboot 中配置文件的加载是存在优先级顺序的,bootstrap 优先级高于 application。因此在 bootstrap 里面写该服务特有的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# nacos配置
server:
port: 3377

spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
config:
server-addr: localhost:8848 #Nacos作为配置中心地址
file-extension: yaml #指定yaml格式的配置


# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
1
2
3
spring:
profiles:
active: dev # 表示开发环境
  1. 主启动
1
2
3
4
5
6
7
8
@EnableDiscoveryClient
@SpringBootApplication
public class NacosConfigClientMain3377
{
public static void main(String[] args) {
SpringApplication.run(NacosConfigClientMain3377.class, args);
}
}
  1. 业务类

在控制器类加入**@RefreshScope**注解使当前类下的配置支持 Nacos 的动态刷新功能。

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
@RefreshScope //在控制器类加入@RefreshScope注解使当前类下的配置支持Nacos的动态刷新功能。
public class ConfigClientController
{
@Value("${config.info}")
private String configInfo;

@GetMapping("/config/info")
public String getConfigInfo() {
return configInfo;
}
}
  1. 在 Nacos 中添加配置信息

Nacos 中的 dataid 的组成格式及与 SpringBoot 配置文件中的匹配规则:
image.png
最后公式:${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

Nacos 界面:
image.png
总结:
image.png

4.2 分类配置

为什么需要分类配置?
为了解决以下问题
问题 1:
实际开发中,通常一个系统会准备,dev 开发环境,test 测试环境,prod 生产环境。
如何保证指定环境启动时服务能正确读取到 Nacos 上相应环境的配置文件呢?
问题 2:
一个大型分布式微服务系统会有很多微服务子项目,每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境……
那怎么对这些微服务配置进行管理呢?

4.2.1 命名空间、分组,DataId 三者之间的关系

202105311539240.png

4.2.2 使用

修改 yml,来匹配不同的配置

1
2
3
4
5
6
# Nacos注册配置,application.yml
spring:
profiles:
#active: test
active: dev
#active: info
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# nacos注册中心
server:
port: 3377

spring:
application:
name: nacos-order
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
config:
server-addr: localhost:8848 #Nacos作为配置中心地址
file-extension: yaml #这里我们获取的yaml格式的配置
namespace: 5da1dccc-ee26-49e0-b8e5-7d9559b95ab0
#group: DEV_GROUP
group: TEST_GROUP

5.Nacos 集群和持久化

架构说明:我们需要 1 个 Nginx+3 个 Nacos+1 个 Mysql202108182000220.webp

5.1 持久化说明

其实就是把 Nacos 的配置保存到 mysql 数据库中来实现持久化

  1. nacos-server-1.1.4\nacos\conf 目录下找到 sql 脚本,并且在数据库中建库运行
  2. nacos-server-1.1.4\nacos\conf 目录下找到 application.properties,并且修改,配置 mysql 数据库
1
2
3
4
5
6
spring.datasource.platform=mysql

db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=123456

5.2 集群搭建

参考: http://www.javaheidong.com/blog/article/471603/c04739243ce86a8d449c/
https://blog.csdn.net/AyinMars/article/details/125805296

二.Sentinel 实现熔断与限流

官网:https://sentinelguard.io/zh-cn/index.html
Sentinel 具有以下特征

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

1.安装 Sentinel

  1. 下载 jar 包:https://github.com/alibaba/Sentinel/releases
  2. 命令行运行 jar 包:java -jar sentinel-dashboard-1.7.2.jar (前提:java8 环境,8080 端口不被占用)
  3. 访问 http://localhost:8080 ,登录账号密码均为 sentinelimage.png

2.使用 Sentinel

  1. 建 cloudalibaba-sentinel-service8401,修改 pom
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
  1. 写 yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server:
port: 8401

spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
#Nacos服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719

management:
endpoints:
web:
exposure:
include: "*"
  1. 主启动
1
2
3
4
5
6
7
8
@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401
{
public static void main(String[] args) {
SpringApplication.run(MainApp8401.class, args);
}
}
  1. 业务类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
public class FlowLimitController
{
@GetMapping("/testA")
public String testA()
{
return "------testA";
}

@GetMapping("/testB")
public String testB()
{
return "------testB";
}
}

准备就绪,开始使用

  1. 启动 Nacos,8848
  2. 启动微服务,8401
  3. 启动 Sentinel,8080
  4. 因为 Sentinel 为懒加载,因此刚启动时在控制台并不能看到效果,需要访问 http://localhost:8401/testAhttp://localhost:8401/testB 之后才能看到效果。

image.png

3.流控规则

image.png
image.png

4.降级规则

Sentinel 提供以下几种熔断策略:

  • 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
  • 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  • 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

image.png
RT(平均响应时间,秒级)

  • 平均响应时间 超出阈值 且 在时间窗口内通过的请求>=5,两个条件同时满足后触发降级
  • 窗口期过后关闭断路器
  • RT 最大 4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX 才能生效)

异常比例(秒级)

  • QPS >= 5 且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

异常数(分钟级)

  • 异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

5.热点 Key 限流

何为热点?
热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的 TopN 数据,并对其访问进行限流或者其它操作
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
image.png

5.1 关于降级方法

系统默认的降级:Blocked by Sentinel (flow limiting)
**
利用**@SentinelResource **注解中的 blockHandler 属性指定兜底方法。

1
2
3
4
5
6
7
8
9
10
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "dealHandler_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2){
return "------testHotKey";
}
public String dealHandler_testHotKey(String p1,String p2,BlockException exception)
{
return "-----dealHandler_testHotKey";
}

5.2 配置热点 key

image.png
限流模式只支持 QPS 模式,固定写死了。(这才叫热点)
@SentinelResource 注解的方法参数索引,0 代表第一个参数,1 代表第二个参数,以此类推
单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。
上面的抓图就是第一个参数有值的话,1 秒的 QPS 为 1,超过就限流,限流后调用 dealHandler_testHotKey 支持方法。
测试:当 QPS 超过 1 秒 1 次点击后马上被限流
image.png

5.3 配置热点 key 的特例情况

image.png

6.系统规则

类似全局配置:总控的功能
1735851e5c89791c_tplv-t2oaga2asx-zoom-in-crop-mark_3024_0_0_0.webp173585a77d6f9865_tplv-t2oaga2asx-zoom-in-crop-mark_3024_0_0_0.webp1735859fabf1cfc7_tplv-t2oaga2asx-zoom-in-crop-mark_3024_0_0_0.webp

7.@SentinelResource

7.1 参数说明

value

资源名称,必需项,因为需要通过 resource name 找到对应的规则,这个是必须配置的。

blockHandler

blockHandler 对应处理 BlockException 的函数名称,可选项,blockHandler 管配置违规
blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。

blockHandlerClass

blockHandler 函数默认需要和原方法在同一个类中,如果希望使用其他类的函数,则需要指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

fallback

fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。

fallbackClass

fallbackClass 的应用和 blockHandlerClass 类似,fallback 函数默认需要和原方法在同一个类中。
若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

7.2 按资源名称限流

image.png

7.3 按照 Url 地址限流

image.png

7.4 配置自定义降级方法

image.png

7.5 Sentinel+OpenFeign

  1. pom
1
2
3
4
5
<!--SpringCloud openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  1. yml
1
2
3
4
# 激活Sentinel对Feign的支持
feign:
sentinel:
enabled: true
  1. 主启动
1
2
3
4
5
6
7
8
9
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
public class OrderNacosMain84
{
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain84.class, args);
}
}
  1. 业务类
1
2
3
4
5
6
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)//调用中关闭9003服务提供者
public interface PaymentService
{
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
1
2
3
4
5
6
7
8
9
@Component
public class PaymentFallbackService implements PaymentService
{
@Override
public CommonResult<Payment> paymentSQL(Long id)
{
return new CommonResult<>(444,"服务降级返回,没有该流水信息",new Payment(id, "errorSerial......"));
}
}

7.6 熔断框架对比

image.png

8.规则持久化

发现一旦重启微服务,sentinel 的规则就会消失,没有持久化
解决方法:使用 nacos 来保存配置。

  1. 修改 8401pom
1
2
3
4
5
<!--SpringCloud alibaba sentinel-datasource-nacos:后续做持久化用到-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
  1. 改 yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
server:
port: 8401

spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
# Nacos服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
# 配置Sentinel dashboard地址
dashboard: localhost:8080
# 默认8719端口,假如被占用会自动从8719开始一次+1扫描,直至找到被占用的端口。
port: 8719

# 添加Nacos数据源配置
datasource:
ds1: # 数据源1
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow # 流控规则

management:
endpoints:
web:
exposure:
include: "*"

feign:
sentinel:
enabled: true # 激活Sentinel对Feign的支持
  1. 在 nacos 中新建配置

image.pngimage.png

三.Seata 处理分布式事务

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

1.分布式事务

分布式事务,就是指不是在单个服务或单个数据库架构下,产生的事务,例如

  • 跨数据源的分布式事务
  • 跨服务的分布式事务
  • 综合情况

例如下单业务:
202205231447738.webp
这时候就需要保证三个操作同时成功,也就是原子性,就需要用到分布式事务。

2. Seata

官网:http://seata.io/zh-cn/

2.1 分布式事务处理过程

一 ID+三组件模型:(班级号,授课老师,班主任,学生)

  • Transaction ID XID:全局唯一的事务 ID
  • TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
  • TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
  • RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与 TC 交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

处理过程:

  1. TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
  2. XID 在微服务调用链路的上下文中传播;
  3. RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
  4. TM 向 TC 发起针对 XID 的全局提交或回滚决议;
  5. TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。

image.png

2.2 使用方法

本地@Transactional
全局@GlobalTransactional
image.png

2.3 AT 模式

参考官网:http://seata.io/zh-cn/docs/overview/what-is-seata.html