Spring cloud负载均衡@LoadBalanced LoadBalancerClient

news2024/12/28 5:57:29

LoadBalance vs Ribbon

由于Spring cloud2020之后移除了Ribbon,直接使用Spring Cloud LoadBalancer作为客户端负载均衡组件,我们讨论Spring负载均衡以Spring Cloud2020之后版本为主,学习Spring Cloud LoadBalance,暂不讨论Ribbon。

两者有什么区别、Spring Cloud为什么移除了Ribbon转向了Spring Cloud LoadBalancer,改日研究。

回顾

上篇文章我们学习了Spring Cloud LoadBalance负载均衡底层原理中的:

  1. @LoadBalanced注解的使用:与@Bean一起作用在RestTemplate上:
   @Bean
   @LoadBalanced
   public RestTemplate restTemplate(){
       return new RestTemplate();
   }

可以实现:在注入RestTemplate对象到Spring IoC容器的同时,启用Spring的负载均衡机制。
2. @LoadBalanced注解的底层原理:在LoadBalancerAutoConfiguration初始化的过程中,创建拦截器LoadBalancerInterceptor,对请求进行拦截从而实现负载均衡。
3. LoadBalancerInterceptor拦截器在执行请求前调用其intercept方法,intercept负责负载均衡的实现(具体的实现逻辑尚未研究)

其中第3点,intercept方法是怎么实现负载均衡的,我们还没有深入研究,这是我们今天这篇文章的主要目的。

Spring Cloud负载均衡原理

LoadBalancerClient及ReactorLoadBalancer初始化

LoadBalancerInterceptor拦截器的intercept方法究竟是怎么实现负载均衡的?

拦截方法intercept中会通过LoadBalancerClient对象(从Spring IoC容器中获取)实现负载均衡,LoadBalancerClient对象的注入以及拦截原理这个过程稍微复杂一点,所以我们先用简单的方式描述其实现逻辑,然后再从源码角度进行跟踪。

我们在上一篇文章中说过的spring-cloud-commons包下的自动配置类(如图):
在这里插入图片描述
比如对@LoadBalanced注解的解析、LoadBalancerInterceptor的注入等等,就是上面自动配置类LoadBalancerAutoConfiguration完成的。

Spring cloud有两个名字一样的自动配置类LoadBalancerAutoConfiguration,位于不同的包下,上面一个是在spring-cloud-commes包下,下面还要提到的一个是在spring-cloud-loadbalancer包下。

在这里插入图片描述
spring-cloud-loadbalancer包下的自动配置类LoadBalancerAutoConfiguration负责注入LoadBalancerClientFactory对象,LoadBalancerClientFactory负责创建子容器(SpringCloud通过子容器来隔离各微服务的访问参数、负载均衡策略等)。创建LoadBalancerClientFactory对象的过程中将LoadBalancerClientConfiguration设置给他的defaultConfigType属性,在子容器初始化的过程中将LoadBalancerClientConfiguration注册为配置类,从而通过LoadBalancerClientConfiguration配置类完成ReactorLoadBalancer的创建并注入子容器中。ReactorLoadBalancer是负载均衡策略接口,默认的负载均衡策略为RoundRobinLoadBalancer。

spring-cloud-loadbalancer包下的另外一个自动配置类BlockingLoadBalancerClientAutoConfiguration负责注入拦截器中的LoadBalancerClient,实际注入的是BlockingLoadBalancerClient对象,BlockingLoadBalancerClient会持有LoadBalancerClientFactory对象。

LoadBalancerInterceptor的intercept方法会转交给BlockingLoadBalancerClient处理,BlockingLoadBalancerClient通过LoadBalancerClientFactory对象向子容器(子容器不存在的话首先创建子容器)获取相关配置以及负载均衡策略RoundRobinLoadBalancer,最终通过RoundRobinLoadBalancer实现负载均衡。

需要注意,子容器不是在系统初始化过程中创建的,而是在处理请求的过程中创建的。

下面分析源码。

LoadBalancerClient

