spring cloud gateway集成sentinel并扩展支持restful api进行url粒度的流量治理

news2024/12/23 3:57:04

sentinel集成网关支持restful接口进行url粒度的流量治理

  • 前言
  • 使用网关进行总体流量治理(sentinel版本:1.8.6)
    • 1、cloud gateway添加依赖:
    • 2、sentinel配置
    • 3、网关类型项目配置
    • 4、通过zk事件监听刷新上报api分组信息
      • 1、非网关项目上报api分组信息
      • 2、网关添加监听事件
      • 3、网关监听事件处理
    • 5、sentinel控制台启动

前言

sentinel作为开源的微服务、流量治理组件,在对restful接口的支持上,在1.7之后才开始友好起来,对于带有@PathVariable的restful接口未作支持,在sentinel中/api/{id}这样的接口,其中/api/1与/api/2会被当做两个不同的接口处理,因此很难去做类似接口的流量治理,但在之后,sentinel团队已经提供了响应的csp扩展依赖,下文将会逐步讲述如何通过sentinel扩展来支持相应的服务流量治理

使用网关进行总体流量治理(sentinel版本:1.8.6)

这里选型为spring cloud gateway,而sentinel也对spring cloud gateway做了特殊照顾

1、cloud gateway添加依赖:

 <!-- alibaba封装的sentinel的starter -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>2021.1</version>
        </dependency>
        <!-- alibaba封装的sentinel支持网关的starter -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
            <version>2021.1</version>
        </dependency>
 <!-- 此包即为sentinel提供的扩展支持restful接口的依赖 -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-spring-webmvc-adapter</artifactId>
            <version>1.8.0</version>
        </dependency>

上述需要重点关注的是sentinel-spring-webmvc-adapter包,此依赖是支持restful接口的关键,不需要我们自己改造。

2、sentinel配置

spring:
  cloud:
    sentinel:
      transport:
      #sentinel控制台地址
        dashboard: 1.1.1.1:8600
        #sentinel通信端口,默认为8179,被占用会继续扫描,一般固定起来
        port: 8700
        #项目所在服务器ip
        client-ip: 2.2.2.2
        #心跳启动
      eager: true

client-ip在某些情况下不配置会出现sentinl控制台页面只有首页,服务一直注册不上去的情况,如果出现这种情况一定要配置上,如果没有这种情况,client-IP可以不配置,同时上述配置的这些ip端口都需要连通。

3、网关类型项目配置

/**
 * @classDesc:
 * @author: cyjer
 * @date: 2023/1/30 9:53
 */
@SpringBootApplication
@EnableCaching
@Slf4j
public class SiriusApplication {

    public static void main(String[] args) {
        System.getProperties().setProperty("csp.sentinel.app.type", "1");
        SpringApplication.run(SiriusApplication.class, args);
        log.info("<<<<<<<<<<启动成功>>>>>>>>>>");
    }

}

如果是网关类型的项目,需要配置csp.sentinel.app.type= 1,普通项目与网关项目,在控制台上所展示和可使用的功能是不同的

4、通过zk事件监听刷新上报api分组信息

通过将接口分组按照不同粒度,如controller粒度,和具体api接口粒度,通过zookeeper修改数据监听的方式,通过网关监听该事件,实现将api分组信息写入到sentinel中。

1、非网关项目上报api分组信息

/**
 * @classDesc: 扫描项目接口上报api
 * @author: cyjer
 * @date: 2023/2/10 13:46
 */
@Configuration
@Slf4j
@Order(1)
@RequiredArgsConstructor
public class ApiDefinitionReporter implements BeanPostProcessor, CommandLineRunner, Constraint {
    private final List<ApiSiriusDefinition> apiSiriusDefinitionList = new ArrayList<>();
    private final GatewayServiceProperties gatewayServiceProperties;
    private final Environment environment;
    private final static char JTR = '/';
    private final static String PASS = "/**";
    private final static String APPLICATION_NAME = "spring.application.name";
    private final static String CONTEXT_PATH = "server.servlet.context-path";
    private final static List<String> PASS_LIST = Arrays.asList("swaggerWelcome", "basicErrorController", "swaggerConfigResource", "openApiResource");
    private final ZookeeperService zookeeperService;

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // url访问路径为:访问基地址basePath+classMappingPath+methodPath
        if (!gatewayServiceProperties.isAutoReportAndRegister() || PASS_LIST.contains(beanName)) {
            return bean;
        }
        Class<?> beanClass = bean.getClass();
        Class<?> targetClass = AopUtils.getTargetClass(bean);
        //判断类上有无controller注解 spring代理类需用spring的注解扫描工具类查找
        RestController restController = AnnotationUtils.findAnnotation(beanClass, RestController.class);
        Controller controller = AnnotationUtils.findAnnotation(beanClass, Controller.class);
        //没有注解直接跳过扫描
        if (null == controller && null == restController) {
            return bean;
        }
        String applicationName = this.getApplicationName();
        //项目访问基地址
        String basePath = this.getBasePath();

