Ali-Sentinel-Spring WebMVC 流控

归档

  • GitHub: Ali-Sentinel-Spring WebMVC 流控

测试

  • 模块:sentinel-dashboard

    • 先启动 DashboardApplication
    • 访问 http://localhost:8080/#/dashboard
      • 登录:sentinel / sentinel
  • 模块:sentinel-demo-spring-webmvc

    • WebMvcDemoApplication 类的 main() 方法改成如下:
        public static void main(String[] args) {
            System.setProperty("csp.sentinel.dashboard.server", "127.0.0.1:8080");
            System.setProperty("project.name", "My-Test-8866");
            SpringApplication.run(WebMvcDemoApplication.class);
        }
    
    • 再启动 WebMvcDemoApplication
      • 访问 http://localhost:10000/hello
      • dashboard 才会显示

原理

  • demo-webmvc 依赖模块:
    • sentinel-spring-webmvc-adapter
      • 用于链路控制适配
    • sentinel-transport-simple-http (相同的有 sentinel-transport-netty-http)
      • 用于控制台交互和心跳检测

链路控制适配

  • com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebInterceptor
/** 使用 SpringMVC 拦截器进行拦截,在 WebMvcConfigurer 里进行配置 */
public class SentinelWebInterceptor extends AbstractSentinelInterceptor {

    private final SentinelWebMvcConfig config;

    public SentinelWebInterceptor() {
        this(new SentinelWebMvcConfig());
    }

    public SentinelWebInterceptor(SentinelWebMvcConfig config) {
        super(config);
        ... // 省略
    }

}
  • com.alibaba.csp.sentinel.adapter.spring.webmvc.AbstractSentinelInterceptor
/** Sentinel 拦截器 (做控制逻辑) */
public abstract class AbstractSentinelInterceptor implements HandlerInterceptor {

    // 拦截前处理
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception 
    {
        try {
            String resourceName = getResourceName(request);

            ... // resourceName 为空返回 true
            
            if (increaseReferece(request, this.baseWebMvcConfig.getRequestRefName(), 1) != 1) {
                return true;  // 只对首个进行拦截处理
            }
            
            ... // 省略上下文处理
            Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN); // 正式流控
            request.setAttribute(baseWebMvcConfig.getRequestAttributeName(), entry);                // 做记录,方便后面退出处理
            return true;
        } catch (BlockException e) {
            try {
                handleBlockException(request, response, e); // 异常处理 sign_m_010
            } finally {
                ContextUtil.exit();
            }
            return false; // 流控限制
        }
    }
    
    // sign_m_010 异常处理
    protected void handleBlockException(HttpServletRequest request, HttpServletResponse response, BlockException e)
        throws Exception 
    {
        if (baseWebMvcConfig.getBlockExceptionHandler() != null) {
            baseWebMvcConfig.getBlockExceptionHandler().handle(request, response, e);
        } else {
            throw e;
        }
    }

    // 完成后处理
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception 
    {
        if (increaseReferece(request, this.baseWebMvcConfig.getRequestRefName(), -1) != 0) {
            return; // 不在最后一个 (相当于首个) 不处理
        }
        
        Entry entry = getEntryInRequest(request, baseWebMvcConfig.getRequestAttributeName());
        if (entry == null) {
            ... // log warn
            return;
        }
        
        traceExceptionAndExit(entry, ex); // 退出处理
        removeEntryInRequest(request);    // 移除 request 属性
        ContextUtil.exit();
    }

}
拦截器添加示例
  • com.alibaba.csp.sentinel.demo.spring.webmvc.config.InterceptorConfig
// 使用 Spring 配置
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        addSpringMvcInterceptor(registry);  
    }

    private void addSpringMvcInterceptor(InterceptorRegistry registry) {
        SentinelWebMvcConfig config = new SentinelWebMvcConfig();
        ... // 省略其他配置
        config.setOriginParser(request -> request.getHeader("S-user"));
        // 添加到拦截器链
        registry.addInterceptor(new SentinelWebInterceptor(config)).addPathPatterns("/**");
    }
}

