Sentinel 对分布式服务进行流量控制

可以下载sentinel的jar包,用java -jar命令直接启动

 默认端口就是8080,这里随便写一下演示,其他修改还是直接看Sentinel网站吧

java -jar -Dserver.port=8080 sentinel的jar包名.jar

 

对需要进行流量控制的服务进行依赖导入(这个依赖直接在父工程引入似乎无效,不知为何)

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

配置application.yml

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8088 #sentinel控制台地址

 

提示:可以用jmeter模拟请求测试。

流控模式

我们可以直接在sentinel的控制台对每个簇点链路添加限流规则,sentinel有三种流控模式可以选择:

1、直接:统计当前资源的请求,出发阈值时对当前资源直接限流,默认是该模式

2、关联:统计与当前资源相关的另一个资源,当关联资源触发阈值时,对当前资源限流

3、链路:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流(也就是指定某个方法每秒调用本方法的次数,如果超过阈值,就限制那个方法的调用请求)

Sentinel默认只标记Controller中的方法为资源,如果想标记其他方法,需要在方法上加上@SentinelResource("取一个资源名")注解

另外,Sentinel默认会将Controller方法做context整合,这会导致所有Conrtoller方法都为同一个父链路的子链路,这样一来链路模式将会失效,所以我们需要给application.yml配置文件添加配置来取消整合

spring:
  cloud:
    sentinel:
      web-context-unify: false

 

流控效果

1、快速失败:QPS(每秒请求数)超过阈值时,拒绝新的请求

2、warm up:QPS超过阈值时,拒绝新的请求;QPS阈值是逐渐提升的,可以避免冷启动时的高并发导致服务宕机

3、排队等待:所有请求都会进入队列,然后按照设定好的阈值依次执行请求。如果请求预期等待时间大于超时时间,则直接拒绝

 

热点参数限流

有时候,对于同一个资源,我们不能对所有请求采取同样的阈值限流。可能因为参数值的不同,我们也需要设定不同的阈值。

例如商品的查询功能,对不同商品,热门商品的阈值理论上应该比普通商品更高。所以我们需要热点参数限流。

注意:热点参数限流对默认的SpringMVC资源无效。用人话来说就是,类似于/order/{orderId}这种自动从Controller中识别的资源名无法设置热点参数限流,我们需要在Controller方法中手动添加@SentinelResource("取一个资源名")注解,使用这种手动添加的资源名才能对该方法进行热点参数限流

 

线程隔离和熔断降级

线程隔离:服务A需要去调用服务B和服务C,那么服务A会给调用服务B和服务C的请求专门划分固定的线程数,这样假如服务B故障了,我们可以只损耗留给服务B的线程,而不会影响服务C。

熔断降级:当服务A去访问服务B时,统计失败次数(如何算失败看具体怎么设定),如果失败比例过高,则后续会直接拒绝服务B的访问。

 

简单使用Feign

我们在SpringCloud中,使用的是Feigh进行服务之间的调用,而且Feigh支持Sentinel功能。所以我们只需要将Feigh和Sentinel进行整合即可。

此处我们将Feigh单独做了一个子工程,然后让其他项目调用他。

先对使用Feigh的项目的application.yml进行配置

feign:
  sentinel:
    enabled: true #开启feigh对sentinel的支持

 

接下来对Feigh项目模块做配置

实现FallbackFactory接口,UserClients为书写远程调用的接口

用匿名内部类实现UserClients接口,书写我们的降级方法,我们采用直接返回一个空的对象

@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClients> {
    @Override
    public UserClients create(Throwable throwable) {
        return new UserClients() {
            @Override
            public User findById(Long id) {
                log.error("查询用户异常",throwable);
                return new User();
            }
        };
    }
}

将实现好的接口类注册为Bean

public class DefaultFeignConfiguration {
    @Bean
    public UserClientFallbackFactory userClientFallbackFactory(){
        return new UserClientFallbackFactory();
    }

}

//已经在使用Feign模块的项目的启动类中配置了该类为默认配置类
//@EnableFeignClients(clients = UserClients.class,defaultConfiguration = DefaultFeignConfiguration.class)

在接口中使用UserClientFallbackFactory

@FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClients {

    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

线程隔离

接下来就能在Sentinel控制台看见GET:http://userservice/user/{id}这种资源名

然后在Sentinel中对该资源进行流量控制,流控选择线程数就可设定为线程隔离

熔断降级

直接在Sentinel控制台中的簇点链路中点击降级操作设置即可

熔断策略有三种:

1、慢调用比例。每次请求超过指定时长则视为慢调用,统计时长内慢调用比例超过限定值,熔断

2、异常比例。统计时长内异常比例超过限定值,熔断

3、异常数。统计时长内异常调用次数超过限定值,熔断

 

授权规则

为了服务的安全,我们一般会设置网关服务,由网关对请求进行处理,筛选,然后再向对应服务发起请求。但网关并不能限制用户直接去访问服务的IP地址,如果服务的IP一旦泄露,网关的作用就消失了。所以,为了确保所有请求一定要经过网关,将未经过网关的请求全部拒绝访问,我们可以使用Sentinel的授权规则。

 

我们需要在进行服务的项目中,实现RequestOriginParser接口。这样当有请求访问该服务时,该方法会向Sentinel传入一个参数,根据该参数是否 处于白名单/不处于黑名单 来判断是否允许访问该服务

@Component
public class HeaderOriginParser implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest httpServletRequest) {
        String origin = httpServletRequest.getHeader("origin");
        if (StringUtils.isEmpty(origin)){
            origin="empty";
        }
        return origin;
    }
}

可以在网关的application.yml添加如下配置,将网关的所有路由都添加默认的请求头,可以看Gateway实现统一网关

我们此处添加了Key为origin,Value为gateway的请求头。上述实现的RequestOriginParser接口中获取了Value,由于Value值是上述图中的白名单,所以网关发出的请求通过授权。

spring:
  cloud:
    gateway:
      default-filters:
        - AddRequestHeader=origin,gateway

 

自定义异常结果

当我们使用Sentinel进行流量控制的时候,如果Sentinel拒绝了访问,返回的消息都是一样的,这样对前端非常不友好,我们可以实现BlockExceptionHandler接口,来辨别异常信息,并对异常进行处理

@Component
public class SentinelExceptionHeadler implements BlockExceptionHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        String msg = "未知异常";
        int status = 429;

        if (e instanceof FlowException){
            msg = "请求被限流了";
        } else if (e instanceof ParamFlowException) {
            msg = "请求被热点参数限流";
        } else if (e instanceof DegradeException) {
            msg = "请求被降级了";
        } else if (e instanceof AuthorityException) {
            msg = "没有访问权限";
            status = 401;
        }
        response.setContentType("application/json;charset=utf-8");
        response.setStatus(status);
        response.getWriter().println("msg:"+msg+",status:"+status);

    }
}

 

规则持久化

Sentinel的规则默认是存在内存中的,所以每次重启服务,都会导致规则消失。所以我们需要对规则进行持久化。最好的是将持久化规则写在nacos等微服务的远程配置中心里,不过这需要去修改Sentinel的源码,具体网上查吧。

posted @ 2024-03-29 11:01  凌碎瞳缘  阅读(14)  评论(0编辑  收藏  举报