【深入浅出SpringCloud源码探究】「Netflix系列之Ribbon+Fegin」微服务化的负载均衡组件源码剖析与实战开发全流程(Ribbon篇)

微服务化的负载均衡组件源码剖析与实战开发全流程

  • 什么是负载均衡
    • 负载均衡的种类
      • 服务器端负载均衡(S-LB)
      • 客户端负载均衡(C-LB)
        • 注解@LoadBalanced
        • LoadBalancerAutoConfiguration类
          • LoadBalancerClient类
            • 源码分析
          • ServiceInstanceChooser类
      • 内置负载均衡策略的介绍
        • IRule
          • `IRule`的源码
          • `IRule`接口定义了3个方法
          • 主要方法是
        • IRule的实现类
          • 八种常见的负载均衡策略
          • 负载均衡的自定义
            • 通过代码实现 - 配置类
          • 同时使用2种以上的不同策略算法
            • 移除@Configuration 注解
            • 操作处理方式
            • 通过配置配置文件实现不同的算法
      • 如何对负载均衡策略进行扩展
        • 继承 AbstractLoadBalancerRule 类
        • 通过配置文件
    • 总结分析

什么是负载均衡

负载均衡是通过将请求流量分发到多个服务器来实现资源分配的一种策略。它可以确保各个服务器在处理请求方面的负载均衡,并能够更高效地利用系统资源。负载均衡的主要目标是避免服务器过载,并通过在不同的服务器之间分发负载,提高系统的可伸缩性和可用性。

负载均衡的种类

通过负载均衡,我们可以在服务器集群中有效地分配请求,从而实现更快的响应时间和更好的用户体验。目前负载均衡的方式分类主要有两种:服务器端负载均衡(nginx)和客户端负载均衡(Ribbon)
在这里插入图片描述

服务器端负载均衡(S-LB)

服务器端负载均衡(如Nginx)是一种将请求流量分发到多个服务器的方法,以提高系统的性能和可靠性。通过将请求分发到不同的服务器,负载均衡可以避免单个服务器的过载,并能够更均衡地分配请求负载,从而提高整体的响应能力。
在这里插入图片描述

客户端负载均衡(C-LB)

客户端负载均衡(如Ribbon)是在客户端层面上进行的负载均衡。当客户端发起请求时,通过负载均衡算法,Ribbon可以选择最合适的服务器来处理请求。这种方式使得客户端可以根据实际情况选择最佳的服务器,提高了系统的可扩展性和容错性。
在这里插入图片描述

注解@LoadBalanced

作用:识别应用名称,并进行负载均衡。

在Spring Cloud应用中,使用RestTemplate进行服务间的通讯时,我们可以添加@LoadBalanced注解来开启负载均衡。

在一个微服务架构中,通常一个服务会有多个实例,并且这些实例部署在不同的服务器上。为了保证服务的高可用性,我们需要在这些实例之间进行负载均衡。Spring Cloud提供了@LoadBalanced注解,它可以在RestTemplate中实现负载均衡:

@Configuration
public class RestTemplateConfiguration {

    @LoadBalanced  // 使用 @LoadBalanced 注解实现负载均衡
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

在RestTemplate添加了@LoadBalanced注解之后,我们就可以直接使用服务名来调用其他服务,而不再需要关心具体的IP和端口号。

@Autowired
private RestTemplate restTemplate;

public String runTaskExecute() {
    return restTemplate.getForObject("http://serviceName/execute", String.class);
}

当发起请求时,Ribbon会自动从服务注册中心获取serviceName的所有实例,然后根据负载均衡策略选择一个实例进行调用,这样就实现了负载均衡。默认负载均衡策略是基于轮询算法,平均分配请求给每个服务实例。

LoadBalancerAutoConfiguration类

Ribbon的负载均衡自动配置需满足两个条件:

