【Sentinel】Sentinel簇点链路的形成

news2024/9/29 15:30:41

说明

一切节点的跟是 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";

    @Override
    public 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=false
            String 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 necessary
    if (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的EntranceNode
        DefaultNode 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 {
                            // 创建一个新的EntranceNode
                            node = 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();
                }
            }
        }
        // 创建一个新的context
        context = 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);
                // 更新map
                map = 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());
                // 创建 SentinelInvocationHandler
                return 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 HardCodedTarget
    if (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://url
        if (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 handle
                if (!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 的,因此全局共享,且创建后内容一直存在
    // 因此一个资源只会创建一次ClusterNode
    if (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

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

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

相关文章

阿里云对象存储OSS文件无法预览,Bucket设置了Referer

您发起的请求头中没有Referer字段或Referer字段为空&#xff0c;与请求Bucket设置的防盗链策略不相符。 解决方案 您可以选择以下任意方案解决该问题。 在请求中增加Referer请求头。 GET /test.txt HTTP/1.1 Date: Tue, 20 Dec 2022 08:48:18 GMT Host: BucketName.oss-examp…

JavaSE ---01 数据类型与运算符

正所谓温故而知新,可以为师矣,虽然已经学过一遍javase,但是回头复习仍然能找到很多初学的时候遗忘的点,所以我们在学习的途中还是要保持空杯心态,这样才能走的更远,切忌眼高手低. 1.变量 说到变量大家都经常去使用,那么什么是变量呢?下面给出变量的定义 变量指的是程序运行时可…

基于windows10的pytorch环境部署及yolov8的安装及测试

第一章 pytorch环境部署留念 第一步&#xff1a;下载安装anaconda 官网地址 &#xff08;也可以到清华大学开源软件镜像站下载&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/&#xff09; 我安装的是下面这个&#xff0c;一通下一步就完事儿。 第二步…

【原创】RockyLinux设置网络/网卡唤醒/NetworkManager设置网络唤醒

前言 由于我的5600G无法安装CentOS系统&#xff0c;因此选择安装了RockyLinux&#xff0c;但是RockyLinux用的是NetworkManager&#xff0c;网上说的都是之前CentOS的方法&#xff0c;因此完全无效&#xff0c;这里来介绍一下RockyLinux如何设置网络唤醒。 修改BIOS设置 我的…

通义大模型使用指南之通义听悟

一、注册 我们可以打开以下网站&#xff0c;用手机号注册一个账号即可。 https://tongyi.aliyun.com/ 二、使用介绍 如图&#xff0c;我们可以看到有三个大项功能&#xff0c;通义千问、通义万相、通义听悟。下来我们体验一下通义听悟的功能。 1、通义听悟 1、1基本功能 当我们…

Java枚举(Enum)的使用

目录 一、枚举类型的定义 二、枚举类型的使用 &#xff08;一&#xff09;、枚举类型的常用方法 &#xff08;二&#xff09;、枚举的简单使用 &#xff08;1&#xff09;、和switch的搭配使用 &#xff08;2&#xff09;、枚举类型的values方法 &#xff08;3&#xff…

JAVA毕业设计103—基于Java+Springboot+vue的药店管理系统(源码+数据库)

基于JavaSpringbootvue的药店管理系统(源码数据库) 一、系统介绍 本系统前后端分离 -功能: 登录、药库药品管理、统计查询、药房管理、物资管理、挂号管理、账号管理、角色管理、权限管理、登录日志管理、药品管理、药品类型管理、客人类型管理 二、所用技术 后端技术栈&a…

PCL 透视投影变换(OpenGL)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 在现实生活中,我们总会注意到离我们越远的东西看起来更小。这个神奇的效果被称之为透视(Perspective)。透视的效果在我们看一条无限长的高速公路或铁路时尤其明显,正如下面图片显示的这样: 由于透视的原因,平行线…

优雅草蜻蜓z系统暗影版前台崩溃,后台提示:系统接口异常502,java项目管理yyc-admin后台管理系统服务无法启动的解决方案

蜻蜓z系统暗影版提示系统接口异常502 蜻蜓z系统系统接口异常502&#xff0c;java项目管理yyc-admin后台管理系统服务无法启动&#xff0c;解决方案 系统稳定运行一些时间后突然由于问题造成无法启动&#xff0c;提示接口异常&#xff1a; 原因过程分析&#xff1a; 现象&am…

【Docker】Docker Swarm介绍与环境搭建

为什么不建议在生产环境中使用Docker Compose 多机器如何管理&#xff1f;如何跨机器做scale横向扩展&#xff1f;容器失败退出时如何新建容器确保服务正常运行&#xff1f;如何确保零宕机时间&#xff1f;如何管理密码&#xff0c;Key等敏感数据&#xff1f; Docker Swarm介…

计算机视觉中的数据预处理与模型训练技巧总结

计算机视觉主要问题有图像分类、目标检测和图像分割等。针对图像分类任务&#xff0c;提升准确率的方法路线有两条&#xff0c;一个是模型的修改&#xff0c;另一个是各种数据处理和训练的技巧(tricks)。图像分类中的各种技巧对于目标检测、图像分割等任务也有很好的作用&#…

制造业中的微小缺陷检测——应用场景分析与算法选择(YoloV8/CANet)

一、缺陷检测任务 缺陷检测的任务通常可以分为三个主要阶段&#xff0c;包括缺陷分类、缺陷定位和缺陷分割。 1.缺陷分类 缺陷分类是检测过程的第一步&#xff0c;目的是将检测到的缺陷区域分类为不同的类别&#xff0c;通常是根据缺陷的性质或类型进行分类。分类的类别包括…

深度学习使用Keras进行迁移学习提升网络性能

上一篇文章我们用自己定义的模型来解决了二分类问题,在20个回合的训练之后得到了大约74%的准确率,一方面是我们的epoch太小的原因,另外一方面也是由于模型太简单,结构简单,故而不能做太复杂的事情,那么怎么提升预测的准确率了?一个有效的方法就是迁移学习。 迁移学习其…

C#反射的应用及相关代码示例

在C#编程中&#xff0c;反射是一种强大的工具&#xff0c;它允许程序在运行时动态地获取类型信息、访问和操作类成员。反射为开发人员提供了更大的灵活性和扩展性&#xff0c;使得我们可以编写更加通用和动态的代码。本文将探讨C#反射的应用&#xff0c;并提供一些相关的代码示…

程序员加油!最新最全Java面试题及解答(上百道题,近5w字,包括Redis、MySQL、框架、微服务、消息中间件、集合、jvm,多线程、常见技术场景)

刚看完黑马教程的新版Java面试专题视频教程&#xff0c;java八股文面试全套真题深度详解&#xff08;含大厂高频面试真题&#xff09;&#xff0c;对面试题分专题整理&#xff0c;方便面试突击 Redis相关面试题 Redis相关面试题 面试官&#xff1a;什么是缓存穿透 ? 怎么解决…

软考系列(系统架构师)- 2016年系统架构师软考案例分析考点

试题一 软件架构&#xff08;质量属性、架构风格对比、根据描述填空&#xff09; 试题二 系统开发&#xff08;用例图参与者、用例关系、类图关系&#xff09; 学生、教师、管理员、时间、打印机【问题2】&#xff08;7分&#xff09; 用例是对系统行为的动态描述&#xff0c;用…

【强连通+背包】CF1763E

Problem - E - Codeforces 题意 思路 首先&#xff0c;先考虑第一个条件&#xff0c;要保证是p个节点互相到达且节点数最少&#xff0c;一定是个强连通&#xff0c;图的形态一定就是和强连通相关的。 然后&#xff0c;因为在这个前提上&#xff0c;要让单向节点数尽可能多&a…

归并排序与计数排序(含代码)

目录 目录&#xff1a; 1:归并排序递归 2:归并排序的非递归 3&#xff1a;计数排序的思想 1&#xff1a;归并排序递归 思路&#xff1a;归并排序是采用分治算法的一种排序&#xff0c;将两个有序的子数组合并到一个数组中去使得数组完全有序&#xff0c;所以我们先使子数组有序…

CAN测量模块总线负载率,你关注了吗?

一 背景 随着新能源汽车的飞速发展&#xff0c;整车系统日趋复杂&#xff0c;整车性能的可靠性也变得愈发重要。在车辆测试过程中&#xff0c;为应对更加多样的试验需求&#xff0c;传感器的种类和数量会随着测量种类而增加&#xff0c;数据量也会因此变得越发庞大&#xff0c…

docker部署prometheus+grafana服务器监控(一)

docker-compose 部署prometheusgrafana Prometheus Prometheus 是有 SoundCloud 开发的开源监控系统和时序数据库&#xff0c;基于 Go 语言开发。通过基于 HTTP 的 pull 方式采集时序数据&#xff0c;通过服务发现或静态配置去获取要采集的目标服务器&#xff0c;支持多节点工…