【Sentinel】Sentinel簇点链路的形成
说明
一切节点的跟是 machine-root,同一个资源在不同链路会创建多个DefaultNode,但是在全局只会创建一个 ClusterNode
machine-root/\/ \EntranceNode1 EntranceNode2/ \/ \DefaultNode(nodeA) DefaultNode(nodeA)| |- - - - - - + - - - - - - - - - +- - - - - - -> ClusterNode(nodeA);
如我们所见,在两个上下文中为“nodeA”创建了两个 DefaultNode,但只创建了一个 ClusterNode
一切的开始
DispatcherServlet是Spring MVC框架中的核心组件,它作为前置控制器,它拦截匹配的请求,并根据相应的规则分发到目标Controller来处理。当请求进入后,首先会执行DispatcherServlet 的 doDispatch 方法
public class DispatcherServlet extends FrameworkServlet {protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {···try {try {···// 执行preHandle方法 // 会进入AbstractSentinelInterceptor 的 preHandle// 会为当前访问的controller接口创建资源if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.// 最终会执行SentinelResourceAspect#invokeResourceWithSentinel(pjp);// 为所有添加注解的方法创建资源mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}···}catch (Exception ex) {···}}catch (Exception ex) {···}}
}
因此,从这里就可以知道,簇点链路中,默认使用 controller 创建的资源一定在使用注解创建的资源之前创建,也就是说,使用注解创建的资源只能作为使用 controller 创建的资源的子节点。
链路创建过程分析
创建 EntranceNode
上面说到,执行会进入AbstractSentinelInterceptor 的 preHandle,进行资源创建
public abstract class AbstractSentinelInterceptor implements HandlerInterceptor {public static final String SENTINEL_SPRING_WEB_CONTEXT_NAME = "sentinel_spring_web_context";@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {try {// 获取资源名,也就是 /order/{orderId}String resourceName = getResourceName(request);if (StringUtil.isEmpty(resourceName)) {return true;}if (increaseReferece(request, this.baseWebMvcConfig.getRequestRefName(), 1) != 1) {return true;}// Parse the request origin using registered origin parser.String origin = parseOrigin(request);// contextName默认是sentinel_spring_web_context,// 如果不想使用这个,而是使用Controller接口路径作为contextName,则需要在application.yml文件中关闭context整合// spring.cloud.sentinel.web-context-unify=falseString contextName = getContextName(request);// 创建context// Context初始化的过程中,会创建EntranceNode,contextName就是EntranceNode的名称ContextUtil.enter(contextName, origin);// 创建资源,簇点链路的形成就在里面Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN);request.setAttribute(baseWebMvcConfig.getRequestAttributeName(), entry);return true;} catch (BlockException e) {···}}
}

在获取 contextName 时,会先判断有没有关闭 context 整合,然后选择返回默认的sentinel_spring_web_contex还是从接口中获取url
@Override
protected String getContextName(HttpServletRequest request) {if (config.isWebContextUnify()) {return super.getContextName(request);}return getResourceName(request);
}@Override
protected String getResourceName(HttpServletRequest request) {// Resolve the Spring Web URL pattern from the request attribute.Object resourceNameObject = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);if (resourceNameObject == null || !(resourceNameObject instanceof String)) {return null;}String resourceName = (String) resourceNameObject;UrlCleaner urlCleaner = config.getUrlCleaner();if (urlCleaner != null) {resourceName = urlCleaner.clean(resourceName);}// Add method specification if necessaryif (StringUtil.isNotEmpty(resourceName) && config.isHttpMethodSpecify()) {resourceName = request.getMethod().toUpperCase() + ":" + resourceName;}return resourceName;
}
然后会进行 context 的创建
protected static Context trueEnter(String name, String origin) {// 第一次肯定为空Context context = contextHolder.get();if (context == null) {// contextNameNodeMap 有1个值(EntranceNode是DefaultNode的子类,是一种特殊的DefaultNode)// 1. sentinel_default_context -> {EntranceNode@10330} Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;// 根据传入的contextName选择看有没有这个name的EntranceNodeDefaultNode node = localCacheNameMap.get(name);// 如果没有就创建一个if (node == null) {if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {setNullContext();return NULL_CONTEXT;} else {LOCK.lock();try {node = contextNameNodeMap.get(name);if (node == null) {if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {setNullContext();return NULL_CONTEXT;} else {// 创建一个新的EntranceNodenode = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);// Add entrance node.// Constants.ROOT是一个EntranceNode, id是machine-root// 将当前创建的EntranceNode添加为Constants.ROOT的子节点Constants.ROOT.addChild(node);Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1);newMap.putAll(contextNameNodeMap);newMap.put(name, node);contextNameNodeMap = newMap;}}} finally {LOCK.unlock();}}}// 创建一个新的contextcontext = new Context(node, name);context.setOrigin(origin);contextHolder.set(context);}return context;
}
创建 DefaultNode
创建资源时,首先会创建一个 slot 执行链,然后依次执行。
第一个节点是 NodeSelectSlot,在里面完成 DefaultNode 的创建。

当第一次访问时,NodeSelectorSlot 中
// volatile保证map多线程的可见性
// 非static变量,每次创建对象时都创建一个新的
private volatile Map<String, DefaultNode> map = new HashMap<String, DefaultNode>(10);@Override
public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args) throws Throwable {// 第一次一定是空,同一个链路中的资源之后的请求不为空// 不同链路中的资源,后续的请求中,第一次访问还是空DefaultNode node = map.get(context.getName());if (node == null) {synchronized (this) {node = map.get(context.getName());if (node == null) {// 创建一个DefaultNode,将他放入到map中node = new DefaultNode(resourceWrapper, null);HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size());cacheMap.putAll(map);cacheMap.put(context.getName(), node);// 更新mapmap = cacheMap;// Build invocation tree// 将刚创建的node设置为当前node的子节点((DefaultNode) context.getLastNode()).addChild(node);}}}// 设置当前节点为刚创建的节点context.setCurNode(node);fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
下面的图是访问/order/query/{name}接口创建的资源

下面的图是访问/order/query/{name}接口创建的资源,不过在这个 Controller 接口里面又调用了 service 中添加了@SentinelResource注解的方法。根据上面的分析,基于注解的资源后创建,因此它作为基于 Controller 创建的资源的子节点

第二次访问/order/query/{name}接口,在NodeSelectorSlot中,node 会获取到,因此直接执行后边的操作

如果 controller 接口上加了@SentinelResource,还是先创建 controller 资源,然后创建 controller 基于注解的资源,然后是 service 的资源。下面的图中,在/order/query/{name}Controller 接口上添加了@SentinelResource注解。


feign 对 Sentinel 支持
开启 feign 对 Sentinel 的支持后,Sentinel 会将 feign 的请求添加到簇点链路中
feign.sentinel.enabled=true
在 Sentinel 的 jar 中,使用 spi 机制加载了一个类com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration

SentinelFeignAutoConfiguration 配置类里定义了Feign.Builder 的实现类 SentinelFeign.builder()
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ SphU.class, Feign.class })
public class SentinelFeignAutoConfiguration {@Bean@Scope("prototype")@ConditionalOnMissingBean@ConditionalOnProperty(name = "feign.sentinel.enabled") // 配置项为true时该bean生效public Feign.Builder feignSentinelBuilder() {return SentinelFeign.builder();}}
SentinelFeign.builder( ) 的 build( ) 方法
主要作用是: 创建 invocationHandlerFactory,重写create( ) 方法;invocationHandlerFactory 用于创建 SentinelInvocationHandler ,代替前面的 FeignCircuitBreakerInvocationHandler。
public Feign build() {super.invocationHandlerFactory(new InvocationHandlerFactory() {public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {GenericApplicationContext gctx = (GenericApplicationContext)Builder.this.applicationContext;BeanDefinition def = gctx.getBeanDefinition(target.type().getName());FeignClientFactoryBean feignClientFactoryBean = (FeignClientFactoryBean)def.getAttribute("feignClientsRegistrarFactoryBean");// 从BeanDefinition 里获取到 fallback、fallbackFactory Class fallback = feignClientFactoryBean.getFallback();Class fallbackFactory = feignClientFactoryBean.getFallbackFactory();String beanName = feignClientFactoryBean.getContextId();if (!StringUtils.hasText(beanName)) {beanName = feignClientFactoryBean.getName();}if (Void.TYPE != fallback) {// 创建 fallback 实例Object fallbackInstance = this.getFromContext(beanName, "fallback", fallback, target.type());// 创建 SentinelInvocationHandlerreturn new SentinelInvocationHandler(target, dispatch, new org.springframework.cloud.openfeign.FallbackFactory.Default(fallbackInstance));} else if (Void.TYPE != fallbackFactory) {FallbackFactory fallbackFactoryInstance = (FallbackFactory)this.getFromContext(beanName, "fallbackFactory", fallbackFactory, FallbackFactory.class);return new SentinelInvocationHandler(target, dispatch, fallbackFactoryInstance);} else {return new SentinelInvocationHandler(target, dispatch);}}private Object getFromContext(String name, String type, Class fallbackType, Class targetType) {Object fallbackInstance = Builder.this.feignContext.getInstance(name, fallbackType);if (fallbackInstance == null) {throw new IllegalStateException(String.format("No %s instance of type %s found for feign client %s", type, fallbackType, name));} else if (!targetType.isAssignableFrom(fallbackType)) {throw new IllegalStateException(String.format("Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s", type, fallbackType, targetType, name));} else {return fallbackInstance;}}});super.contract(new SentinelContractHolder(this.contract));return super.build();
}
在 invoke 方法里面为feign请求创建资源创建资源
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {if ("equals".equals(method.getName())) {try {Object otherHandler = args.length > 0 && args[0] != null? Proxy.getInvocationHandler(args[0]): null;return equals(otherHandler);}catch (IllegalArgumentException e) {return false;}}else if ("hashCode".equals(method.getName())) {return hashCode();}else if ("toString".equals(method.getName())) {return toString();}Object result;MethodHandler methodHandler = this.dispatch.get(method);// only handle by HardCodedTargetif (target instanceof Target.HardCodedTarget) {Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target;MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP.get(hardCodedTarget.type().getName()+ Feign.configKey(hardCodedTarget.type(), method));// resource default is HttpMethod:protocol://urlif (methodMetadata == null) {result = methodHandler.invoke(args);}else {String resourceName = methodMetadata.template().method().toUpperCase()+ ":" + hardCodedTarget.url() + methodMetadata.template().path();Entry entry = null;try {ContextUtil.enter(resourceName);// 为feign请求创建资源entry = SphU.entry(resourceName, EntryType.OUT, 1, args);// 调用服务端接口result = methodHandler.invoke(args);}catch (Throwable ex) {// fallback handleif (!BlockException.isBlockException(ex)) {Tracer.trace(ex);}if (fallbackFactory != null) {try {//异常时 调用熔断逻辑Object fallbackResult = fallbackMethodMap.get(method).invoke(fallbackFactory.create(ex), args);return fallbackResult;}catch (IllegalAccessException e) {····}}else {···}}finally {···}}}else {result = methodHandler.invoke(args);}return result;
}
如果 service 中使用 feign,则 feign 的调用 也会现实在链路中,他和使用注解创建的service 资源是同级的,但是先创建 feign,后创建 service 注解资源


使用注解和 feign 创建的资源,EntryType 都是 OUT,只有 controller 资源的EntryType 是 IN。
EntryType:枚举标记资源调用方向。
创建ClusterNode
在创建 ClusterNode 时,使用 static 变量存储。将创建的 ClusterNode 与当前 node 进行关联。
/*** 请记住,相同的资源(ResourceWrapper.equals(Object))将在全局范围内共享相同的ProcessorSlotChain,而与上下文无关。* 因此,如果代码进入entry(Context,ResourceWrapper,DefaultNode,int,boolean,Object...),* 则资源名称必须相同,但上下文名称可能不同。要获得不同上下文中相同资源的总统计数据,* 相同的资源在全局范围内共享相同的ClusterNode。此映射在应用运行时间越长,就会变得越稳定。* 因此,我们不使用并发映射,而是使用锁。因为此锁仅在开始时发生,而并发映射将始终保持锁定状态。*/
private static volatile Map<ResourceWrapper, ClusterNode> clusterNodeMap = new HashMap<>();@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,boolean prioritized, Object... args) throws Throwable {// 如果不是第一次访问这个资源,则clusterNode是一定有的// 所以直接将DefaultNode和ClusterNode进行关联// 因为保存ClusterNode的map是static 的,因此全局共享,且创建后内容一直存在// 因此一个资源只会创建一次ClusterNodeif (clusterNode == null) {synchronized (lock) {if (clusterNode == null) {// Create the cluster node.clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType());HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16));newMap.putAll(clusterNodeMap);newMap.put(node.getId(), clusterNode);clusterNodeMap = newMap;}}}node.setClusterNode(clusterNode);/** if context origin is set, we should get or create a new {@link Node} of* the specific origin.*/if (!"".equals(context.getOrigin())) {Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin());context.getCurEntry().setOriginNode(originNode);}fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
说明
如果一个请求中要经过多个资源保护的方法(controller 资源*1,注解资源*n),则上面的流程会进行多次,分别根据资源创建类型执行对应的方法,从而将每次的资源添加到前面资源的字节的中,形成于给完整的簇点链路
后面就是限流的一些 slot
相关文章:
【Sentinel】Sentinel簇点链路的形成
说明 一切节点的跟是 machine-root,同一个资源在不同链路会创建多个DefaultNode,但是在全局只会创建一个 ClusterNode machine-root/\/ \EntranceNode1 EntranceNode2/ \/ \DefaultNode(nodeA) DefaultNode(nodeA)|…...
Elasticsearch之mapping
文章目录 以显式的方式创建一个映射查看某个具体索引的mapping定义向已存在的映射中添加一个新的属性查看映射中指定字段的定义信息更新已存在映射的某个字段 1、 官方文档地址 2、 字段类型 1、定义:映射是定义文档及其包含的字段如何存储和索引的过程。 2、每个…...
6、PostgreSQL 数据类型之一:数字类型和货币类型
PostgreSQL 作为一个强大的开源关系型数据库管理系统,本身支持多种数据类型,包括标准 SQL 数据类型以及一些扩展数据类型。 PostgreSQL 支持多种数据类型的设计理念是为了满足不同应用场景的需求,提供更大的灵活性和数据处理能力。原因如下&…...
计算机视觉与深度学习 | 基于点线融合的视觉惯性SLAM前端
===================================================== github:[https://github.com/MichaelBeechan] CSDN:[https://blog.csdn.net/u011344545] ===================================================== 引言 本文中将介绍视觉惯性SLAM的前端部分,首先是传感器数据处理…...
MDK与keilC51共存的方法
MDK与keilC51共存的方法 在网上搜的资料MDK与KeilC51安装顺序都搞反了,而且大家都没成功过,反倒是转发了很多错误的教程。 用此安装方法解决了MDK与KeilC51的共存问题。所有功能完美运行。 因为MDK功能比KeilC51多,所以要先安装KeilC51 1、先…...
c_指针
文章目录 *(p1)1表示第 1 行第 1 个元素的地址。如何理解呢?下标运算符的规则括号 int a; // 1.一个整数 int *a; // 2.一个指向整数的指针 int **a; // 3.一个指向指针的指针, 它所指向的指针又指向一个整数型数据 ;一个指向 …...
循环队列c语言版
一、循环队列结构体 typedef int QueueDataType; #define CQ_MAX_SIZE 10typedef struct CircularQueue {QueueDataType data[CQ_MAX_SIZE];/**标记队列首*/QueueDataType head;/**标记队列尾部*/QueueDataType rear;} CircularQueue; 二、循环队列操作函数声明 /**创建队…...
SprringMVC拦截器
1、拦截器的配置 SpringMVC中的拦截器用于拦截控制器方法的执行 SpringMVC中的拦截器需要实现HandlerInterceptor SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置: <bean class"com.test.interceptor.FirstInterceptor"></bean> …...
redis的实际使用
Redis是一种内存数据库,常用于缓存、会话管理、消息队列等。在项目中合理使用Redis可以提高系统性能和可扩展性。以下是一些使用Redis的建议: 1. 缓存常用数据:将经常使用的数据缓存在Redis中,以减少数据库的读取次数,…...
造车先做三蹦子-之二:自制数据集(5x5数据集)230102
#Jupyter Notebook231001import torch import torch.nn as nn import torch.optim as optim# 定义模型 class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.fc1 = nn.Linear(25, 50)self.fc2 = nn.Linear(50, 6)def forward(self, x):x = x.view(-1, 25…...
JS操作DOM及CSS
JS创造于1994年,其目的是为浏览器显示的文档赋予动态行为。 1 Web编程基础 本节讲解如何编写Web应用中的js程序,如果将这些程序加载到浏览器,以及如何获取输入、产出输出,如何运行响应事件的异步代码。 1.1 js 脚本 虽然现在不…...
Linux内核VFS详解
Linux内核VFS是什么? Linux内核VFS(Virtual File System)是Linux操作系统中的一个关键组件,用于提供文件系统抽象层。它允许用户空间和内核空间的各个部分以一种一致的方式访问不同类型的文件系统,包括磁盘文件系统(如EXT4、XFS、NTFS)、网络文件系统(如NFS、CIFS)、…...
在自己的服务器上部署个人博客和开源项目:实现数字存在感
在数字时代,拥有自己的服务器不再是一项难以实现的任务。通过云计算和开源技术的广泛应用,个人可以轻松地拥有自己的服务器,并在其上部署个人博客以及开源项目,为自己在互联网上创造一个数字存在感。本文将介绍如何在自己的服务器…...
【AI视野·今日Robot 机器人论文速览 第五十九期】Fri, 20 Oct 2023
AI视野今日CS.Robotics 机器人学论文速览 Fri, 20 Oct 2023 Totally 29 papers 👉上期速览✈更多精彩请移步主页 Daily Robotics Papers CCIL: Continuity-based Data Augmentation for Corrective Imitation Learning Authors Liyiming Ke, Yunchu Zhang, Abhay D…...
Chromium浏览器启动参数
文章目录 Chromium浏览器启动参数1. --disable-web-security2. --disable-gpu3. --incognito4. --no-sandbox5. --disable-infobars6. --disable-notifications7. --disable-extensions8. --disable-translate9. --disable-popup-blocking10. --remote-debugging-port=<port…...
【计算机视觉】MoCo v3 讲解
MoCo v3 论文信息 标题:An Empirical Study of Training Self-Supervised Vision Transformers 作者:Xinlei Chen, Saining Xie, Kaiming He 期刊:ICCV 2021 发布时间与更新时间:2021.04.05 2021.04.08 2021.05.05 2021.08.16 主题:计算机视觉、对比学习、MoCo arXiv:[21…...
MySQL - 对字符串字段创建索引
在数据库中,对字符串字段创建索引可以加速字符串字段的查询: 直接创建完整索引:这是最简单的方式,直接对整个字符串字段创建索引。这种方式占用的空间较大,但查询性能通常较好,特别是在精确匹配的情况下。…...
Qt pro文件中 CONFIG += debug 作用
作用 在 Qt 项目文件(.pro 文件)中,CONFIG debug 的作用是指定项目以调试模式进行构建。 当在项目文件中添加 debug 到 CONFIG 变量时,Qt 构建系统将使用调试配置来编译项目。 这意味着编译器将生成带有调试信息的可执行文件&a…...
java解析生成定时Cron表达式工具类
Cron表达式工具类CronUtil 构建Cron表达式 /****方法摘要:构建Cron表达式*param taskScheduleModel*return String*/public static String createCronExpression(TaskScheduleModel taskScheduleModel){StringBuffer cronExp new StringBuffer("");if(…...
庆祝1024
在CSDN1024这一天,我不禁回想起自己这几年来在这个平台上的经历。回忆着初来时的稚嫩,如今的迷茫与期待,我深深地感受到自己还需不断努力。 回想起八年前,我刚刚步入计算机科学与技术的领域,满怀激情地加入了CSDN这个高…...
[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...
高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...
免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...
HybridVLA——让单一LLM同时具备扩散和自回归动作预测能力:训练时既扩散也回归,但推理时则扩散
前言 如上一篇文章《dexcap升级版之DexWild》中的前言部分所说,在叠衣服的过程中,我会带着团队对比各种模型、方法、策略,毕竟针对各个场景始终寻找更优的解决方案,是我个人和我司「七月在线」的职责之一 且个人认为,…...
用递归算法解锁「子集」问题 —— LeetCode 78题解析
文章目录 一、题目介绍二、递归思路详解:从决策树开始理解三、解法一:二叉决策树 DFS四、解法二:组合式回溯写法(推荐)五、解法对比 递归算法是编程中一种非常强大且常见的思想,它能够优雅地解决很多复杂的…...
数据库正常,但后端收不到数据原因及解决
从代码和日志来看,后端SQL查询确实返回了数据,但最终user对象却为null。这表明查询结果没有正确映射到User对象上。 在前后端分离,并且ai辅助开发的时候,很容易出现前后端变量名不一致情况,还不报错,只是单…...