        //如果类上有controller注解再查找requestMapping注解
        RequestMapping requestMapping = AnnotationUtils.findAnnotation(beanClass, RequestMapping.class);
        String classMappingPath = this.getClassMappingPath(requestMapping);

        //按照controller分组上报
        if (StringUtils.isNotBlank(classMappingPath)) {
            String controllerGroupPath = basePath + classMappingPath + PASS;
            ApiSiriusDefinition controllerGroup = new ApiSiriusDefinition();
            controllerGroup.setGatewayId(gatewayServiceProperties.getGatewayId());
            controllerGroup.setResource("服务:" + applicationName + ",控制器:" + targetClass.getSimpleName() + ",路径:" + controllerGroupPath);
            controllerGroup.setUrlPath(controllerGroupPath);
            apiSiriusDefinitionList.add(controllerGroup);
        }

        //查找类中所有方法,进行遍历
        Method[] methods = targetClass.getMethods();
        for (Method method : methods) {
            //查找方法上RequestMapping注解
            String methodPath = "";
            String requestType = "";
            RequestMapping methodRequestMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
            if (methodRequestMapping != null) {
                String[] value = methodRequestMapping.value();
                RequestMethod[] requestMethods = methodRequestMapping.method();
                if (value.length == 0) {
                    if (requestMethods.length == 0) {
                        return bean;
                    }
                    RequestMethod requestMethod = requestMethods[0];
                    requestType = requestMethod.name();
                    if (requestMethod.equals(RequestMethod.POST)) {
                        PostMapping postMapping = AnnotationUtils.findAnnotation(method, PostMapping.class);
                        methodPath = this.joinMethodPath(postMapping.value());
                    } else if (requestMethod.equals(RequestMethod.GET)) {
                        GetMapping getMapping = AnnotationUtils.findAnnotation(method, GetMapping.class);
                        methodPath = this.joinMethodPath(getMapping.value());
                    } else if (requestMethod.equals(RequestMethod.DELETE)) {
                        DeleteMapping deleteMapping = AnnotationUtils.findAnnotation(method, DeleteMapping.class);
                        methodPath = this.joinMethodPath(deleteMapping.value());
                    } else if (requestMethod.equals(RequestMethod.PATCH)) {
                        PatchMapping patchMapping = AnnotationUtils.findAnnotation(method, PatchMapping.class);
                        methodPath = this.joinMethodPath(patchMapping.value());
                    } else if (requestMethod.equals(RequestMethod.PUT)) {
                        PutMapping putMapping = AnnotationUtils.findAnnotation(method, PutMapping.class);
                        methodPath = this.joinMethodPath(putMapping.value());
                    }
                }

                ApiSiriusDefinition apiSiriusDefinition = new ApiSiriusDefinition();
                String urlPath = basePath + classMappingPath + methodPath;
                apiSiriusDefinition.setUrlPath(urlPath);
                apiSiriusDefinition.setRequestType(requestType);
                apiSiriusDefinition.setGatewayId(gatewayServiceProperties.getGatewayId());
                apiSiriusDefinition.setResource("服务:" + applicationName + ",请求类型:" + requestType + ",路径:" + urlPath);
                apiSiriusDefinitionList.add(apiSiriusDefinition);
            }

        }
        return bean;
    }

    private String joinMethodPath(String[] value) {
        if (value.length != 0) {
            String str = this.trimStrWith(value[0], JTR);
            return JTR + str;
        }
        return "";
    }

    private String getContextPath() {
        String contextPath = environment.getProperty(CONTEXT_PATH);
        contextPath = this.trimStrWith(contextPath, JTR);
        return StringUtils.isBlank(contextPath) ? "" : contextPath;
    }

    public String getApplicationName() {
        String applicationName = environment.getProperty(APPLICATION_NAME);
        applicationName = this.trimStrWith(applicationName, JTR);
        return StringUtils.isBlank(applicationName) ? "" : applicationName;
    }

    private String getBasePath() {
        String contextPath = this.getContextPath();
        String applicationName = this.getApplicationName();
        if (StringUtils.isBlank(contextPath)) {
            return JTR + applicationName;
        }
        return JTR + applicationName + JTR + contextPath;
    }

    private String getClassMappingPath(RequestMapping requestMapping) {
        if (null != requestMapping) {
            String requestMappingUrl = requestMapping.value().length == 0 ? "" : requestMapping.value()[0];
            requestMappingUrl = this.trimStrWith(requestMappingUrl, JTR);
            return JTR + requestMappingUrl;
        }
        return "";
    }

    public String trimStrWith(String str, char trimStr) {
        if (StringUtils.isBlank(str)) {
            return str;
        }
        int st = 0;
        int len = str.length();
        char[] val = str.toCharArray();
        while ((st < len) && (val[st] <= trimStr)) {
            st++;
        }
        while ((st < len) && (val[len - 1] <= trimStr)) {
            len--;
        }
        return ((st > 0) || (len < str.length())) ? str.substring(st, len) : str;
    }

    @Override
    public void run(String... args) {
        if (StringUtils.isBlank(this.getApplicationName())) {
            throw new RuntimeException(APPLICATION_NAME + " should not be null");
        }
        if (!apiSiriusDefinitionList.isEmpty()) {
            log.info("<<<<< start to report api information to api governance platform >>>>>");
            try {
                zookeeperService.create(API_DEFINITION + SPLIT + getApplicationName(), JSONArray.toJSONString(apiSiriusDefinitionList));
                zookeeperService.update(API_DEFINITION + SPLIT + getApplicationName(), JSONArray.toJSONString(apiSiriusDefinitionList));
            } catch (Exception e) {
                log.error("reported api information failed,stack info:", e);
            }
            log.info("<<<<< successfully reported api information >>>>>");
        }
    }

}