从应用层入手分析,先看上一篇文章的案例中的orderServicede的代码:

@Service
public class OrderService {
    @Autowired
    private RestTemplate restTemplate;
    public String getOrder(){
        //通过userService获取user信息
        String url="http://userservice/user/getUser";
        System.out.println("url"+url);
        User user=restTemplate.getForObject(url,User.class);
        System.out.println(user);
        return user.getName();
    }
}

restTemplate.getForObject最终会调用到LoadBalancerInterceptor的intercept方法:

    private LoadBalancerClient loadBalancer;
    
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
		String serviceName = originalUri.getHost();
		Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
		return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
	}

调用loadBalancer的execute方法,而loadBalancer是LoadBalancerClient 对象、是LoadBalancerInterceptor初始化过程中通过方法参数从SpringIoC容器中注入进来的。

前面提到过,自动配置类BlockingLoadBalancerClientAutoConfiguration负责注入拦截器中的LoadBalancerClient,实际注入的是BlockingLoadBalancerClient对象。为了不影响可读性,我们稍后再看这部分源码。

继续跟踪loadBalancer的execute方法。首先看一下LoadBalancerClient 的类结构;
在这里插入图片描述
接口LoadBalancerClient继承自接口ServiceInstanceChooser,接口定义了choose方法及execute方法(包括其重载方法)。其中execute是调用入口、也是模板方法:根据请求的服务serviceId(比如userService)通过调用choose方法获取到最终要调用的服务实例serviceInstance,最终调用到服务实例所提供的服务:

	@Override
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
		String hint = getHint(serviceId);
		LoadBalancerRequestAdapter<T, TimedRequestContext> lbRequest = new LoadBalancerRequestAdapter<>(request,
				buildRequestContext(request, hint));
		Set<LoadBalancerLifecycle> supportedLifecycleProcessors = getSupportedLifecycleProcessors(serviceId);
		supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
		ServiceInstance serviceInstance = choose(serviceId, lbRequest);
		if (serviceInstance == null) {
			supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
					new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, new EmptyResponse())));
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		return execute(serviceId, serviceInstance, lbRequest);
	}

继续跟踪choose方法,是在BlockingLoadBalancerClient类中实现的。

BlockingLoadBalancerClient

我们已经知道注入到Spring Ioc容器中的LoadBalancerClient其实是BlockingLoadBalancerClient对象,所以继续跟踪BlockingLoadBalancerClient的choose方法:

	@Override
	public <T> ServiceInstance choose(String serviceId, Request<T> request) {
		ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId);
		if (loadBalancer == null) {
			return null;
		}
		Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();
		if (loadBalancerResponse == null) {
			return null;
		}
		return loadBalancerResponse.getServer();
	}

我们需要重点关注的是两个方法:

第一个是LoadBalancerClientFactory的getInstance方法:通过serviceId从子容器中拿到ReactiveLoadBalancer,参数serviceId(服务Id),指的就是我们注册到Eureka注册中心的服务Id,比如前面案例中的userService。

第二个是ReactiveLoadBalancer的choose方法:根据不同的负载均衡策略,从服务队列中拿到serviceInstance。Spring cloud提供了两种负载均衡策略:随机策略RandomLoadBalancer和循环策略RoundRobinLoadBalancer。

我们先来看第一步:从子容器中获取ReactiveLoadBalancer对象。

子容器的创建

如果是首次调用、子容器不存在的情况下,LoadBalancerClientFactory负责创建子容器。

LoadBalancerClientFactory是reactiveLoadBalancer.Factory的实现类,继承自虚拟类NamedContextFactory,创建子容器的大部分代码都在NamedContextFactory类中。

我们首先看一下这个LoadBalancerClientFactory是怎么初始化的Spring IoC容器中的。其实前面已经说过了,是通过spring-cloud-loadbalancer包下的自动配置类LoadBalancerAutoConfiguration负责注入。

