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

SpringCloud学习笔记

个人学习进度:视频跟敲+笔记(12天)

学习视频:尚硅谷微服务速通(7小时左右课程)

资源:

1.pdf:微服务pdf(课程):https://pan.baidu.com/s/1g_TAuBjQ9Dw6WU7b7lwvCA?pwd=yyds 

2.语雀笔记(尚硅谷速通):尚硅谷 · 语雀

3.nacos面板地址:

  • http://192.168.43.71:8848/nacos/index.html

alt+enter:快捷键的使用(智能提示!!!)

测试接口:http://localhost:8000/create?userId=1&productId=100

一.微服务概念(名词了解)

SpringCloud微服务架构中,常见的概念和名词通过具体的技术栈得以实现和解决实际问题。

微服务架构风格,就像是把一个单独的应用程序开发为一套小服务,每个小服务运行在自己进程中,并使用轻量级机制通信,通常是 HTTP API。

简而言之:拒绝大型单体应用,基于业务边界进行服务微化拆分,各个服务独立部署运行。

1.微服务架构

微服务架构是将应用程序分解为一组小型、独立服务的开发方法。在SpringCloud中,每个服务可以独立开发、部署和扩展,它们通过轻量级通信协议(如HTTP RESTful API)进行交互。

场景理解:微服务架构是一种将应用程序分解为一组小型、独立服务的开发方法。每个服务专注于特定的业务功能,并且可以独立开发、部署和扩展。就像一家大型餐厅,有负责烹饪的厨师团队(后端服务)、负责接待客人的服务员团队(前端服务)、负责食材采购的采购团队(数据访问服务)等,每个团队独立运作但又相互协作,共同为顾客(用户)提供完整的用餐体验(应用程序功能)。

实现与解决:通过SpringBoot可以快速创建独立运行的服务,SpringCloud则提供了服务之间的通信、发现等基础设施支持,使得服务能够相互协作完成复杂的业务功能。

Spring Cloud 系列

  • 官网:Spring Cloud
  • 远程调用:OpenFeign
  • 网关:Gateway

Spring Cloud Alibaba 系列

  • 官网:https://sca.aliyun.com/
  • 注册中心/配置中心:Nacos
  • 服务保护:Sentinel
  • 分布式事务:Seata

2.服务注册与发现

是微服务架构中用于服务实例注册、存储及查询定位,以便服务间通信协作的关键机制。

3.配置中心

集中管理应用配置信息,支持动态调整配置,实现配置与代码分离,便于统一管理。

4.API网关

作为微服务架构的统一入口,负责路由、鉴权、限流等任务,简化服务调用。

5.服务熔断器

当服务调用失败率高时,熔断依赖服务调用链路,避免故障扩散,保障系统稳定。

6.服务降级

在系统故障或资源不足时,优先保障核心业务,降低非核心服务的质量或可用性。

7.分布式事务

确保分布式系统中多个服务的一系列操作要么全部成功,要么全部回滚,维持数据一致性。

8.服务监控与追踪

实时监控微服务运行状态和性能指标,追踪服务调用链路,便于及时发现和解决问题。

9. 实际示例(步骤)

假设我们要构建一个电商系统,包含用户服务、订单服务和库存服务:

1. 服务注册与发现:每个服务在启动时向Nacos注册,其他服务通过Nacos发现并调用。

2. 配置管理:所有服务的配置信息(如数据库连接)存储在Nacos中,服务启动时从Nacos获取配置。

3. API网关:使用SpringCloud Gateway作为系统入口,处理用户请求的路由、认证和限流。

4. 服务熔断与降级:在订单服务调用支付服务时,若支付服务出现故障,使用Resilience4j熔断调用,并返回降级提示给用户。

5. 分布式事务:在下单流程中,涉及订单服务、库存服务和支付服务的数据库操作,使用Seata保证分布式事务的一致性。

6. 服务监控与追踪:通过SpringCloud Sleuth和Zipkin记录服务调用链路和性能指标,便于后续分析和优化。

10.微服务配图

二.Nacos配置(注册中心)

1.项目搭建

第一步:下载nacos

  • 下载软件包:📎nacos-server-2.4.3.zip
  • 启动控制面板:startup.cmd -m standalone
  • 打开地址:http://localhost:8848/nacos/index.html

第二步:创建SpringBoot项目

SpringBoot版本:3.3.4

项目名称:cloud,根pom如下:

<?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>3.3.4</version><relativePath/> <!-- lookup parent from repository --></parent><modules><module>services</module></modules><modelVersion>4.0.0</modelVersion><groupId>com.weiyan</groupId><artifactId>spring-cloud-demo</artifactId><version>1.0-SNAPSHOT</version><packaging>pom</packaging><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><spring-cloud.version>2023.0.3</spring-cloud.version><spring-cloud-alibaba.version>2023.0.3.2</spring-cloud-alibaba.version></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${spring-cloud-alibaba.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement></project>

创建services:

下面有两个子模块:service-order(订单服务)和service-product(商品服务)

第三步:引入公共依赖

services的pom中引入公共依赖,刷新:

<!--    导入公共依赖--><dependencies>
<!--        服务发现--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>
<!--        远程调用--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency></dependencies>

services中的两个子模块分别引入web和test依赖:

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

maven如下所示,按组查看:

第四步:编写配置文件和主类

配置文件名:application.properties

 # 服务名称
spring.application.name=service-order
# 服务端口
server.port=8000
# nacos注册中心地址
spring.cloud.nacos.server-addr=127.0.0.1:8848

主类:@SpringBootApplication注解

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

第五步:查看服务启动

启动其中一个服务后,查看nacos的控制面板:

  • http://192.168.43.71:8848/nacos/index.html

先打开nacos的目录文件,bin目录下,cmd命令弹窗,启动:startup.cmd -m standalone

如下所示:

OK,项目搭建完成

2.demo1(集群服务)

第一步:打开services

右击鼠标,赋值一个新的服务,需要修改配置

第二步:修改配置和端口

项目名称修改,可以在后面加上数字,如:-2

需要修改选项(Modify Option):Program argument

输入新的端口号:(自定义)

--server.port=8001

第三步:全选执行

ctrl+鼠标全选服务,run,运行:

运行成功,打开nacos面板(期间弹窗不关闭):http://localhost:8848/nacos/index.html

第四步:添加主类服务注解(必须的)

@EnableDiscoveryClient // 开启服务注册发现功能,需要重新启动服务
@SpringBootApplication
public class ProductMainApplication {public static void main(String[] args) {SpringApplication.run(ProductMainApplication.class, args);}
}

开启服务注册发现功能后,可以运行下面测试类:ServiceInstance为服务实例

@SpringBootTest
public class DiscoveryTest {//标准注册中心api(通用)@AutowiredDiscoveryClient discoveryClient;//nacos注册中心api@AutowiredNacosServiceDiscovery nacosServiceDiscovery;@Testvoid nacosServiceDiscoveryTest() throws NacosException {for (String service : nacosServiceDiscovery.getServices()) {//微服务名称System.out.println("service = " + service);//获取ip+port(每个微服务名称下有几个端口)List<ServiceInstance> instances = nacosServiceDiscovery.getInstances(service);for (ServiceInstance instance : instances) {//主机地址+端口号System.out.println("ip:"+instance.getHost()+";"+"port = " + instance.getPort());}}}@Testvoid discoveryClientTest(){for (String service : discoveryClient.getServices()) {//微服务名称System.out.println("service = " + service);//获取ip+port(每个微服务名称下有几个端口)List<ServiceInstance> instances = discoveryClient.getInstances(service);for (ServiceInstance instance : instances) {//主机地址+端口号System.out.println("ip:"+instance.getHost()+";"+"port = " + instance.getPort());}}}
}

运行结果如下:

3.demo2(远程调用)

功能实现:(简单示例)

1.一个订单下有多个商品,这里以一个商品为例

2.订单远程调用商品模块数据,计算的出总金额

3.远程查询订单商品列表

数据公共模块(model)

所有微服务的数据模型要公共使用,创建一个公共模块model,数据在bean目录下:

//可以使用@Data注解
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>annotationProcessor</scope>
</dependency>

@Data
public class Order {private Long id;private BigDecimal totalAmount;private Long userId;private String nickName;private String address;private List<Object> productList;
}
@Data
public class Product {private Long id;private BigDecimal price;private String productName;private int num;
}

在services的模块下的pom文件中;导入model模块的使用:

<dependency><groupId>com.weiyan</groupId><artifactId>model</artifactId><version>1.0-SNAPSHOT</version>
</dependency>

业务代码实现:

配置文件config:

RestTemplate 是 Spring 框架中提供的一个用于简化 HTTP 请求和处理响应的工具类。[远程调用]

@Configuration
public class ProductServiceConfig {@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}
}

@Configuration
public class OrderServiceConfig {@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}
}

service接口层

public interface ProductService{Product getProductById(Long productId);
}
public interface OrderService {//  根据用户id和商品id,创建购物清单Order cresteOrder(Long productId,Long userId);
}

sevice的实现层(impl)

