SpringCloud完整教程

一下内容为本人在听黑马程序员的课程时整理的

  • 微服务技术栈在这里插入图片描述

在这里插入图片描述

⎛⎝≥⏝⏝≤⎛⎝ ⎛⎝≥⏝⏝≤⎛⎝ ⎛⎝≥⏝⏝≤⎛⎝ ⎛⎝≥⏝⏝≤⎛⎝

1、微服务框架

1.1、认识微服务

1.1.1、服务架构演变

**单体架构:**将业务的所有功能集中在一个项目中开发,打包成一个包部署

优点:

  • 架构简单
  • 部署成本低

缺点:

  • 耦合度高

**分布式架构:**根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,称为一个服务

优点:

  • 降低服务耦合
  • 有利于服务升级扩展

服务治理:

分布式架构要考虑的问题:

  • 服务拆分力度如何?
  • 服务集群地址如何维护?
  • 服务之间如何实现远程调用?
  • 服务健康如何感知?

微服务

微服务是一种经过良好架构设计的分布式架构方案,微服务架构特征:

  • 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
  • 面向服务:微服务对外暴露业务接口
  • 自治:团队独立、技术独立、数据独立、部署独立
  • 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

微服务结构:

微服务这种方案需要技术框架来落地,全球的互联网公司都在积极尝试自己的微服务落地技术。在国内最知名的就是SpringCloud和阿里巴巴的Dubbo

在这里插入图片描述

在这里插入图片描述

1.1.2、SpringCloud

SpringCloud

  • SpringCloud是目前国内使用最广泛的微服务框架
  • SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验

在这里插入图片描述

  • SpringCloud与SpringBoot的版本兼容关系如下:

在这里插入图片描述

1.2、服务拆分即远程调用

服务拆分注意事项:

  1. 不同微服务,不要重复开发相同业务
  2. 微服务数据独立,不要访问其它微服务的数据库
  3. 微服务可以将自己的业务暴露为接口,供其它服务调用

工程结构有两种:

  • 独立Project
  • Maven聚合

案例:拆分服务

  • 将hm-service中与商品管理相关功能拆分到一个微服务module中,命名为item-service
  • 将hm-service中与购物车有关的功能拆分到一个微服务module中,命名为cart-service

远程调用

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=C%3A%5CUsers%5C13478%5CDesktop%5C%E8%87%AA%E5%AD%A6%E6%88%90%E6%89%8D%5CJavaWeb%5C%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%BC%在这里插入图片描述
Spring给我们提供了一个RestTemplate工具,可以方便的实现Http请求的发送。使用步骤如下:

1、注入RestTemplate到Spring容器

@Bean
public RestTemplate restTemplate(){return new RestTemplate();
}

2、发起远程调用

public <T> ResponseEntity<T> exchange(Sring url, //请求路径HttpMethod method, //请求方式@Nullable HttpEntity<?> requestEntity, //请求实体,可以为空Class<T> responseType, //返回值类型Map<String,?> urlVariables //请求参数
)

1.3、服务治理

1.3.1、注册中心

服务治理中的三个角色分别是什么?

  • 服务提供者:暴露服务接口,供其它服务调用
  • 服务消费者:调用其他服务提供的接口
  • 注册中心:记录并监控微服务各实例状态,推送服务变更信息

在这里插入图片描述

消费者如何知道提供者的地址号?

  • 服务提供者会在启动时注册自己信息到注册中心,消费者可以从注册中心订阅和拉取服务信息

消费者如何得知服务状态变更?

  • 服务提供者通过心跳机制向注册中心报告自己的健康状态,当心跳异常时注册中心会将异常服务剔除,并通知订阅了该服务的消费者

当提供者有多个实例时,消费者应该选择哪一个?

  • 消费者可以通过负载均衡算法,从多个实例中选择一个
1.3.2、Nacos注册中心

Nacos是目前国内企业中占比最多的注册中心组件。它是阿里巴巴的产品,目前已经加入SpringCloudAlibaba中

在这里插入图片描述

部署Nacos:‍‌‬⁠⁠‌‌⁠‍⁠‍‌‌‍‌‬‌day03-微服务01 - 飞书云文档 (feishu.cn)

1.3.3、服务注册

服务注册步骤:

1、引入nacos discovery依赖:

<!--nacos 服务注册发现-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

2、配置nacos

cloud:
nacos:discovery:server-addr: 192.168.88.132:8848
1.3.4、服务发现

消费者需要连接nacos以拉取和订阅服务,因此服务发现的前两步与服务注册时一样的,后面再加上服务调用即可:

  1. 引入nacos discovery依赖
  2. 配置nacos地址
  3. 服务发现
private final DiscoveryClient discoveryClient;private void handleCartItems(List<CartVO> vos){//1.根据服务名称,拉取服务的实例列表List<ServiceInstance> instances = discoveryClient.getInstances("item-service");//2.负载均衡,挑选一个实例ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));//3.获取实例的IP和端口URU uri = instance.getUri();//....
}

1.4、OpenFeign

1.4.1、快速入 门

我们利用Nacos实现了服务的治理,利用RestTemplate实现了服务的远程调用。但是远程调用的代码太复杂了:

而且这种调用方式,与原本的本地方法调用差异太大,编程时的体验也不统一,一会儿远程调用,一会儿本地调用。

