当前位置: 首页 > news >正文

微服务架构与SpringCloudAlibaba全解析

1、什么是微服务架构?

微服务架构(Microservice Architecture)是一种架构概念,旨在通过将功能分解到各个离散的服务中以实现对解决方案的解耦。微服务架构是个很有趣的概念,它的主要作用是将功能分解到离散的各个服务当中,从而降低系统的耦合性,并提供更加灵活的服务支持。

l 概念:把一个大型的单个应用程序和服务拆分为数个甚至数十个的支持微服务,它可扩展单个组件而不是整个的应用程序堆栈,从而满足服务等级协议。

l 定义:围绕业务领域组件来创建应用,这些应用可独立地进行开发、管理和迭代。在分散的组件中使用云架构和平台式部署、管理和服务功能,使产品交付变得更加简单。

l 本质:用一些功能比较明确、业务比较精练的服务去解决更大、更实际的问题。

微服务架构是一种将复杂应用程序分解为一组小型、独立服务的软件设计方法。这种架构风格代表了从传统单体应用向分布式系统的演进,旨在提高系统的灵活性、可维护性和可扩展性。

微服务架构的核心特征:

服务原子化:

每个微服务专注于单一业务能力,遵循"单一职责原则"

服务边界按业务领域而非技术层次划分

独立完成从数据管理到用户界面的完整业务功能

自治性:

每个服务运行在独立的进程中

拥有独立的数据存储(可采用不同数据库技术)

可独立开发、测试、部署和扩展

技术栈不受限制(多语言、多框架共存)

轻量级通信:

服务间通过定义良好的API进行协作

通常采用HTTP/REST或gRPC等轻量协议

异步消息机制(如Kafka、RabbitMQ)用于事件驱动场景

2、SpringCloud是什么?

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署 [1]。Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。

Spring Cloud基于 Spring Boot 提供了一套一站式微服务解决方案,包括服务注册与发现,配置中心,全链路监控,服务网关,负载均衡,熔断器等组件,除了基于 NetFlix 的开源组件做高度抽象封装之外,还有一些选型中立的开源组件。

SpringCloud 利用 SpringBoot 的开发便利性巧妙地简化了分布式系统基础设施的开发,SpringCloud 为开发人员提供了快速构建分布式系统的一些工具,包括配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等,它们都可以用 SpringBoot 的开发风格做到一键启动和部署。

SpringCloud ,是各个微服务架构落地技术的集合体,俗称微服务全家桶。

3、SpringCloud的优缺点?

优点:

  1. 每个服务足够内聚,足够小,代码容易理解
  2. 开发简单,开发效率高,一个服务只干一件事
  3. 微服务能够被小团队单独开发
  4. 微服务是松耦合的,无论是开发还是部署阶段都是独立的
  5. 微服务可以使用不同语言开发
  6. 易于和第三方集成,微服务允许容易且灵活的方式自动集成部署,通过持续集成工具,如jenkins,hudson、bamboo
  7. 微服务易于被一个开发人员理解,修改和维护
  8. 微服务允许你利用融合最新技术
  9. 微服务只是业务逻辑代码
  10. 每个微服务都有子集的存储能力

缺点:

  1. 开发人员要处理分布式系统的复杂性
  2. 多服务运维难度,随着服务的增加,运维的难度也加大
  3. 系统部署依赖
  4. 系统间通信成本
  5. 数据一致性
  6. 系统集成测试

4、Spring Cloud 和 Spring Boot 的区别

Spring Boot 专注于快速方便的开发单个个体微服务。

Spring Cloud 是关注全局的微服务协调整理治理框架,它将 Spring Boot 开发的一个个单体微服务整合并管理起

来,为各个微服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务。

Spring Boot 可以离开 Spring Cloud 独立使用开发项目,但是 Spring Cloud 离不开 Spring Boot,属于依赖的关系。

Spring Boot 专注于快速、方便的开发单个微服务个体,Spring Cloud 关注全局的服务治理框架。

5、目前主流的微服务架构选择?

目前市场主流选择:

SpringCloud Alibaba+SpringCloud原生

网关:Spring Cloud GateWay

服务注册与发现、配置中心:SpringCloud Alibaba Nacos

服务间调用:Spring Cloud OpenFeign

负载均衡:Spring Cloud LoadBalance

客户端容错保护:SpringCloud Alibaba Sentinel

消息中间件:RabbitMQ

分布式事务:Seata

6、SpringCloud Alibaba

https://start.aliyun.com/

Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发微服务架构的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发微服务架构。

依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里分布式应用解决方案,通过阿里中间件来迅速搭建分布式应用系统。

7、注册中心nacos

Nacos(Naming and Configuration Service)是由阿里巴巴开源的一款云原生应用配套工具,主要应用于服务发现、配置管理、服务管理等场景,旨在简化微服务架构中的服务治理工作。Nacos特别适合构建和管理现代云原生应用的微服务架构。

为什么需要Nacos?

随着微服务架构的普及,现代应用程序越来越趋向于模块化,每个服务都作为一个独立的应用程序进行开发、部署和运维。随着服务数量的增加,管理和协调这些服务的复杂性也随之增加。Nacos旨在解决这些问题,它提供了服务发现与配置管理的一站式解决方案,帮助开发者轻松管理和监控分布式系统中的服务实例。

Nacos的工作原理

Nacos工作原理主要涉及以下几个关键步骤:

服务注册:当一个服务启动时,它会向Nacos服务器注册自身的信息(例如IP地址、端口号等)。

服务发现:当一个服务需要调用另一个服务时,会通过客户端向Nacos服务器请求目标服务的实例信息,Nacos服务器会返回目标服务的实例列表(包括服务的IP地址、端口号、健康状态和其他元数据等)。

配置下发:Nacos服务器存储服务的配置信息。当配置信息需要更新时,Nacos服务器会推送更新的通知给相关的服务实例,客户端可以实时拉取最新的配置信息,并在应用中生效。

健康检查:Nacos定期对注册的服务实例进行健康状况检查。当服务实例出现故障时,Nacos服务器会将其标记为不可用,并从服务注册表中自动移除不可用的服务实例。

Nacos的优势

Nacos的优势主要包括:

  • 易用性:简洁的API设计使得集成非常方便,还提供了用户友好的界面和丰富的API接口,使得管理和监控配置及服务实例变得更加直观。
  • 高性能:Nacos在设计上考虑了高性能和高可用性,支持集群部署和水平扩展,能够根据业务需求增加或减少服务器节点、处理大规模的服务发现请求,还具备高吞吐量和低延迟的特点。
  • 兼容性:支持多种语言和服务框架,如Spring Cloud、Dubbo等。
  • 灵活性:可以通过插件机制来扩展其功能,满足特定需求。例如通过插件支持多种数据库类型和对配置数据进行加解密等。
  • 安全性:支持多租户隔离,可以针对不同的环境和团队创建不同的命名空间;支持基于角色的访问控制(RBAC),确保数据的安全性。

bootstrap.yaml

windows单机启动

nacos-server-2.5.1.zip

进入bin目录下

startup.cmd -m standalone

nacos配置概念

命名空间(Namespace)

基于进行用户粒度的配置隔离。不同的命名空间下,可以存在相同的 Group 或 Data ID 的配置。Namespace 的常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。

配置分组(Group)

Nacos 中的一组配置集,是组织配置的维度之一。通过一个有意义的字符串(如 Buy 或 Trade )对配置集进行分组,从而区分 Data ID 相同的配置集。当您在 Nacos 上创建一个配置时,如果未填写配置分组的名称,则配置分组的名称默认采用 DEFAULT_GROUP 。配置分组的常见场景:不同的应用或组件使用了相同的配置类型,如 database_url 配置和 MQ_topic 配置。

配置集 ID(Data ID)

