# 《Seckill秒杀系统》第88章:业务网关整合Sentinel流控

作者:冰河
星球:http://m6z.cn/6aeFbs (opens new window)
博客:https://binghe.gitcode.host (opens new window)
文章汇总:https://binghe.gitcode.host/md/all/all.html (opens new window)
源码获取地址:https://t.zsxq.com/0dhvFs5oR (opens new window)

沉淀,成长,突破,帮助他人,成就自我。

  • 本章难度:★★☆☆☆
  • 本章重点:秒杀系统业务网关整合Sentinel流控,掌握业务网关整合Sentinel的流程,并能够将其灵活应用到自身实际项目中。

大家好,我是冰河~~

整合业务网关之后,客服端请求微服务时,就可以通过业务网关来统一转发请求了,也就是可以通过业务网关进行路由,将请求路由到正确的微服务上。并且后续可以在业务网关中实现鉴权、流控和风控等等一系列的功能。那业务网关可以整合Sentinel实现流控吗?

# 一、前言

在前面的文章中,秒杀系统整合了业务网关,并且对业务网关整合了Nacos,实现了业务网关对微服务的动态感知功能,后续只要有微服务注册到Nacos,业务网关都会动态感知到,并且会根据一定的负载均衡策略正确的将请求路由到对应的微服务,那可以业务网关可以整合Sentinel实现流控吗?改如何实现呢?

# 二、本章诉求

业务网关整合Sentinel实现流控,掌握业务网关整合Sentinel的流程,重点理解业务网关与Sentinel搭配实现流控的核心原理,并能够熟练应用到自身实际项目中。

# 三、整合Sentinel

Sentinel从1.6.0版本开始,提供了SpringCloud Gateway的适配模块,并且可以提供两种资源维度的限流,一种是route维度;另一种是自定义API分组维度。

  • route维度:对application.yml文件中配置的spring.cloud.gateway.routes.id限流,并且资源名为spring.cloud.gateway.routes.id对应的值。
  • 自定义API分组维度:利用Sentinel提供的API接口来自定义API分组,并且对这些API分组进行限流。

# 3.1 实现route维度限流

业务网关整合Sentinel实现route维度限流的步骤如下所示。

(1)添加Maven依赖

在seckill-gateway工程下的pom.xml文件中添加如下Maven依赖。

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
1
2
3
4

(2)修改SeckillGatewayConfig配置类

SeckillGatewayConfig类的源码详见:seckill-gateway工程下的io.binghe.seckill.gateway.config.SeckillGatewayConfig。修改后的源码如下所示。

@Configuration
@ComponentScan(value = {"io.binghe.seckill", "com.alibaba.cola"})
@ServletComponentScan(basePackages = {"io.binghe.seckill"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SeckillGatewayConfig {
    private final List<ViewResolver> viewResolvers;

    private final ServerCodecConfigurer serverCodecConfigurer;

    @Value("${spring.cloud.gateway.discovery.locator.route-id-prefix}")
    private String routeIdPrefix;

    public SeckillGatewayConfig(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }
    /**
     * 初始化一个限流的过滤器
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }
    @PostConstruct
    public void init() {
        this.initGatewayRules();
        this.initBlockHandlers();
    }
    /**
     * 配置初始化的限流参数
     */
    private void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        //用户微服务
        rules.add(this.getGatewayFlowRule(getResource("seckill-user")));
        //秒杀活动微服务
        rules.add(this.getGatewayFlowRule(getResource("seckill-activity")));
        //秒杀商品微服务
        rules.add(this.getGatewayFlowRule(getResource("seckill-goods")));
        //秒杀订单微服务
        rules.add(this.getGatewayFlowRule(getResource("seckill-order")));
        //库存微服务
        rules.add(this.getGatewayFlowRule(getResource("seckill-stock")));
        //预约微服务
        rules.add(this.getGatewayFlowRule(getResource("seckill-reservation")));
        //加载规则
        GatewayRuleManager.loadRules(rules);
    }

    private String getResource(String targetServiceName){
        if (routeIdPrefix == null){
            routeIdPrefix = "";
        }
        return routeIdPrefix.concat(targetServiceName);
    }

    private GatewayFlowRule getGatewayFlowRule(String resource){
        //传入资源名称生成GatewayFlowRule
        GatewayFlowRule gatewayFlowRule = new GatewayFlowRule(resource);
        //限流阈值
        gatewayFlowRule.setCount(1);
        //统计的时间窗口,单位为
        gatewayFlowRule.setIntervalSec(1);
        return gatewayFlowRule;
    }

    /**
     * 配置限流的异常处理器
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }
    /**
     * 自定义限流异常页面
     */
    private void initBlockHandlers() {
        BlockRequestHandler blockRequestHandler = (serverWebExchange, throwable)-> {
            Map<String, Object> map = new HashMap<>();
            map.put("code", 1001);
            map.put("codeMsg", "Sentinel-接口被限流了");
            return ServerResponse.status(HttpStatus.OK).
                    contentType(MediaType.APPLICATION_JSON_UTF8).
                    body(BodyInserters.fromObject(map));
        };
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

SeckillGatewayConfig类的源代码看上去比较多,但是都是一些非常简单的方法,冰河在这里就不再赘述了。

这里有个需要特别注意的地方:

Sentinel1.8.4整合SpringCloud Gateway使用的API类型为Route ID类型时,也就是基于route维度时,由于Sentinel为SpringCloud Gateway网关生成的API名称规则如下:

生成的规则为:${spring.cloud.gateway.discovery.locator.route-id-prefix}后面直接加上目标微服务的名称,如下所示。 ${spring.cloud.gateway.discovery.locator.route-id-prefix}目标微服务的名称。其中,${spring.cloud.gateway.discovery.locator.route-id-prefix}是在yml文件中配置的访问前缀。

为了让通过服务网关访问目标微服务链接后,请求链路中生成的API名称与流控规则中生成的API名称一致,以达到启动项目即可实现访问链接的限流效果,而无需登录Setinel管理界面手动配置限流规则,可以将生成GatewayFlowRule对象的resource参数设置为${spring.cloud.gateway.discovery.locator.route-id-prefix}目标微服务的名称

当然,如果不按照上述配置,也可以在项目启动后,通过服务网关访问目标微服务链接后,在Sentinel管理界面的请求链路中找到对应的API名称所代表的请求链路,然后手动配置限流规则。

# 查看完整文章

加入冰河技术 (opens new window)知识星球,解锁完整技术文章与完整代码