通过扫描项目下的controller和相应的mapping注解中的属性拼接出url来,通过zk来更新节点数据

2、网关添加监听事件

zk操作查看另一篇文章zookeeper相关操作

/**
 * @classDesc: 网关核心应用
 * @author: cyjer
 * @date: 2023/1/30 9:53
 */
@Component
@Slf4j
@RequiredArgsConstructor
public class GatewayApplication implements ApplicationListener<ContextRefreshedEvent> {
    private final GatewayServiceProperties properties;
    private final ApiDefinitionService apiDefinitionService;
    private final ZookeeperService zookeeperService;
    private final ApiGroupProcesser apiGroupProcesser;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        //拉取api governance platform 信息
        apiDefinitionService.refreshApiGovernanceInfo(properties.getGatewayId());
        log.info("<<<<<<<<<<刷新api分组信息完成>>>>>>>>>>");
        zookeeperService.create(Constraint.API_DEFINITION, "init");
        zookeeperService.addWatchChildListener(Constraint.API_DEFINITION, apiGroupProcesser);
        log.info("<<<<<<<<<<api上报监听器配置完成>>>>>>>>>>");

    }
}

通过事件处理,首先启动时刷新api信息,同时尝试初始化zk节点,然后注册监听watch。

3、网关监听事件处理

/**
 * @classDesc: api分组上报
 * @author: cyjer
 * @date: 2023/2/10 11:13
 */
@Slf4j
@Component
public class ApiGroupProcesser extends AbstractChildListenerProcess implements ApiDefinitionConstraint {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private GatewayServiceProperties gatewayServiceProperties;

    @Override
    public void process(CuratorFramework curatorFramework, PathChildrenCacheEvent cacheEvent) {
        ChildData data = cacheEvent.getData();
        if (Objects.nonNull(data) && cacheEvent.getType().equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)) {
            log.info("<<<<<<<<<<上报api分组到sentinel>>>>>>>>>>");
            String path = data.getPath();
            String content = new String(data.getData(), StandardCharsets.UTF_8);
            Set<ApiDefinition> definitions = GatewayApiDefinitionManager.getApiDefinitions();
            List<ApiSiriusDefinition> list = JSONArray.parseArray(content, ApiSiriusDefinition.class);
            for (ApiSiriusDefinition apiGroup : list) {
                ApiDefinition api = new ApiDefinition(apiGroup.getResource())
                        .setPredicateItems(new HashSet<ApiPredicateItem>() {
                            {
                                add(new ApiPathPredicateItem().setPattern(apiGroup.getUrlPath())
                                        .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                            }
                        });

                definitions.add(api);
            }
            GatewayApiDefinitionManager.loadApiDefinitions(definitions);
            redisTemplate.opsForHash().put(API_INFO_REDIS_PREFIX + gatewayServiceProperties.getGatewayId(), path, JSONArray.toJSONString(list));
            log.info("<<<<<<<<<<上报api分组到sentinel成功>>>>>>>>>>");
        }
    }
}