  1. RestTemplate类必须在当前项目的环境中可用,@ConditionalOnClass(RestTemplate.class)
  2. Spring的Bean工厂中必须存在LoadBalancerClient的实现类的Bean实例。@ConditionalOnBean(LoadBalancerClient.class)
    在这里插入图片描述

下面是LoadBalancerAutoConfiguration的源码:

/**
 * Auto configuration for Ribbon (client side load balancing).
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Will Tran
 */
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
    @LoadBalanced
    @Autowired(required = false)
    private List<RestTemplate> restTemplates = Collections.emptyList();

    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializer(final List<RestTemplateCustomizer> customizers) {
        return new SmartInitializingSingleton() {
            @Override
            public void afterSingletonsInstantiated() {
                for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                    for (RestTemplateCustomizer customizer : customizers) {
                        customizer.customize(restTemplate);
                    }
                }
            }
        };
    }
}
LoadBalancerClient类

LoadBalancerClient的接口的定义,该接口在Spring Cloud中被用来实现客户端的负载均衡。它是一个Spring Cloud特有的接口,扩展自ServiceInstanceChooser接口。这个接口定义了如何从服务注册中心获取服务实例并对其进行负载均衡。

import java.io.IOException;
import java.net.URI;

/**
 * Represents a client side load balancer.
 * 
 * @author Spencer Gibb
 */
public interface LoadBalancerClient extends ServiceInstanceChooser {

    /**
     * Execute request using a serviceInstance from the LoadBalancer for the specified service.
     *
     * @param serviceId the service id to look up the LoadBalancer
     * @param request allows implementations to execute pre and post actions such as incrementing metrics
     * @return the result of the LoadBalancerRequest callback on the selected ServiceInstance 
     */
    <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

    /**
     * Execute request using a ServiceInstance from the LoadBalancer for the specified service.
     *
     * @param serviceId the service id to look up the LoadBalancer
     * @param serviceInstance the service to execute the request to
     * @param request allows implementations to execute pre and post actions such as incrementing metrics
     * @return the result of the LoadBalancerRequest callback on the selected serviceInstance 
     */
    <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;

    /**
     * Create a proper URI with a real host and port for systems to utilize.
     * Some systems use a URI with the logical service name as the host, such as http://myservice/path/to/service.
     * This will replace the service name with the host:port from the serviceInstance.
     *
     * @param instance
     * @param original a URI with the host as a logical service name
     * @return a reconstructed URI
     */
    URI reconstructURI(ServiceInstance instance, URI original);
}

源码分析

这些方法主要在客户端向服务端发起请求时使用,以实现负载均衡的效果:

  • execute(String serviceId, LoadBalancerRequest<T> request) throws IOException:负载均衡器选择的服务实例执行请求。serviceId是要查找负载均衡器的服务的id,request参数让实现类在服务调用前后执行一些操作,如度量增量等。

  • execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException:增加了serviceInstance参数,该参数表示要执行请求的服务,是在已经选择了服务实例后执行请求。

  • reconstructURI(ServiceInstance instance, URI original):一些系统使用的是逻辑服务名作为主机的URI,如http://myservice/path/to/service,这种URI中的主机名不是真实的网络地址,而是服务名。

在基于服务名进行路由的微服务系统,为了获取实际的网络地址(host:port),就需要这个转换方法,把逻辑服务名替换为从服务实例获取的真实主机名和端口

ServiceInstanceChooser类

接口ServiceInstanceChooser,该接口在Ribbon中被用作负载均衡策略的接口,应用可以实现这个接口来自定义自己的负载均衡策略。在这个接口中定义了一个choose方法,该方法用于从负载均衡器中为特定服务选择一个服务实例。

/**
 * Implemented by classes which use a load balancer to choose a server to
 * send a request to.
 *
 * @author Ryan Baxter
 */
public interface ServiceInstanceChooser {

    /**
     * Choose a ServiceInstance from the LoadBalancer for the specified service.
     *
     * @param serviceId the service id to look up the LoadBalancer
     * @return a ServiceInstance that matches the serviceId
     */
    ServiceInstance choose(String serviceId);
}