控制台交互和心跳检测

  • sentinel-transport-netty-http 模块做示例
交互服务启动
  • SPI 设置 CommandCenter 的实现为 NettyHttpCommandCenter

  • 启动栈示例:

java.lang.RuntimeException: 栈跟踪
    at com.alibaba.csp.sentinel.transport.command.NettyHttpCommandCenter.start(NettyHttpCommandCenter.java:46)    // sign_m_204
    at com.alibaba.csp.sentinel.transport.init.CommandCenterInitFunc.init(CommandCenterInitFunc.java:40)
    at com.alibaba.csp.sentinel.init.InitExecutor.doInit(InitExecutor.java:53)
    at com.alibaba.csp.sentinel.Env.<clinit>(Env.java:36)
    at com.alibaba.csp.sentinel.SphU.entry(SphU.java:294)
    at com.alibaba.csp.sentinel.adapter.spring.webmvc.AbstractSentinelInterceptor.preHandle(AbstractSentinelInterceptor.java:105)
    ... // 来自 HTTP 处理链
  • com.alibaba.csp.sentinel.transport.command.NettyHttpCommandCenter
@Spi(order = Spi.ORDER_LOWEST - 100)
public class NettyHttpCommandCenter implements CommandCenter {

    private final HttpServer server = new HttpServer();

    @Override
    public void beforeStart() throws Exception {
        // sign_use_010  SPI 加载实例
        Map<String, CommandHandler> handlers = CommandHandlerProvider.getInstance().namedHandlers();
        server.registerCommands(handlers);  // 注册命令处理器
    }

    // sign_m_204
    @Override
    public void start() throws Exception {
        new RuntimeException("栈跟踪").printStackTrace();
        pool.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    server.start(); // sign_m_205
                } ... // catch
            }
        });
    }
}
  • com.alibaba.csp.sentinel.transport.command.netty.HttpServer
public final class HttpServer {

    private static final int DEFAULT_PORT = 8719;

    // sign_m_205
    public void start() throws Exception {
        ... // EventLoopGroup
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new HttpServerInitializer()); // 增加 HttpServerHandler 到双向处理链,ref: sign_c_205
            int port;
            ... // 读取 port
            
            int retryCount = 0;
            ChannelFuture channelFuture = null;
            while (true) {
                int newPort = getNewPort(port, retryCount); // 尝试 3 次才端口递增 1
                try {
                    channelFuture = b.bind(newPort).sync();
                    ... // log
                    break;
                } catch (Exception e) {
                    TimeUnit.MILLISECONDS.sleep(30);
                    ... // log
                    retryCount ++;
                }
            }
            ... // channel 赋值
        } ...   // finally
    }

}
  • com.alibaba.csp.sentinel.transport.command.netty.HttpServerHandler
// sign_c_205 Netty 入站处理器
public class HttpServerHandler extends SimpleChannelInboundHandler<Object> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        FullHttpRequest httpRequest = (FullHttpRequest)msg;
        try {
            CommandRequest request = parseRequest(httpRequest);             // 组装消息
            if (StringUtil.isBlank(HttpCommandUtils.getTarget(request))) {
                writeErrorResponse(BAD_REQUEST.code(), "Invalid command", ctx);
                return;
            }
            handleRequest(request, ctx, HttpUtil.isKeepAlive(httpRequest)); // 处理请求 sign_m_210
        } ... // catch
    }

    // sign_m_210 处理请求
    private void handleRequest(CommandRequest request, ChannelHandlerContext ctx, boolean keepAlive)
        throws Exception 
    {
        String commandName = HttpCommandUtils.getTarget(request);
        CommandHandler<?> commandHandler = getHandler(commandName);         // 查找处理器
        if (commandHandler != null) {
            CommandResponse<?> response = commandHandler.handle(request);   // sign_use_100  处理命令
            writeResponse(response, ctx, keepAlive);
        } else {
            writeErrorResponse(BAD_REQUEST.code(), String.format("Unknown command \"%s\"", commandName), ctx);
        }
    }
}
命令处理器
  • 接口为 com.alibaba.csp.sentinel.command.CommandHandler
  • SPI 加载参考:交互服务启动 sign_use_010
    • 具体实现为:com.alibaba.csp.sentinel.command.CommandHandlerProvider #namedHandlers
  • 使用者参考:交互服务启动 sign_use_100