Nacos 中的某个配置集的 ID。配置集 ID 是组织划分配置的维度之一。Data ID 通常用于组织划分系统的配置集。一个系统或者应用可以包含多个配置集,每个配置集都可以被一个有意义的名称标识。Data ID 通常采用类 java 包(如 com.taobao.tc.refund.log.level)的命名规则保证全局唯一性。此命名规则非强制。

配置集:一组相关或者不相关的配置项的集合称为配置集。在系统中,一个配置文件通常就是一个配置集,包含了系统各个方面的配置。例如,一个配置集可能包含了数据源、线程池、日志级别等配置项。

Springboot整合nacos(Nacos 服务作为配置中心)

1、pom文件引入

<!--nacos配置管理依赖-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

2、编写配置文件

spring:application:name: springboot-nacoscloud:nacos:config:server-addr: 127.0.0.1:8848namespace:group: DEFAULT_GROUPfile-extension: yamlencode: UTF-8
spring:profiles:active: @activatedProperties@  # 通过Maven属性替换

3、编写控制器

@RefreshScope注解:@RefreshScope 是 Spring Cloud 提供的一个注解,用于实现配置的动态刷新。它允许应用程序在不重启的情况下,获取并应用配置中心(如 Nacos、Consul、Apollo)中最新的配置值。

4、nacos 的配置

建议的命名规则
  1. 应用相关性dataId应体现它所关联的应用名称,这有助于快速定位和区分不同应用的配置。
${应用名}-${环境标识}.properties/yml
  1. 环境区分:在命名中加入环境标识(如dev、test、prod),以便于区分不同环境的配置。
  2. 功能或模块标识:如果配置仅针对应用中的某个模块或具有特定功能,可以在应用名后加上模块或功能名称。
${应用名}-${模块名}-${环境标识}.properties/yml
  1. 版本控制:虽然不常见,但在某些场景下,为了追踪配置的版本变化,可以在命名中加入版本号或修订日期。
  2. 文件类型:明确指出配置文件的格式,通常是.properties.yml
示例

假设有一个名为myapp的应用,其中有一个user-service模块,需要为开发、测试和生产环境分别配置,可以这样命名:

  • 开发环境:myapp-user-service-dev.yml
  • 测试环境:myapp-user-service-test.yml
  • 生产环境:myapp-user-service-prod.yml
nacos配置

5、启动项目访问测试接口

Springboot整合nacos(Nacos 服务注册与发现)

1、引入pom依赖

<!--nacos配置管理依赖-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Spring Cloud LoadBalancer -->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

2、创建商品信息服务,并进行配置

3、在商品信息服务中创建测试接口

4、在naocs测试服务中创建RestTemplate配置类

5、在nacos测试服务编写配置

6、在nacos测试服务中编写测试接口

7、启动服务,并在nacos中查看服务是否被注册

8、访问测试接口,查看nacos测试服务中调用商品信息服务是否成功

RestTemplate介绍

RestTemplate 是 Spring 提供的一个同步 HTTP 客户端,通常用于在 Spring 应用程序中发起 HTTP 请求和与外部服务进行通信。它封装了 HttpURLConnection,简化了与 RESTful 服务的交互,能够发起 GET、POST、PUT、DELETE 等 HTTP 请求。

  • getForObject(String url, Class<T> responseType, Object... uriVariables):发送 GET 请求并直接返回响应体。
  • getForEntity(String url, Class<T> responseType, Object... uriVariables):发送 GET 请求并返回 ResponseEntity,包含状态码、头和响应体。
  • postForObject(String url, Object request, Class<T> responseType, Object... uriVariables):发送 POST 请求并直接返回响应体。
  • postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables):发送 POST 请求并返回 ResponseEntity。
  • postForLocation(String url, Object request, Object... uriVariables):发送 POST 请求并返回新创建资源的 URI。
  • put(String url, Object request, Object... uriVariables):发送 PUT 请求更新指定资源,不返回响应体。
  • delete(String url, Object... uriVariables):发送 DELETE 请求删除指定资源。
  • headForHeaders(String url, Object... uriVariables):发送 HEAD 请求并获取响应头信息。
  • optionsForAllow(String url, Object... uriVariables):发送 OPTIONS 请求并获取允许的 HTTP 方法集合。
  • exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables):通用方法,可自定义 HTTP 方法、请求头、请求体并获取响应。

Ribbon介绍

Ribbon 是一个 客户端负载均衡器,它用于管理和调度请求到多个服务实例。Ribbon 是 Spring Cloud 的一部分,它负责从服务注册中心(如 Eureka 或 Nacos)中获取服务实例的列表,并根据负载均衡策略将请求分发到这些服务实例上。Ribbon 提供了多种负载均衡策略,比如轮询、加权轮询、随机、最少连接等。

Ribbon的工作原理:

服务发现:Ribbon 会与服务注册中心(例如 Eureka 或 Nacos)通信,动态获取服务实例的列表。

负载均衡:Ribbon 使用负载均衡策略来选择一个服务实例进行请求。例如,默认的轮询策略会按顺序将请求分配到每个实例上。

请求转发:Ribbon 将根据负载均衡策略,选择一个合适的服务实例,并将请求发送到该实例。

@LoadBalanced注解

@LoadBalanced 是 Spring Cloud 提供的一个注解,用于标记 RestTemplate。当一个 RestTemplate 被标注为 @LoadBalanced 时,它会自动启用 Ribbon 的客户端负载均衡功能。