负载均衡器具备以下几个主要职能:
在这里插入图片描述

  1. 能够根据特定的服务ID从负载均衡器中选取一个合适的服务实例。

  2. 能够利用选取的服务实例执行特定的任务或请求。

  3. 能够为系统生成一个有效的“主机名:端口号”格式的URI,以便于系统的其他部分使用。


内置负载均衡策略的介绍

IRule

IRule是Ribbon客户端内置负载均衡策略的接口定义,所有Ribbon内建策略或者自定义策略都需要实现这个接口。它主要的决定了服务选择的策略,即根据什么样的规则从一组服务中选取一个有效的服务实例。

IRule的源码
package com.netflix.loadbalancer;

/*
 * The class that will be used by clients of Ribbon API to pick a server from
 * the already filtered list of servers. The responsibility of implementations
 * will be to spread the load of request traffic among the list of servers.
 */
public interface IRule {
    /*
     * set load balancer
     *
     * @param lb
     */
    public void setLoadBalancer(ILoadBalancer lb);

    /*
     * get load balancer
     *
     * @return
     */
    public ILoadBalancer getLoadBalancer();

    /*
     * Choose a server from load balancer.
     *
     * @param key An object that the load balancer may use to determine
     *            which server to return. key is defined by client, and
     *            can be anything.  load balancer implementations may
     *            choose to return a server based on key or not.
     *
     * @return server chosen
     */
    public Server choose(Object key);
}
IRule接口定义了3个方法
  1. setLoadBalancer(ILoadBalancer lb): 用来设置负载均衡器。
  2. getLoadBalancer(): 用来获取负载均衡器。
  3. choose(Object key): 用来从已经过滤过的服务列表中选择合适的服务。key参数是由客户端定义的,可以是任何对象。实现此接口的类可以选择根据key选择服务,也可以忽略key
主要方法是
public abstract Server choose(Object key);
  • 出参: Server:这是Ribbon定义的一个类,代表了一个可以达到的、执行某个服务的物理或者虚拟的实例。
  • 入参: choose(Object key):此方法根据传入的key(key的具体含义根据实现类的解读可能有所不同),选择并返回一个Server。
IRule的实现类

当你实现IRule接口时,你可以自己定义服务选择的规则,比如你可以根据服务的实际情况(如服务器负载、网络延迟等信息)来选择最符合你需求的服务,创建出定制化的负载均衡策略。

八种常见的负载均衡策略

在这里插入图片描述
八种常见的负载均衡策略(BestAvailableRule、AvailabilityFilteringRule等)都是IRule的实现类,每种策略都有自己独特的服务选择规则。

策略名称描述
BestAvailableRule该策略选择并发请求最少的server。若某个Server处于熔断状态,将忽略该Server。
AvailabilityFilteringRule过滤掉连续连接失败被标记为熔断的Server,以及并发连接高的Server。
ZoneAvoidanceRule复合判断Server所在区域的性能和Server的可用性来选择Server,剔除不可用的区域的所有Server和连接数过多的Server。
RandomRule随机策略,会在所有可用的Server中进行随机选择。
RetryRule为选定的负载均衡策略添加重试机制。在配置的时间段内若无法成功选择Server,将会持续重试。
RoundRobinRule轮询策略,每次请求会轮询选择一个Server。此为默认策略。
WeightedResponseTimeRule根据响应时间分配权重,响应时间较长的Server权重越小,被选中的可能性越低。
ResponseTimeWeightedRule与WeightedResponseTimeRule一致,是其旧版本名称。
负载均衡的自定义
通过代码实现 - 配置类

仅需两个简洁的配置,即可确定轮询的策略。

@Configuration
public class RandomRuleConfig {
	@Bean
	public IRule randomRule() {
		return new RandomRule();
	}
}

@Configuration
@RibbonClient(name = "client-balance-provider", configuration = RandomRuleConfig.class)
public class ProviderConfiguration {
}

注意,如果在RandomRuleConfig类中添加了@Configuration注解,所有的负载均衡策略将被覆盖。这是因为如果RandomRuleConfig类被SpringContext扫描到,这会导致所有的策略被@RibbonClients共享,从而实现覆盖。