@Service
public class ProductServiceImpl  implements ProductService {@Overridepublic Product getProductById(Long productId){Product product = new Product();product.setId(productId);//设置默认值product.setProductName("苹果-"+productId);product.setPrice(new BigDecimal("99"));product.setNum(2);return product;}
}
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {@AutowiredDiscoveryClient discoveryClient;@AutowiredRestTemplate restTemplate;@Overridepublic Order cresteOrder(Long productId, Long userId) {//从远程获取一个商品数据(示例:模拟只有一个商品)Product product = getProductFromRemote(productId);Order order = new Order();order.setId(1L);//总金额:商品的金额*商品购买的数量order.setTotalAmount(  product.getPrice().multiply(new BigDecimal(product.getNum())));order.setAddress("北京");order.setNickName("张三");order.setUserId(userId);//远程查询商品列表order.setProductList(Arrays.asList(product));return order;}//获取远程调用的商品数据private Product getProductFromRemote(Long productId){//1.获取所有商品服务所在的所有机器IP+port(获取实例)List<ServiceInstance> instances = discoveryClient.getInstances("service-product");//2.获取实例,远程调用一个url地址ServiceInstance instance = instances.get(0);String url = "http://"+instance.getHost() + ":" + instance.getPort()+"/product/"+productId;log.info("远程请求:{}",url);//3.给远程发送请求RestTemplate(),线程安全的。url返回的json数据自动转换为class对象Product product = restTemplate.getForObject(url, Product.class);return product;}
}

Controller层

@RestController
public class ProductController {@AutowiredProductService productService;//根据商品id,查询商品@GetMapping("/product/{id}")public Product getProduct(@PathVariable("id") Long productId){Product product =productService.getProductById(productId);return product;}
}
@RestController
public class OrderController {@AutowiredOrderService orderService;//创建订单,商品id+用户id@GetMapping("/create")public Order createOrder(@RequestParam("productId") Long productId,@RequestParam("userId") Long userId){Order order =orderService.cresteOrder(productId,userId);return order;}
}

测试结果:

这里默认设置商品数量为2,价格99,id和商品名称动态变化

商品服务:http://localhost:9002/product/4

订单服务:http://localhost:8000/create?userId=2&productId=100

{"id": 4,"price": 99,"productName": "苹果-4","num": 2
}

{"id": 1,"totalAmount": 198,"userId": 2,"nickName": "张三","address": "北京","productList": [{"id": 100,"price": 99,"productName": "苹果-100","num": 2}]
}

日志信息:

@Slf4j注释,使用{}占位符(使用了):log.info("远程请求:{}", url)

示例:log.info("用户 {} 访问了 {} 页面", userId, pageUrl);

结果如下:

停掉其中一个服务后,依旧可以运行:(远程请求端口变了)

缺陷:每次固定发给第一个服务端口,除非宕机了

改进:实现负载均衡,每次请求分别发给不同的服务端口(均衡)

4.demo3(负载均衡)

方法一:choose方法

依赖导入:

<!-- 负载均衡 -->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--测试  -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>

测试类:

@SpringBootTest
public class LoadBalancerTest {@AutowiredLoadBalancerClient loadBalancerClient;@Testvoid test() {ServiceInstance instance = loadBalancerClient.choose("service-product");System.out.println("choose="+instance.getHost() + ":" + instance.getPort());instance = loadBalancerClient.choose("service-product");System.out.println("choose="+instance.getHost() + ":" + instance.getPort());instance = loadBalancerClient.choose("service-product");System.out.println("choose="+instance.getHost() + ":" + instance.getPort());instance = loadBalancerClient.choose("service-product");System.out.println("choose="+instance.getHost() + ":" + instance.getPort());}
}

改进方法:

//上面引入@AutowiredLoadBalancerClient loadBalancerClient;
//负载均衡获取远程调用的商品数据private Product getProductFromRemoteWithLoadBalance(Long productId){ServiceInstance instance = loadBalancerClient.choose("service-product");String url = "http://"+instance.getHost() + ":" + instance.getPort()+"/product/"+productId;log.info("远程请求:{}",url);//3.给远程发送请求RestTemplate(),线程安全的。url返回的json数据自动转换为class对象Product product = restTemplate.getForObject(url, Product.class);return product;}

多次请求,刷新界面后,日志的远程请求地址变了:

方法二:注解实现

RestTemplate 是 Spring 框架中提供的一个用于简化 HTTP 请求和处理响应的工具类。

添加注解实现负载均衡: @LoadBalanced

@Configuration
public class OrderServiceConfig {@LoadBalanced  //注解式实现负载均衡@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}
}
 private Product getProductFromRemoteWithLoadBalanceAnnotation(Long productId){//这里ip地址直接写成对方微服务的名字,会动态替换String url = "http://service-product/product/"+productId;log.info("远程请求:{}",url);//restTemplate在请求发送前:动态将service-product负载均衡的替换成一个IP地址Product product = restTemplate.getForObject(url, Product.class);return product;}

因为restTemplate动态替换了,也不知道想谁发送了请求,在商品控制层输出,hello打招呼:

这里8000日志输出,ip地址被隐藏了,动态替换。不知道向谁发出的远程请求。

@RestController
public class ProductController {@AutowiredProductService productService;//根据商品id,查询商品@GetMapping("/product/{id}")public Product getProduct(@PathVariable("id") Long productId){System.out.println("hello");Product product =productService.getProductById(productId);return product;}
}

一共远程请求了5次,9000和9001分别两次,9002请求一次。(如下所示)

5.配置中心

一.基本使用

依赖引入:

<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

配置文件:

# 指定配置中心地址
spring.cloud.nacos.server-addr=localhost:8848
spring.config.import=nacos:service-order.properties

Naco控制面板,添加配置:

重启项目:controller层添加:

@Value("${order.timeout}")
String orderTimeout;
@Value("${order.auto-confirm}")
String orderAutoConfirm;//接口
@GetMapping("/config")
public String config(){return "order.timeout="+orderTimeout+" order.auto-confirm="+orderAutoConfirm;
}

运行项目,调用接口:

如果其他服务模块没有使用配置中心,添加配置:

# 禁用导入检查(配置中心)
spring.cloud.nacos.config.import-check.enabled=false

二.动态刷新

第一种方法:

controller层添加注解:@RefreshScope

修改配置:

接口数据页自动刷新:

第二种方法(推荐):

ConfigurationProperties

创建包:Properties,存放采用配置,统一管理

@Component
//配置批量绑定在nacos下,可以无需@RefreshScope就能实现自动刷新(前缀)
@ConfigurationProperties(prefix = "order") 
@Data
public class OrderProperties {String timeout;String autoConfirm;
//    String dbUrl;
}

修改控制层:

//@RefreshScope  //自动刷新
@RestController
public class OrderController {@AutowiredOrderService orderService;//    @Value("${order.timeout}")
//    String orderTimeout;
//    @Value("${order.auto-confirm}")
//    String orderAutoConfirm;@AutowiredOrderProperties orderProperties;@GetMapping("/config")public String config(){return "order.timeout="+orderProperties.getTimeout()+"; " +"order.auto-confirm="+orderProperties.getAutoConfirm() +";";}//创建订单,商品id+用户id@GetMapping("/create")public Order createOrder(@RequestParam("productId") Long productId,@RequestParam("userId") Long userId){Order order =orderService.cresteOrder(productId,userId);return order;}
}

三.配置监听

启动类添加:

	@BeanApplicationRunner applicationRunner(NacosConfigManager manager){return args -> {ConfigService configService = manager.getConfigService();configService.addListener("service-order.properties", "DEFAULT_GROUP", new Listener() {@Overridepublic Executor getExecutor() {return Executors.newFixedThreadPool(4);}@Overridepublic void receiveConfigInfo(String configInfo) {System.out.println("变化的配置:configInfo = " + configInfo);}});};}

四.数据隔离

用名称空间来区分多套环境:

首先,创建不同命名空间:(四个)

在不同命名空间下,设置配置,其中分组来区分微服务(每个组就是一个微服务):

order.timeout=1min
order.auto-confirm=1h

依次完成配置和操作,开始克隆:

server:port: 8000
spring:
#  激活环境profiles:active: devapplication:name: service-ordercloud:nacos:server-addr: 127.0.0.1:8848config:
#        导入检查禁用import-check:enabled: false# 动态取值,看当前激活环境,默认为devnamespace: ${spring.profiles.active:dev}#同一个文件中定义多个独立的配置块,使用---
---
# 导入不同空间下的order组的配置文件
spring:config:import:- nacos:common.properties?group=order- nacos:database.properties?group=orderactivate:on-profile: test---
spring:config:import:- nacos:common.properties?group=order- nacos:database.properties?group=orderactivate:on-profile: prod---
spring:config:import:- nacos:common.properties?group=order- nacos:database.properties?group=orderactivate:on-profile: dev

6.Nacos经典面试题

一.注册中心宕机

二.配置项优先级

以配置中心的数据为准,在项目中优先级低(规则如下:)

小结:

三.OpenFeign(远程调用)

1.基本介绍

OpenFeign 是一个声明式远程调用客户端;通过注解方式更加便捷。

上面使用的编程式远程调用请求:RestTemplate(自己手动);这里声明式:OpenFeign(注解)

2.基本使用

其中mvc注解两套使用逻辑:

1.标注在controller上,是接受这样的请求

2.标注在feignClient上,是发生这样的请求

实现功能:

1.引入依赖:

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

2.主类中添加注解:@EnableFeignClients //开启Feign远程调用功能

3.创建feign包,创建接口:自动连接注册中心,负载均衡的挑选一个

@FeignClient(value = "service-product")  //feign客户端+远程微服务名
public interface ProductFeignClient {//    其中mvc注解两套使用逻辑:
//        1.标注在controller上,是接受这样的请求
//        2.标注在feignClient上,是发生这样的请求@GetMapping("/product/{id}")Product getProductById(@PathVariable("id") Long id);
}

4.修改OrderServiceimpl中代码:

   //引入OpenFeignClient@AutowiredProductFeignClient productFeignClient;@Overridepublic Order cresteOrder(Long productId, Long userId) {//从远程获取一个商品数据(示例:模拟只有一个商品)//  Product product = getProductFromRemoteWithLoadBalanceAnnotation(productId);//  改用openfeign注解远程调用Product product = productFeignClient.getProductById(productId);Order order = new Order();order.setId(1L);//总金额:商品的金额*商品购买的数量order.setTotalAmount(  product.getPrice().multiply(new BigDecimal(product.getNum())));order.setAddress("北京");order.setNickName("张三");order.setUserId(userId);//远程查询商品列表order.setProductList(Arrays.asList(product));return order;}

5.测试结果:同样实现远程调用,而且实现了负载均衡使用

两次

一次

3.远程调用技巧

1.远程使用第三方api:指定url地址[墨迹天气为例]

2.调用业务api(自己 编写):直接复制对方的controller签名

4.进阶配置

一.开启日志

yml文件配置:

logging:level:# 监控日志包名(整个包),级别debug,记录详细记录com.atguigu.order.feign: debug

config容器中配置:feign下的logger

@Configuration
public class OrderServiceConfig {// 创建RestTemplate实例, 放到容器中并返回(线程安全)@LoadBalanced  //注解式实现负载均衡@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}//日志注解@BeanLogger.Level feignLoggerLevel() {return Logger.Level.FULL;}
}

二.超时控制

connectTimeout:连接超时

readTimeout:读取超时{业务处理时间}

默认配置如下所示:读取60s,连接10s

订单impl设置睡眠时间:目的,延迟创建订单的业务处理时间100s

 //设置连接时间:100秒try{TimeUnit.SECONDS.sleep(100);}catch (InterruptedException e){throw new RuntimeException(e);}

调用接口,创建订单:http://localhost:8000/create?userId=1&productId=100

在超时前已一直加载转圈:

时间到了之后,返回500错误:读取超时

控制台输出:超时信息

三.超时配置

订单order的配置文件,添加一个application-feign.yml;并在yml中调用激活:

#  激活环境profiles:active: test
# 激活环境标识include: feign
spring:cloud:openfeign:client:config:# 客户端名称(其中default默认配置)default:logger-level: fullconnect-timeout: 1000read-timeout: 2000service-product:logger-level: fullconnect-timeout: 3000read-timeout: 5000

添加这个配置后,5s后就有返回结果:500

四.重试机制

@Bean
Retryer retryer(){return new Retryer.Default();
}

可以查看重试默认配置:5次,请求失败了也会重新请求,最多5次,每次间隔100ms

时间间隔=超时+延迟等待(100ms*1.5)

   public Default() {this(100L, TimeUnit.SECONDS.toMillis(1L), 5);}

使用结果测试:http://localhost:8000/create?userId=1&productId=100

五.拦截器

应用:上游放共享数据,下游可以收到(全局有效)

@Component
public class XTokenRequestInterceptor implements RequestInterceptor {/*** 请求拦截器* @param requestTemplate 请求模版**/@Overridepublic void apply(RequestTemplate requestTemplate) {requestTemplate.header("X-Token", UUID.randomUUID().toString());}
}

控制层:测试输出

@RestController
public class ProductController {@AutowiredProductService productService;//根据商品id,查询商品@GetMapping("/product/{id}")public Product getProduct(@PathVariable("id") Long productId, HttpServletRequest request){String header = request.getHeader("X-Token");System.out.println("hello...token="+header);Product product =productService.getProductById(productId);return product;}
}

结果(先注释之前的超时控制):http://localhost:8000/create?userId=1&productId=100

六.Fallback兜底返回

引入依赖:sentinel

        <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency>
@FeignClient(value = "service-product",fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {@GetMapping("/product/{id}")Product getProductById(@PathVariable("id") Long id);
}
@Component
public class ProductFeignClientFallback implements ProductFeignClient {@Overridepublic Product getProductById(Long id) {System.out.println("兜底回调....");Product product = new Product();product.setId(id);product.setPrice(new BigDecimal("0"));product.setProductName("未知商品");product.setNum(0);return product;}
}

注意:

1.注释掉上面的重传机制和超时设置

2.停掉商品的服务

3.开启熔断:

feign:sentinel:enabled: true

返回结果:

当商品服务开启时:

5.经典面试题

客服端负载均衡与服务端负载均衡的区别:

四.Sentinel(服务保护)

1.基本使用

常用功能:服务保护,限流,熔断降级

官网:home | Sentinel

wiki:https://github.com/alibaba/Sentinel/wiki

下载控制台:📎sentinel-dashboard-1.8.8.jar

启动命令:java -jar sentinel-dashboard-1.8.8.jar

访问面板:http://localhost:8080/#/login

1.1依赖和配置:

<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

其中order的feign的yml配置

spring:cloud:sentinel:transport:dashboard: localhost:8080eager: true   # 默认懒加载,这里改为一开始就启动

product的配置:

spring.cloud.sentinel.transport.dashboard=localhost:8080
spring.cloud.sentinel.eager=true

1.2面板和流控测试

测试接口:http://localhost:8000/create?userId=1&productId=100

添加注解:OrderServiceImpl下的创建方法添加:@SentinelResource(value = "createOrder")

测试:每秒创建一个,多了报错。

快速刷新请求,出现默认错误:

2.异常处理

2.1自定义BlockException异常

自定义返回的json对象:R

@Data
public class R {private Integer code;private String msg;private Object data;public static R ok() {R r = new R();r.setCode(200);return r;}public static R ok(String msg,Object data) {R r = new R();r.setCode(200);r.setMsg(msg);r.setData(data);return r;}public static R error() {R r = new R();r.setCode(500);return r;}public static R error(Integer code,String msg) {R r = new R();r.setCode(code);r.setMsg(msg);return r;}
}

编写自定义异常:

@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {private ObjectMapper objectMapper = new ObjectMapper();@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response,String resourceName, BlockException e) throws Exception {// response.setStatus(429); //too many requests,这里//这里把对象转换为json格式的范湖类型response.setContentType("application/json;charset=utf-8");PrintWriter writer = response.getWriter();R error = R.error(500, resourceName + " 被Sentinel限制了,原因:" + e.getClass());String json = objectMapper.writeValueAsString(error);writer.write(json);writer.flush();writer.close();}
}

重启项目,重新配置sentinel的限流,多次刷新后:设置create限流

若如果添加的createOrder限流,多次刷新,出现:

原因:只给资源起了名,没有标注blockhander和fallback,异常没人管,直接回调Springboot的异常

(设置全局异常可以处理)

2.2blockHandler(指定兜底回调)

对createOrder限流,多次刷新

 @SentinelResource(value = "createOrder",blockHandler = "createOrderFallback")@Overridepublic Order createOrder(Long productId, Long userId) {
//        Product product = getProductFromRemoteWithLoadBalanceAnnotation(productId);//使用Feign完成远程调用Product product = productFeignClient.getProductById(productId);Order order = new Order();order.setId(1L);// 总金额order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));order.setUserId(userId);order.setNickName("zhangsan");order.setAddress("尚硅谷");//远程查询商品列表order.setProductList(Arrays.asList(product));return order;}//兜底回调public Order createOrderFallback(Long productId, Long userId, BlockException e){Order order = new Order();order.setId(0L);order.setTotalAmount(new BigDecimal("0"));order.setUserId(userId);order.setNickName("未知用户");order.setAddress("异常信息:"+e.getClass());return order;}

显示流控异常:

2.3OpenFeign - 兜底回调

对GET:http://service-product/product/{id}添加流量控制

默认会调用之前写好的兜底回调:

3.流控规则

3.1 开启集群

示例:均摊阈值设置为5,每个集群有三个微服务。

单击均摊:表示每个微服务可以每秒5次请求

总体阈值:表示三个微服务1秒一共5次请求

3.2流控模式

(1)默认:直接+快速失败(如上)

(2)链路:控流B,限制C,示例:秒杀场景

spring:cloud:sentinel:transport:dashboard: localhost:8080eager: trueweb-context-unify: false  #先关闭统一的上下文

秒杀接口:

@GetMapping("/kill")public Order killOrder(@RequestParam("productId") Long productId,@RequestParam("userId") Long userId){Order order =orderService.cresteOrder(productId,userId);order.setId(Long.MAX_VALUE);return order;}

这种方式,断开链路,仅对某一个链路生效

(3)关联模式:写优先策略

接口:

@GetMapping("/writeDb")public String writeDb(){return "writeDb";}@GetMapping("/readDb")public String readDb(){return "readDb";}

正常读接口没有问题,但当写接口1s内请求过多时,读接口被限制住了

开两个窗口测试:(系统内出现资源竞争时,使用关联策略)

3.3流控效果[直接模式]

3.3.1快速失败

orderController添加日志注解@SLf4j

   @GetMapping("/writeDb")public String writeDb(){return "writeDb";}@GetMapping("/readDb")public String readDb(){log.info("readDb");   //日志打印return "readDb";}
// 异常处理首先设置状态码response.setStatus(429); //too many requests

重启项目,添加链路规则,限流1

使用apipost进行压力测试:并发数10次,进行5秒

设置后置断言,成功为200,失败设置了状态码为429

开始测试:

3.3.2 预热/冷启动

慢慢启动,直到到达峰值

这个设置表示,3秒内,逐渐达到峰值10,一开始默认为三分之一处,3个请求。

3.3.3均速排队

其其中QPS数量不能大于1000,默认单位为ms

队排不上,会被抛弃;底层使用漏桶算法。

4.熔断规则

熔断时间到了之后,半开状态,有探测数据进行探测

1.测试:修改商品服务接口,故意休眠2秒

  //根据商品id,查询商品@GetMapping("/product/{id}")public Product getProduct(@PathVariable("id") Long productId, HttpServletRequest request){String header = request.getHeader("X-Token");System.out.println("hello...token="+header);Product product =productService.getProductById(productId);//休眠2秒try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}return product;}

2.太慢了,会使用兜底回调,30s内熔断了。5s内先进行熔断检测,统计5s内的前五个请求的异常比例

3.可知,30s后会处于半开状态,当商品服务正常使用,不慢时,又可以正常使用了。(超时注释掉)

5.热点参数

可以限流对象,不再是资源级别,而是精细到参数级别,对于请求方法的某一个参数进行限制。

应用:

1.商城中的秒杀下单,用户id需要限制。[防止机器人,疯狂下单]

2.用户id为vip,不限流QPS

3商品666是下架商品,需要限流

需要,先自定义资源,不可以和请求方法重名:@SentinelResource注解

资源名称为seckill-order,对它进行热点规则限流

 @GetMapping("/seckill")@SentinelResource(value = "seckill-order",fallback = "seckillFallback")public Order seckill(@RequestParam(value = "userId",required = false) Long userId,@RequestParam(value = "productId",defaultValue = "1000") Long productId){Order order = orderService.createOrder(productId, userId);order.setId(Long.MAX_VALUE);return order;}public Order seckillFallback(Long userId,Long productId, BlockException exception){System.out.println("seckillFallback....");Order order = new Order();order.setId(productId);order.setUserId(userId);order.setAddress("异常信息:"+exception.getClass());return order;}

测试:http://localhost:8000/seckill?userId=1&productId=100

1.设置热点规则,每个参数0[用户ID],无论id是几,每秒用户id只能创建一个订单

携带用户id限流空,不带用户id不限流

2.用户6为VIP,不限制QPS请求

3.商品666号为下架商品,不允许访问(限制第二个参数)

4.将上面的 BlockException改为Throwable

可以处理所有异常,使用兜底回调,返回兜底回调,而不是500报错界面

项目重启后,sentinel设置的规则都失效了。sentinel没有对这些规则实现持久化,需要结合nacos,配置好后,然后连接数据库中,实现规则的持久化。

五.GateWay(网关)

官网:Spring Cloud Gateway

1.基本配置(路由)

创建对应模块,引入依赖:

    <dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>annotationProcessor</scope></dependency></dependencies>

配置文件application.yml:

spring:profiles:include: routeapplication:name: gatewaycloud:nacos:server-addr: 127.0.0.1:8848# localhost/api/order
server:port: 80

路由规则:

spring:cloud:gateway:routes:- id: order-routeuri: lb://service-orderpredicates:- Path=/api/order/**- id: order-routeuri: lb://service-productpredicates:- Path=/api/product/**   

启动服务:

http://localhost/api/order/readDb

2.断言

参数(个数/类型)

作用

After

1/datetime

在指定时间之后

Before

1/datetime

在指定时间之前

Between

2/datetime

在指定时间区间内

Cookie

2/string,regexp

包含cookie名且必须匹配指定值

Header

2/string,regexp

包含请求头且必须匹配指定值

Host

N/string

请求host必须是指定枚举值

Method

N/string

请求方式必须是指定枚举值

Path

2/List<String>,bool

请求路径满足规则,是否匹配最后的/

Query

2/string,regexp

包含指定请求参数

RemoteAddr

1/List<String>

请求来源于指定网络域(CIDR写法)

Weight

2/string,int

按指定权重负载均衡

XForwardedRemoteAddr

1/List<string>

从X-Forwarded-For请求头中解析请求来源,并判断是否来源于指定网络域

测试:

spring:cloud:gateway:routes:- id: bing-routeuri: https://www.baidu.com/predicates:- name: Pathargs:patterns: /s- name: Queryargs:param: wdregexp: haha- name: Vipargs:param: uservalue: leifengyangorder: 10metadata:hello: world- id: order-routeuri: lb://service-orderpredicates:- name: Pathargs:patterns: /api/order/**matchTrailingSlash: trueorder: 1- id: product-routeuri: lb://service-productpredicates:- name: Pathargs:patterns: /api/product/**order: 1

编写一个自定义的路由断言工厂:

@Component
public class VipRoutePredicateFactory extends AbstractRoutePredicateFactory<VipRoutePredicateFactory.Config> {public VipRoutePredicateFactory() {super(Config.class);}@Overridepublic Predicate<ServerWebExchange> apply(Config config) {return new GatewayPredicate() {@Overridepublic boolean test(ServerWebExchange serverWebExchange) {// localhost/s?wd=haha&user=leifengyangServerHttpRequest request = serverWebExchange.getRequest();String first = request.getQueryParams().getFirst(config.param);return StringUtils.hasText(first) && first.equals(config.value);}};}@Overridepublic List<String> shortcutFieldOrder() {return Arrays.asList("param", "value");}/*** 可以配置的参数*/@Validatedpublic static class Config {@NotEmptyprivate String param;@NotEmptyprivate String value;public @NotEmpty String getParam() {return param;}public void setParam(@NotEmpty String param) {this.param = param;}public @NotEmpty String getValue() {return value;}public void setValue(@NotEmpty String value) {this.value = value;}}
}

必须要指定参数和用户才可以!

3.过滤器

参数(个数/类型)

作用

AddRequestHeader

2/string

添加请求头

AddRequestHeadersIfNotPresent

1/List<string>

如果没有则添加请求头,key:value方式

AddRequestParameter

2/string、string

添加请求参数

AddResponseHeader

2/string、string

添加响应头

CircuitBreaker

1/string

仅支持forward:/inCaseOfFailureUseThis方式进行熔断

CacheRequestBody

1/string

缓存请求体

DedupeResponseHeader

1/string

移除重复响应头,多个用空格分割

FallbackHeaders

1/string

设置Fallback头

JsonToGrpc

请求体Json转为gRPC

LocalResponseCache

2/string

响应数据本地缓存

MapRequestHeader

2/string

把某个请求头名字变为另一个名字

ModifyRequestBody

仅 Java 代码方式

修改请求体

ModifyResponseBody

仅 Java 代码方式

修改响应体

PrefixPath

1/string

自动添加请求前缀路径

PreserveHostHeader

0

保护Host头

RedirectTo

3/string

重定向到指定位置

RemoveJsonAttributesResponseBody

1/string

移除响应体中的某些Json字段,多个用,分割

RemoveRequestHeader

1/string

移除请求头

RemoveRequestParameter

1/string

移除请求参数

RemoveResponseHeader

1/string

移除响应头

RequestHeaderSize

2/string

设置请求大小,超出则响应431状态码

RequestRateLimiter

1/string

请求限流

RewriteLocationResponseHeader

4/string

重写Location响应头

RewritePath

2/string

路径重写

RewriteRequestParameter

2/string

请求参数重写

RewriteResponseHeader

3/string

响应头重写

SaveSession

0

session保存,配合spring-session框架

SecureHeaders

0

安全头设置

SetPath

1/string

路径修改

SetRequestHeader

2/string

请求头修改

SetResponseHeader

2/string

响应头修改

SetStatus

1/int

设置响应状态码

StripPrefix

1/int

路径层级拆除

Retry

7/string

请求重试设置

RequestSize

1/string

请求大小限定

SetRequestHostHeader

1/string

设置Host请求头

TokenRelay

1/string

OAuth2的token转发

1.网关会将原路径原封不动的转过去,在网关向下转的时候,进行路径重写。

2.使用过滤器,路径重写:(使用通义灵码解析,会有就行,正则表达式)

3.注释掉之前的路径,重启项目

spring:cloud:gateway:routes:- id: bing-routeuri: https://www.baidu.com/predicates:- name: Pathargs:patterns: /s- name: Queryargs:param: wdregexp: haha- name: Vipargs:param: uservalue: leifengyangorder: 10metadata:hello: world- id: order-routeuri: lb://service-orderpredicates:- name: Pathargs:patterns: /api/order/**matchTrailingSlash: truefilters: #将/api/order/ab/c  转换为/ab/c- RewritePath=/api/order/?(?<segment>.*), /$\{segment}- OnceToken=X-Response-Token, jwtorder: 1- id: product-routeuri: lb://service-productpredicates:- name: Pathargs:patterns: /api/product/**filters:- RewritePath=/api/product/?(?<segment>.*), /$\{segment}order: 1

4.配置默认过滤器

      default-filters:- AddResponseHeader=X-Response-Abc, 123

5.全局过滤器

@Component
@Slf4j
public class RtGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();ServerHttpResponse response = exchange.getResponse();String uri = request.getURI().toString();long start = System.currentTimeMillis();log.info("请求【{}】开始:时间:{}",uri,start);//========================以上是前置逻辑=========================Mono<Void> filter = chain.filter(exchange).doFinally((result)->{//=======================以下是后置逻辑=========================long end = System.currentTimeMillis();log.info("请求【{}】结束:时间:{},耗时:{}ms",uri,end,end-start);}); //放行   10sreturn filter;}@Overridepublic int getOrder() {return 0;}
}

4.微服务跨域(允许跨域)

spring:cloud:gateway:globalcors:cors-configurations:'[/**]':allowed-origin-patterns: '*'allowed-headers: '*'allowed-methods: '*'

六.Seata(分布式事务)---了解使用

1.基础环境

建表:

CREATE DATABASE IF NOT EXISTS `storage_db`;
USE  `storage_db`;
DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl` (`id` int(11) NOT NULL AUTO_INCREMENT,`commodity_code` varchar(255) DEFAULT NULL,`count` int(11) DEFAULT 0,PRIMARY KEY (`id`),UNIQUE KEY (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO storage_tbl (commodity_code, count) VALUES ('P0001', 100);
INSERT INTO storage_tbl (commodity_code, count) VALUES ('B1234', 10);-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`branch_id` bigint(20) NOT NULL,`xid` varchar(100) NOT NULL,`context` varchar(128) NOT NULL,`rollback_info` longblob NOT NULL,`log_status` int(11) NOT NULL,`log_created` datetime NOT NULL,`log_modified` datetime NOT NULL,`ext` varchar(100) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;CREATE DATABASE IF NOT EXISTS `order_db`;
USE  `order_db`;
DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (`id` int(11) NOT NULL AUTO_INCREMENT,`user_id` varchar(255) DEFAULT NULL,`commodity_code` varchar(255) DEFAULT NULL,`count` int(11) DEFAULT 0,`money` int(11) DEFAULT 0,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`branch_id` bigint(20) NOT NULL,`xid` varchar(100) NOT NULL,`context` varchar(128) NOT NULL,`rollback_info` longblob NOT NULL,`log_status` int(11) NOT NULL,`log_created` datetime NOT NULL,`log_modified` datetime NOT NULL,`ext` varchar(100) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;CREATE DATABASE IF NOT EXISTS `account_db`;
USE  `account_db`;
DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (`id` int(11) NOT NULL AUTO_INCREMENT,`user_id` varchar(255) DEFAULT NULL,`money` int(11) DEFAULT 0,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO account_tbl (user_id, money) VALUES ('1', 10000);
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`branch_id` bigint(20) NOT NULL,`xid` varchar(100) NOT NULL,`context` varchar(128) NOT NULL,`rollback_info` longblob NOT NULL,`log_status` int(11) NOT NULL,`log_created` datetime NOT NULL,`log_modified` datetime NOT NULL,`ext` varchar(100) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

接口测试:

2.远程链路

  1. 下载seata

📎apache-seata-2.1.0-incubating-bin.tar.gz

  1. 解压并启动:seata-server.bat

http://127.0.0.1:7091

导入依赖:

<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

编写file.conf文件:

service {#transaction service group mappingvgroupMapping.default_tx_group = "default"#only support when registry.type=file, please don't set multiple addressesdefault.grouplist = "127.0.0.1:8091"#degrade, current not supportenableDegrade = false#disable seatadisableGlobalTransaction = false
}

添加全局事务注解:@GlobalTransactional


http://www.mrgr.cn/news/100715.html

相关文章:

  • 推荐系统中 Label 回收机制之【时间窗口设计】
  • Unity-Shader详解-其三
  • 实现一个简单回调列表
  • 51c自动驾驶~合集37
  • Docker安装的mysql限制ip访问
  • ACTF2025 - WEB Excellent-Site
  • 二叉树知识点
  • 软考高项(信息系统项目管理师)第 4 版全章节核心考点解析(第4版课程精华版)
  • 复旦大学发布全球首款二维半导体芯片——无极
  • LangChain入门(四) 部署应用程序
  • 2. python协程/异步编程详解
  • 细说STM32单片机FreeRTOS互斥量及其编程实例
  • Harbor默认Redis与Notary组件弱口令漏洞分析与修复指南
  • NVIDIA高级辅助驾驶领域的创新实践与云计算教育启示
  • 双系统安装 ios放同一个u盘 ventory使用+windows安装,双系统互相访问中间盘 切换默认启动系统
  • 数据分析1
  • 【大模型】Coze AI 智能体工作流从配置到使用实战详解
  • 软考高项(信息系统项目管理师)第 4 版全章节核心考点解析(力扬老师课程精华版)
  • 【Linux应用】交叉编译环境配置、ARM虚拟机环境编译,以及最简单粗暴的环境移植(直接从目标板上复制)
  • 9.idea中创建springboot项目