具体来说,@LoadBalanced 让 Spring Boot 在创建 RestTemplate Bean 时,自动为它配置 Ribbon 的负载均衡器。它的作用是把服务名(如 http://supermarket-product-recognition)转化为 Ribbon 负载均衡器选择的服务实例地址,而无需手动指定服务的具体地址(如 localhost:9001)。

负载均衡的两种方式

服务器端负载均衡:例如Nginx,通过Nginx进行负载均衡,先发送请求,然后通过负载均衡算法,在多个服务器之间选择一个进行访问;即在服务器端再进行负载均衡算法分配。

客户端负载均衡:例如spring cloud中的ribbon,客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,这是客户端负载均衡;即在客户端就进行负载均衡算法分配。

8、OpenFeign介绍

1、常见HTTP客户端

HttpClient

HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 Http 协议的客户端编程工具包,并且它支持 HTTP 协议最新版本和建议。HttpClient 相比传统 JDK 自带的 URLConnection,提升了易用性和灵活性,使客户端发送 HTTP 请求变得容易,提高了开发的效率。

Okhttp

一个处理网络请求的开源项目,是安卓端最火的轻量级框架,由 Square 公司贡献,用于替代 HttpUrlConnection 和 Apache HttpClient。OkHttp 拥有简洁的 API、高效的性能,并支持多种协议(HTTP/2 和 SPDY)。

HttpURLConnection

HttpURLConnection 是 Java 的标准类,它继承自 URLConnection,可用于向指定网站发送 GET 请求、POST 请求。HttpURLConnection 使用比较复杂,不像 HttpClient 那样容易使用。

RestTemplate

RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便捷访问远程 HTTP 服务的方法,能够大大提高客户端的编写效率。

2、使用Feign实现远程HTTP调用

什么是Feign?

Feign是Netflix开发的一个轻量级RESTful的HTTP服务客户端(用它来发起请求,远程调用的),是以 Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用,Feign被 广泛应用在Spring Cloud 的解决方案中。

Feign与OpenFeign的区别

Feign是Spring Cloud组件中一个轻量级RESTful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用接口,就可以调用服务注册中心的服务。

OpenFeign是Spring Cloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中.

3、整合OpenFeign

1、pom文件引入依赖

    <!-- OpenFeign --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>

2、启动类添加注解@EnableFeignClients

3、编写调用接口服务

4、编写测试接口进行测试

5、如何添加Feign请求拦截器

4、Client设置

Feign 默认使用 JDK 原生的 URLConnection 来发送 HTTP 请求(不支持线程池),Feign 支持集成其他更强大的 HTTP 客户端来替代默认的 URLConnection:

Apache HttpClient:功能丰富的 HTTP 客户端库

OkHttp:Square 开发的高效 HTTP 客户端,支持 HTTP/2

Apache HttpClient集成

1、pom文件引入
<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-httpclient</artifactId>
</dependency>
2、配置文件添加配置
feign:httpclient:# 使用apche httpclient 禁用Feign默认否的HttpClientenabled: true# feign设置最大连接数 默认200max-connections: 200# feign设置每个路由的连接数 默认50max-connections-per-route: 50
3、验证是否配置成功

当存在 ApacheHttpClient 类(即引入了 feign-httpclient 依赖),feign.httpclient.enabled 为 true 或未配置使用该配置。

OkHttp集成

1、pom文件引入
<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-okhttp</artifactId>
</dependency>
2、添加配置文件
feign:okhttp:enabled: truehttpclient:max-connections: 200max-connections-per-route: 50
3、验证是否配置成功

5、超时配置

Request.Options 是 Feign 中用于配置 HTTP 请求超时时间的重要类,它控制着请求的连接和读取超时行为。

1、自定义配置超时

2、测试调用接口超时

9、Sentinel的使用

什么是服务雪崩效应?

在分布式系统中,由于网络原因或自身的原因,服务一般无法保证 100% 可用。如果一个服务出现了问题,调用这个服务就会出现线程阻塞的情况,此时若有大量的请求涌入,就会出现多条线程阻塞等待,进而导致服务瘫痪。

由于服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的"雪崩效应"。

雪崩发生的常见原因:程序Bug,大流量请求,硬件故障,缓存击穿

我们无法完全杜绝雪崩的发生,只能做好足够的容错,保证子啊一个服务发送问题时,不影响到其他服务的正常运行。

常见的容错思路:

1、隔离

微服务系统中,隔离策略是流量治理的关键组成部分,其主要目的是避免单个服务的故障引发整个系统的连锁反应。

通过隔离,系统能够局部化问题,确保单个服务的问题不会影响到其他服务,从而维护整体系统的稳定性和可靠性。

核心隔离

核心隔离通常是指将资源按照 “核心业务”与 “非核心业务”进行划分,优先保障“核心业务”的稳定运行。

热点隔离

热点隔离通常是指一种针对高频访问数据(热点数据)的隔离策略。

用户隔离

用户隔离通常是指按照不同的分组形成不同的服务实例。这样某个服务实例宕机了也只会影响对应分组的用户,而不会影响全部用户。

2、超时

在上游服务调用下游服务的时候,上游服务设置一个最大响应时间,如果超过这个时间,下游未作出反应,上游服务就断开请求,释放掉线程。

3、限流

限制请求核心服务提供者的流量,使大流量拦截在核心服务之外,这样可以更好的保证核心服务提供者不出问题,对于一些出问题的服务可以限制流量访问。

限流是一种针对服务提供者的策略,用于控制对特定服务接口或服务实例的访问量。其目的在于保护服务提供者免受过大请求流量的影响,确保服务稳定性。限流措施可以在服务提供者或服务消费者两端实现,通过设定流量阈值并采取排队、拒绝请求或返回错误信息等方式来控制流量,从而保护服务。

4、熔断

在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。

服务熔断一般有三种状态:

  • 熔断关闭状态(Closed)

服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制。

  • 熔断开启状态(Open)

后续对该服务接口的调用不再经过网络,直接执行本地的fallback方法。

  • 半熔断状态(Half-Open)

尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断开启状态。

5、降级

所谓降级就是我们调用的服务异常超时等原因不能正常返回的情况下,我们返回一个缺省的值。

降级是针对服务消费者的应对策略,在服务出现异常或限流时,通过对服务调用进行降级处理,确保消费者端能够在异常情况下正常工作。降级的目的在于转变为弱依赖状态,使系统能够在服务不可用时提供基本的功能或数据。这种策略可以在服务消费者端实施,通过返回默认值、提供备用数据或简化功能等方式来保证系统的可用性。

常见的容错组件

Hystrix

Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。

Resilience4J

Resilicence4J一款非常轻量、简单,并且文档非常清晰、丰富的熔断工具,这也是Hystrix官方推荐的替代产品。不仅如此,Resilicence4j还原生支持Spring Boot 1.x/2.x,而且监控也支持和prometheus等多款主流产品进行整合。

Sentinel

Sentinel 是阿里巴巴开源的一款断路器实现,本身在阿里内部已经被大规模采用,非常稳定。

容错组件对比

比较维度

Sentinel

Hystrix

resilience4j

隔离策略

信号量隔离(并发线程数限流)

线程池隔离/信号量隔离

信号量隔离

熔断降级策略

基于慢调用比例、异常比例、异常数

基于异常比例

基于异常比例、响应时间

实时统计实现

滑动窗口(LeapArray)

滑动窗口(基于RxJava)

Ring Bit Buffer

动态规则配置

支持近十种动态数据源

支持多种数据源

有限支持

扩展性

多个扩展点

插件的形式

接口的形式

基于注解的支持

支持

支持

支持

单机限流

基于QPS,支持基于调用关系的限流

有限的支持

Rate Limiter

集群流控

支持

不支持

不支持

流量整形

支持预热模式与匀速排队控制效果

不支持

简单的 Rate Limiter 模式

系统自适应保护

支持

不支持

不支持

热点识别/防护

支持

不支持

不支持

多语言支持

Java/Go/C++

Java

Java

Service Mesh 支持

支持 Envoy/Istio

不支持

不支持

控制台

提供开箱即用的控制台,可配置规则、实时监控、机器发现等

简单的监控查看

不提供控制台,可对接其它监控系统

什么是Sentinel?

Sentinel (分布式系统的流量防卫兵) 是阿里开源的一套用于服务容错的综合性解决方案。它以流量为切入点, 从流量控制、熔断降级、系统负载保护等多个维度来保护服务的稳定性。

Sentinel 具有以下特征:

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

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

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

完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快

速地定制逻辑。例如定制规则管理、适配动态数据源等。

Sentinel分为两个部分:

核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo/Spring Cloud 等框架也有较好的支持。

控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

Sentinel功能及设计理念

流量控制

流量控制在网络传输中是一个常用的概念,它用于调整网络包的数据。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。

熔断降级

当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败(或者其他处理方式),避免影响到其它的资源而导致级联故障。

微服务整合sentinel

1、使用代码实现限流

1、pom文件中引入依赖
<!-- SpringCloud Alibaba Sentinel -->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2、编写测试限流接口

@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。

3、测试结果展示

2、通过配置实现限流

1、pom文件中引入依赖
<!-- SpringCloud Alibaba Sentinel -->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2、增加配置文件

3、sentinel-dashboard下载及使用

下载地址:https://github.com/alibaba/Sentinel

启动命令:

java -jar sentinel-dashboard.jar

默认用户名/密码: sentinel/sentinel

4、启动项目进行测试

启动项目,在sentinel-dashboard中查看 http://localhost:8080/#/login

Sentinel的规则

1、流控规则

流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:

Field

说明

默认值

resource

资源名,即限流规则的作用对象

count

限流阈值

grade

限流阈值类型(QPS 或并发线程数)

QPS 模式

limitApp

流控针对的调用来源

default,代表不区分调用来源

strategy

调用关系限流策略:直接、链路、关联

直接

controlBehavior

流量控制效果(直接拒绝、Warm Up、匀速排队)

直接拒绝

clusterMode

是否集群限流

流量控制,其原理是监控应用流量的QPS(每秒查询率)或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

第1步: 点击【簇点链路】,我们就可以看到访问过的接口地址,然后点击对应的【流控】按钮,进入流控规则配置页面。新增流控规则界面如下:

自定义异常返回

流控模式介绍

sentinel共有三种流控模式,分别是:

直接(默认):指定来源对于该资源的访问达到限流条件时,开启限流。

关联:当与该资源设置了关联的资源达到限流条件(来源+阈值类型+单机阈值)时,开启限流 [适合做应用让步]

链路:当从某个上游资源接口访问过来的流量达到限流条件时,开启限流。

直接流控模式

直接流控模式是最简单的模式,当指定的接口达到限流条件时开启限流。

关联流控模式

当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db 和 write_db 这两个资源分别代表数据库读写,我们可以给 read_db 设置限流规则来达到写优先的目的:设置 strategy 为 RuleConstant.STRATEGY_RELATE 同时设置 refResource 为 write_db。这样当写库操作过于频繁时,读数据的请求会被限流。

链路流控模式

链路流控模式指的是,当从某个接口过来的资源达到限流条件时,开启限流。它的功能有点类似于针对来源配置项,区别在于:针对来源是针对上级微服务,而链路流控是针对上级接口,也就是说它的粒度更细。

流控效果

过某个阈值的时候,则采取措施进行流量控制。流量控制的效果包括以下几种:快速失败(直接拒绝)、Warm Up(预热)、匀速排队(排队等待)。对应 FlowRule 中的 controlBehavior 字段。

快速失败(默认)

当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过测试确定了系统的准确水位时。

Warm Up

它从开始阈值到最大QPS阈值会有一个缓冲阶段,一开始的阈值是最大QPS阈值的1/3,然后慢慢增长,直到最大阈值。

即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过“冷启动”,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。

冷加载因子: codeFactor 默认是3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。

排队等待

让请求以均匀的速度通过,单机阈值为每秒通过数量,其余的排队等待;它还会设置一个超时时间,当请求超过超时时间还未处理,则会被丢弃。

这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。

注意:勾选排队模式暂时不支持 QPS > 1000 的场景。【因为他的单位毫秒,所以最多是1毫秒通过一个,也就是1000qps】

2、降级规则

熔断降级规则说明熔断降级规则(DegradeRule)包含下面几个重要的属性:

Field

说明

默认值

resource

资源名,即规则的作用对象

grade

熔断策略,支持慢调用比例/异常比例/异常数策略

慢调用比例

count

最小请求数,慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值

timeWindow

熔断时长,单位为 s

minRequestAmount

最小请求数,熔断触发的最小请求数,请求数小于该值时即使异常比率超过阈值也不会熔断(1.7.0 引入)

5

statIntervalMs

统计时长,(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入)

1000 ms

slowRatioThreshold

比例阈值,慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)