发送心跳
  • SPI 设置 HeartbeatSender 的实现为 HttpHeartbeatSender

  • 启动栈示例:

java.lang.RuntimeException: 心跳启动栈跟踪
	at com.alibaba.csp.sentinel.transport.init.HeartbeatSenderInitFunc.scheduleHeartbeatTask(HeartbeatSenderInitFunc.java:87)   // sign_m_310
	at com.alibaba.csp.sentinel.transport.init.HeartbeatSenderInitFunc.init(HeartbeatSenderInitFunc.java:61)
	at com.alibaba.csp.sentinel.init.InitExecutor.doInit(InitExecutor.java:53)
	at com.alibaba.csp.sentinel.Env.<clinit>(Env.java:36)
	at com.alibaba.csp.sentinel.SphU.entry(SphU.java:294)
	at com.alibaba.csp.sentinel.adapter.spring.webmvc.AbstractSentinelInterceptor.preHandle(AbstractSentinelInterceptor.java:105)
    ... // 来自 HTTP 处理链
  • com.alibaba.csp.sentinel.transport.init.HeartbeatSenderInitFunc
    // sign_m_310 开启心跳定时任务
    private void scheduleHeartbeatTask(final HeartbeatSender sender, long interval) {
        new RuntimeException("心跳启动栈跟踪").printStackTrace();
        pool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    sender.sendHeartbeat(); // 发心跳 sign_m_320
                } ... // catch
            }
        }, 5000, interval, TimeUnit.MILLISECONDS);
        ... // log
    }
  • com.alibaba.csp.sentinel.transport.heartbeat.HttpHeartbeatSender
@Spi(order = Spi.ORDER_LOWEST - 100)
public class HttpHeartbeatSender implements HeartbeatSender {

    private final Protocol consoleProtocol; // 控制台通信协议
    private final String consoleHost;       // 控制台 IP
    private final int consolePort;          // 控制台端口

    // sign_m_320 发心跳
    @Override
    public boolean sendHeartbeat() throws Exception {
        if (StringUtil.isEmpty(consoleHost)) {
            return false;
        }
        URIBuilder uriBuilder = new URIBuilder();
        uriBuilder.setScheme(consoleProtocol.getProtocol()).setHost(consoleHost).setPort(consolePort)
            // setPath() 默认用 "/registry/machine" (相当于注册,这也是为什么要请求后控制台才显示)
            // 处理方法为 MachineRegistryController #receiveHeartBeat()
            .setPath(TransportConfig.getHeartbeatApiPath())
            .setParameter("app", AppNameUtil.getAppName())
            .setParameter("app_type", String.valueOf(SentinelConfig.getAppType()))
            .setParameter("v", Constants.SENTINEL_VERSION)
            .setParameter("version", String.valueOf(System.currentTimeMillis()))
            .setParameter("hostname", HostNameUtil.getHostName())
            .setParameter("ip", TransportConfig.getHeartbeatClientIp())
            .setParameter("port", TransportConfig.getPort())
            .setParameter("pid", String.valueOf(PidUtil.getPid()));

        HttpGet request = new HttpGet(uriBuilder.build());
        request.setConfig(requestConfig);
        // Send heartbeat request.
        CloseableHttpResponse response = client.execute(request);
        response.close();
        ... // 省略状态判断
    }

}
总结
  • 要有请求,才会去注册
  • 控制台才会显示服务

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

