微服务SpringCloud,SpringAliBaBa(2)

news2024/9/20 18:42:21

微服务02 

1.网关路由

网络的关口,负责请求的路由、转发、身份校验。

有了网关之后,微服务的地址不用在暴露了,就暴露个网关地址。

快速入门

routes代表一套路由,pridicates是规则,对请求做出判断,看是哪个微服务做出处理。uri到达目标微服务的地址。 

1.2.2.引入依赖

hm-gateway模块的pom.xml文件中引入依赖:

<?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>

1.2.3.启动类

hm-gateway模块的com.hmall.gateway包下新建一个启动类:

代码如下: 

package com.hmall.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

1.2.4.配置路由

接下来,在hm-gateway模块的resources目录新建一个application.yaml文件,内容如下:

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

1.2.5.测试

启动GatewayApplication,以 http://localhost:8080 拼接微服务接口路径来测试。例如:

http://localhost:8080/items/page?pageNo=1&pageSize=1

路由属性 

 对进入网关的前端请求,和微服务处理完之后得到的结果进行加工处理。

这就是filter

名称

说明

示例

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

权重处理

filter获得请求头 

filter的请求头为truth 

 拿到请求头

2.网关登录校验

单体架构时我们只需要完成一次用户登录、身份校验,就可以在所有业务中获取到用户信息。而微服务拆分后,每个微服务都独立部署,不再共享数据。也就意味着每个微服务都需要做登录校验,这显然不可取。

2.1.鉴权思路分析

我们的登录是基于JWT来实现的,校验JWT的算法复杂,而且需要用到秘钥。如果每个微服务都去做登录校验,这就存在着两大问题:

  • 每个微服务都需要知道JWT的秘钥,不安全

  • 每个微服务重复编写登录校验代码、权限校验代码,麻烦

既然网关是所有微服务的入口,一切请求都需要先经过网关。我们完全可以把登录校验的工作放到网关去做,这样之前说的问题就解决了:

  • 只需要在网关和用户服务保存秘钥

  • 只需要在网关开发登录校验功能

此时,登录校验的流程如图:

不过,这里存在几个问题:

  • 网关路由是配置的,请求转发是Gateway内部代码,我们如何在转发之前做登录校验?

  • 网关校验JWT之后,如何将用户信息传递给微服务?

  • 微服务之间也会相互调用,这种调用不经过网关,又该如何传递用户信息?

2.2.网关过滤器

登录校验必须在请求转发到微服务之前做,否则就失去了意义。而网关的请求转发是Gateway内部代码实现的,要想在请求转发之前做登录校验,就必须了解Gateway内部工作的基本原理。

网关请求处理流程(重点)

 

如图所示:

  1. 客户端请求进入网关后由HandlerMapping对请求做判断,找到与当前请求匹配的路由规则(Route),然后将请求交给WebHandler去处理。

  2. WebHandler则会加载当前路由下需要执行的过滤器链(Filter chain),然后按照顺序逐一执行过滤器(后面称为Filter)。

  3. 图中Filter被虚线分为左右两部分,是因为Filter内部的逻辑分为prepost两部分,分别会在请求路由到微服务之前之后被执行。

  4. 只有所有Filterpre逻辑都依次顺序执行通过后,请求才会被路由到微服务。

  5. 微服务返回结果后,再倒序执行Filterpost逻辑。

  6. 最终把响应结果返回。

如图中所示,最终请求转发是有一个名为NettyRoutingFilter的过滤器来执行的,而且这个过滤器是整个过滤器链中顺序最靠后的一个。如果我们能够定义一个过滤器,在其中实现登录校验逻辑,并且将过滤器执行顺序定义到NettyRoutingFilter之前,这就符合我们的需求了!

2.3.自定义过滤器

无论是GatewayFilter还是GlobalFilter都支持自定义,只不过编码方式、使用方式略有差别。

2.3.2.自定义GlobalFilter

自定义GlobalFilter则简单很多,直接实现GlobalFilter即可,而且也无法设置动态参数:

@Component
public class PrintAnyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 编写过滤器逻辑
        System.out.println("未登录,无法访问");
        // 放行
        // return chain.filter(exchange);

        // 拦截
        ServerHttpResponse response = exchange.getResponse();
        response.setRawStatusCode(401);
        return response.setComplete();
    }

    @Override
    public int getOrder() {
        // 过滤器执行顺序,值越小,优先级越高
        return 0;
    }
}
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @author 啟王朝
 * date2024/6/18 23:01
 */

