想要年薪百万,阿里Sentinel支持RESTful接口都搞不定?

简介: 在Spring MVC或者Spring Boot中的RESTful接口中,有大量的@PathVariable注解,也就是把参数放在URL里。但是在Sentinel中把每一次请求的URL作为唯一的资源名,进行匹配和流量控制的,这就造成了一个接口是一个资源却被当作多个资源看待,无法达到流量控制的目的。

最近正准备用阿里Sentinel,发现RESTful接口支持的不是很好。有些童鞋可能对Sentinel不是很了解,我们先简单介绍一下。

Sentinel简介

Sentinel是一套阿里巴巴开源的流量防卫框架,Github地址是:https://github.com/alibaba/Sentinel。随着微服务的流行,服务与服务之间的稳定性越来越重要。Sentinel以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

更多介绍可以在Github文档中了解。

问题描述

在Spring MVC或者Spring Boot中的RESTful接口中,有大量的@PathVariable注解,也就是把参数放在URL里,比如:

@RestController
public class DemoController {
    @GetMapping(value = "/hello/{name}")
    public String helloWithName(@PathVariable String name) {
        return "Hello, " + name;
    }
}

但是在Sentinel中把每一次请求的URL作为唯一的资源名,进行匹配和流量控制的,这就造成了一个接口本应是一个资源却被当作多个资源看待,无法达到流量控制的目的。

白嫖小贴士:什么是资源?只要通过 Sentinel API 包围起来的代码,就是资源,能够被 Sentinel 保护起来。例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。每一个资源都有自己唯一的资源名,用于标识这个资源。

查找问题原因

问题的根本原因就在于:Sentinel是如何把每一次请求URL作为唯一的资源名的?阅读和调试Sentinel的源码后,我找到CommonFilterdoFilter方法,以下是主要代码:

//调用filterTarget方法获取当前请求的URL
String target = FilterUtil.filterTarget(sRequest);
UrlCleaner urlCleaner = WebCallbackManager.getUrlCleaner();
if (urlCleaner != null) {
    target = urlCleaner.clean(target);
}
if (!StringUtil.isEmpty(target)) {
    String origin = parseOrigin(sRequest);
    String contextName = webContextUnify ? WebServletConfig.WEB_SERVLET_CONTEXT_NAME : target;
    ContextUtil.enter(contextName, origin);

    if (httpMethodSpecify) {
        //如果配置加HTTP方法名做前缀,URL前加HTTP方法名后作为资源名。
        String pathWithHttpMethod = sRequest.getMethod().toUpperCase() + COLON + target;
        urlEntry = SphU.entry(pathWithHttpMethod, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
    } else {
        //如果不加HTTP方法名做前缀,就直接使用URL作为资源名。
        urlEntry = SphU.entry(target, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
    }
}

在上面的代码中,我们看见了请求URL作为资源名的整个过程,同时也发现有一个UrlCleaner接口,请求URL会经过它的clean方法进行处理。我们就在这个UrlCleaner接口上做文章了。

解决方案

RestfulPattern

首先我们先创建一个类,用来存放URL和对应的正则表达式:

package onemore.study.sentineldemo;

import java.util.regex.Pattern;

/**
 * @author 万猫学社
 */
public class RestfulPattern implements Comparable<RestfulPattern> {
    private Pattern pattern;
    private String realResource;

    public RestfulPattern(Pattern pattern, String realResource) {
        this.pattern = pattern;
        this.realResource = realResource;
    }

    public Pattern getPattern() {
        return pattern;
    }

    public String getRealResource() {
        return realResource;
    }

    @Override
    public int compareTo(RestfulPattern o) {
        return o.getPattern().pattern().compareTo(this.getPattern().pattern());
    }
}

RestfulUrlCleaner

再写一个实现UrlCleaner接口的类,在clean方法中写自己的逻辑:

package onemore.study.sentineldemo;

import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author 万猫学社
 */
public class RestfulUrlCleaner implements UrlCleaner {

    private List<RestfulPattern> patterns = new ArrayList<>();

    private RestfulUrlCleaner() {
    }

    /**
     * 根据流量控制规则创建与之匹配的RestfulUrlCleaner
     * @param rules 流量控制规则
     * @return RestfulUrlCleaner
     */
    public static RestfulUrlCleaner create(List<FlowRule> rules) {
        RestfulUrlCleaner cleaner = new RestfulUrlCleaner();
        if (rules == null || rules.size() == 0) {
            return cleaner;
        }
        Pattern p = Pattern.compile("\\{[^\\}]+\\}");
        for (FlowRule rule : rules) {
            Matcher m = p.matcher(rule.getResource());
            //如果发现类似{xxx}的结构,断定其为RESTful接口
            if (m.find()) {
                cleaner.patterns.add(
                        new RestfulPattern(Pattern.compile(m.replaceAll("\\\\S+?")), rule.getResource()));
            }
        }
        //根据正则表达式重新排序
        Collections.sort(cleaner.patterns);
        return cleaner;
    }

    @Override
    public String clean(String originUrl) {
        for (RestfulPattern pattern : patterns) {
            if (pattern.getPattern().matcher(originUrl).matches()) {
                return pattern.getRealResource();
            }
        }
        return originUrl;
    }
}

单元测试

为了验证代码的正确性,我们再写一下单元测试:

package onemore.study.sentineldemo;

import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import org.junit.Assert;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 万猫学社
 */
public class RestfulUrlCleanerTest {

    @Test
    public void test(){
        List<FlowRule> rules = new ArrayList<>();
        rules.add(new FlowRule("/hello"));
        rules.add(new FlowRule("/hello/{name}"));
        rules.add(new FlowRule("/hello/{firstName}/{lastName}"));
        rules.add(new FlowRule("/hello/{firstName}/and/{lastName}"));
        RestfulUrlCleaner cleaner = RestfulUrlCleaner.create(rules);

        Assert.assertEquals("/hello", cleaner.clean("/hello"));
        Assert.assertEquals("/hello/{name}", cleaner.clean("/hello/onemore"));
        Assert.assertEquals("/hello/{firstName}/{lastName}", cleaner.clean("/hello/onemore/study"));
        Assert.assertEquals("/hello/{firstName}/and/{lastName}", cleaner.clean("/hello/onemore/and/study"));
    }
}

运行一下单元测试,发现没有错误。

设置UrlCleaner

在实际开发中,流量控制规则可能配置在Redis、ZooKeeper或者 Apollo中。无论在哪里,流量控制规则每次发生变更时都要重新设置UrlCleaner。我们就以硬编码流量控制规则为例:

package onemore.study.sentineldemo;

import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

@Configuration
public class DemoConfiguration {
    @PostConstruct
    public void initRules() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource("/hello/{name}");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //设置QPS限流阈值为1
        rule.setCount(1);
        rules.add(rule);

        WebCallbackManager.setUrlCleaner(RestfulUrlCleaner.create(rules));
        FlowRuleManager.loadRules(rules);
    }
}

至此,RESTful接口多资源的问题被完美解决。

相关文章
|
6月前
|
XML 物联网 API
服务端和客户端 RESTful 接口上传 Excel 的 Python 代码
本文作者木头左是物联网工程师,分享如何使用 Python 和 Flask-RESTful 构建一个简单的 RESTful API,实现文件上传功能,特别支持Excel文件。通过安装Flask和Flask-RESTful库,创建Flask应用,实现文件上传接口,并将其添加到API。该方法具有简单易用、灵活、可扩展及社区支持等优点。
服务端和客户端 RESTful 接口上传 Excel 的 Python 代码
|
3月前
|
安全 API 开发者
Web 开发新风尚!Python RESTful API 设计与实现,让你的接口更懂开发者心!
在当前的Web开发中,Python因能构建高效简洁的RESTful API而备受青睐,大大提升了开发效率和用户体验。本文将介绍RESTful API的基本原则及其在Python中的实现方法。以Flask为例,演示了如何通过不同的HTTP方法(如GET、POST、PUT、DELETE)来创建、读取、更新和删除用户信息。此示例还包括了基本的路由设置及操作,为开发者提供了清晰的API交互指南。
115 6
|
4月前
|
XML JSON API
RESTful API设计最佳实践:构建高效、可扩展的接口
【8月更文挑战第17天】RESTful API设计是一个涉及多方面因素的复杂过程。通过遵循上述最佳实践,开发者可以构建出更加高效、可扩展、易于维护的API。然而,值得注意的是,最佳实践并非一成不变,随着技术的发展和业务需求的变化,可能需要不断调整和优化API设计。因此,保持对新技术和最佳实践的关注,是成为一名优秀API设计师的关键。
|
5月前
|
API 数据安全/隐私保护 开发者
Web 开发新风尚!Python RESTful API 设计与实现,让你的接口更懂开发者心!
【7月更文挑战第23天】Python的RESTful API设计在Web开发中流行,提升效率与体验。REST强调HTTP方法(GET, POST, PUT, DELETE)操作资源。使用Flask框架可快速实现API,如管理用户信息。示例代码展示如何创建、读取、更新和删除用户,通过不同HTTP方法和URL路径。实际应用中,增加验证、错误处理和权限控制可增强API的安全性和稳定性。安装Flask后,可运行代码测试API功能。
68 6
|
5月前
|
安全 API 网络架构
Python RESTful API设计新篇章,打造高效、易用、安全的Web服务接口,你准备好了吗?
【7月更文挑战第22天】在数字化转型中,RESTful API借助Python的Flask和Django REST Framework,提供高效、易用和安全的接口设计。Flask示例展示了简洁的CRUD操作,Swagger等工具增进API文档的易用性,而HTTPS、JWT和输入验证确保安全性。Python RESTful API设计涉及效率、可用性和安全,是不断进化的Web服务接口的关键。准备好踏上这一新篇章了吗?一起探索,创造卓越!
69 2
|
6月前
|
存储 前端开发 安全
Nuxt3 实战 (十):使用 Supabase 实现 RESTful 风格 API 接口
这篇文章介绍了如何使用Supabase实现RESTful风格的API接口,用于网站分类和子站点的增删改查(CURD)功能。文章首先阐述了表设计,包括ds_categorys和ds_websites两张表的列名、类型和用途,并提到了为每张表添加的user_id和email字段以支持用户身份识别。接着,文章描述了接口设计,以ds_websites表为例,说明了如何通过RESTful API实现CURD功能,并给出了使用SupabaseClient实现插入数据的相关代码。文章最后提供了项目效果预览和总结,指出学习了Nuxt3创建接口及调用Supabase数据库操作。
122 2
Nuxt3 实战 (十):使用 Supabase 实现 RESTful 风格 API 接口
|
6月前
|
XML 安全 API
API攻防-接口安全&SOAP&OpenAPI&RESTful&分类特征导入&项目联动检测
API攻防-接口安全&SOAP&OpenAPI&RESTful&分类特征导入&项目联动检测
105 5
|
5月前
|
JSON 数据格式
MysbatisPlus-核心功能-IService开发基础业务接口,MysbatisPlus_Restful风格,新增@RequestBody指定是为了接收Json数据的,使用swagger必须注解
MysbatisPlus-核心功能-IService开发基础业务接口,MysbatisPlus_Restful风格,新增@RequestBody指定是为了接收Json数据的,使用swagger必须注解
|
6月前
|
存储 API
什么是RESTful接口风格
什么是RESTful接口风格
281 0
|
6月前
使用Jetty编写RESTful接口
使用Jetty编写RESTful接口
下一篇
DataWorks