1. 慢调用比例

核心概念:当单位统计时长内,响应时间大于设定阈值(Slow Request Threshold)的请求 的比例超过设定的阈值时,触发熔断。

工作流程:

  1. 您需要设定一个 时间阈值(Count),例如 500ms。
  2. Sentinel 会统计在时间窗口内(如最近1分钟),所有请求的响应时间。
  3. 任何响应时间超过 500ms 的请求,都被记为“慢调用”。
  4. 计算慢调用数目占总请求数的比例。
  5. 如果这个比例超过了您设定的 比例阈值(slowRatioThreshold),例如 0.5(50%),并且在时间窗口内的请求数超过了 minRequestAmount(如5次),则触发熔断。

适用场景:

用于保证服务的 SLA(服务等级协议),确保大多数请求能在预期时间内返回。

当依赖的数据库、外部API等响应变慢,导致自身服务响应时间变长时,通过熔断来避免堆积的请求拖垮整个服务。

2. 异常比例

核心概念:当单位统计时长内,请求异常(被Sentinel捕获的异常)的比例 超过阈值时,触发熔断。

工作流程:

  1. 您需要设定一个 异常比例阈值(Count),例如 0.6(60%)。
  2. Sentinel 会统计在时间窗口内的总请求数和抛出异常的请求数。
  3. 计算异常数目占总请求数的比例。
  4. 如果这个比例超过了设定的阈值(60%),并且在时间窗口内的请求数超过了 minRequestAmount,则触发熔断。

适用场景:

当依赖的服务出现不稳定,调用时频繁抛出超时异常、网络异常或业务异常时。

适用于对 服务的稳定性 要求较高的场景,不希望大量的错误响应影响系统整体。

3. 异常数

核心概念:当单位统计时长内,请求异常的数量 超过阈值时,触发熔断。

工作流程:

  1. 您需要设定一个 异常数量阈值(Count),例如 10次。
  2. Sentinel 会统计在时间窗口内发生的异常数量。
  3. 如果异常数量累计超过了设定的阈值(10次),并且在时间窗口内的请求数超过了 minRequestAmount,则触发熔断。

适用场景:

与异常比例策略类似,但更关注 异常的绝对数量 而不是比例。

在请求量相对较小的服务中,即使比例不高,但出现一定数量的异常也可能意味着严重问题,此时使用异常数策略更合适。

3、热点规则

热点,即经常被访问的数据。在应用系统中,总有一部分数据或业务是高频访问的,例如:

  • 热门商品的查看接口
  • 热点新闻的阅读接口
  • 某个特定用户ID的频繁操作
  • 秒杀活动中的某一个商品ID

热点规则 的目的就是对某个资源的某个参数进行精细化的流量控制,允许你为特定的参数值单独设置流控阈值,而不是对整个资源一视同仁。

热点参数必须是int,double,String,long,float,char,byte(8种)

4、授权规则

很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过:

  • 若配置白名单,则只有请求来源位于白名单内时才可通过;
  • 若配置黑名单,则请求来源位于黑名单时不通过,其余的请求通过。

例如:活动和订单都会调用用户系统获取用户信息,我们可以将活动设置为黑名单。

5、系统规则

系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能地在最大吞吐量的同时保证系统整体的稳定性。

系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。

系统规则支持以下的模式:

  • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5。
  • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
  • 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

限流算法

计数器固定窗口算法:将时间划分为固定的窗口(如1秒),在每个窗口内设置一个计数器。请求到来时,计数器+1,如果计数器超过了窗口的阈值,则拒绝后续请求。时间窗口结束时,重置计数器。

计数器滑动窗口算法:将固定窗口细分成更小的时间片(比如1秒的窗口分成10个100ms的片),每个小片独立计数。窗口的移动是平滑的,通过统计当前时间点往前推一个窗口周期内的所有小片的请求总数来判断是否限流。

漏桶算法:以一个恒定的速率处理请求,就像水从漏桶底部以一个固定速率流出。当请求流入的速率过快时,漏桶会积水(请求排队),如果水满(队列满),则溢出(拒绝请求)。

令牌桶算法:系统以一个恒定的速率向一个桶里放入“令牌”。请求处理前,需要先从桶中获取一个令牌。只有拿到令牌的请求才能被处理。如果桶里没有令牌,请求则被限流。

10、GateWay的使用

Spring Cloud Gateway 是基于 Spring 5、Spring Boot 2 和 Project Reactor 等技术构建的 API 网关,旨在为微服务架构提供一种简单且有高效的 API 路由管理功能。它是 Spring Cloud 生态系统中很重要的一部分,主要用于处理请求的路由、负载均衡、安全、监控等任务。

Spring Cloud Gateway 是由 WebFlux + Netty + Reactor 实现的响应式的 API 网关。它不能在传统的 servlet 容器中工作,也不能构建成 war 包。

通过API网关访问服务的好处

  • 客户端通过 API 网关与微服务交互时,客户端只需要知道 API 网关地址即可,而不需要维护大量的服务地址,简化了客户端的开发。
  • 客户端直接与 API 网关通信,能够减少客户端与各个服务的交互次数。
  • 客户端与后端的服务耦合度降低。
  • 节省流量,提高性能,提升用户体验。
  • API 网关还提供了安全、流控、过滤、缓存、计费以及监控等 API 管理功能。