5、sentinel控制台启动

java -Dserver.port=8600 -Dcsp.sentinel.dashboard.server=localhost:8600 -Dproject.name=sentinel-dashboard -Xms512m -Xmx512m -Xmn256m -XX:MaxMetaspaceSize=100m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/oom/log -Dfile.encoding=UTF-8 -XX:+UseG1GC -jar sentinel-dashboard-1.8.6.jar

打开sentinel控制台,请求几次接口后
在这里插入图片描述

可以看到相应的api分组信息和url路径匹配都已加载,在进行流量治理的时候就可以支持restful接口和controller粒度的治理了

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

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

相关文章

I.MX6ULL_Linux_系统篇(16) uboot分析-启动流程

原文链接&#xff1a;I.MX6ULL_系统篇(16) uboot分析-启动流程 – WSY Personal Blog (cpolar.cn) 前面我们详细的分析了 uboot 的顶层 Makefile&#xff0c;了解了 uboot 的编译流程。本章我们来详细的分析一下 uboot 的启动流程&#xff0c;理清 uboot 是如何启动的。通过对 …

虹科资讯| 虹科AR荣获汽车后市场“20佳”维修工具评委会提名奖!

2022 虹科荣获20佳维修工具 评委会提名奖 特大喜讯&#xff0c;在2月16日《汽车维修与保养》杂志主办的第十八届汽车后市场“20佳”评选活动中&#xff0c;虹科的产品“M400智能AR眼镜”凭借在AR领域的专业实力&#xff0c;通过层层筛选&#xff0c;在102款入围产品中脱颖而出…

GIT:【基础三】Git工作核心原理

目录 一、Git本地四个工作区域 二、Git提交文件流程 一、Git本地四个工作区域 工作目录(Working Directory)&#xff1a;电脑上存放开发代码的地方。暂存区(Stage/Index)&#xff1a;用于l临时存放改动的文件&#xff0c;本质上只是一个文件&#xff0c;保存即将提交到文件列…

[ 对比学习篇 ] 经典网络模型 —— Contrastive Learning

&#x1f935; Author &#xff1a;Horizon Max ✨ 编程技巧篇&#xff1a;各种操作小结 &#x1f3c6; 神经网络篇&#xff1a;经典网络模型 &#x1f4bb; 算法篇&#xff1a;再忙也别忘了 LeetCode [ 对比学习篇 ] 经典网络模型 —— Contrastive Learning&#x1f680; …

MongoDB介绍及使用教程

文章目录一、MongoDB介绍1. 什么是MongoDB2. 为什么要用MongoDB3. MongoDB的应用场景4. MongoDB基本概念二、MongoDB使用教程1.下载安装&#xff08;Windows&#xff09;2.MongoDB Conpass简单使用&#xff08;选学&#xff09;3.使用navicat连接MongoDB4.JAVA项目中使用MongoD…

JVM11 垃圾回收

1.1GC分类与性能指标 垃圾收集器没有在规范中进行过多的规定&#xff0c;可以由不同的厂商、不同版本的JVM来实现。 从不同角度分析垃圾收集器&#xff0c;可以将GC分为不同的类型。 Java不同版本新特性 语法层面&#xff1a;Lambda表达式、switch、自动拆箱装箱、enumAPI层面…

AI稳定生成图工业链路打造

前沿这篇文章会以比较轻松的方式&#xff0c;跟大家交流下如何控制文本生成图片的质量。要知道如何控制文本生成质量&#xff0c;那么我们首先需要知道我们有哪些可以控制的参数和模块。要知道我们有哪些控制的参数和模块&#xff0c;我们就得知道我们文本生成图片的这架机器或…

新手福利——x64逆向基础

一、x64程序的内存和通用寄存器 随着游戏行业的发展&#xff0c;x32位的程序已经很难满足一些新兴游戏的需求了&#xff0c;因为32位内存的最大值为0xFFFFFFFF&#xff0c;这个值看似足够&#xff0c;但是当游戏对资源需求非常大&#xff0c;那么真正可以分配的内存就显得捉襟…

测试人员如何运用好OKR

在软件测试工作中是不是还不知道OKR是什么?又或者每次都很害怕写OKR?或者总觉得很迷茫&#xff0c;不知道目标是什么? OKR 与 KPI 的区别 去年公司从KPI换OKR之后&#xff0c;我也有一段抓瞎的过程&#xff0c;然后自己找了两本书看&#xff0c;一本是《OKR工作法》&#xf…