因此,我们必须想办法改变远程调用的开发模式,让远程调用像本地方法调用一样简单。而这就要用到OpenFeign组件了。

其实远程调用的关键点就在于四个:

  • 请求方式
  • 请求路径
  • 请求参数
  • 返回值类型

OpenFeign是一个声明式的http客户端,是SpringCloud在Eureka公司开源的Feign基础上改造而来的

其作用就是基于SpringMVC的常见注解,帮我们优雅的实现http请求的发送

OpenFeign已经被SpringCloud自动装配,实现非常简单

1、引入依赖

  <!--openFeign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--负载均衡器--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency>

2、通过@EnableFeignClients注解,启用OpenFeign功能

@EnabeleFeignClients
@SpringBootApplication
public class CartApplication{  /....}

3、编写FeignClient

@FeignClient("item-service")
public interface ItemClient {@GetMapping("/items")List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}

4、使用FeignClient,实现远程调用

   private void handleCartItems(List<CartVO> vos) {// 1.获取商品idSet<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());//2.查询商品List<ItemDTO> items = itemClient.queryItemByIds(itemIds);if (CollUtils.isEmpty(items)){return;}// 3.转为 id 到 item的mapMap<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));// 4.写入vofor (CartVO v : vos) {ItemDTO item = itemMap.get(v.getItemId());if (item == null) {continue;}v.setNewPrice(item.getPrice());v.setStatus(item.getStatus());v.setStock(item.getStock());}}
1.4.2、连接池

OpenFeign对Http请求做了优雅的伪装,不过其底层发起http请求,依赖于其他的框架。这些框架可以自己选择,包括一下三种:

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

OpenFeign整合OKHttp的步骤:

1、引入依赖

<!--OK http 的依赖 -->
<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-okhttp</artifactId>
</dependency>

2、开启连接池功能

feign:okhttp:enabled: true # 开启OKHttp功能
1.4.3、最佳实践

当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。有两种解决方法:

方式一:指定FeignClient所在的包

@EnableFeignClients(basePackages = "com.hmall.api.client")

方式二:指定FeignClient字码节

@EnableFeignClients(clients={UserClient.class})
1.4.4、日志

OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。而且其日志级别有4级:

  • NONE:不记录任何日志信息,这是默认值
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据

由于Feign默认的日志级别就是NONE,所以默认我们看不到请求日志

要自定义级别需要声明一个类型为Logger.Level的Bean,在其中定义日志级别:

public class DefaultFeignConfig{@Beanpublic Logger.Level feignLogLevel(){return Logger.Level.FULL;}
}

但此时这个Bean并未生效,想要配置某个FeignClient的日志,可以在@FeignClient注解中声明

@FeignClient(value = "item-service",configuration = DefaultFeignConfig.class)

如果想要全局配置,让所有的FeignClient都按照这个日志配置,则需要再@EnableFeignClients注解中声明:

@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)

2、微服务-网关及配置管理

2.1、网关

网关就是网络的开关,负责请求的路由、转发、身份校验

在这里插入图片描述

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

1、Spring Cloud Gateway

  • Spring官方出品
  • 基于WebFlux响应式编程
  • 无需调优即可获得优异性能

2、Netfilx Zuul

  • Netfilx出品
  • 基于Servlet的阻塞式编程
  • 需要调优才能获得与SpringCloudGateway类似的性能

2.2、网关路由

2.2.1、快速入门

1、创建新模块

2、引入网关依赖

<?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><artifactId>hmall</artifactId><groupId>com.heima</groupId><version>1.0.0</version></parent><modelVersion>4.0.0</modelVersion><artifactId>hm-gateway</artifactId><properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target></properties><dependencies><!--common--><dependency><groupId>com.heima</groupId><artifactId>hm-common</artifactId><version>1.0.0</version></dependency><!--网关--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!--nacos discovery--><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-loadbalancer</artifactId></dependency></dependencies><build><finalName>${project.artifactId}</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>

3、编写启动类

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

4、配置路由规则

在这里插入图片描述

server:port: 8080
spring:application:name: gatewaycloud:nacos:server-addr: 192.168.88.132:8848gateway:routes:- id: item # 路由规则id,自定义,唯一uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务- Path=/items/**,/search/** # 这里是以请求路径作为判断规则- id: carturi: lb://cart-servicepredicates:- Path=/carts/**- id: useruri: lb://user-servicepredicates:- Path=/users/**,/addresses/**- id: tradeuri: lb://trade-servicepredicates:- Path=/orders/**- id: payuri: lb://pay-servicepredicates:- Path=/pay-orders/**
2.2.2、路由属性

网关路由对应的Java类型是RouteDefinition,其中常见的属性有:

  • id:路由唯一标示
  • uri:路由目标地址
  • predicates:路由断言,判断请求是否符合当前路由
  • filters:路由过滤器,对请求或响应做特殊处理

Spring提供了12种基本的RoutePredicateFactory实现:

路由过滤器

