黑马商城微服务保护学习笔记

242 阅读12分钟

服务保护

最近看黑马微服务课程学习了微服务保护的相关原理,使用 sentinel 对微服务进行请求限流,线程隔离,服务熔断。

一、微服务保护的原理

1. 雪崩问题

雪崩问题在微服务架构中是指一个或多个微服务出现故障时,导致整个系统的不稳定甚至崩溃的现象。其产生原因及常见解决方案如下:

产生原因

  • 依赖关系复杂性:微服务之间存在复杂的依赖关系,一个服务故障可能导致依赖它的其他服务无法正常工作。
  • 大规模部署:大量服务实例运行时,部分实例出现问题可能影响整个系统。
  • 同时故障:硬件故障、网络问题或配置错误等原因可能导致多个服务同时故障。
  • 超时和重试:服务长时间未响应时,其他服务可能发起重试请求,增加负载,最终导致系统崩溃。
  • 资源耗尽:服务资源(如数据库连接、线程池)被过度消耗时,可能无法响应请求,引发雪崩。

常见解决方案

  • 超时处理:为每个微服务请求设置合理的超时时间,避免长时间等待。
  • 舱壁模式:通过隔离不同服务或组件,防止一个故障或高负载情况影响整个系统。
  • 限流:控制对服务的请求速率,确保不会超出服务的处理能力。
  • 熔断器模式:监控服务健康状态,当服务连续出现故障或响应时间超过阈值时,熔断器会打开,阻止进一步的请求流量流向该服务。
  • 降级策略:在服务不可用或性能下降时,提供有限但稳定的功能,而不是完全失败。

2. 本项目问题分析

在黑马商城项目中,多个微服务间存在相互调用。比如购物车服务需要调用商品服务,当商品服务调用失败,发生了阻塞,则购物车服务也无法继续执行。如果此时还有更多别的微服务(比如订单服务)也在调用购物车服务和商品服务,那么就会导致更多的服务发生阻塞甚至崩溃,导致出现系统服务群的雪崩现象。为了解决这一问题,需要用到微服务保护的方案,包括请求限流,线程隔离,服务熔断。

二、Sentinel 的使用入门

1.在微服务中引入 sentinel 核心库的依赖,并在 application.yaml 中进行配置。

在这里插入图片描述

在这里插入图片描述

2.下载 sentinel-dashboard 控制台的 jar 包,通过终端的 java 命令执行,启动 sentinel-dashboard 控制台。

将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控制台,由于还没有调用微服务故看不到监控信息。

在这里插入图片描述

在黑马商城调用购物车服务后,可以看到控制台对cart-service服务的流量监控。

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

4.在簇点资源后可以设置流控和熔断等。

在这里插入图片描述

三、请求限流

请求限流,即对服务的接口进行请求访问的流量限制,比如限制某服务的 QPS,防止由于访问量过高而导致服务故障。只需在 sentinel 控制台的簇点链路后面点击流控按钮,设置 QPS,即可对其做限流配置。

在这里插入图片描述

四、线程隔离

线程隔离,即将服务的接口的可用线程数进行限制,避免由于故障的线程数占满该服务拥有的线程数而导致服务故障。

1.OpenFeign 整合 Sentinel

在 application.yaml 文件中开启 OpenFeign 的 sentinel 功能。

在这里插入图片描述

2.在 sentinel 的控制台的簇点资源的流控设置中添加并发线程数的配置。

配置线程隔离,在 sentinel 控制台,点击查询商品的 FeignClient 对应的簇点资源后面的流控按钮,设置并发线程数。

在这里插入图片描述

在这里插入图片描述

五、服务熔断

1. Sentinel 服务熔断原理

服务熔断是指在检测到某个服务出现故障或响应时间过长时,自动中断对该服务的调用,以防止故障扩散到整个系统。

Sentinel 通过以下几种方式实现服务熔断:

  • 错误比例熔断:当某个服务的错误比例超过设定阈值时,触发熔断。
  • 响应时间熔断:当某个服务的平均响应时间超过设定阈值时,触发熔断。
  • 异常数熔断:当某个服务的异常数超过设定阈值时,触发熔断。

Sentinel 服务熔断实现步骤

  • 引入依赖:在项目的 pom.xml 文件中引入 Sentinel 相关依赖。
  • 配置规则:在 Sentinel 控制台中配置熔断规则,包括资源名、阈值类型、阈值等。
  • 监控和管理:通过 Sentinel 控制台实时监控服务的运行状态,并根据需要调整熔断规则。

2.编写 fallback 降级处理逻辑

一般选择可以对远程调用的异常做处理的 FallbackFactoryFeignClient 编写失败后的降级逻辑。编写 ItemClientFallbackFactory 类,在 DefaultFeignConfig 类中将 ItemClientFallbackFactory 注册为一个 bean,在 ItemClient 接口中使用 ItemClientFallbackFactory

ItemClientFallbackFactory 类如下:

package com.hmall.api.fallback;

import com.hmall.api.client.ItemClient;
import com.hmall.api.dto.ItemDTO;
import com.hmall.api.dto.OrderDetailDTO;
import com.hmall.common.utils.CollUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;