WPF_ObservableCollection基本使用及其注意项

文章目录一、引言二、ObservableCollection三、结语一、引言 在GUI编程中经常会用到条目控件&#xff0c;常见的如ComboBox&#xff08;下拉列表框&#xff09;&#xff0c;它内部往往有多个项。 在使用一些图形框架&#xff08;Qt、WinForm&#xff09;上进行原始开发时&…

安卓mvvm

AndroidX的意思是android extension libraries, 也就是安卓扩展包 AndroidX其实是Jetpack类库的命名空间 (190条消息) AndroidX初识_Neda Wang的博客-CSDN博客https://blog.csdn.net/weixin_38261570/article/details/111500044 viewmodel ViewModel类旨在以注重生命周期的方…

【机器学习】决策树-C4.5算法

1.C4.5算法 C4.5算法与ID3相似&#xff0c;在ID3的基础上进行了改进&#xff0c;采用信息增益比来选择属性。ID3选择属性用的是子树的信息增益&#xff0c;ID3使用的是熵&#xff08;entropy&#xff0c; 熵是一种不纯度度量准则&#xff09;&#xff0c;也就是熵的变化值&…

回溯算法理论基础及组合问题

文章目录回溯算法理论基础什么是回溯法回溯法的效率回溯法解决的问题如何理解回溯法回溯法模板组合问题回溯算法理论基础 什么是回溯法 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。 回溯是递归的副产品&#xff0c;只要有递归就会有回溯。 所以以下讲解中&…

LPWAN及高效弹性工业物联网核心技术方案

20多年前的一辆拖拉机就是一个纯机械的产品&#xff0c;里面可能并没有电子或者软件的构成&#xff1b;而随后随着软件的发展&#xff0c;拖拉机中嵌入了软件&#xff0c;它能控制发动机的功率及拖拉机防抱死系统&#xff1b;接下来&#xff0c;通过融入各种软件&#xff0c;拖…

js逆向基础篇-某房地产网站-登录

提示!本文章仅供学习交流,严禁用于任何商业和非法用途,如有侵权,可联系本文作者删除! 网站链接:aHR0cHM6Ly9tLmZhbmcuY29tL215Lz9jPW15Y2VudGVyJmE9aW5kZXgmY2l0eT1iag== 案例分析: 本篇文章分析的是登录逻辑。话不多说,先看看登录中有哪些加密参数,在登录页面随便输入…

K8S DNS解析过程和延迟问题

一、Linux DNS查询解析原理&#xff08;对于调用glibc库函数gethostbyname的程序&#xff09;我们在浏览器访问www.baidu.com这个域名&#xff0c;dns怎么查询到这台主机呢&#xff1f;  1、在浏览器中输入www.baidu.com域名&#xff0c;操作系统会先查找本地DNS解析器缓存&a…

实例2:树莓派GPIO控制外部LED灯闪烁

实例2&#xff1a;树莓派GPIO控制外部LED灯闪烁 实验目的 通过背景知识学习&#xff0c;了解四足机器人mini pupper搭载的微型控制计算机&#xff1a;树莓派。通过树莓派GPIO操作的学习&#xff0c;熟悉GPIO的读写控制。通过外部LED灯的亮灭控制&#xff0c;熟悉树莓派对外界…

vue3 + vite 使用 svg 可改变颜色

文章目录vue3 vite 使用 svg安装插件2、配置插件 vite.config.js3、根据vite配置的svg图标文件夹&#xff0c;建好文件夹&#xff0c;把svg图标放入4、在 src/main.js内引入注册脚本5、创建一个公共SvgIcon.vue组件6.1 全局注册SvgIcon.vue组件6.2、在想要引入svg的vue组件中引…

Boom 3D最新版本下载电脑音频增强应用工具

为了更好地感受音乐的魅力&#xff0c;Boom 3D 可以让你对音效进行个性化增强&#xff0c;并集成 3D 环绕立体声效果&#xff0c;可以让你在使用任何耳机时&#xff0c;都拥有纯正、优质的音乐体验。Boom 3D是一款充满神奇魅力的3D环绕音效升级版&#xff0c;BOOM 3D是一个全新…

MyBatis 之四(动态SQL之 if、trim、where、set、foreach 标签)

文章目录动态 SQL1. if 标签2. trim 标签3. where 标签4. set 标签5. foreach 标签回顾一下&#xff0c;在上一篇 MyBatis 之三&#xff08;查询操作 占位符#{} 与 ${}、like查询、resultMap、association、collection&#xff09;中&#xff0c;学习了针对查询操作的相关知识点…