微服务网关与传统API网关的区别

• 粒度:微服务网关是基于微服务架构设计的,它的粒度更细,可以对每个微服务进行独立的路由和管理。而传统的API网关通常对整个应用程序的API进行管理。

• 功能定位:微服务网关不仅提供路由转发和负载均衡等功能,还承担了微服务架构中的其他职责,如安全认证、请求过滤、转换和聚合等。传统的API网关主要关注请求的路由和转发。

• 解耦性:微服务网关通过隐藏后端微服务的实现细节,实现了客户端与微服务之间的解耦,每个微服务可以独立演化和部署。而传统的API网关通常对整个应用程序进行管理,各个模块之间的耦合性较高。

• 灵活性:微服务网关提供了更高的灵活性,可以根据不同的路由规则将请求路由到不同的微服务实例上,实现动态的负载均衡和容错处理。传统的API网关通常是将请求转发到固定的后端服务。

• 性能:微服务网关的粒度更细,可以更精确地控制请求的路由和处理,从而提高系统的性能。传统的API网关可能需要处理更大规模的请求,性能可能相对较低

GateWay可以做什么?

路由转发:Spring Cloud Gateway 允许我们定义路由规则,将进入的请求根据不同的路径或条件转发到不同的下游服务。这对于微服务架构中服务的管理和维护非常重要。
负载均衡:因为网关可以做路由转发,所以借助他也能实现非常方便的负载均衡。一般都是集成LoadBalancer实现。
统一授权:在Gateway中,我们可以继承 OAuth2、JWT 等安全协议的集成,来进行统一的登录、授权。

流量过滤:就像统一授权一样,我们也可以基于Spring Cloud Gateway实现统一的过滤,比如一些黑名单的过滤,一些恶意请求的过滤等等。
限流降级:我们还可以在Gateway中集成Sentinel等组件,来实现统一的限流。这样我们就可以在网关层面实现一个统一的流量管控,避免下游服务因为流程扛不住而被打挂。
跨域支持:在微服务架构中,服务通常分布在不同的域中。Spring Cloud Gateway 提供跨域请求支持,使得不同域的服务可以安全、有效地相互通信。