「 网络安全常用术语解读 」漏洞利用预测评分系统EPSS详解

1. 概览 EPSS&#xff08;Exploit Prediction Scoring System&#xff0c;漏洞利用预测评分系统&#xff09; 提供了一种全新的高效、数据驱动的漏洞管理功能。EPSS是一项数据驱动的工作&#xff0c;使用来自 CVE 的当前威胁信息和现实世界的漏洞数据。 EPSS 模型产生 0 到 1&…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-15-GPIO中断控制实验

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

大语言模型LLM原理篇

大模型席卷全球&#xff0c;彷佛得模型者得天下。对于IT行业来说&#xff0c;以后可能没有各种软件了&#xff0c;只有各种各样的智体&#xff08;Agent&#xff09;调用各种各样的API。在这种大势下&#xff0c;笔者也阅读了很多大模型相关的资料&#xff0c;和很多新手一样&a…

电脑ip地址设置成什么比较好

随着信息技术的快速发展&#xff0c;IP地址已成为电脑在网络世界中的“身份证”。它不仅是电脑在网络中进行通信的基础&#xff0c;也直接关系到网络连接的稳定性、安全性和效率。然而&#xff0c;面对众多IP地址设置选项&#xff0c;许多用户可能会感到困惑。那么&#xff0c;…

图形网络的自适应扩散 笔记

1 Title Adaptive Diffusion in Graph Neural Networks&#xff08;Jialin Zhao、Yuxiao Dong、Ming Ding、Evgeny Kharlamov、Jie Tang&#xff09;【NIPS 2021】 2 Conclusion The neighborhood size in GDC is manually tuned for each graph by conductin…

docker-compose集成elasticsearch7.17.14+kibana7.17.14

1.docker和compose版本必须要高 2.准备ik分词器&#xff08;elasticsearch-analysis-ik-7.17.14&#xff09;&#xff0c;下面会用到 https://github.com/infinilabs/analysis-ik/releases?page2 3.配置es-compose.yml&#xff08;切记映射容器内路径不能更改,es和kibana服务…

每日OJ题_记忆化搜索⑤_力扣329. 矩阵中的最长递增路径

目录 力扣329. 矩阵中的最长递增路径 解析代码1_爆搜递归&#xff08;超时&#xff09; 解析代码2_记忆化搜索 力扣329. 矩阵中的最长递增路径 329. 矩阵中的最长递增路径 难度 困难 给定一个 m x n 整数矩阵 matrix &#xff0c;找出其中 最长递增路径 的长度。 对于每…

【LeetCode算法】389. 找不同

提示&#xff1a;此文章仅作为本人记录日常学习使用&#xff0c;若有存在错误或者不严谨得地方欢迎指正。 文章目录 一、题目二、思路三、解决方案 一、题目 给定两个字符串 s 和 t &#xff0c;它们只包含小写字母。字符串 t 由字符串 s 随机重排&#xff0c;然后在随机位置添…

移动端自动化测试工具 Appium 之 main 启动

文章目录 一、背景二、生成xml文件2.1、创建xml方法2.2、执行主类MainTest2.3、自动生成的xml2.4、工程目录2.5、执行结果 三、命令行执行appium服务四、主方法启动类五、集成Jenkins六、总结 一、背景 Jenkins 做集成测试是不错的工具&#xff0c;那么UI自动化是否可以&#…

macOS12安装 php7.1和apache

1. 安装php 7.1 macOS12不再自带php brew tap shivammathur/php 查看可安装版本 brew search php 安装指定版本&#xff08;禅道适用PHP运行环境(7.0/7.1/7.2版本)&#xff09; brew install php7.1 环境配置 vim ~/.zshrc export PATH"/usr/local/opt/php7.1/bin:…