/**
 * 作用:Ordered 实现排序,值越小代表优先级越高
 */
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //  todo 模拟登录校验逻辑
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();
        System.out.println("headers = " + headers);
        return chain.filter(exchange);  //  将exchange中数据传递给下一个过滤器,实现共享
    }

    /**
     * 作用: public int getOrder() 优先级最高
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

2.4.登录校验

接下来,我们就利用自定义GlobalFilter来完成登录校验。

2.4.1.JWT工具

登录校验需要用到JWT,而且JWT的加密需要秘钥和加密工具。这些在hm-service中已经有了,我们直接拷贝过来:

具体作用如下:

  • AuthProperties:配置登录校验需要拦截的路径,因为不是所有的路径都需要登录才能访问

  • JwtProperties:定义与JWT工具有关的属性,比如秘钥文件位置

  • SecurityConfig:工具的自动装配

  • JwtTool:JWT工具,其中包含了校验和解析token的功能

  • hmall.jks:秘钥文件

其中AuthPropertiesJwtProperties所需的属性要在application.yaml中配置:

hm:
  jwt:
    location: classpath:hmall.jks # 秘钥地址
    alias: hmall # 秘钥别名
    password: hmall123 # 秘钥文件密码
    tokenTTL: 30m # 登录有效期
  auth:
    excludePaths: # 无需登录校验的路径
      - /search/**
      - /users/login
      - /items/**

2.4.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();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.获取Request
        ServerHttpRequest request = exchange.getRequest();
        // 2.判断是否不需要拦截
        if(isExclude(request.getPath().toString())){
            // 无需拦截,直接放行
            return chain.filter(exchange);
        }
        // 3.获取请求头中的token
        String token = null;
        List<String> headers = request.getHeaders().get("authorization");
        if (!CollUtils.isEmpty(headers)) {
            token = headers.get(0);
        }
        // 4.校验并解析token
        Long 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;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

 网关传递用户

AuthGlobalFilter.java
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();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.获取Request
        ServerHttpRequest request = exchange.getRequest();
        // 2.判断是否不需要拦截
        if (isExclude(request.getPath().toString())) {
            // 无需拦截,直接放行
            return chain.filter(exchange);
        }
        // 3.获取请求头中的token
        String token = null;
        List<String> headers = request.getHeaders().get("authorization");
        if (!CollUtils.isEmpty(headers)) {
            token = headers.get(0);
        }
        // 4.校验并解析token
        Long 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);
        //  在网关的登录校验器中,把获取到的用户写入请求头中
        String userInfo = userId.toString();
        ServerWebExchange swe = exchange.mutate()
                .request(builder -> builder.header("user-info", userInfo))
                .build();
        // 6.放行
        return chain.filter(swe);
    }

    private boolean isExclude(String antPath) {
        for (String pathPattern : authProperties.getExcludePaths()) {
            if (antPathMatcher.match(pathPattern, antPath)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

2.5.微服务获取用户

现在,网关已经可以完成登录校验并获取登录用户身份信息。但是当网关将请求转发到微服务时,微服务又该如何获取用户身份呢?

由于网关发送请求到微服务依然采用的是Http请求,因此我们可以将用户信息以请求头的方式传递到下游微服务。然后微服务可以从请求头中获取登录用户信息。考虑到微服务内部可能很多地方都需要用到登录用户信息,因此我们可以利用SpringMVC的拦截器来实现登录用户信息获取,并存入ThreadLocal,方便后续使用。

据图流程图如下:

因此,接下来我们要做的事情有:

  • 改造网关过滤器,在获取用户信息后保存到请求头,转发到下游微服务

  • 编写微服务拦截器,拦截请求获取用户信息,保存到ThreadLocal后放行

2.5.1.保存用户到请求头

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

2.5.2.拦截器获取用户

在hm-common中已经有一个用于保存登录用户的ThreadLocal工具:

 

其中已经提供了保存和获取用户的方法:

接下来,我们只需要编写拦截器,获取用户信息并保存到UserContext,然后放行即可。

由于每个微服务都有获取登录用户的需求,因此拦截器我们直接写在hm-common中,并写好自动装配。这样微服务只需要引入hm-common就可以直接具备拦截器功能,无需重复编写。

我们在hm-common模块下定义一个拦截器:

 具体代码如下:

package com.hmall.common.interceptor;

import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class UserInfoInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.获取请求头中的用户信息
        String userInfo = request.getHeader("user-info");
        // 2.判断是否为空
        if (StrUtil.isNotBlank(userInfo)) {
            // 不为空,保存到ThreadLocal
                UserContext.setUser(Long.valueOf(userInfo));
        }
        // 3.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用户
        UserContext.removeUser();
    }
}

 接着在hm-common模块下编写SpringMVC的配置类,配置登录拦截器:

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;

@Configuration
//   @ConditionalOnClass({MybatisPlusInterceptor.class, BaseMapper.class})  因为网关不支持mvc,所以让其只是在微服务中生效
//@ConditionalOnClass({MybatisPlusInterceptor.class, BaseMapper.class})
@ConditionalOnClass(DispatcherServlet.class)  //  让其旨在mvc中生效
public class MyBatisConfig {
    @Bean
    @ConditionalOnMissingBean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 1.分页拦截器
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        paginationInnerInterceptor.setMaxLimit(1000L);
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        return interceptor;
    }
}

不过,需要注意的是,这个配置类默认是不会生效的,因为它所在的包是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

2.5.3.恢复购物车代码

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

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

修改其中的queryMyCarts方法:

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmall.api.client.ItemClient;
import com.hmall.api.dto.ItemDTO;
import com.hmall.cart.domain.dto.CartFormDTO;
import com.hmall.cart.domain.po.Cart;
import com.hmall.cart.domain.vo.CartVO;
import com.hmall.cart.mapper.CartMapper;
import com.hmall.cart.service.ICartService;
import com.hmall.common.exception.BizIllegalException;
import com.hmall.common.utils.BeanUtils;
import com.hmall.common.utils.CollUtils;
import com.hmall.common.utils.UserContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

 * @since 2023-05-05
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {
 private final ItemClient itemClient;

   
    @Override
    public List<CartVO> queryMyCarts() {
        // 1.查询我的购物车列表
        //     UserContext.getUser()  这里面存的就是用户id
        System.out.println("UserContext.getUser() = " + UserContext.getUser());
        List<Cart> carts = lambdaQuery().eq(Cart::getUserId, UserContext.getUser()).list();
//        List<Cart> carts = lambdaQuery().eq(Cart::getUserId, 1L/*UserContext.getUser()*/).list();
        if (CollUtils.isEmpty(carts)) {
            return CollUtils.emptyList();
        }

        // 2.转换VO
        List<CartVO> vos = BeanUtils.copyList(carts, CartVO.class);

        // 3.处理VO中的商品信息
        handleCartItems(vos);

        // 4.返回
        return vos;
    }
}