名称说明示例
After是某个时间点后的请求- After=2037-01-20T17:42:47.789-07:00[America/Denver]
Before是某个时间点之前的请求- Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]
Between是某两个时间点之前的请求- Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]
Cookie请求必须包含某些cookie- Cookie=chocolate, ch.p
Header请求必须包含某些header- Header=X-Request-Id, \d+
Host请求必须是访问某个host(域名)- Host=.somehost.org,.anotherhost.org
Method请求方式必须是指定方式- Method=GET,POST
Path请求路径必须符合指定规则- Path=/red/{segment},/blue/**
Query请求参数必须包含指定参数- Query=name, Jack或者- Query=name
RemoteAddr请求者的ip必须是指定范围- RemoteAddr=192.168.1.1/24
weight权重处理

2.3、网关登录校验

在这里插入图片描述

网关请求处理流程

在这里插入图片描述

2.3.1、自定义过滤器

网关过滤器有两种,分别是:

  • GatewayFilter:路由过滤器,作用于任意指定的路由;默认不生效,要配置到路由后生效
  • GlobalFilter:全局过滤器,作用范围是所有路由;声明后自动生效

两种过滤器的过滤方法签名完全一致

GlobalFilter

自定义GlobalFilter比较简单,直接实现GlobalFilter接口即可

@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// TODO 模拟登录校验逻辑ServerHttpRequest request = (ServerHttpRequest) exchange.getRequest();HttpHeaders headers = request.getHeaders();System.out.println("headers =" + headers);//放行return chain.filter(exchange);}@Overridepublic int getOrder() {return 0;}
}
2.3.2、实现登录校验

需求:在网关中基于过滤器实现登录校验功能

package com.hmall.gateway.filters;import com.hmall.common.exception.UnauthorizedException;
import com.hmall.common.utils.CollUtils;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.util.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.List;@Component
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class AuthGlobalFilter implements GlobalFilter, Ordered {private final JwtTool jwtTool;private final AuthProperties authProperties;private final AntPathMatcher antPathMatcher = new AntPathMatcher();@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1.获取RequestServerHttpRequest request = exchange.getRequest();// 2.判断是否不需要拦截if(isExclude(request.getPath().toString())){// 无需拦截,直接放行return chain.filter(exchange);}// 3.获取请求头中的tokenString token = null;List<String> headers = request.getHeaders().get("authorization");if (!CollUtils.isEmpty(headers)) {token = headers.get(0);}// 4.校验并解析tokenLong userId = null;try {userId = jwtTool.parseToken(token);} catch (UnauthorizedException e) {// 如果无效,拦截ServerHttpResponse response = exchange.getResponse();response.setRawStatusCode(401);return response.setComplete();}// TODO 5.如果有效,传递用户信息System.out.println("userId = " + userId);// 6.放行return chain.filter(exchange);}private boolean isExclude(String antPath) {for (String pathPattern : authProperties.getExcludePaths()) {if(antPathMatcher.match(pathPattern, antPath)){return true;}}return false;}@Overridepublic int getOrder() {return 0;}
}
2.3.3、网关传递用户

在这里插入图片描述

需求:修改gateway模块中的登录校验拦截器,在校验成功后保存到下游请求头中。

提示:要修改转发到微服务的请求,需要用到ServerWebExchange类提供的API,示例如下:

exchange.mutate() //mutate就是对下游请求做更改.request(builder->builder.header("user-info",userInfo)).build();

需求:由于每个微服务都可能有获取登录用户的需求,因此我们直接在hm-common模块定义拦截器,这样微服务只需要引入依赖即可生效,无需重复编写

首先,修改登录校验拦截器的处理逻辑,保存用户信息到请求头中:

// TODO 5.如果有效,传递用户信息
String userInfo = userId.toString();
ServerWebExchange ex = exchange.mutate().request(builder -> builder.header("user-info",userInfo)).build();

UserInfoInterceptor

public class UserInfoInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1、获取登录用户信息String userInfo = request.getHeader("user-info");//2、判断是否获取了用户,如果有,存入ThreadLocalif(StrUtil.isNotBlank(userInfo)){UserContext.setUser(Long.valueOf(userInfo));}//3、放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//清理用户UserContext.removeUser();}
}

MvcConfig

@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new UserInfoInterceptor());}
}

不过,需要注意的是,这个配置类默认是不会生效的,因为它所在的包是com.hmall.common.config,与其它微服务的扫描包不一致,无法被扫描到,因此无法生效。

基于SpringBoot的自动装配原理,我们要将其添加到resources目录下的META-INF/spring.factories文件中:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.hmall.common.config.MyBatisConfig,\com.hmall.common.config.MvcConfig

之前我们无法获取登录用户,所以把购物车服务的登录用户写死了,现在需要恢复到原来的样子。

找到cart-service模块的com.hmall.cart.service.impl.CartServiceImpl

    @Overridepublic List<CartVO> queryMyCarts() {// 1.查询我的购物车列表List<Cart> carts = lambdaQuery().eq(Cart::getUserId, UserContext.getUser()).list();if (CollUtils.isEmpty(carts)) {return CollUtils.emptyList();}// 2.转换VOList<CartVO> vos = BeanUtils.copyList(carts, CartVO.class);// 3.处理VO中的商品信息handleCartItems(vos);// 4.返回return vos;}
2.3.4、OpenFeign传递用户

微服务项目中的很多业务要多个微服务共同合作完成,而这个过程也需要传递登录用户信息,例如:

在这里插入图片描述

OPenFeign中提供了一个拦截器接口,所有有OPenFeign发起的请求都会先调用拦截器处理请求

public class DefaultFeignConfig {@Beanpublic Logger.Level feignLogLevel(){return Logger.Level.FULL;}@Beanpublic RequestInterceptor useInfoRequestInterceptor(){return new RequestInterceptor() {@Overridepublic void apply(RequestTemplate requestTemplate) {Long userId = UserContext.getUser();if (userId!=null){requestTemplate.header("user-info",userId.toString());}}};}}

2.4、配置管理

  • 微服务重复配置过多,维护成本高
  • 业务配置经常变动,每次都要重启服务
  • 网关路由配置写死,如果变更要重启网关

在这里插入图片描述

2.4.1、配置共享

添加一些共享配置到Nacos中,包括Jdbc,MybatisPlus、日志、Swagger、OPenFeign等配置

在这里插入图片描述

2.4.2、拉取共享配置

基于NacosConfig拉取共享配置代替微服务的本地配置

在这里插入图片描述

1、引入依赖

  <!--nacos配置管理--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><!--读取bootstrap文件--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId></dependency>

2、新建bootstrap.yaml

spring:application:name: cart-service # 服务名称profiles:active: devcloud:nacos:server-addr: 192.168.150.101 # nacos地址config:file-extension: yaml # 文件后缀名shared-configs: # 共享配置- dataId: shared-jdbc.yaml # 共享mybatis配置- dataId: shared-log.yaml # 共享日志配置- dataId: shared-swagger.yaml # 共享日志配置
2.4.3、配置热更新

配置热更新:当修改配置文件中的配置时,微服务无需重启即可使配置生效

前提条件:

1、nacos中要有一个与微服务名有关的配置文件

在这里插入图片描述

2、微服务中要以特定方式读取需要热更新的配置属性(推荐第一种)

在这里插入图片描述

案例:实现购物车添加商品上限的配置热部署

需求:购物车的限定数量目前是写死在业务中的,将其改为读取配置文件属性,并将属性交给Nacos管理,实现热更新

@Data
@Component
@ConfigurationProperties(prefix = "hm.cart")
public class CartProperties {private Integer maxItems;
}

CartServiceImpl


private CartProperties cartProperties;private void checkCartsFull(Long userId) {int count = Math.toIntExact(lambdaQuery().eq(Cart::getUserId, userId).count());if (count >= cartProperties.getMaxItems()) {throw new BizIllegalException(StrUtil.format("用户购物车课程不能超过{}", cartProperties.getMaxItems()));}}

在这里插入图片描述

测试:购物车中只能添加一个商品

在这里插入图片描述

2.4.4、动态路由

要实现动态路由首先要将路由配置保存到Nacos,当Nacos中的路由配置变更时,推送最新配置到网关,实时更新网关中的路由信息

我们要完成两件事情:

1、监听Nacos配置变更的消息

在Nacos管网中给出了手动监听Nacos配置变更的SDK:Java SDK (nacos.io)

   private final NacosConfigManager nacosConfigManager;@PostConstructpublic void  initRouteConfigListener() throws NacosException {//1.项目启动时,先拉取一次配置,并且添加配置监听器String configInfo = nacosConfigManager.getConfigService().getConfigAndSignListener(dataId, group, 5000, new Listener() {@Overridepublic Executor getExecutor() {return null;}@Overridepublic void receiveConfigInfo(String s) {//2.监听到配置变更,需要去更新路由表}});//3.第一次读取到配置,也需要更新到路由表updateConfigInfo(configInfo);}

2、当配置变更时,将最新的路由信息更新到网关路由表

监听到路由信息后,可以利用RouteDefinitionWriter来更新路由表

public interface RouteDefinitionWriter{//更新理由到路由表,如果路由id重复,则会覆盖旧的路由Mono<Void> save(Mono<RouteDefinition> route);//根据路由id删除某个路由Mono<void> delete(Mono<String> routeId);
}

路由配置语法

为了方便解析从Nacos读取到底路由配置,推荐使用json格式的路由配置,模块如下:

在这里插入图片描述

public void updateConfigInfo(String configInfo){log.debug("监听到路由配置信息"+configInfo);//1.解析配置信息,转为RouteDefinitionList<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);//2.更新前先删除旧的路由表for (String routeId : routeIds) {writer.delete(Mono.just(routeId)).subscribe();}routeIds.clear();//3.判断是否有新的路由要更新if (CollUtils.isEmpty(routeDefinitions)){//无新路由配置,直接结束return;}//4.更新路由routeDefinitions.forEach(routeDefinition -> {//更新路由writer.save(Mono.just(routeDefinition)).subscribe();//记录路由id,方便将来删除routeIds.add(routeDefinition.getId());});
}

在Nacos中新增配置

在这里插入图片描述

[{"id": "item","predicates": [{"name": "Path","args": {"_genkey_0":"/items/**", "_genkey_1":"/search/**"}}],"filters": [],"uri": "lb://item-service"},{"id": "cart","predicates": [{"name": "Path","args": {"_genkey_0":"/carts/**"}}],"filters": [],"uri": "lb://cart-service"},{"id": "user","predicates": [{"name": "Path","args": {"_genkey_0":"/users/**", "_genkey_1":"/addresses/**"}}],"filters": [],"uri": "lb://user-service"},{"id": "trade","predicates": [{"name": "Path","args": {"_genkey_0":"/orders/**"}}],"filters": [],"uri": "lb://trade-service"},{"id": "pay","predicates": [{"name": "Path","args": {"_genkey_0":"/pay-orders/**"}}],"filters": [],"uri": "lb://pay-service"}
]

路由表的更新有一定的延迟

3、服务保护和分布式事务

3.1、雪崩问题

微服务调用链路中的某个服务故障,引起整个链路中所有微服务不可用,这就是雪崩

雪崩问题产生的原因是什么?

  • 微服务互相调用,服务提供者出现故障或阻塞
  • 服务调用者没有做好异常处理,导致自身故障
  • 调用链中的所有服务级联失败,导致整个集群故障

解决问题的思路?

  • 尽量避免服务出现故障或阻塞
    • 保证代码的健壮性
    • 保证网络畅通
    • 能应对较高的并发需求

3.2、解决方案

3.2.1、请求限流

限制访问微服务的请求的并发量,避免服务因流量激增出现故障

在这里插入图片描述

3.2.2、线程隔离

线程隔离:也叫做舱壁模式,模拟船舱隔板的防水原理。通过限制每个业务能使用的线程数量而将故障业务隔离,避免故障扩展。

在这里插入图片描述

3.2.3、服务断熔

服务断熔:由断路器统计请求的异常比例或慢调用比例,如果超出阈值则会熔断该业务,则拦截该接口的请求

熔断期间,所有的请求快速失败,全都做fallback逻辑

在这里插入图片描述

3.3.4、服务保护技术
** **SentinelHystrix
线程隔离信号量隔离线程池隔离/信号量隔离
熔断策略基于慢调用比例或异常比例基于异常比率
限流基于 QPS,支持流量整形有限的支持
Fallback支持支持
控制台开箱即用,可配置规则、查看秒级监控、机器发现等不完善
配置方式基于控制台,重启后失效基于注解或配置文件,永久生效

3.3、Sentinel

3.3.1、初识Sentinel

Sentinel是阿里巴巴开源的一款微服务流量控制组件

官网:https://b11et3un53m.feishu.cn/wiki/QfVrw3sZvihmnPkmALYcUHIDnff#YRqVd7bn8odK9mx5F1tccqcrn2l

使用步骤:

1、下载jar包

在这里插入图片描述

2、运行

java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

在浏览器中输入localhost:8090
在这里插入图片描述

3.3.2、微服务整合

我们子啊cart-service模块中整合sentinel,连接sentinel-dashboard控制台,步骤如下:

1、引入sentinel依赖

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

2、配置控制台

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

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

重启服务,建立与Sentinel的连接,在黑马商城中访问购物车的相关业务
在这里插入图片描述

3.3.3、簇点链路

簇点链路,就是单机调用链路。是一次请求进入服务后经过的每一个被Sentinel监控的资源链。默认Sentinel会监控SpringMVC的每一个EndPoint(http接口)。限流、熔断等都是针对簇点链路中的资源设置的。而资源名默认就是接口的请求路径:

在这里插入图片描述

RestFul风格的API请求一般都相同,这会导致簇点资源名称重复。因此我们要修改配置,把请求方式+请求路径作为簇点资源名称:

spring:cloud:sentinel:transport:dashboard: localhost:8090 #Sentinel的控制台地址http-method-specify: true #开启请求方式前缀

在这里插入图片描述

在这里插入图片描述

3.3.4、请求限流

在簇点链路后面点击流控按钮,即可对其做限流配置

Cloud.assets%5Cimage-20240808211321541.png&pos_id=img-2cbB1qiD-1723212019655)

在Jmeter中进行测试

在这里插入图片描述

如图,出现429代表实现限流
在这里插入图片描述

3.3.5、线程隔离

当商品服务出现阻塞或故障时,调用商品服务的购物车服务可能因此而被拖慢,甚至资源耗尽。所有必须限制购物车服务中查询商品这个业务的可用线程数,实现线程隔离

在Sentinel控制台中,会出现Feign接口的簇点资源,点击后面的流控按钮,即可配置线程隔离:

在这里插入图片描述

在ItemController中模拟业务延迟

 @ApiOperation("根据id批量查询商品")@GetMappingpublic List<ItemDTO> queryItemByIds(@RequestParam("ids") List<Long> ids){//模拟业务延迟ThreadUtil.sleep(500);return itemService.queryItemByIds(ids);}

限制购物车模块的tomcat线程数

server:port: 8082tomcat:threads:max: 25accept-count: 25max-connections: 100

在这里插入图片描述
在这里插入图片描述

可以看出大部分都异常了

3.3.6、Fallback

1、将FeignClient作为Sentinel的簇点资源:

feign:sentinel:enabled: true

2、FeignClient的FallBack有两种配置方法:

  • 方式一:FallbackClass,无法对远程调用的异常做处理
  • 方式二:FallbackFactory,可以对远程调用的异常做处理,通常都会选这种
@Slf4j
public class ItemClientFallbackFactory implements FallbackFactory<ItemClient> {@Overridepublic ItemClient create(Throwable cause) {return new ItemClient() {@Overridepublic List<ItemDTO> queryItemByIds(Collection<Long> ids) {log.error("查询商品失败",cause);return CollUtils.emptyList();}@Overridepublic void deductStock(List<OrderDetailDTO> items) {log.error("扣减商品库存失败",cause);throw new RuntimeException(cause);}};}
}

DefaultFeignConfig

  @Beanpublic ItemClientFallbackFactory itemClientFallbackFactory(){return new ItemClientFallbackFactory();}

ItemClient

@FeignClient(value = "item-service",fallbackFactory = ItemClientFallbackFactory.class)
public interface ItemClient {}

在这里插入图片描述

在这里插入图片描述

3.3.7、服务熔断

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

在这里插入图片描述

在这里插入图片描述

3.4、分布式事务

​ 在分布式系统中,如果一个业务需要多个服务合作完成,而且每一个服务都有事务,多个事务必须同时成功或失败,这样的事务就是分布式事务。其中的每个服务就是一个分支事务。整个业务成为全局事务

下单业务,前端请求首先进入订单服务,创建订单并写入数据库。然后订单服务调用购物车服务和库存服务:

  • 购物车服务负责清理购物车信息
  • 库存服务负责扣减商品库存

在这里插入图片描述

3.4.1、初识Seata

Seata是2019年1月蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。致力于提供高性能和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案

在这里插入图片描述

在这里插入图片描述
分布式事务解决思路:

在这里插入图片描述

Seata架构

Seata事务管理中有三个重要的角色:

  • TC(Transaction Cooridinator)-事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚
  • TM(Tansaction Manager)-事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务
  • RM(Resource Manager)-资源管理器:管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态
3.4.2、部署TC服务

1、准备数据库表

导入数据库表seata

2、准备配置文件

导入seata目录到虚拟机

将nacos连接到网络

docker network connect hm-net nacos

3、Docker部署

docker run --name seata \
-p 8099:8099 \
-p 7099:7099 \
-e SEATA_IP=192.168.88.130 \
-v ./seata:/seata-server/resources \
--privileged=true \
--network hm-net \
-d \
seataio/seata-server:1.5.2

在浏览器中输入你的端口号:7099进入seata

在这里插入图片描述

3.4.3、微服务集成Seata

1、首先,要在项目中引入Seata依赖:

<!--统一配置管理--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><!--读取bootstrap文件--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId></dependency><!--seata--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId></dependency>

2、然后,在application.yml中添加配置,让微服务找到TC服务地址

seata:registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址type: nacos # 注册中心类型 nacosnacos:server-addr: 192.168.88.130:8848 # nacos地址namespace: "" # namespace,默认为空group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUPapplication: seata-server # seata服务名称username: nacospassword: nacostx-service-group: hmall # 事务组名称service:vgroup-mapping: # 事务组与tc集群的映射关系hmall: "default"

在这里插入图片描述
如果jdk是11以上版本要在启动类上修改

在这里插入图片描述

运行成功:
在这里插入图片描述

3.4.4、XA模式

XA模范是X/Open组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA规范描述了全局的TM与局部的RM之间的接口,几乎所有主流的关系型数据库都对XA规范提供了支持。Seata的XA模式如下:

一阶段工作:

  1. RM注册分支事务到TC
  2. RM执行分支业务sql但不提交
  3. RM报告执行状态到TC

二阶段工作:

  • TC检测各分支事务执行状态
    • a、如果都成功,通知所有RM提交事务
    • b、如果有失败,通知所有RM回滚事务
  • RM接收TC指令,提交或回滚事务

在这里插入图片描述

XA模式的有优点是什么?

  • 事务的强一致性,满足ACID原则
  • 常用数据库都支持,显示简单,并且没有代码侵入

XA模式的缺点是什么?

  • 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
  • 依赖关系型数据库实现事务

实现XA模式

Seata的starter已经完成了XA模式的自动配置,实现非常简单,步骤如下:

1、修改application.yml文件(每个参与事务的微服务),开启XA模式:

seata:data-source-proxy-mode: XA

2、给发起全局事务的入口方法添加@GlobalTransaction注解,本例中是OrderServiceImpl中的create方法:

在这里插入图片描述

3、重启服务并测试

3.4.5、AT模式

Seata主推的是AT模式,AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模式中组员锁定周期过长的缺陷

阶段一RM的工作:

  • 注册分支事务
  • 记录undo-log(数据快照)
  • 执行业务sql并提交
  • 报告事务状态

阶段二提交时RM的工作:

  • 删除undo-log即可

阶段二回滚时RM的工作:

  • 根据undo-log恢复数据到更新前

在这里插入图片描述

简述AT模式与XA模式最大的区别是什么?

  • XA模式一阶段不提交事物,锁定资源;AT模式一阶段直接提交,不锁定资源。
  • XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚
  • XA模式强一致;AT模式最终

实现AT模式:

首先,添加资料汇总的Seata-at.sql到微服务对应的数据库中:

然后,修改application.yml文件,将事务模式修改为AT模式:

seata:data-source-proxy-mode: AT

913253" style=“zoom:50%;” />

如果jdk是11以上版本要在启动类上修改

[外链图片转存中…(img-QC5g9MwU-1723212019658)]

运行成功:

[外链图片转存中…(img-NEsbuGCP-1723212019658)]

3.4.4、XA模式

XA模范是X/Open组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA规范描述了全局的TM与局部的RM之间的接口,几乎所有主流的关系型数据库都对XA规范提供了支持。Seata的XA模式如下:

一阶段工作:

  1. RM注册分支事务到TC
  2. RM执行分支业务sql但不提交
  3. RM报告执行状态到TC

二阶段工作:

  • TC检测各分支事务执行状态
    • a、如果都成功,通知所有RM提交事务
    • b、如果有失败,通知所有RM回滚事务
  • RM接收TC指令,提交或回滚事务

[外链图片转存中…(img-R3xeQSL1-1723212019658)]

XA模式的有优点是什么?

  • 事务的强一致性,满足ACID原则
  • 常用数据库都支持,显示简单,并且没有代码侵入

XA模式的缺点是什么?

  • 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
  • 依赖关系型数据库实现事务

实现XA模式

Seata的starter已经完成了XA模式的自动配置,实现非常简单,步骤如下:

1、修改application.yml文件(每个参与事务的微服务),开启XA模式:

seata:data-source-proxy-mode: XA

2、给发起全局事务的入口方法添加@GlobalTransaction注解,本例中是OrderServiceImpl中的create方法:
在这里插入图片描述

3、重启服务并测试

3.4.5、AT模式

Seata主推的是AT模式,AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模式中组员锁定周期过长的缺陷

阶段一RM的工作:

  • 注册分支事务
  • 记录undo-log(数据快照)
  • 执行业务sql并提交
  • 报告事务状态

阶段二提交时RM的工作:

  • 删除undo-log即可

阶段二回滚时RM的工作:

  • 根据undo-log恢复数据到更新前

[外链图片转存中…(img-y05XX17y-1723212019659)]

简述AT模式与XA模式最大的区别是什么?

  • XA模式一阶段不提交事物,锁定资源;AT模式一阶段直接提交,不锁定资源。
  • XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚
  • XA模式强一致;AT模式最终

实现AT模式:

首先,添加资料汇总的Seata-at.sql到微服务对应的数据库中:

然后,修改application.yml文件,将事务模式修改为AT模式:

seata:data-source-proxy-mode: AT

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/401404.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为云Api调用怎么生成Authorization鉴权信息,StringToSign拼接流程

请求示例 Authorization 为了安全&#xff0c;华为云的 Api 调用都是需要在请求的 Header 中携带 Authorization 鉴权的&#xff0c;这个鉴权15分钟内有效&#xff0c;超过15分钟就不能用了&#xff0c;而且是需要调用方自己手动拼接的。 Authorization的格式为 OBS 用户AK:…

Linux系统移植——开发板烧写

目录&#xff1a; 目录&#xff1a; 一、什么是EMMC分区&#xff1f; 1.1 eMMC分区 1.2 分区的管理 二、相关命令介绍&#xff1a; 2.1 mmc 2.1.1 主要功能 2.1.2 示例用法 2.2 fdisk 2.2.1 基本功能 2.2.2 交互模式常用命令 2.2.3 注意事项 三、U-BOOT烧写 3.1 mmc命令 3.2 f…

【Linux入门】Linux环境搭建

目录 前言 一、发行版本 二、搭建Linux环境 1.Linux环境搭建方式 2.虚拟机安装Ubuntu 22.02.4 1&#xff09;安装VMWare 2&#xff09;下载镜像源 3&#xff09;添加虚拟机 4&#xff09;换源 5&#xff09;安装VM Tools 6)添加快照 总结 前言 Linux是一款自由和开放…

JAVA集中学习第五周学习记录(二)

系列文章目录 第一章 JAVA集中学习第一周学习记录(一) 第二章 JAVA集中学习第一周项目实践 第三章 JAVA集中学习第一周学习记录(二) 第四章 JAVA集中学习第一周课后习题 第五章 JAVA集中学习第二周学习记录(一) 第六章 JAVA集中学习第二周项目实践 第七章 JAVA集中学习第二周学…

RCE远程命令执行

命令执行的常用函数 system()&#xff1a;能将字符串作为系统命令执行&#xff0c;且返回命令执行结果。 #system(string $command, int &$result_code null): string|false system(whoami); exec()&#xff1a;能将字符串作为系统命令执行&#xff0c;但是只返回执行结果…

MySQL 的 InnoDB 缓冲池里有什么?--InnoDB存储梳理(二)

文章目录 缓冲池的配置介绍一张表 INNODB_BUFFER_POOL_PAGES字段解释 缓冲池的配置 以下配置的意思&#xff0c;缓冲池在内存中的大小为20M&#xff1b;只有1个缓冲池实例&#xff1b;每一块的大小&#xff0c;插入缓冲占的百分比 # InnoDB 缓存池配置 innodb_buffer_pool_si…

Python之循环语句

这是《Python入门经典以解决计算问题为导向的Python编程实践》中58-65的内容&#xff0c;主要将了while循环语句和for循环语句。 循环 一、while循环语句语法&#xff1a;工作原理&#xff1a;案例解读要点 二、for循环语句语法工作原理、案例&#xff1a;寻找完全数 三、whil…

学习记录——day30 网络编程 端口号port 套接字socket TCP实现网络通信

目录 一、端口号 port 二、套接字 socket 1、原理 2、socket函数介绍 三、TCP实现网络通信 1、原理 2、TCP通信原理图 3、TCP相关函数 1&#xff09;bind 绑定 2&#xff09;listen 监听 3&#xff09;accept 接收连接请求 4&#xff09;recv 接收 5&#xff09;sen…

Ubuntu系统中安装ffmpeg工具(详细图文教程)

&#x1f4aa; 专业从事且热爱图像处理&#xff0c;图像处理专栏更新如下&#x1f447;&#xff1a; &#x1f4dd;《图像去噪》 &#x1f4dd;《超分辨率重建》 &#x1f4dd;《语义分割》 &#x1f4dd;《风格迁移》 &#x1f4dd;《目标检测》 &#x1f4dd;《暗光增强》 &a…

RAG:系统评估,以RAGAS为例

面试的时候经常会问到&#xff0c;模型和系统是怎么评估的&#xff0c;尤其是RAG&#xff0c;这么多组件&#xff0c;还有端到端&#xff0c;每部分有哪些指标评估&#xff0c;怎么实现的。今天整理下 目前最通用的是RAGAS框架&#xff0c;已经在langchain集成了。在看它之前&…

Java面试--设计模式

设计模式 目录 设计模式1.单例模式&#xff1f;2.代理模式&#xff1f;3.策略模式&#xff1f;4.工厂模式&#xff1f; 1.单例模式&#xff1f; 单例模式是Java的一种设计思想&#xff0c;用此模式下&#xff0c;某个对象在jvm只允许有一个实例&#xff0c;防止这个对象多次引…

文本分类任务算法演变(一)

文本分类任务算法演变 1.简介和应用场景1.1使用场景-打标签1.2使用场景-电商评论分析1.3使用场景-违规检测1.4使用场景-放开想象空间 2贝叶斯算法2.1预备知识-全概率公式2.2贝叶斯公式2.3文本分类中的应用2.3.1任务如下 2.4贝叶斯的优缺点 3.支持向量机3.1支持向量机-决策函数3…

libnl教程(2):发送请求

文章目录 前言示例示例代码构造请求创建套接字发送请求 简化示例 前言 前置阅读要求&#xff1a;libnl教程(1):订阅内核的netlink广播通知 本文介绍&#xff0c;libnl如何向内核发送请求。这包含三个部分&#xff1a;构建请求&#xff1b;创建套接字&#xff1b;发送请求。 …

Web开发:web服务器-Nginx的基础介绍(含AI文稿)

目录 一、Nginx的功能&#xff1a; 二、正向代理和反向代理的区别 三、Nginx负载均衡的主要功能 四、nginx安装目录下的各个文件&#xff08;夹&#xff09;的作用&#xff1a; 五、常用命令 一、Nginx的功能&#xff1a; 1.反向代理&#xff1a;例如我有三台服务器&#x…

大数据项目——实战项目:广告数仓(第二部分)集群环境部署

目录 第4章 广告数仓架构设计 第5章 集群环境准备 5.1 服务器准备 5.1.1 创建3台虚拟机 5.1.2 SSH无密登录配置 5.1.3 编写集群分发脚本xsync 5.1.4 JDK准备 5.1.5 环境变量配置说明 5.2 Hadoop部署 5.2.1 完全分布式运行模式&#xff08;开发重点&#xff09; 5.2…

去噪、梯度与边缘检测

图像噪点消除 噪声在图像处理中指的是图像中出现的干扰因素&#xff0c;通常由采集设备或传输过程引起。噪声使得图像的亮度变得不均匀或引入了随机的干扰点。常见的噪声类型包括&#xff1a; 高斯噪声&#xff1a;符合正态分布的噪声&#xff0c;会使图像变得模糊或出现噪点。…

Java基础之switch分支语句

switch分支语句 ​ switch语句也称为分支语句&#xff0c;其和if语句有点类似&#xff0c;都是用来判断值是否相等&#xff0c;但switch默认只支持byte、short、int、char这四种类型的比较&#xff0c;JDK8中也允许String类型的变量做对比。 语法&#xff1a; switch (表达式…

8.15 哈希表中等 139 Word Break review 467 Unique Substrings in Wraparound String

139 Word Break【逐一对比vs.多种 分割 组合】 片面思考的思路&#xff1a; class Solution { public:bool wordBreak(string s, vector<string>& wordDict) {//字符串和对应的字典&#xff0c;如果s种可以用空格分隔出一个或多个字典里的词就返回true//核心&#x…

windows 安装TVM

TVM支持在Windows环境下使用&#xff0c;但需要一些额外的配置。以下是如何在Windows Python环境中安装TVM的详细步骤。 1. 安装TVM的预备条件 在Windows上安装TVM之前&#xff0c;需要确保系统已经安装了以下工具和依赖项&#xff1a; Visual Studio: 安装包含C开发工具的V…

利用ZXing.Net Bindings for EmguCV识别条形码及绘制条形码边框17(C#)

上一篇博文&#xff1a;绘制条形码的效果不是很好&#xff1a;利用Emgucv绘制条形码边框16(C#)-CSDN博客 测试环境&#xff1a; win11 64位操作系统 visual studio 2022 ZXing.Net.Bindings.EmguCV 0.16.4 测试步骤如下&#xff1a; 1 新建.net framework 4.8的控制台项目…