uni-app 滚动到指定位置

方法1&#xff1a;使用标签&#xff0c;可以将页面横向&#xff08;或纵向&#xff09;滚动到指定位置 无法滚动 将代码放在setTimeout&#xff0c;nextTick里执行 <!-- 左边 --><scroll-view show-scrollbar"false" scroll-y"true" class"…

Flutter笔记:Widgets Easier组件库(13)- 使用底部弹窗

Flutter笔记 Widgets Easier组件库&#xff08;13&#xff09;使用底部弹窗 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this …

局域网语音对讲系统_IP广播对讲系统停车场解决方案

局域网语音对讲系统_IP广播对讲系统停车场解决方案 需求分析&#xff1a; 随着国民经济和社会的发展&#xff0c; 选择坐车出行的民众越来越多。在保护交通安全的同时&#xff0c;也给停车场服务部门提出了更高的要求。人们对停车场系统提出了更高的要求与挑战&#xff0c; 需要…

Android 开机启动模式源码分析

在机器关机情况下&#xff0c;长按Power键启动机器&#xff0c;如果这时机器低电&#xff0c;会提示低电&#xff0c;机器不会正常启动&#xff1a; 而代码如下&#xff1a; 如果不是低电&#xff0c;正常情况是可以启动的。 在关机情况下&#xff0c;插入USB&#xff0c;机…

AUS GLOBAL 再次荣登皇家贝蒂斯俱乐部官网

AUS GLOBAL 作为一家备受信赖的金融服务领导者&#xff0c;一直以来都在致力于为客户提供卓越的交易体验和专业的服务。再次登上皇家贝蒂斯俱乐部官网Banner&#xff0c;不仅是对我们过去合作的肯定&#xff0c;更是对未来合作的信心和期待。这标志着我们之间的合作更加稳固和成…

IP报文在设备间传递的封装过程

IP报文传递过程 1、PC1访问PC2报文传递过程1.1、PC1准备数据请求报文封装1.2、PC1准备ARP请求报文1.3、PC2准备ARP响应报文1.4、PC1完成数据请求报文封装 2、PC1访问PC3报文传递过程2.1、PC1准备数据请求报文封装2.2、PC1准备获取网关MAC地址的ARP请求报文2.3、网关准备ARP响应…

kotlin语法快速入门--(完整版)

Kotlin语法入门 文章目录 Kotlin语法入门一、变量声明1、整型2、字符型3、集合3.1、创建array数组3.2、创建list集合3.3、不可变类型数组3.4、Set集合--不重复添加元素3.5、键值对集合Map 4、kotlin特有的数据类型和集合4.1、Any、Nothing4.2、二元组--Pair4.3、三元组--Triple…

vue数据大屏并发请求

并发? 处理并发 因为js是单线程的&#xff0c;所以前端的并发指的是在极短时间内发送多个数据请求&#xff0c;比如说循环中发送 ajax , 轮询定时器中发送 ajax 请求. 然后还没有使用队列, 同时发送 的. 1. Promise.all 可以采用Promise.all处理并发&#xff0c; 当所有pro…

gjfjiv是什么意思

GJFJV-4B1&#xff0c;gjfjv-6a1a&#xff0c;gjfjv光缆 室内光缆型号命名 产品描述 多样的光缆结构选择&#xff0c;可在有限的空间内布设&#xff0c;且无缠绕效应 可于建设物间导管托盘和通道中使用 理想的网络光缆在保证对光纤的保护前提下易于布设&#xff0c;插接和识…

数据链路层之 以太网协议

以太网协议 这个协议即规定了数据链路层&#xff0c;同时也规定了物理层的内容。平时使用到的网线&#xff0c;其实也叫做“以太网线”&#xff08;遵守以太网协议的网线&#xff09;。 以太网帧格式 以太网数据帧 帧头 载荷 帧尾。 帧头&#xff1a;目的地址、源地址、类型…
最新文章