import java.util.Collection;
import java.util.List;


/**
* 这个类 ItemClientFallbackFactory 实现了 FallbackFactory<ItemClient> 接口,
* 用于创建 ItemClient 的回退逻辑。当 Feign 客户端调用失败时,
* 会使用这个回退工厂类生成回退逻辑,记录错误日志,并返回空列表或抛出异常。
* 通过使用 @Slf4j 注解,可以简化日志记录的过程。
*/
@Slf4j
public class ItemClientFallbackFactory implements FallbackFactory<ItemClient> {
    /*这是 ItemClientFallbackFactory 类的声明:
    @Slf4j:生成 SLF4J 日志记录器,简化日志记录。
    ItemClientFallbackFactory 类实现了 FallbackFactory<ItemClient> 接口,用于创建 ItemClient 的回退逻辑。*/

    @Override
    public ItemClient create(Throwable cause) {
        /*create 方法:重写 FallbackFactory 接口中的 create 方法,用于创建 ItemClient 的回退逻辑
        * Throwable cause:表示导致回退的异常。*/

        return new ItemClient() {
            /*return new ItemClient() { ... }:返回一个匿名内部类,实现 ItemClient 接口,并定义回退逻辑。*/

            @Override
            public List<ItemDTO> queryItemByIds(Collection<Long> ids) {
                /*queryItemByIds 方法:实现 ItemClient 接口中的 queryItemByIds 方法,查询商品信息。*/

                log.error("查询商品失败!",cause);
                /*log.error("查询商品失败!", cause):记录错误日志,输出查询商品失败的信息和异常原因。*/

                return CollUtils.emptyList();
                /*return CollUtils.emptyList():返回一个空的商品列表,作为回退逻辑。*/

            }

            @Override
            public void deductStock(List<OrderDetailDTO> items) {
                /*deductStock 方法:实现 ItemClient 接口中的 deductStock 方法,扣减商品库存。*/

                log.error("扣减商品库存失败!",cause);
                /*log.error("扣减商品库存失败!", cause):记录错误日志,输出扣减商品库存失败的信息和异常原因。*/

                throw new RuntimeException(cause);
                /*throw new RuntimeException(cause):抛出运行时异常,作为回退逻辑。*/

            }
        };
    }
}

DefaultFeignConfig 类如下:

package com.hmall.api.config;

import com.hmall.api.fallback.ItemClientFallbackFactory;
import com.hmall.common.utils.UserContext;
import feign.Logger;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;


/**
* 这个配置类 DefaultFeignConfig 主要用于配置 Feign 的日志级别和请求拦截器。
* 通过使用 @Bean 注解,可以将方法返回的对象注册为 Spring 容器中的 Bean,从而实现依赖注入和管理。
* 具体来说,它配置了 Feign 的日志级别为 FULL,记录所有请求和响应的详细信息,并配置了一个请求拦截器,在请求发送前将用户信息添加到请求头中。
* 此外,还配置了一个回退工厂,用于处理 Feign 客户端的回退逻辑。
*/
public class DefaultFeignConfig {
    // DefaultFeignConfig 类:这是一个配置类,用于配置 Feign 的日志级别和请求拦截器。


    /**
    *
    * 日志配置
    * OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。而且其日志级别有4级:
    * - NONE:不记录任何日志信息,这是默认值。
    * - BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
    * - HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
    * - FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
    * Feign默认的日志级别就是NONE,所以默认我们看不到请求日志。
    *
    * @return
    */
    @Bean
    // 使用 @Bean 注解标记方法,表示该方法返回的对象会被 Spring 容器管理

    public Logger.Level feignLoggerLevel() {

        return Logger.Level.FULL;
        // 返回 Feign 的 Logger 级别为 FULL,表示记录所有请求和响应的详细信息

    }





    /*
    网关登录校验
    1.网关中的AuthGlobalFilter过滤器进行JWT登录校验,获取token中的用户信息,并将用户信息保存到请求头,以便转发给微服务。
    2.微服务中的UserInfoInterceptor拦截器取出来自网关传来的token中的用户信息,并保存在ThreadLocal中,以便微服务调用处理业务。
    3.对于复杂的微服务间相互调用,用OpenFeign的userInfoRequestInterceptor拦截器将用户信息保存到请求头。
    */

    @Bean
    /*@Bean 注解:Spring 注解,用于将方法的返回值注册为 Spring 容器中的一个 Bean。*/
    public RequestInterceptor userInfoRequestInterceptor() {
        /*userInfoRequestInterceptor 方法:配置一个请求拦截器,在请求发送前将用户信息添加到请求头中。*/

        return new RequestInterceptor() {
            /*RequestInterceptor:Feign 提供的接口,用于拦截和修改请求。*/
            @Override
            public void apply(RequestTemplate template) {
                /*apply 方法:重写 RequestInterceptor 接口中的 apply 方法,用于在请求发送前修改请求模板。
                RequestTemplate template:表示请求模板对象。*/

                Long userId = UserContext.getUser();
                /*UserContext.getUser():调用 UserContext 类的 getUser 方法,获取当前用户 ID。*/

                if( userId != null ) {


                    template.header("user-info", userId.toString());
                    /*template.header("user-info", userId.toString()):如果用户 ID 不为空,将其添加到请求头中。*/

                }


            }
        };



    }