我们从源码角度验证一下,LoadBalancerAutoConfiguration源码:

	@ConditionalOnMissingBean
	@Bean
	public LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerClientsProperties properties) {
		LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory(properties);
		clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList));
		return clientFactory;
	}

看一下LoadBalancerClientFactory的构造方法:

	public LoadBalancerClientFactory(LoadBalancerClientsProperties properties) {
		super(LoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME);
		this.properties = properties;
	}

父类的构造方法:

	public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName, String propertyName) {
		this.defaultConfigType = defaultConfigType;
		this.propertySourceName = propertySourceName;
		this.propertyName = propertyName;
	}

可以看到LoadBalancerClientFactory创建的时候将LoadBalancerClientConfiguration.class赋值给他的父类NamedContextFactory的defaultConfigType属性,在创建子容器的时候LoadBalancerClientConfiguration类会被注册为子容器的配置类、从而通过LoadBalancerClientConfiguration完成ReactorLoadBalancer对象的注入(注入到子容器中)

NamedContextFactory

先对LoadBalancerClientFactory做一个简单的认识。

在这里插入图片描述LoadBalancerClientFactory继承自虚拟类NamedContextFactory,实现了接口DisposableBean和ApplicationContextAware,这两个接口我们并不陌生,在Spring生命周期回调的学习过程中中我们了解过这两个接口,Spring会在容器创建完成后通过ApplicationContextAware的setApplicationContext方法把ApplicationContext送回来、在容器销毁的时候回调DisposableBean接口的destroy方法。LoadBalancerClientFactory实现了这两个接口,所以LoadBalancerClientFactory就可以获取到Spring IoC根容器的applicationContext:

	@Override
	public void setApplicationContext(ApplicationContext parent) throws BeansException {
		this.parent = parent;
	}

检查NamedContextFactory的setApplicatonContext方法,发现他把Spring IoC容器设置为自己的父容器了:这也很好理解,从NamedContextFactory类名称判断,这个类的目的就是要创建“Named”容器、也就是命名容器,其实我们后面会发现就是用serviceId命名的容器,比如我们有userservice,那就会创建一个名字叫userservice的容器。通过ApplicationContextAware回调setApplicationContext方法将Spring Ioc容器设置为命名容器的“父容器”。

继续跟踪LoadBalancerClientFactory的getInstance方法,调用到父类NamedContextFactory的getInstance:

	@Override
	public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) {
		return getInstance(serviceId, ReactorServiceInstanceLoadBalancer.class);
	}
	public <T> T getInstance(String name, Class<T> type) {
		AnnotationConfigApplicationContext context = getContext(name);
		try {
			return context.getBean(type);
		}
		catch (NoSuchBeanDefinitionException e) {
			// ignore
		}
		return null;
	}

最终是向Spring的ApplicationContext获取类型为ReactorServiceInstanceLoadBalancer的bean。其中ApplicationContext通过getContext方法获取:

    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

	protected AnnotationConfigApplicationContext getContext(String name) {
		if (!this.contexts.containsKey(name)) {
			synchronized (this.contexts) {
				if (!this.contexts.containsKey(name)) {
					this.contexts.put(name, createContext(name));
				}
			}
		}
		return this.contexts.get(name);
	}

首先从contexts查找,contexts是以serviceId为键值的ConcurrentHashMap,缓存创建的ApplicationContext,如果尚未创建,则调用createContext方法创建后缓存到contexts中。

这个名字为contexts的ConcurrentHashMap其实就是NamedContextFactory的核心:创建的ApplicationContext缓存在以serviceId为键值的HashMap中,获取的时候以serviceId到contexts中去查找,查找到则直接返回、查找不到则创建后缓存。

createContext方法:

protected AnnotationConfigApplicationContext createContext(String name) {
		AnnotationConfigApplicationContext context;
		if (this.parent != null) {
			// jdk11 issue
			// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
			// https://github.com/spring-cloud/spring-cloud-openfeign/issues/475
			DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
			if (parent instanceof ConfigurableApplicationContext) {
				beanFactory.setBeanClassLoader(
						((ConfigurableApplicationContext) parent).getBeanFactory().getBeanClassLoader());
			}
			else {
				beanFactory.setBeanClassLoader(parent.getClassLoader());
			}
			context = new AnnotationConfigApplicationContext(beanFactory);
			context.setClassLoader(this.parent.getClassLoader());
		}
		else {
			context = new AnnotationConfigApplicationContext();
		}
		if (this.configurations.containsKey(name)) {
			for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
				context.register(configuration);
			}
		}
		for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
			if (entry.getKey().startsWith("default.")) {
				for (Class<?> configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}
		//注意这里会注册this.defaultConfigType到容器中
		context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
		context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,
				Collections.<String, Object>singletonMap(this.propertyName, name)));
		if (this.parent != null) {
			// Uses Environment from parent as well as beans
			context.setParent(this.parent);
		}
		context.setDisplayName(generateDisplayName(name));
		context.refresh();
		return context;
	}

代码比较长但是并不复杂,仔细看一下其实就是Spring IoC容器的初始化过程:

  1. 创建DefaultListableBeanFactory
  2. 创建AnnotationConfigApplicationContext
  3. 加载属于当前serviceId的配置
  4. 加载所有的“默认”配置(也就是以default.开头的配置项)
  5. 加载配置文件(从配置文件及环境变量中加载),注册配置类 this.defaultConfigType,其实就是LoadBalancerClientConfiguration配置类
  6. 设置父容器(Spring Ioc的主容器设置为父容器)
  7. 刷新容器
  8. 返回容器

一个需要关注的重点就是:子容器创建的过程中,将配置类LoadBalancerClientConfiguration注册到容器中,在容器刷新的时候,这个配置类会被加载。

ReactorLoadBalancer & LoadBalancerClientConfiguration

子容器创建出来之后,我们还是返回到上面的NamedContextFactory的getInstance方法中:

	@Override
	public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) {
		return getInstance(serviceId, ReactorServiceInstanceLoadBalancer.class);
	}

会向子容器获取ReactorServiceInstanceLoadBalancer对象。

所以我们现在两个任务:第一个是了解一下ReactorServiceInstanceLoadBalancer类,第二个是要了解到注入到子容器中的ReactorServiceInstanceLoadBalancer究竟是个什么对象。

第一步:看一眼ReactorLoadBalancer的类结构:
在这里插入图片描述
ReactorServiceInstanceLoadBalancer接口继承自ReactorLoadBalancer,Spring Cloud提供了他的两个实现类:随机策略类和轮询策略类。

第二步,注入到子容器中的ReactorServiceInstanceLoadBalancer究竟是个什么对象?就需要研究一下ReactorLoadBalancer的初始化过程。