3.配置管理

这些问题都可以通过统一的配置管理器服务解决。而Nacos不仅仅具备注册中心功能,也具备配置管理的功能:

服务保护和分布式事务 

01 雪崩问题

 

雪崩解决方案 

1.2.Sentinel

微服务保护的技术有很多,但在目前国内使用较多的还是Sentinel,所以接下来我们学习Sentinel的使用。

1.2.1.介绍和安装

Sentinel是阿里巴巴开源的一款服务保护框架,目前已经加入SpringCloudAlibaba中。官方网站:

Sentinel 的使用可以分为两个部分:

  • 核心库(Jar包):不依赖任何框架/库,能够运行于 Java 8 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。在项目中引入依赖即可实现服务限流、隔离、熔断等功能。

  • 控制台(Dashboard):Dashboard 主要负责管理推送规则、监控、管理机器信息等。

为了方便监控微服务,我们先把Sentinel的控制台搭建出来。

1)下载jar包

下载地址:Docs

也可以直接使用课前资料提供的版本: 

2)运行

将jar包放在任意非中文、不包含特殊字符的目录下,重命名为sentinel-dashboard.jar

 然后运行如下命令启动控制台:

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

3)访问

访问http://localhost:8090页面,就可以看到sentinel的控制台了:

需要输入账号和密码,默认都是:sentinel