    @Bean
    /*@Bean 注解:表示该方法返回的对象会被 Spring 容器管理。*/
    public ItemClientFallbackFactory itemClientFallbackFactory() {
        /*itemClientFallbackFactory 方法:返回一个 ItemClientFallbackFactory 实例,用于处理 Feign 客户端的回退逻辑。*/

        return new ItemClientFallbackFactory();



    }


}

ItemClient 接口如下:

package com.hmall.api.client;



import com.hmall.api.dto.ItemDTO;

import com.hmall.api.dto.OrderDetailDTO;
import com.hmall.api.fallback.ItemClientFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

import java.util.Collection;
import java.util.List;




/*
1. @FeignClient
原理:@FeignClient 是 Spring Cloud 提供的一个注解,用于声明一个 Feign 客户端接口。通过这个注解,Spring 会自动生成该接口的实现类,并将其注册为 Spring 容器中的一个 Bean。
作用:@FeignClient 注解用于定义一个 Feign 客户端,指定要调用的微服务名称和回退逻辑。
2. value = "item-service"
原理:value 属性用于指定要调用的微服务名称。在这个例子中,item-service 是要调用的微服务的名称。
作用:value = "item-service" 表示这个 Feign 客户端将调用名为 item-service 的微服务。
3. fallbackFactory = ItemClientFallbackFactory.class
原理:fallbackFactory 属性用于指定一个回退工厂类,当 Feign 客户端调用失败时,会使用这个回退工厂类生成回退逻辑。
作用:fallbackFactory = ItemClientFallbackFactory.class 表示当调用 item-service 微服务失败时,将使用 ItemClientFallbackFactory 类生成回退逻辑。
使用场景
微服务通信:在微服务架构中,使用 Feign 客户端可以简化微服务之间的通信。通过 @FeignClient 注解,可以方便地定义和调用其他微服务的接口。
容错处理:通过 fallbackFactory 属性,可以为 Feign 客户端配置回退逻辑,当调用失败时,自动执行回退逻辑,增强系统的容错能力。
*/
@FeignClient(value = "item-service",fallbackFactory = ItemClientFallbackFactory.class)
public interface ItemClient {
    // 注意是接口interface类型,写错好几次了

    @GetMapping("/items")
    // 声明请求路径
    List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);

    /*这里只需要声明接口,无需实现方法。接口中的几个关键信息:
    - @FeignClient("item-service") :声明服务名称
    - @GetMapping :声明请求方式
    - @GetMapping("/items") :声明请求路径
    - @RequestParam("ids") Collection<Long> ids :声明请求参数
    - List<ItemDTO> :返回值类型

    有了上述信息,OpenFeign就可以利用动态代理帮我们实现这个方法,并且向http://item-service/items发送一个GET请求,携带ids为请求参数,并自动将返回值处理为List<ItemDTO>。
    我们只需要直接调用这个方法,即可实现远程调用了。*/



    @PutMapping("/items/stock/deduct")
    // 声明请求路径和请求方式
    void deductStock(@RequestBody List<OrderDetailDTO> items);
    // 在ItemClient中声明该接口,以便别的微服务模块使用itemClient来调用item-service中的函数

}

3. 在 sentinel 控制台设置熔断

对于商品服务这种不太健康的接口,我们应该停止调用,直接走降级逻辑,避免影响到当前服务。也就是将商品查询接口熔断。当商品服务接口恢复正常后,再允许调用。这其实就是断路器的工作模式了。

Sentinel 中的断路器不仅可以统计某个接口的慢请求比例,还可以统计异常请求比例。当这些比例超出阈值时,就会熔断该接口,即拦截访问该接口的一切请求,降级处理;当该接口恢复正常时,再放行对于该接口的请求。

在 sentinel 控制台通过点击簇点后的熔断按钮来配置熔断策略.

在这里插入图片描述

在这里插入图片描述

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

  • RT超过200毫秒的请求调用就是慢调用
  • 统计最近1000ms内的最少5次请求,如果慢调用比例不低于0.5,则触发熔断
  • 熔断持续时长20s

六、微服务保护学习总结

  1. 在微服务中整合了 sentinel 核心库,并配置连接上用 jar 包单独启动的 sentinel-dashboard 控制台。
  2. 请求限流:在 sentinel 控制台的簇点链路后面点击流控按钮,设置 QPS,即可对其做限流配置。
  3. 线程隔离:需要在 yaml 配置中将 OpenFeign 整合 Sentinel。配置线程隔离,在 sentinel 控制台,点击查询商品的 FeignClient 对应的簇点资源后面的流控按钮,设置并发线程数。
  4. 服务熔断:使用可以对远程调用的异常做处理的 FallbackFactory,给 FeignClient 编写失败后的降级逻辑。在控制台通过点击簇点后的熔断按钮来配置熔断策略。