子容器通过配置类LoadBalancerClientConfiguration实现ReactorLoadBalancer的注入,默认实现类是RoundRobinLoadBalancer:

	@Bean
	@ConditionalOnMissingBean
	public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
			LoadBalancerClientFactory loadBalancerClientFactory) {
		String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
		return new RoundRobinLoadBalancer(
				loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
	}

OK!

真相大白了,默认就是轮询策略RoundRobinLoadBalancer。

负载均衡策略的配置

Spring Cloud默认的负载均衡策略是RoundRobinLoadBalancer,我们可以通过配置调整负载均衡策略为随机策略RandomLoadBalancer。

调整方法很简单,官网说了:

在这里插入图片描述
余事以后再说吧。

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

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

相关文章

kubectl 本地远程链接k8s多个集群,远程管控多集群,查看日志 部署服务(windows版)

文章目录 一、前言二、windows上安装kubectl和mobaxterm2.1 准备安装包2.2 安装kubectl2.3 链接k8s集群2.4 查看某一个pod的容器日志2.5 切换context 上下文配置&#xff0c;实现在多个k8s集群间动态切换 一、前言 现如今是一个万物皆上云 的时代&#xff0c;各种云层出不穷&am…

【Rust】快速教程——从hola,mundo到所有权

前言 学习rust的前提如下&#xff1a; &#xff08;1&#xff09;先把Rust环境装好 &#xff08;2&#xff09;把VScode中关于Rust的插件装好 \;\\\;\\\; 目录 前言先写一个程序看看Rust的基础mut可变变量let重定义覆盖变量基本数据类型复合类型&#xff08;&#xff09;和 [ …

3.1 Windows驱动开发:内核远程堆分配与销毁

在开始学习内核内存读写篇之前&#xff0c;我们先来实现一个简单的内存分配销毁堆的功能&#xff0c;在内核空间内用户依然可以动态的申请与销毁一段可控的堆空间&#xff0c;一般而言内核中提供了ZwAllocateVirtualMemory这个函数用于专门分配虚拟空间&#xff0c;而与之相对应…

加密狗作用是什么?工作原理及使用方法

加密狗是一种用于软件保护的硬件设备&#xff0c;通常被用于防止软件被非法复制、篡改或者恶意使用。以下是加密狗的作用、工作原理及使用方法&#xff1a; 作用 加密狗的主要作用是提供软件保护&#xff0c;它能够通过加密算法对软件进行加密&#xff0c;以防止软件被非法复制…

Linux学习第42天:Linux RS232/485/GPS 驱动实验:天外来客

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 Linux的学习笔记今天更新到了第42天。鉴于国往笔记内容整理中出现的问题&#xff0c;我尽量按照平时学习时笔记的要求进行优化。尽量不再大段大段的贴代码。而是…

Ubuntu18.04平台下Qt开发程序打包的一些问题总结

目录 前言 一、在Ubuntu18.04开发环境下打包有两种方式 1、利用linuxdeployqt软件进行打包 2、利用编写shell脚本的方式进行打包 二、详细介绍shell脚本打包的方式 1、新建一个空的文件夹 2、准备脚本copylib.sh 3、准备脚本xxxx.sh。 4、给上述两个脚本添加可执行权限…

Behave介绍和快速示例

Behave是一个用于行为驱动开发 (Behavior-Driven Development, BDD) 的 Python 库。使用 Behave&#xff0c;可以编写自然语言格式的使用场景来描述软件的行为&#xff0c;然后用 Python 实现这些场景下的步骤&#xff0c;形成可直接运行的测试。 Behave的目标是帮助用户、开发…

【Machine Learning in R - Next Generation • mlr3】

本篇主要介绍mlr3包的基本使用。 一个简单的机器学习流程在mlr3中可被分解为以下几个部分&#xff1a; 创建任务 比如回归、分裂、生存分析、降维、密度任务等等挑选学习器&#xff08;算法/模型&#xff09; 比如随机森林、决策树、SVM、KNN等等训练和预测 创建任务 本次示…

JVM——类加载器(JDK8及之前,双亲委派机制)

目录 1.类加载器的分类1.实现方式分类1.虚拟机底层实现2.JDK中默认提供或者自定义 2.类加载器的分类-启动类加载器3.类加载器的分类-Java中的默认类加载器4.类加载器的分类-扩展类加载器5.类加载器的分类-类加载器的继承 2.类加载器的双亲委派机制 类加载器&#xff08;ClassLo…

Vue3.3 + Vite4.3 + TypeScript5+ Element-Plus:从零到一构建企业级后台管理系统(前后端开源)

vue3-element-admin 是基于 vue-element-admin 升级的 Vue3 Element Plus 版本的后台管理前端解决方案&#xff0c;技术栈为 Vue3 Vite4 TypeScript Element Plus Pinia Vue Router 等当前主流框架。 相较于其他管理前端框架&#xff0c;vue3-element-admin 的优势在于一…

笔记本配USB拓展坞网卡经常时不时掉线

ThinkPad X1 Carbon大概是2015年的老笔记本,自己买的USB3.0拓展坞(带网卡)的,我的笔记本不带网卡, 使用网线时不时右下角显网络掉线 , 就掉了,各种排查排除,不是线,路由器,拓展坞问题,最后更新拓展坞网卡芯片驱动,解决问题.下面是步骤: 搜索关键字:Realtek USB GbE Family Cont…

OpenCV必知必会基础3(包括色彩空间的变换、ROI、OpenCV中最重要的结构体Mat以及获取图像的属性)

文章目录 OpenCV的色彩空间——RGB与BGROpenCV的色彩空间——HSV与HSLHSV主要用于OpenCV中HSL OpenCV色彩空间转换YUV主要用于视频中题目 图像操作的基石Numpy【基础操作】np.arraynp.zerosnp.onesnp.fullnp.identitynp.eye Numpy基本操作之矩阵的检索与赋值Numpy基本操作三——…

c# - - - Application.StartupPath(程序安装目录)和Environment.CurrentDirectory(程序工作目录)

Application.StartupPath 应用程序的安装目录&#xff0c;不会改变。 在C:\Users\Administrator\source\repos\ConsoleApp6\bin\Debug目录中&#xff0c;运行ConsoleApp6.exe。 安装目录为&#xff1a;C:\Users\Administrator\source\repos\ConsoleApp6\bin\Debug 在C:\Users…

Git相关: 拉取、git push提交 过程遇到的错误

目录 解决git push报错error: RPC failed; HTTP 413 curl 22 关于这个问题&#xff0c;其实千万别用gitlab,因为你怎么推送 也不可能把几G的文件推上去。 error: RPC failed; HTTP 413 curl 22 The requested URL returned error: 413 se 解决git push报错error: RPC failed;…

【外汇天眼】解析外汇交易平台:深度了解DD与NDD两大模式

外汇交易平台种类繁多&#xff0c;涵盖不同的分类与运营模式&#xff0c;令投资者难以甄别&#xff0c;也增加了选择的难度。为了解决这一问题&#xff0c;我们将更深入地了解外汇平台的多样性。 在线外汇交易平台主要分为两大类&#xff1a;处理平台模式&#xff08;Dealing …

springcloud仓库管理系统源码

开发技术&#xff1a; jdk1.8&#xff0c;mysql5.7&#xff0c;idea&#xff0c;nodejs&#xff0c;vscode springcloud springboot mybatis vue elementui 功能介绍&#xff1a; 统计分析&#xff1a;查看产品&#xff0c;销售数量&#xff1b;统计近7日出入库统计 客户管…

RHCE8 资料整理(六)

RHCE8 资料整理 第 6 篇 软件管理第 23 章 用rpm管理软件23.1 rpm查询23.2 rpm卸载23.3 软件包升级23.4 rpm验证 第 24 章 用yum/dnf管理软件包24.1 yum架构介绍24.2 用光盘搭建yum源24.3 创建私有仓库24.4 yum客户端配置24.5 yum基本使用24.5.1 查询24.5.2 安装与卸载24.5.3 下…

ETL数据转换工具类型与适用场景

ETL数据转换工具在企业数据管理中扮演着重要的角色&#xff0c;能够帮助企业从多个数据源中提取、转换和加载数据&#xff0c;实现数据整合和分析。以下是针对Kettle、DataX和ETLCloud这几个工具的详细介绍及其适用场景。 Kettle&#xff08;Pentaho Data Integration&#xf…

提高生存能力的7个关键技巧!

作为一款备受热议和玩家喜爱的多人在线射击游戏&#xff0c;《绝地求生》中生存能力的提高是取得胜利的关键。在这篇实用干货分享中&#xff0c;我们将详细说明7个关键技巧&#xff0c;帮助你在游戏中提高生存能力&#xff0c;获得更多胜利。 1.选择降落点&#xff1a;选择适合…

【Azure 架构师学习笔记】-Azure Storage Account(6)- File Layer

本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Storage Account】系列。 接上文 【Azure 架构师学习笔记】-Azure Storage Account&#xff08;5&#xff09;- Data Lake layers 前言 上一文介绍了存储帐户的概述&#xff0c;还有container的一些配置&#xff0c;在…