同时使用2种以上的不同策略算法
移除@Configuration 注解

以下是整理和优化后的代码:

// Define the round robin rule configuration.
public class RoundRobinRuleConfig {
    @Bean
    public IRule roundRobinRule() {
        return new RoundRobinRule();
    }
}

RoundRobinRuleConfig类定义了roundRobinRule方法,该方法生成了RoundRobinRule的实例,表示采用轮询负载均衡策略。

// The configuration class for the Ribbon client.
@Configuration
@RibbonClient(name = "spring-cloud-provider2", configuration = RoundRobinRuleConfig.class)
public class Provider2Configuration {}

Provider2Configuration类则是spring-cloud-provider2服务的Ribbon配置类,该类指定在调用spring-cloud-provider2服务时,使用RoundRobinRuleConfig类中定义的轮询策略。

操作处理方式

需要新增RoundRobinRuleConfig类,并移除其中的@Configuration注解。同时,也要移除RandomRuleConfig类中的@Configuration注解。

  1. 可以通过指定@ComponentScan的扫描路径实现(默认情况下,扫描路径为主类所在的所有文件夹)。

  2. RoundRobinRuleConfig类移至主类之外,防止主程序进行扫描,一定要确保SpringContext无法扫描到这些类。

通过配置配置文件实现不同的算法

通过配置文件,可以让应用来灵活的选择不同的负载均衡策略。设置格式为:<application-name>.ribbon.NFLoadBalancerRuleClassName=<fully-qualified-class-name>

这样就可以根据应用的实际需要,选择最适合的负载均衡策略。

#设置策略 
spring-cloud-provider.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
#spring-cloud-provider2.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

如何对负载均衡策略进行扩展

当内置的负载均衡策略不满足业务需求的时候,我们就需要自定义 Ribbon 的负载策略。

继承 AbstractLoadBalancerRule 类