登录后,即可看到控制台,默认会监控sentinel-dashboard服务本身:

1.2.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

3)访问cart-service的任意端点

重启cart-service,然后访问查询购物车接口,sentinel的客户端就会将服务访问的信息提交到sentinel-dashboard控制台。并展示出统计信息:

 点击簇点链路菜单,会看到下面的页面:

 1.2.3 簇点链路

 

所谓簇点链路,就是单机调用链路,是一次请求进入服务后经过的每一个被Sentinel监控的资源。默认情况下,Sentinel会监控SpringMVC的每一个Endpoint(接口)。

因此,我们看到/carts这个接口路径就是其中一个簇点,我们可以对其进行限流、熔断、隔离等保护措施。

不过,需要注意的是,我们的SpringMVC接口是按照Restful风格设计,因此购物车的查询、删除、修改等接口全部都是/carts路径:

默认情况下Sentinel会把路径作为簇点资源的名称,无法区分路径相同但请求方式不同的接口,查询、删除、修改等都被识别为一个簇点资源,这显然是不合适的。

所以我们可以选择打开Sentinel的请求方式前缀,把请求方式 + 请求路径作为簇点资源名:

首先,在cart-serviceapplication.yml中添加下面的配置:

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8090
      http-method-specify: true # 开启请求方式前缀

然后,重启服务,通过页面访问购物车的相关接口,可以看到sentinel控制台的簇点链路发生了变化:

1.3.请求限流

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

1.4.1.OpenFeign整合Sentinel

修改cart-service模块的application.yml文件,开启Feign的sentinel功能:

需要注意的是,默认情况下SpringBoot项目的tomcat最大线程数是200,允许的最大连接是8492,单机测试很难打满。

所以我们需要配置一下cart-service模块的application.yml文件,修改tomcat连接:

server:
  port: 8082
  tomcat:
    threads:
      max: 50 # 允许的最大线程数
    accept-count: 50 # 最大排队等待数量
    max-connections: 100 # 允许的最大连接

 然后重启cart-service服务,可以看到查询商品的FeignClient自动变成了一个簇点资源:

1.4 线程隔离

 Fallback

 

 1.5 服务熔断

状态机包括三个状态:

  • closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态

  • open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态持续一段时间后会进入half-open状态

  • half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。

    • 请求成功:则切换到closed状态

    • 请求失败:则切换到open状态

我们可以在控制台通过点击簇点后的熔断按钮来配置熔断策略:

 

这种是按照慢调用比例来做熔断,上述配置的含义是:

  • RT超过200毫秒的请求调用就是慢调用

  • 统计最近1000ms内的最少5次请求,如果慢调用比例不低于0.5,则触发熔断

  • 熔断持续时长20s

 配置完成后,再次利用Jemeter测试,可以发现

在一开始一段时间是允许访问的,后来触发熔断后,查询商品服务的接口通过QPS直接为0,所有请求都被熔断了。而查询购物车的本身并没有受到影响。

此时整个购物车查询服务的平均RT影响不大:

2.分布式事务

首先我们看看项目中的下单业务整体流程:

由于订单、购物车、商品分别在三个不同的微服务,而每个微服务都有自己独立的数据库,因此下单过程中就会跨多个数据库完成业务。而每个微服务都会执行自己的本地事务:

  • 交易服务:下单事务

  • 购物车服务:清理购物车事务

  • 库存服务:扣减库存事务

整个业务中,各个本地事务是有关联的。因此每个微服务的本地事务,也可以称为分支事务。多个有关联的分支事务一起就组成了全局事务。我们必须保证整个全局事务同时成功或失败。

我们知道每一个分支事务就是传统的单体事务,都可以满足ACID特性,但全局事务跨越多个服务、多个数据库,是否还能满足呢?

我们来做一个测试,先进入购物车页面:

 目前有4个购物车,然结算下单,进入订单结算页面:

 然后将购物车中某个商品的库存修改为0

 然后,提交订单,最终因库存不足导致下单失败:

 然后我们去查看购物车列表,发现购物车数据依然被清空了,并未回滚:

事务并未遵循ACID的原则,归其原因就是参与事务的多个子业务在不同的微服务,跨越了不同的数据库。虽然每个单独的业务都能在本地遵循ACID,但是它们互相之间没有感知,不知道有人失败了,无法保证最终结果的统一,也就无法遵循ACID的事务特性了。

这就是分布式事务问题,出现以下情况之一就可能产生分布式事务问题:

  • 业务跨多个服务实现

  • 业务跨多个数据源实现

接下来这一章我们就一起来研究下如何解决分布式事务问题。

解决思路 

 

 Seata架构

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《第一行代码 第3版》学习笔记——第十一章 网络技术

1 webview用法 class MainActivity : ComponentActivity() {SuppressLint("SetJavaScriptEnabled")override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {NetWorkDemoTheme {// A surface container using the bac…

史上最全整合nacos单机模式整合哈哈哈哈哈

Nacos 是阿里巴巴推出的一个新开源项目&#xff0c;它主要是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 Nacos提供了一组简单易用的特性集&#xff0c;帮助用户快速实现动态服务发现、服务配置、服务元数据及流量管理。 Nacos 的关键特性包括&#x…

线程池概念、线程池的不同创建方式、线程池的拒绝策略

文章目录 &#x1f490;线程池概念以及什么是工厂模式&#x1f490;标准库中的线程池&#x1f490;什么是工厂模式&#xff1f;&#x1f490;ThreadPoolExecutor&#x1f490;模拟实现线程池 &#x1f490;线程池概念以及什么是工厂模式 线程的诞生是因为&#xff0c;频繁的创…

【Linux】Socket阻塞和非阻塞、同步与异步

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;Linux系列专栏&#xff1a;Linux基础 &#x1f525; 给大家…

20240623日志:大模型压缩-sliceGPT

context 1. 剪枝方案图释2. 正交矩阵Q 1. 剪枝方案图释 Fig. 1.1 剪枝方案 图中的阴影是表示丢弃掉这部分数据。通过引入正交矩阵 Q Q Q使 Q ⊤ Q Q Q ⊤ I \mathrm{Q}^\top\mathrm{Q}\mathrm{Q}\mathrm{Q}^\top\mathrm{I} Q⊤QQQ⊤I&#xff0c;来大量缩减 X X X的列数和 W …

[职场] 保险理赔工作简历自我评价范文 #媒体#经验分享#笔记

保险理赔工作简历自我评价范文 保险行业是指将通过契约形式集中起来的资金&#xff0c;用以补偿被保险人的经济利益业务的行业。下面是保险理赔工作简历自我评价范文&#xff0c;供大家参考。 保险公司工作自我评价1 我已在__保险公司工作_个年头。在同事和领导的热情帮忙下&…

query2doc:用大模型做query检索拓展

原文&#xff1a; 前沿重器[38] | 微软新文query2doc&#xff1a;用大模型做query检索拓展 比较主流的检索方案&#xff1a; 字面检索&#xff08;sparse&#xff0c;稀疏&#xff09;向量检索&#xff08;dense&#xff0c;稠密&#xff09; query对文档文段的召回&#xff…

【消息队列】六万字长文详细带你RabbitMQ从入门到精通

目录 1、基础篇1.1 为什么要用消息队列MQ1.2 什么是消息队列&#xff1f;1.3 RabbitMQ体系结构介绍1.4 RabbitMQ安装1.5 Hello World1.5.1 目标1.5.2 具体操作 1.6 RabbitMQ用法1.6.1 Work Queues1.6.2 Publish/Subscribe1.6.3 Routing1.6.4 Topics1.6.5 工作模式小结 2. 进阶篇…

维度建模中的事实表

在维度建模中&#xff0c;根据粒度划分&#xff0c;数据表主要分为维度表和事实表。而事实表又可以分为三种类型&#xff1a;事务型事实表、周期快照型事实表和累计快照型事实表。本文将详细介绍这三种事实表&#xff0c;并提供相应的代码示例。 目录 1. 事务型事实表示例 2. 周…

推荐系统三十六式学习笔记:原理篇.模型融合13|经典模型融合办法:线性模型和树模型的组合拳

目录 为什么要融合&#xff1f;“辑度组合”原理逻辑回归梯度提升决策树GBDT二者结合 总结 推荐系统在技术实现上一般划分为三个阶段&#xff1a;挖掘、召回、排序 。 为什么要融合&#xff1f; 挖掘的工作是对用户和物品做非常深入的结构化分析&#xff0c;各个角度各个层面…

硬盘监控和分析工具:Smartctl

文章目录 1. 概述2. 安装3. 使用4. smartctl属性信息介绍 1. 概述 Smartctl&#xff08;S.M.A.R.T 自监控&#xff0c;分析和报告技术&#xff09;是类Unix系统下实施SMART任务命令行套件或工具&#xff0c;它用于打印SMART自检和错误日志&#xff0c;启用并禁用SMRAT自动检测…

PR模板 | RGB特效视频标题模板Titles | MOGRT

RGB特效视频标题模板mogrt免费下载 4K分辨率&#xff08;38402160&#xff09; 支持任何语言 友好的界面 输入和输出动画 快速渲染 视频教程 免费下载&#xff1a;https://prmuban.com/39055.html 更多pr模板视频素材下载地址&#xff1a;https://prmuban.com

初见:AntDB智能运维“三剑客“之ADC

引言 6月15日&#xff0c;PostgreSQL数据库技术峰会广州站圆满落幕。峰会上&#xff0c;亚信安慧数据库智能运维产品负责人李志龙介绍了AntDB的6大数据库引擎和3大工具产品能力。 这里的3大工具分别指&#xff1a; AntDB数据库迁移工具包 MTK 数据库智能运维平台 ACC AntDB数据…

Modbus协议在工业自动化中的应用

Modbus协议介绍 Modbus是一种常用的工业现场总线通信协议,被广泛应用于工业自动化领域。它是一种简单、易实现的主从式通信协议,具有高度的可靠性和通用性。本文将从Modbus协议的基本概念、通信模式、数据格式、常见应用场景等方面进行全面介绍,并通过图文并茂的方式帮助读者更…

ardupilot开发 --- 视觉伺服 篇

风驰电掣云端飘&#xff0c;相机无法对上焦 视觉伺服分类视觉伺服中的坐标系成像模型推导IBVS推导参考文献 视觉伺服分类 控制量是在图像空间中推导得到还是在欧式空间中推导得到&#xff0c;视觉伺服又可以分类为基于位置(PBVS)和基于图像的(IBVS)视觉伺服。 视觉伺服中的坐…

关于docker存储overlay2相关问题

报错如下&#xff1a; 报错原因&#xff1a;使用rm -rf 清理overlay2导致的&#xff0c;非正常清理。 正常清理命令如下&#xff1a; # 清理Docker的所有构建缓存 docker builder prune# 删除旧于24小时的所有构建缓存 docker builder prune --filter "until24h"#删…

node.js环境安装以及Vue-CLI脚手架搭建项目教程

目录 ▐ vue-cli 搭建项目的优点 ▐ 安装node.js环境 ▐ 搭建vue脚手架项目 ▐ 项目结构解读 ▐ 常用命令 ▐ 创建组件 ▐ 组件路由 ▐ vue-cli 搭建项目的优点 传统的前端项目架构由多个html文件&#xff0c;且每个html文件都是相互独立的&#xff0c;导入外部组件时需…

wireshark常用过滤命令

wireshark常用过滤命令 wireshark抓包介绍单机单点&#xff1a;单机多点&#xff1a;双机并行&#xff1a; wireshark界面认识默认布局调整布局(常用)显示FCS错误 wireshark常见列Time回包数据报对应网络模型 wireshark基本操作结束抓包再次开始抓包 **wireshark常用过滤命令**…

【实物资料包】基于STM32智能台灯设计

【实物资料包】基于STM32智能台灯设计 需要资料的请在文章结尾获取哦~~~~&#xff08;如有问题私信我即可&#xff09; 1.介绍 1 添加wifi模块模块&#xff0c;可通过wifi模块APP或者手动按钮切换自动/手动模式 2 自动模式下&#xff0c;台灯可以感应是否有人落座&#xff0…

【BSCP系列第2期】XSS攻击的深度剖析和利用(文末送书)

文章目录 前言一、官方地址二、开始&#xff08;15个&#xff09;1&#xff1a;Lab: DOM XSS in document.write sink using source location.search inside a select element2&#xff1a;Lab: DOM XSS in AngularJS expression with angle brackets and double quotes HTML-e…