Ali-Sentinel-Spring WebMVC 流控

ops/2024/10/19 7:29:19/

归档

  • GitHub: Ali-Sentinel-Spring WebMVC 流控

测试

  • 模块:sentinel-dashboard

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

    • WebMvcDemoApplication 类的 main() 方法改成如下:
    java">    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)
      • 用于控制台交互和心跳检测

链路控制适配

java">/** 使用 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
java">/** Sentinel 拦截器 (做控制逻辑) */
public abstract class AbstractSentinelInterceptor implements HandlerInterceptor {// 拦截前处理@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {try {String resourceName = getResourceName(request);... // resourceName 为空返回 trueif (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;}}// 完成后处理@Overridepublic 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 warnreturn;}traceExceptionAndExit(entry, ex); // 退出处理removeEntryInRequest(request);    // 移除 request 属性ContextUtil.exit();}}
拦截器添加示例
java">// 使用 Spring 配置
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {@Overridepublic 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_204at 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
java">@Spi(order = Spi.ORDER_LOWEST - 100)
public class NettyHttpCommandCenter implements CommandCenter {private final HttpServer server = new HttpServer();@Overridepublic void beforeStart() throws Exception {// sign_use_010  SPI 加载实例Map<String, CommandHandler> handlers = CommandHandlerProvider.getInstance().namedHandlers();server.registerCommands(handlers);  // 注册命令处理器}// sign_m_204@Overridepublic void start() throws Exception {new RuntimeException("栈跟踪").printStackTrace();pool.submit(new Runnable() {@Overridepublic void run() {try {server.start(); // sign_m_205} ... // catch}});}
}
  • com.alibaba.csp.sentinel.transport.command.netty.HttpServer
java">public final class HttpServer {private static final int DEFAULT_PORT = 8719;// sign_m_205public void start() throws Exception {... // EventLoopGrouptry {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new HttpServerInitializer()); // 增加 HttpServerHandler 到双向处理链,ref: sign_c_205int port;... // 读取 portint retryCount = 0;ChannelFuture channelFuture = null;while (true) {int newPort = getNewPort(port, retryCount); // 尝试 3 次才端口递增 1try {channelFuture = b.bind(newPort).sync();... // logbreak;} catch (Exception e) {TimeUnit.MILLISECONDS.sleep(30);... // logretryCount ++;}}... // channel 赋值} ...   // finally}}
  • com.alibaba.csp.sentinel.transport.command.netty.HttpServerHandler
java">// sign_c_205 Netty 入站处理器
public class HttpServerHandler extends SimpleChannelInboundHandler<Object> {@Overrideprotected 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_310at 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
java">    // sign_m_310 开启心跳定时任务private void scheduleHeartbeatTask(final HeartbeatSender sender, long interval) {new RuntimeException("心跳启动栈跟踪").printStackTrace();pool.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {try {sender.sendHeartbeat(); // 发心跳 sign_m_320} ... // catch}}, 5000, interval, TimeUnit.MILLISECONDS);... // log}
  • com.alibaba.csp.sentinel.transport.heartbeat.HttpHeartbeatSender
java">@Spi(order = Spi.ORDER_LOWEST - 100)
public class HttpHeartbeatSender implements HeartbeatSender {private final Protocol consoleProtocol; // 控制台通信协议private final String consoleHost;       // 控制台 IPprivate final int consolePort;          // 控制台端口// sign_m_320 发心跳@Overridepublic 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.ppmy.cn/ops/38624.html

相关文章

SQL STRING_SPLIT函数,将指定的分隔符将字符串拆分为子字符串行

文章目录 STRING_SPLIT (Transact-SQL)1、语法2、参数3、样例样例1样例2 STRING_SPLIT (Transact-SQL) STRING_SPLIT 是一个表值函数&#xff0c;它根据指定的分隔符将字符串拆分为子字符串行。 1、语法 STRING_SPLIT ( string , separator [ , enable_ordinal ] ) 2、参数…

某度假村培训体系搭建项目成功案例纪实

——建立分层分类的培训体系&#xff0c;引入场景化培训&#xff0c;确保培训落到实处 【客户行业】文旅行业、酒店行业、文旅企业、度假村 【问题类型】培训体系搭建 【客户背景】 某度假村是一家集住宿、娱乐、健身等服务为一体的综合服务场所&#xff0c;度假村内部环境…

Linux(centos)安装 MySQL 8 数据库(图文详细教程)

前言 前几天写了个window系统下安装Mysql的博客&#xff0c;收到很多小伙伴私信需要Linux下安装Mysql的教程&#xff0c;今天这边和大家分享一下&#xff0c;话不多说&#xff0c;看教程。 一、删除以前安装的MySQL服务 一般安装程序第一步都需要清除之前的安装痕迹&#xff…

如何用opencv去掉单元格的边框线,以提高Tesseract识别率?

在OpenCV中处理从表格切割下来的图片&#xff0c;并去掉单元格的边框线&#xff0c;以提升Tesseract的识别准确率&#xff0c;确实是一个具有挑战性的任务。在这种情况下&#xff0c;我们需要采取一种策略来预处理图像&#xff0c;使得数字与背景之间的对比度增强&#xff0c;同…

算法随想录第二天打卡|977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II

977.有序数组的平方 Python #最后反转列表 class Solution:def sortedSquares(self, nums: List[int]) -> List[int]:left,right0,len(nums)-1new_nums[]while left<right:num1nums[left]**2num2nums[right]**2if num1<num2:new_nums.append(num2)right-1else:new_n…

Linux网络部分——DHCP、FTP

目录 一、DHCP动态主机配置协议 1. DHCP工作原理&#xff08;流程&#xff09; 2. 使用DHCP的好处 3.DHCP的分配方式 4.DHCP安装和配置【☆】 二、FTP文件传输协议 1. FTP传输模式 2.FTP安装与配置【☆】 3. FTP设置白名单和黑名单【☆】 一、DHCP动态主机配置协议 DH…

大数据技术概述_1.大数据的定义

1.维基百科的定义 大数据是指其大小或复杂性无法通过现有常用的软件工具&#xff0c;以合理的成本并在可接受的时限内对其进行捕获、管理和处理的数据集。这些困难包括数据的收入、存储、搜索、共享、分析和可视化。 2.Granter的定义 Granter公司关注大数据的三个量化指标&…

【进程等待】阻塞等待 | options非阻塞等待

目录 waitpid 阻塞等待 options&非阻塞等待 pid_t返回值 阻塞等待VS非阻塞等待 waitpid 回顾上篇&#xff1a; pid_ t waitpid(pid_t pid, int *status, int options); 返回值&#xff1a; 当正常返回的时候waitpid返回收集到的子进程的进程ID&#xff1b;如果设置了…