扩展了AbstractLoadBalancerRule`抽象类,用于定义自定义的负载均衡规则。这个规则是,从所有可用的服务实例(upList)中,选择端口为7779的服务实例为提供服务的实例。如果没有找到,或者选中的实例已经不可用,则重新选择。如果因为线程中断导致获取服务实例错误,直接返回null。

/**
 * Custom Ribbon load balancing strategy.
 */
public class RoncooCustomRule extends AbstractLoadBalancerRule {

    private Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }

        Server server = null;

        while (server == null) {
            if (Thread.interrupted()) {
                return null;
            }
            // Available service instances
            List<Server> upList = lb.getReachableServers();
            // Only fetch service instance with port: 7779
            for (Server s : upList) {
                if (s.getPort() == 7779) {
                    server = s;
                    break;
                }
            }
            if (server == null) {
                Thread.yield();
                continue;
            }
            if (server.isAlive()) {
                return server;
            }
            server = null;
            Thread.yield();
        }
        return server;
    }
    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        // No-Operation
    }
}
通过配置文件
spring-cloud-provider.ribbon.NFLoadBalancerRuleClassName=com.roncoo.education.configuration.RoncooCustomRule

总结分析

Ribbon是Netflix开发的一款基于HTTP和TCP的客户端负载均衡器。它在Spring Cloud环境中广泛使用于执行HTTP请求的负载均衡,其主要知识点和技术特性包括:

  1. 客户端负载均衡:Ribbon是一个客户端的负载均衡器,这意味着它会在客户端运行,并在发起请求时对一组服务实例进行选择。

  2. 灵活的负载均衡策略:Ribbon内置了多种负载均衡策略,如轮询、随机、响应时间加权等。用户也可以自定义策略以满足特殊要求。

  3. 故障转移:在访问某个服务实例失败时,Ribbon可以自动选择另一个实例进行访问,以提高系统的可用性。

  4. 扩展性良好:Ribbon的结构让它可以方便地扩展和定制,以支持各种各样的需求。
    在这里插入图片描述

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

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

相关文章

linux20day 排序sort 字符处理cut cpu使用占比排序 awk文本数据处理

目录 1、排序sort参数用法排序&#xff08;-n&#xff09;从大到小 倒叙&#xff08;-r&#xff09; cpu使用占比排序&#xff08;ps aux --sort -%cpu&#xff09; 2、截取到某个字符串 cut3、awk处理文本文件用法&#xff1a;打印等于 和不等于 1、排序sort 经常用于排序 参…

Spring对JUnit4和junit5的支持

Junit4支持 第一步&#xff1a;准备工作&#xff1a; 引入JUnit4的依赖&#xff0c;Spring对JUnit支持的依赖还是&#xff1a;spring-test&#xff0c;如下&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://ma…

Docker-consule 服务发现与注册

consul服务更新和服务发现 什么是服务注册与发现 服务注册与发现是微服务架构中不可或缺的重要组件。起初服务都是单节点的&#xff0c;不保障高可用性&#xff0c;也不考虑服务的压力承载&#xff0c;服务之间调用单纯的通过接口访问。直到后来出现了多个节点的分布式架构&…

数据库基础(实体,管理系统,日志,数据类型,键与约束)

基本概念 数据&#xff08;Data&#xff09;&#xff1a; 数据是描述事物的信息&#xff0c;可以是数字、文字、图像、音频等形式。数据库中存储的就是这些数据&#xff0c;这些数据可以是具体的实体&#xff08;如一个人的信息&#xff09;&#xff0c;也可以是抽象的概念&…

HTTP 503错误:服务不可用,原因及解决方案

在Web开发中&#xff0c;HTTP状态码是用于表示Web服务器响应的各种状态。其中&#xff0c;HTTP 503错误表示服务不可用&#xff0c;这意味着服务器暂时无法处理请求。这个错误通常是由于服务器过载、维护或其他原因导致的。 原因&#xff1a; 服务器过载&#xff1a;当服务器…

产品入门第五讲:Axure交互和情境

目录 一.Axure交互和情境的介绍 1.交互介绍 概念 常见的Axure交互设计技巧 2.情境介绍 概念 常见的Axure情境设计技巧&#xff1a; 二.实例展示 1.ERP登录页到主页的跳转 2.ERP的菜单跳转到各个页面 &#x1f4da;&#x1f4da; &#x1f3c5;我是默&#xff0c;一个…

HarmonyOS 应用开发 —— ArkTS 可复用代码块梳理

目录 ArkTS 复用代码块弹窗提醒网络请求消息通知如何给任意组件添加 multiState&#xff1f;如何给 ListItem 添加删除按钮&#xff0c; ArkTS 复用代码块 记录一下自己这几天学习成果&#xff0c;我发官方文档很全&#xff0c;都是有时候查找起来不是很容易&#xff0c;因此总…

Golang学习之路一开山篇

Golang学习之路一开山篇 初识 Golang 我第一次接触 Golang 是在2016年, 当时在深圳工作, 项目需要用Golang, 当时在犹豫要不要学还是走, 毕竟Java开发搞了很多年了, 说放弃还是有难度的, 其实也不是放弃Java, 说不定其他项目还是要使用Java. 在领导的再三劝说下, 开启了Golan…

vue中哪些数组的方法可以做到响应式

Vue2 中为什么直接通过数组的索引修改元素是不会触发视图更新 vue2 为什么不直接监听数组 Vue2 对于数组提供了一些变异方法 重写数组方法源码分析 定义拦截器将拦截器挂载到数组上面收集依赖 扩展&#xff1a;理解Vue2如何解决数组和对象的响应式问题 对复杂对象的处理 复杂对…

2023,真人漫改走上IP高地

你能接受自己的纸片人老公/老婆变成了真人吗&#xff1f; 无论大家能不能接受&#xff0c;真人漫改都已经成为了影视行业的新趋势&#xff0c;而阅文集团收购腾讯动漫的举措&#xff0c;无疑是为漫改剧添了一把火。 在阅文宣布以6亿人民币的价格收购腾讯动漫旗下的相关业务以…

scipy.signal.hilbert和scipy.fftpack.hilbert的区别

提示&#xff1a;分析scipy.signal.hilbert和scipy.fftpack.hilbert在应用的区别 一、代码 import matplotlib import matplotlib.pyplot as plt import numpy as np from pyhht import EMD from scipy.signal import hilbert import tftb.processing from scipy import signa…

【Linux】Redis 数据库安装教程(Ubuntu 22.04)

前言 Redis是一个开源的内存数据库&#xff0c;它可以用作键值存储、缓存和消息代理。它支持各种数据结构&#xff0c;包括字符串、哈希、列表、集合、有序集合等。Redis通常被用于构建高性能、可扩展的应用程序&#xff0c;特别是那些需要快速访问数据和实时数据处理的应用场…

Windows 11上边两个空格导致我多熬了1个多小时

将图中的文件路径复制&#xff0c;然后到文件管理器里边去搜索。 发现找不到&#xff0c;可是明明就在这里啊。 我百思不得其解&#xff0c;还以为是IDEA出了问题&#xff0c;我只能是重新启动项目&#xff0c;结果还是告诉我找不到文件。 要是同一个目录下已经有一个名为a…

Gateway No servers available for service

springCloud集成网关测试报错找不到服务&#xff0c;如下 造成这种错误可能是下面两种原因 1、nacos注册的服务不在一个命名空间内&#xff0c;导致找不到服务503 spring cloud:nacos:discovery:server-addr: 127.0.0.1:8848config:server-addr: 127.0.0.1:8848file-extensio…

如何测试和挑选 2024 年最佳 Mac 数据恢复软件

数据是无价的。有些具有货币价值&#xff0c;例如您的银行帐户详细信息或官方文件。其他的则具有情感价值且不可替代&#xff0c;例如家庭照片。所有这些都存储在您的硬盘中&#xff0c;任何事情都可能出错。它可能会遇到技术错误&#xff0c;例如恶意软件攻击或驱动器故障&…

WeChatMsg: 导出微信聊天记录 | 开源日报 No.108

Mozilla-Ocho/llamafile Stars: 3.5k License: NOASSERTION llamafile 是一个开源项目&#xff0c;旨在通过将 lama.cpp 与 Cosmopolitan Libc 结合成一个框架&#xff0c;将 LLM (Large Language Models) 的复杂性折叠到单个文件可执行程序中&#xff0c;并使其能够在大多数…

Axure动态面板的应用与ERP系统登录界面、主页左侧菜单栏、公告栏的绘制

目录 一、动态面板 1.1 简介 1.2 使用动态面板的原因 二、动态面板之轮播图实现案例 2.1 完成步骤 2.2 最终效果 三、动态面版之多方式登录案例 四、动态面板之后台主界面左侧菜单栏 五、ERP登录界面 六、ERP主界面菜单栏 七、ERP公告栏 八、登录页面跳转公告栏 一…

数据结构 | Log-Structured Merge Tree (LSM Tree)

今天介绍LSM Tree这个数据结构&#xff0c;严格意义上来说&#xff0c;他并不像他的名字一样是一棵树型的数据结构&#xff0c;而更多是一种设计思想。 LSM Tree最先在1996年被提出&#xff0c;后来被广泛运用于现代NoSQL&#xff08;非关系型数据库&#xff09;系统中&#xf…

25 redis 中 cluster 集群的工作模式

前言 我们这里首先来看 redis 这边实现比较复杂的 cluster集群模式 整个 cluster集群 中会包含多对 MasterSlave 的组合, 然后这多对 MasterSlave 来分解 16384 个 slot 然后 客户端这边 set, get 的时候, 先根据 key 计算对应存储的 slot, 然后 服务器这边响应 MOVED 目标…

Python自动化测试如何自动生成测试用例?

汽车软件开发自动化测试攻略 随着软件开发在造车行业中占有越来越重要的地位&#xff0c;敏捷开发的思想在造车领域中也逐渐地被重视起来&#xff0c;随之而来的是整车厂对自动化测试需求越来越强烈。本文结合北汇在自动化测试方面的丰富经验&#xff0c;简单介绍一下实施自动…