核心概念

  1. Route(路由):路由是 Spring Cloud Gateway 的基本构建块。每个路由由一个 ID、一个目标 URI、一组谓词(Predicates)和一组过滤器(Filters)组成。路由定义了如何将请求从客户端转发到后端服务。
  2. Predicate(断言Predicate是用于匹配请求的条件。Spring Cloud Gateway 提供了多种内置,如路径匹配、HTTP 方法匹配、头匹配等。只有当请求满足所有匹配条件时,路由才会生效。
  3. Filter(过滤器):过滤器用于在请求被路由之前或之后对请求和响应进行修改。Spring Cloud Gateway 提供了多种内置过滤器,如添加/修改请求头、请求重写、限流等。开发者也可以自定义过滤器。

工作原理

  1. 请求匹配:当客户端请求到达 Spring Cloud Gateway 时,网关会使用定义的路由和Predicate来匹配请求。首先,网关会遍历所有路由,检查请求是否满足每个路由的Predicate条件。
  2. 路由转发:一旦找到匹配的路由,网关会应用路由定义的过滤器。过滤器可以在请求被转发到目标 URI 之前或响应返回给客户端之前对请求和响应进行修改。
  3. 响应处理:请求被转发到目标 URI 后,目标服务会处理请求并返回响应。网关会再次应用过滤器对响应进行处理,然后将最终响应返回给客户端。

Gateway的工作流程

  • 客户端将请求发送到 Spring Cloud Gateway 上。
  • Spring Cloud Gateway 通过 Gateway Handler Mapping 找到与请求相匹配的路由,将其发送给 Gateway Web Handler。
  • Gateway Web Handler 通过指定的过滤器链(Filter Chain),将请求转发到实际的服务节点中,执行业务逻辑返回响应结果。
  • 过滤器之间用虚线分开是因为过滤器可能会在转发请求之前(pre)或之后(post)执行业务逻辑。
  • 过滤器(Filter)可以在请求被转发到服务端前,对请求进行拦截和修改,例如参数校验、权限校验、流量监控、日志输出以及协议转换等。
  • 过滤器可以在响应返回客户端之前,对响应进行拦截和再处理,例如修改响应内容或响应头、日志输出、流量监控等。
  • 响应原路返回给客户端。

Gateway的简单使用

1、pom文件引入

        <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency>

2、进行配置

# 服务器配置
server:# 服务器端口号设置为9003port: 9003
# Spring Cloud Gateway配置
spring:cloud:gateway:# 路由配置routes:# 商品目录路由ID- id: supermarket-product-catalog# 路由指向的URI地址uri: http://localhost:9001# 路由断言,用于匹配请求路径predicates:# 匹配以/supermarketproduct开头的路径- Path=/supermarketproduct/**# 过滤器配置,用于修改请求或响应filters:# 去除路径前缀1级,即去掉/supermarketproduct- StripPrefix=1

3、启动相关服务调用接口测试

访问地址:http://localhost:9003/supermarketproduct/productcatalogservice/test

转发地址:http://localhost:9001/productcatalogservice/test

Gateway整合nacos

修改配置文件

startup.cmd -m standalone

进行测试

路由断言配置

断言/谓词(Predicate):用于指定匹配路由的判断条件,只有请求符合特定的判断条件,GateWay才会根据匹配的路由,把请求转发到相应的微服务。

内置的断言工厂类

(1)基于日期时间类型的断言工厂类

基于日期时间类型的断言工厂类根据日期时间进行判断,主要有以下三个。

• AfterRoutePredicateFactory:接收一个日期时间参数,判断请求的日期时间是否晚于指定的参数值。

# 当且仅当请求时的时间After配置的时间时,才转发该请求
# 若请求时的时间不是After配置的时间时,则会返回404 not found
- After=2025-09-20T11:34:42.917822900+08:00[Asia/Shanghai]

• BeforeRoutePredicateFactory:接收一个日期时间参数,判断请求的日期时间是否早于指定的参数值。

# 当且仅当请求时的时间Before配置的时间时,才转发该请求
- Before=2025-09-20T11:34:42.917822900+08:00[Asia/Shanghai]

• BetweenRoutePredicateFactory:接收两个日期时间参数,判断请求的日期时间是否在指定的时间段内

# 当且仅当请求时的时间Between配置的时间段时,才转发该请求
- Between=2025-09-20T11:34:42.917822900+08:00[Asia/Shanghai], 2028-08-16T11:34:42.917822900+08:00[Asia/Shanghai]

注:日期格式如何获取呢?

@Test
public void test() {String timeStr = ZonedDateTime.now()// 减1小时.minusHours(1).format(DateTimeFormatter.ISO_DATE_TIME);System.out.println(timeStr);
}// 执行结果
2025-10-09T14:23:59.214+08:00[Asia/Shanghai]
(2)基于远程地址的断言工厂类:RemoteAddrRoutePredicateFactory。

RemoteAddrRoutePredicateFactory接收一个表示IP地址段的参数,判断发出请求的主机地址是否在指定的地址段内。

# 当且仅当请求IP是192.168.1.1/24网段,例如192.168.1.10,才转发该请求
- RemoteAddr=192.168.1.1/24
(3)基于Cookie的断言工厂类:CookieRoutePredicateFactory。

CookieRoutePredicateFactory接收两个参数:Cookie名字和正则表达式,判断请求中是否具有给定名字的Cookie,并且该Cookie的值是否与给定的正则表达式匹配。

# 当且仅当请求带有名为chocolate,并且值符合正则表达式 ch.p 的Cookie时,才转发该请求
- Cookie=chocolate, ch.p
(4)基于HTTP请求头的断言工厂类:HeaderRoutePredicateFactory。

HeaderRoutePredicateFactory接收两个参数:HTTP请求头中一个项的名字和正则表达式,判断HTTP请求头中是否具有给定名字的项,并且该项的值是否与给定的正则表达式匹配。示例代码如下:

# 当且仅当请求带有名为X-Request-Id,并且值符合正则表达式 \d+ 的Header时,才转发该请求 
- Header=X-Request-Id, \d+
(5)基于远程主机的断言工厂类:HostRoutePredicateFactory。

HostRoutePredicateFactory接收一个参数,表示主机名字的匹配模式,判断发出请求的主机的名字是否与给定的名字模式匹配。

# 当且仅当名为Host的Header符合**.bitfullstack.cn或**.xinwhale.cn时,才转发该请求# 例如:www.bitfullstack.cn、api.bitfullstack.cn、www.xinwhale.cn等Host就满足该匹配
- Host=**.bitfullstack.cn,**.xinwhale.cn
(6)基于HTTP请求方式的断言工厂类:MethodRoutePredicateFactory。

MethodRoutePredicateFactory接收一个参数,表示HTTP请求方式,判断请求方式是否为给定值。HTTP请求方式包括GET、POST、PUT和DELETE等。

# 当且仅当HTTP请求方法为GET时,才转发该请求
- Method=GET
(7)基于Path请求路径的断言工厂类:PathRoutePredicateFactory。

PathRoutePredicateFactory接收一个参数,表示路径的匹配模式,判断请求的URI是否与给定的路径模式匹配。

# 当且仅当访问路径是/find/*、/some-example/list及/bar/**时,才转发该请求# segment是一个特殊的占位符,表示单层路径匹配,而/**则是多层路径的匹配
- Path=/find/{segment},/example/list,/bar/**
(8)基于Query请求参数的断言工厂类:QueryRoutePredicateFactory。

QueryRoutePredicateFactory接收两个参数:请求参数名字和正则表达式,判断请求是否具有给定名字的请求参数,并且该请求参数的值是否与给定的正则表达式匹配。

# 当且仅当请求带有名为foo的参数,且参数值与正则表达式 ba. 相匹配,才转发该请求
- Query=foo, ba.
(9)基于路由权重的断言工厂类:WeightRoutePredicateFactory。

WeightRoutePredicateFactory接收两个参数:组名和权重,对同一个组内的路由按照权重进行转发。

spring:cloud:gateway:routes:- id: weight_highuri: https://weighthigh.orgpredicates:- Weight=group1, 8- id: weight_lowuri: https://weightlow.orgpredicates:- Weight=group1, 2

对于同一组多个 URI地址,路由器会根据设置权重,按比例将请求转发给相应的URI实现负载均衡

过滤器

过滤器用于在请求被路由之前或之后对请求和响应进行修改。Spring Cloud Gateway 提供了多种内置过滤器,如添加/修改请求头、请求重写、限流等。开发者也可以自定义过滤器。

内置过滤器介绍

类别

过滤器名称

作用

配置示例

说明

路径相关

StripPrefix

去除请求路径的前缀

- StripPrefix=2

去掉路径前2个部分

PrefixPath

为路径添加前缀

- PrefixPath=/api

在路径前添加 /api

RewritePath

重写请求路径

- RewritePath=/old/(?<segment>.*), /new/$\{segment}

使用正则表达式重写路径

请求参数

AddRequestParameter

添加请求参数

- AddRequestParameter=foo, bar

添加参数 foo=bar

RemoveRequestParameter

移除请求参数

- RemoveRequestParameter=token

移除 token 参数

请求头

AddRequestHeader

添加请求头

- AddRequestHeader=X-Request-Foo, Bar

添加请求头

RemoveRequestHeader

移除请求头

- RemoveRequestHeader=X-Request-Foo

移除请求头

SetRequestHeader

设置请求头

- SetRequestHeader=X-Request-Foo, Bar

设置/覆盖请求头

响应头

AddResponseHeader

添加响应头

- AddResponseHeader=X-Response-Foo, Bar

添加响应头

RemoveResponseHeader

移除响应头

- RemoveResponseHeader=X-Response-Foo

移除响应头

SetResponseHeader

设置响应头

- SetResponseHeader=X-Response-Foo, Bar

设置/覆盖响应头

重定向

RedirectTo

重定向到指定URL

- RedirectTo=302, https://example.com

302重定向

状态码

SetStatus

设置响应状态码

- SetStatus=401

设置HTTP状态码

熔断器

CircuitBreaker

熔断器保护

- name: CircuitBreaker<br> args:<br> name: myCircuitBreaker<br> fallbackUri: forward:/fallback

集成 Resilience4j

重试

Retry

请求重试机制

- name: Retry<br> args:<br> retries: 3<br> statuses: BAD_GATEWAY

配置重试策略

请求大小

RequestSize

限制请求大小

- name: RequestSize<br> args:<br> maxSize: 5MB

限制最大请求体大小

限流

RequestRateLimiter

请求限流

- name: RequestRateLimiter<br> args:<br> redis-rate-limiter.replenishRate: 10<br> redis-rate-limiter.burstCapacity: 20

基于Redis的限流

认证

SecureHeaders

添加安全头

- SecureHeaders

添加安全相关的HTTP头

跨域

SaveSession

保存WebSession

- SaveSession

在转发前保存WebSession

11、分布式事务Seata的使用

@Transactional(声明式事务)

@Transactional 是 Spring 框架提供的一个声明式事务管理注解,用于简化数据库事务的管理。它可以将一系列数据库操作作为一个原子性操作执行,要么全部成功,要么全部回滚。

Spring 通过 AOP(面向切面编程)代理来实现事务管理。当你调用一个 @Transactional 方法时,实际上是在调用一个由 Spring 生成的代理对象的方法。

@Transactional 关键属性详解

1. value / transactionManager
  • 作用:指定使用哪个事务管理器。在单数据源应用中,通常使用默认值即可。但在多数据源(多个不同数据库)的项目中,必须明确指定要使用的事务管理器 Bean 的名称。
@Transactional("orderTransactionManager")
public void createOrder() {// 使用名为 "orderTransactionManager" 的事务管理器// ...
}
2. propagation (传播行为)

这是最重要也是最复杂的属性。它定义了当前事务方法与另一个事务方法相互调用时,事务应该如何传播。

PROPAGATION_REQUIRED(默认): 如果当前没有事务, 就创建一个新事务, 如果当前存在事务,就加入该事务, 该设置是最常用的设置。

PROPAGATION_SUPPORTS: 支持当前事务, 如果当前存在事务, 就加入该事务, 如果当前不存在事务, 就以非事务执行。

PROPAGATION_MANDATORY: 支持当前事务, 如果当前存在事务, 就加入该事务, 如果当前不存在事务, 就抛出异常。

PROPAGATION_REQUIRES_NEW: 创建新事务, 无论当前存不存在事务, 都创建新事务。

PROPAGATION_NOT_SUPPORTED: 以非事务方式执行操作, 如果当前存在事务, 就把当前事务挂起。

PROPAGATION_NEVER: 以非事务方式执行, 如果当前存在事务, 则抛出异常。

PROPAGATION_NESTED: 如果当前存在事务, 则在嵌套事务内执行。 如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。

3.isolation (隔离级别)

定义了事务与其他并发事务之间的隔离程度。主要为了解决数据库并发问题:脏读、不可重复读、幻读。

  • Isolation.DEFAULT(默认值)
    • 使用底层数据库的默认隔离级别。例如,MySQL 的 InnoDB 默认是可重复读(REPEATABLE_READ),Oracle 默认是读已提交(READ_COMMITTED)。

  • Isolation.READ_UNCOMMITTED(读未提交)
    • 最低级别,允许读取尚未提交的数据变更。可能导致脏读、不可重复读、幻读
  • Isolation.READ_COMMITTED (读已提交)
    • 允许读取并发事务已经提交的数据。可以防止脏读,但不可重复读幻读仍可能发生。
  • Isolation.REPEATABLE_READ(可重复读)
    • 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改。可以防止脏读不可重复读,但幻读仍可能发生。
    • MySQL 的 InnoDB 通过 MVCC 机制在该级别解决了幻读问题
  • Isolation.SERIALIZABLE(可串行化)
    • 最高级别,完全服从 ACID 的隔离级别。所有事务依次逐个执行,可以防止所有并发问题。但性能开销最大,因为它通常是完全锁定表。

分布式事务Seata

https://seata.apache.org/zh-cn/

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案,AT模式是阿里首推的模式

Seata提供了4中不同的分布式事务解决方案:

  • XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
  • TCC模式:最终一致的分阶段事务模式,有业务侵入
  • AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式
  • SAGA模式:长事务模式,有业务侵入

在 Seata 的架构中,一共有三个角色:

TC :事务协调者 ,全称为 Transaction Coordinator,实际上指全局事务的发起者,维护全局和分支事务的状态,驱动全局事务提交或回滚。

RM :资源管理器 全称为 Resource Manager,实际上一个全局事务中的各个本地事务,管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

TM :事务管理器,全称为 Transaction Manager,实际上指Seata服务器,定义全局事务的范围:开始全局事务、提交或回滚全局事务。

工作流程图如下:

在 Seata 中,TC 为单独部署的 Server 服务端,TM 和 RM 为嵌入到应用中的 Client 客户端。 一个分布式事务的生命周期如下:

  1. TM 请求 TC 开启一个全局事务。TC 会生成一个 XID 作为该全局事务的编号。XID会在微服务的调用链路中传播,保证将多个微服务的子事务关联在一起。
  2. RM 请求 TC 将本地事务注册为全局事务的分支事务,通过全局事务的 XID 进行关联。
  3. TM 请求 TC 告诉 XID 对应的全局事务是进行提交还是回滚。
  4. TC 驱动 RM 们将 XID 对应的自己的本地事务进行提交还是回滚。

AT模式

Seata AT模式的设计思路

Seata AT模式的核心是对业务无侵入,是一种改进后的两阶段提交。它本身依赖于数据库的事务实现,它也是 Seata 默认的工作模式。它的本质是在本地事务分支提交的同时,用一张数据库表记录对应的undo日志记录(此undo日志非mysql的undo日志)。在回滚阶段采用这条回滚日志记录进行反向补偿。

其设计思路如下:

一阶段: 先要在各个本地事务相关的数据库中分别创建一张相同的undo日志记录表。在本地事务提交的同时记录本地事务操作对应的undo日志记录,同时还要释放本地锁和连接资源。

二阶段:事务协调者在通知各个资源管理器做相应的事务处理,具体表现为:如果事务是提交,则删除对应的undo记录即可。如果是回滚,则根据undo记录做相应的反向补偿操作。这个过程是异步进行的,效率较高。

超市物品管理系统进行库存的插入(商品信息服务、价格与促销服务、库存服务)

商品信息服务(supermarket-product-catalog)

价格与促销服务(supermarket-pricing-promotion)

库存服务(supermarket-inventory)

1、创建数据库表(分别创建3个数据库)

-- 商品信息表
CREATE TABLE `product` (`product_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品ID',`product_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '商品编码',`product_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '商品名称',`category_id` bigint(20) DEFAULT NULL COMMENT '分类ID',`brand` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '品牌',`specification` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '规格说明',`unit` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '计量单位',`barcode` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '商品条码',`qr_code` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '二维码',`product_image_url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '商品图片URL',`description` text COLLATE utf8mb4_unicode_ci COMMENT '商品描述',`supplier_id` bigint(20) DEFAULT NULL COMMENT '供应商ID',`is_active` tinyint(4) DEFAULT '1' COMMENT '是否启用',`created_by` bigint(20) DEFAULT NULL COMMENT '创建人ID',`created_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`updated_by` bigint(20) DEFAULT NULL COMMENT '更新人ID',`updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',`deleted` tinyint(4) DEFAULT '0' COMMENT '软删除标记',PRIMARY KEY (`product_id`),UNIQUE KEY `product_code` (`product_code`),UNIQUE KEY `uk_product_code` (`product_code`),UNIQUE KEY `barcode` (`barcode`),UNIQUE KEY `uk_barcode` (`barcode`),KEY `idx_category_id` (`category_id`),KEY `idx_supplier_id` (`supplier_id`),KEY `idx_is_active` (`is_active`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商品主表';-- 商品价格表
CREATE TABLE `product_price` (`price_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '价格ID',`product_id` bigint(20) DEFAULT NULL COMMENT '商品ID',`store_id` bigint(20) DEFAULT NULL COMMENT '门店ID',`selling_price` decimal(10,2) DEFAULT NULL COMMENT '售价',`member_price` decimal(10,2) DEFAULT NULL COMMENT '会员价',`cost_price` decimal(10,2) DEFAULT NULL COMMENT '成本价',`effective_date` date DEFAULT NULL COMMENT '生效日期',`expiry_date` date DEFAULT NULL COMMENT '过期日期,NULL表示长期有效',`is_active` tinyint(4) DEFAULT '1' COMMENT '是否启用',`created_by` bigint(20) DEFAULT NULL COMMENT '创建人ID',`created_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`updated_by` bigint(20) DEFAULT NULL COMMENT '更新人ID',`updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',`deleted` tinyint(4) DEFAULT '0' COMMENT '软删除标记',PRIMARY KEY (`price_id`),UNIQUE KEY `uk_product_store_effective` (`product_id`,`store_id`,`effective_date`),KEY `idx_store_id` (`store_id`),KEY `idx_is_active` (`is_active`),KEY `idx_effective_date` (`effective_date`)
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商品价格表';-- 商品库存表
CREATE TABLE `inventory` (`inventory_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '库存ID',`product_id` bigint(20) DEFAULT NULL COMMENT '商品ID',`store_id` bigint(20) DEFAULT NULL COMMENT '门店ID',`quantity_on_hand` int(11) DEFAULT '0' COMMENT '现有库存数量',`quantity_reserved` int(11) DEFAULT '0' COMMENT '预留库存',`quantity_available` int(11) GENERATED ALWAYS AS ((`quantity_on_hand` - `quantity_reserved`)) STORED COMMENT '可用库存',`warning_quantity` int(11) DEFAULT NULL COMMENT '库存预警数量',`reorder_quantity` int(11) DEFAULT NULL COMMENT '建议订购数量',`last_count_date` datetime DEFAULT NULL COMMENT '最后盘点时间',`created_by` bigint(20) DEFAULT NULL COMMENT '创建人ID',`created_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`updated_by` bigint(20) DEFAULT NULL COMMENT '更新人ID',`updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',`deleted` tinyint(4) DEFAULT '0' COMMENT '软删除标记',PRIMARY KEY (`inventory_id`),KEY `idx_store_id` (`store_id`),KEY `idx_quantity_available` (`quantity_available`),KEY `idx_warning_quantity` (`warning_quantity`)
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='库存表';

2、创建三个服务分别实现不同数据库以及表数据的插入功能

商品信息服务(supermarket-product-catalog)

价格与促销服务(supermarket-pricing-promotion)

库存服务(supermarket-inventory)

3、测试3个服务接口

新增商品数据: http://localhost:9001/product/create

新增创建商品价格: http://localhost:9005/productPrice/create

新增创建库存: http://localhost:9006/inventory/create

4、实现商品信息录入

编写Feignlient调用

编写接口调用

编写service实现
@Slf4j
@Service
public class ProductCreateServiceImpl implements ProductCreateService {@Autowiredprivate ProductFeignClient productFeignClient;@Autowiredprivate ProductPriceFeignClient productPriceFeignClient;@Autowiredprivate InventoryPriceFeignClient inventoryPriceFeignClient;@Overridepublic boolean createProduct(ProductCreateReq req) {log.info("创建商品: {}", JSON.toJSONString( req));Product product = new Product();product.setProductCode(req.getProductCode());product.setProductName(req.getProductName());product.setCategoryId(req.getCategoryId());product.setBrand(req.getBrand());product.setSpecification(req.getSpecification());product.setUnit(req.getUnit());product.setBarcode(req.getBarcode());product.setQrCode(req.getQrCode());product.setProductImageUrl(req.getProductImageUrl());product.setDescription(req.getDescription());//TODO 1、调用商品信息服务Result result = productFeignClient.productCreate(product);if (!StringUtil.equals("0000", result.getCode())) {log.warn("创建商品失败:{}", result.getMessage());return false;}log.info("创建商品成功:{}", result.getData());String jsonString = JSON.toJSONString(result.getData());Product product1 = JSON.parseObject(jsonString, Product.class);//TODO 2、调用价格信息服务ProductPrice productPrice = new ProductPrice();productPrice.setProductId(product1.getProductId());productPrice.setSellingPrice(req.getSellingPrice());productPrice.setMemberPrice(req.getMemberPrice());productPrice.setCostPrice(req.getCostPrice());productPriceFeignClient.productPriceCreate(productPrice);//TODO 3、调用库存信息服务Inventory inventory = new Inventory();inventory.setProductId(product1.getProductId());inventory.setQuantityOnHand(req.getQuantityOnHand());inventoryPriceFeignClient.inventoryCreate(inventory);log.info("创建商品成功!!!!!!!!!!!:{}", result.getData());return true;}
}

当库存服务报错时商品信息服务和商品价格服务不会回滚

5、分布式事务实现

seata下载地址

https://seata.apache.org/zh-cn/download/seata-server

修改registry.conf配置

双击seata-server.bat启动

nacos配置新增

# 数据存储方式,db代表数据库
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
store.db.user=root
store.db.password=root
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
# 事务、日志等配置
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000# 客户端与服务端传输方式
transport.serialization=seata
transport.compressor=none
# 关闭metrics功能,提高性能
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898

创建seata数据库、执行sql语句
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(`xid`                       VARCHAR(128) NOT NULL,`transaction_id`            BIGINT,`status`                    TINYINT      NOT NULL,`application_id`            VARCHAR(32),`transaction_service_group` VARCHAR(32),`transaction_name`          VARCHAR(128),`timeout`                   INT,`begin_time`                BIGINT,`application_data`          VARCHAR(2000),`gmt_create`                DATETIME,`gmt_modified`              DATETIME,PRIMARY KEY (`xid`),KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(`branch_id`         BIGINT       NOT NULL,`xid`               VARCHAR(128) NOT NULL,`transaction_id`    BIGINT,`resource_group_id` VARCHAR(32),`resource_id`       VARCHAR(256),`branch_type`       VARCHAR(8),`status`            TINYINT,`client_id`         VARCHAR(64),`application_data`  VARCHAR(2000),`gmt_create`        DATETIME(6),`gmt_modified`      DATETIME(6),PRIMARY KEY (`branch_id`),KEY `idx_xid` (`xid`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(`row_key`        VARCHAR(128) NOT NULL,`xid`            VARCHAR(128),`transaction_id` BIGINT,`branch_id`      BIGINT       NOT NULL,`resource_id`    VARCHAR(256),`table_name`     VARCHAR(32),`pk`             VARCHAR(36),`status`         TINYINT      NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',`gmt_create`     DATETIME,`gmt_modified`   DATETIME,PRIMARY KEY (`row_key`),KEY `idx_status` (`status`),KEY `idx_branch_id` (`branch_id`),KEY `idx_xid_and_branch_id` (`xid` , `branch_id`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;CREATE TABLE IF NOT EXISTS `distributed_lock`
(`lock_key`       CHAR(20) NOT NULL,`lock_value`     VARCHAR(20) NOT NULL,`expire`         BIGINT,primary key (`lock_key`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);

双击启动

在每个微服务连接的数据库中创建undo_log表
-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log`  (`branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'global transaction id',`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'undo_log context,such as serialization',`rollback_info` longblob NOT NULL COMMENT 'rollback info',`log_status` int(11) NOT NULL COMMENT '0:normal status,1:defense status',`log_created` datetime(6) NOT NULL COMMENT 'create datetime',`log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'AT transaction mode undo table' ROW_FORMAT = Compact;-- ----------------------------
-- Records of undo_log
-- ----------------------------

6、项目整合seata

引入依赖
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
yml新增配置
seata:registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址# 参考tc服务自己的registry.conf中的配置type: nacosnacos: # tcserver-addr: 127.0.0.1:8848namespace: ""group: SEATA_GROUPapplication: seata-server # tc服务在nacos中的服务名称tx-service-group: my_test_tx_group # 事务组,根据这个获取tc服务的cluster名称service:vgroup-mapping:  # 注意这里是 vgroup-mapping,不是 group-mappingmy_test_tx_group: default  # 映射到 default 集群
新增全局事务注解

测试库存操作异常后其他服务是否回滚

本项目使用的版本:

<spring-boot.version>2.6.3</spring-boot.version>
<spring-cloud.version>2021.0.1</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>

seata-server-1.4.2

nacos-server-2.5.1

sentinel-dashboard-1.8.8.jar

Spring Cloud Alibaba版本选择(根据实际情况选择)

https://github.com/alibaba/spring-ai-alibaba?spm=5176.29160081.0.0.2f24aa5c5j5UxS

https://sca.aliyun.com/

http://www.dtcms.com/a/517282.html

相关文章:

  • 从 “瞎埋点” 到 “精准分析”:WebTagging 设计 + 页面埋点指南(附避坑清单)
  • 重构高阶智驾:天瞳威视以国产芯片,解锁Robotaxi平民化路径
  • 网站如何做水晶按钮做装修公司的网站
  • 苏州专业网站建设设计网站正常打开速度
  • 台州品牌网站设计androidstudio开发app教程
  • 基于C++ UA Server SDK开发高性能与跨平台 OPC UA 服务器
  • 操作系统 进程(4)上下文切换与系统调用
  • Kotlin和Java在匿名内部类和接口的使用中的区别及对比
  • mysql做网站怎么查看数据库坪山住房及建设局网站
  • 河北省建设局网站网站建设公司深圳
  • Request method ‘POST‘ not supported,问题分析和解决
  • KH|记录KingHistroian4.0卸载过程
  • Spring MVC 接口匹配性能优化:.do后缀引发的性能瓶颈分析
  • ps图做ppt模板下载网站有哪些创意设计生活用品
  • Windows API 文件结构与功能分类详解
  • 网站建设费开票收候开在哪个类别里做用户名验证的网站服务器
  • 常熟制作网站的地方wordpress解决速度慢
  • 自己可以做网站推广吗最新的新闻 最新消息
  • Redis不停机升级5.0.3->8.0.4
  • 做网站语言排名2018网站开发的方法和步骤
  • 网页设计入门首先要学什么企业网站优化与推广
  • 抓住园艺消费升级!亚马逊卖家如何从“卖单品”升级为“做品牌”
  • FPGA-ZYNQ学习对BD的保存与应用
  • 博罗县建设局网站免费好玩的网页游戏
  • 强化运动控制领域布局,杰美康机电授权世强硬创代理
  • 容器适配器、关联容器的相关算法题目
  • 微网站的好处优秀产品设计公司哪家好
  • 树莓派Pico 2W 开发环境搭建
  • 零基础从头教学Linux(Day 54)
  • Dexmal原力灵机发布Dexbotic,从此具身智能研发有了“加速器”