Hadoop之MapReduce

一.YARN 架构

1.YARN 是什么?

YARN 是 Apache Hadoop 生态系统中的核心资源管理和调度平台。它的诞生是为了解决 Hadoop 1.0 中 MapReduce 框架的两个主要问题:

  1. 可扩展性差:在 Hadoop 1.0 中,JobTracker 同时负责资源管理和作业调度/监控,随着集群节点和作业数量的增加,它成为了系统瓶颈。

  2. 不支持多种计算模型:Hadoop 1.0 只能运行 MapReduce 任务,无法有效支持像 Spark、Flink、Tez 这样的其他计算框架。

YARN 的基本思想是:将资源管理和作业调度/监控的功能分离开来。它提供了一个通用的、跨框架的资源管理平台,允许各种计算框架(如 MapReduce, Spark, Flink, Hive on Tez 等)在一个集群上共享资源,协同工作。

2. YARN 的核心组件与架构

YARN 采用了经典的 Master/Slave 架构,主要由以下几个核心组件构成:

1. ResourceManager (RM) - 主节点(Master)

ResourceManager 是整个 YARN 集群的资源总管 ,负责整个系统的资源管理和分配。RM 将

各个资源部分(计算、内存等)安排给基础 NodeManager(NM)。

RM 与 ApplicationMaster(AM) 一起分配资源,RM与NM 一起启动和监视它们的基础应用程序。AM 承担了以前的 TaskTracker 的一些角色,RM 承担了JobTracker 的角色。

它主要由两个组件构成:

  • 调度器(Scheduler) :纯调度器,它根据容量、队列等限制条件,将系统资源(如 CPU、内存)分配给各个正在运行的应用程序。它不负责监控或跟踪应用程序的状态,而是将这些工作交由应用程序相关的AM完成

  • 应用程序管理器(ApplicationsManager):应用程序管理器负责管理整个系统中所有应用程序,包括应用程序提交、与调度器协商资源以启动AM、监控AM运行状态并在失败时重新启动它。

2. NodeManager (NM) - 从节点(Slave)

NodeManager 是每个工作节点上的代理,负责:

  • 管理单个节点上的资源。

  • 向 ResourceManager 汇报本节点的资源使用情况(CPU,内存等)。

  • 接收并处理来自 ResourceManager 和 ApplicationMaster 的命令,例如启动或停止容器(Container)。

  • 为应用启用调度器,以分配给应用的Container

  • 保证已经启用的Container不会使用超过分配的资源量

  • 为task构建Container环境,包括二进制可执行文件.jars等

  • 为所在节点提供一个管理本地存储资源的简单服务

3. ApplicationMaster (AM) - 应用程序主管(每个应用一个)

它是一个应用程序级别的守护进程,每个应用程序(例如一个 MapReduce 作业,一个 Spark 作业)都有一个自己的 AM。

  • 负责与 ResourceManager 的调度器协商资源

  • 从 NodeManager 上启动和监控具体的任务(如 Map Task, Reduce Task)。

  • 跟踪任务状态和进度,并向 ResourceManager 汇报。

  • 在任务失败时,向 ResourceManager 申请新的资源来重启任务。

4.ResourceRequest和Container

  • 一个应用程序可以通过AM请求特定的资源需求来满足它的资源需要。调度器会分配一个Container来响应资源需求,用于满足由AM在ResourceManager中提出的需求。ResourceRequest具有以下形式:< 资源名称,优先级,资源需求,Container数 >。

  • 本质上Container是一种资源分配形式,是ResourceManager为ResourceRequest成功分配资源的结果。Container为应用程序授予在特定主机上使用资源(如内存,CPU等)的权利。

  • ApplicationMaster必须取走Container,并交给NodeManager,NodeManager会利用相应的资源来启动Container的任务进程。出于安全考虑,Container的分配要以一种安全的方式进行验证,来保证ApplicationMaster不能伪造集群中的应用。

3.基于YARN的运行机制解析

二.Shuffle和排序

Shuffle 是一个过程,它将 Map 阶段(数据读取和初步处理)的输出,按照 Key 重新分发到 Reduce 阶段所在节点上的过程。你可以把它想象成打扑克时的洗牌,将分散在不同人手中的牌(Map 的输出)收集起来,然后重新发牌,确保相同点数的牌(相同的 Key)都发到同一个人(同一个 Reduce 任务)手里。

1.为什么需要 Shuffle?

因为"物以类聚"。例如,我们要统计一篇文章中每个单词出现的次数(WordCount)。

  1. Map 阶段 :每台机器处理文章的一部分,输出类似 (apple, 1), (banana, 1), (apple, 1) 的键值对。

  2. Shuffle 阶段 :我们需要把所有 apple 的记录发送到同一台 负责统计 apple 的机器上,把所有 banana 的记录发送到另一台负责统计 banana 的机器上。

  3. Reduce 阶段 :每台机器收到所有分配给自己的键值对后,进行聚合计算(例如,把所有的 (apple, 1) 加起来)。

没有 Shuffle,Reduce 任务就无法对全局所有相同 Key 的数据进行聚合。

**2.**Shuffle 的详细过程(以 MapReduce 为例)

  1. Map 端

    • 分区(Partitioning):Map 任务在处理完数据后,会为每个输出键值对计算一个分区号,决定这个键值对属于哪个 Reduce 任务。

    • 溢写(Spill) :Map 任务将输出先写入内存的一个环形缓冲区。当缓冲区达到一定阈值(如80%),会启动一个后台线程,将缓冲区中的数据排序后溢写到本地磁盘,生成一个小的临时文件。

    • 合并(Merge) :当 Map 任务结束时,它会将磁盘上所有的小的临时文件合并成一个大的、已分区且已排序的输出文件。

  2. 拷贝(Copy)

    • Reduce 任务启动后,会通过 HTTP 请求从各个已完成 Map 任务的节点上拉取(Fetch) 属于自己的那部分数据。
  3. Reduce 端

    • 合并(Merge) :Reduce 任务从多个 Map 节点拉取数据,这些数据可能也是多个小文件。Reduce 任务会将这些文件在内存或磁盘上进行多路归并,最终形成一个整体上有序的数据文件,作为 Reduce 任务的输入。

3.Sort排序:在MapReduce计算框架中主要用到了两种排序算法:快速排序和归并排序

(1)三次排序过程详解

第一次排序:Map 端内存缓冲区排序
  • 时机:Map 任务输出,内存环形缓冲区达到溢出阈值(如 80%)时。

  • 位置:内存。

  • 操作 :在数据溢出到磁盘之前,后台线程会对缓冲区每个分区内的数据按照 Key 进行排序。

  • 算法快速排序

    • 原因:快速排序是一种高效的内部排序算法,平均时间复杂度为 O(n log n),且是原地排序,非常适合在内存中对大量数据进行排序。
  • 结果 :生成一个在磁盘上的溢写文件 ,这个文件内部是分区的,且每个分区内的数据是按键有序的。

第二次排序:Map 端溢写文件合并排序
  • 时机:Map 任务结束时,所有溢写文件需要被合并成一个最终的 Map 输出文件。

  • 位置:磁盘。

  • 操作 :将多个已经内部有序的溢写文件(可能还包括内存中未溢出的数据)合并成一个文件。

  • 算法归并排序

    • 原因:当有多个有序的子文件时,归并排序是最高效的将它们合并成一个整体有序文件的方法。其时间复杂度为 O(n log k),其中 k 是子文件的数量。它只需要一次顺序扫描,非常适合磁盘 I/O 的特性。
  • 结果 :生成一个最终的、整体有序的 Map 输出文件。这个文件仍然是分区的,但现在是每个分区内全局有序(对于这个 Map 任务而言)。

第三次排序:Reduce 端文件合并排序
  • 时机:Reduce 任务从所有 Map 任务节点拉取到属于自己的数据分片后,在开始执行 Reduce 函数之前。

  • 位置:磁盘(或内存与磁盘结合)。

  • 操作 :Reduce 任务会从多个 Map 节点拉取数据,这些数据都是每个 Map 任务最终输出的、已经排好序的文件片段。Reduce 任务需要将这些来自不同 Map 任务但属于同一分区的有序文件合并成一个整体有序的文件,作为 Reduce 函数的输入。

  • 算法归并排序

    • 原因:与第二次排序完全相同。面对多个有序的输入流,归并排序是最佳选择。在 Hadoop 中,这个阶段通常是一个多路归并。
  • 结果 :生成一个提供给 Reduce 函数的、全局有序的输入数据流。这样,Reduce 函数可以顺序处理所有相同 Key 的数据,并在遇到下一个 Key 时知道前一个 Key 的所有值都已处理完毕。

三.wordcount实例代码

1.mapper类

java 复制代码
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {


    /**
     * Map函数,处理每一行数据
     * @param key: 行的起始偏移量
     * @param value: 一行的内容
     * @param context: 上下文对象,用于输出Mapper的结果
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    protected void map(LongWritable key, Text value, Mapper<LongWritable,Text,Text,IntWritable>.Context context)
         throws IOException, InterruptedException {

        // 将一行文本转换为String
        String line = value.toString();
        
        String [] words = line.split("\\s+");
        
        // 遍历所有单词,将每一个单词与数值1拼接成键值对写出
            for(String word :words){
               context.write(new Text(word),new IntWritable(1));
            }
    }
}

注意public void map()这个函数的四个参数分别是

1. KEYIN - 输入键的类型

  • 默认类型LongWritable

  • 含义 :表示每行文本在文件中的起始字节偏移量(这一行第一个字符,是这个块的第几个字符,起始从0 开始计算)

  • 示例0, 6, 13 等位置

2. VALUEIN - 输入值的类型

  • 默认类型Text

  • 含义 :表示一行的文本内容

  • 示例"Hello", "Hadoop", "HHA"

3. KEYOUT - 输出键的类型

  • 自定义类型:根据业务需求定义

  • 含义:Mapper处理后的中间结果的键

  • WordCount示例Text(单词)

4. VALUEOUT - 输出值的类型

  • 自定义类型:根据业务需求定义

  • 含义:Mapper处理后的中间结果的值

  • WordCount示例IntWritable(词频,通常为1)

2.reducer类

java 复制代码
import java.io.IOException;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
/**
 * KEYIN: Reducer输入Key的类型,对应Mapper的KEYOUT (Text)
 * VALUEIN: Reducer输入Value的类型,对应Mapper的VALUEOUT (IntWritable)
 * KEYOUT: Reducer输出Key的类型,这里是单词 (Text)
 * VALUEOUT: Reducer输出Value的类型,这里是总词频 (IntWritable)
 */
public class WordCountReducer 
    extends Reducer<Text, IntWritable, Text, IntWritable> {

    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, 
        Reducer<Text, IntWritable, Text, IntWritable>.Context context)
            throws IOException, InterruptedException {

        int times = 0;
        
        // 遍历该单词对应的所有计数值(1)
        for (IntWritable value : values) {
            // 累加求和
            times += value.get();
        }
        
        // 输出最终结果:<单词, 总词频>
        context.write(key, new IntWritable(times));
    }
}

1. KEYIN - 输入键的类型

  • 对应Mapper的KEYOUT

  • 含义:Mapper输出的中间结果的键

  • WordCount示例Text(单词)

2. VALUEIN - 输入值的类型

  • 对应Mapper的VALUEOUT

  • 含义:Mapper输出的中间结果的值列表

  • WordCount示例IntWritable(词频计数的列表)

3. KEYOUT - 输出键的类型

  • 自定义类型:根据最终输出需求定义

  • 含义:Reducer处理后的最终结果的键

  • WordCount示例Text(单词)

4. VALUEOUT - 输出值的类型

  • 自定义类型:根据最终输出需求定义

  • 含义:Reducer处理后的最终结果的值

  • WordCount示例IntWritable(总词频)

3.driver类

java 复制代码
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;

public class WordCountDriver {

    public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
        // 1.获取配置信息
        // 加载默认的配置,即core-default.xml、hdfs-default.xml、mapped-default.xml和yarn-default.xml中的配置信息
        // 然后读取项目中的core-site.xml、hdfs-site.xml、mapped-site.xml和yarn-site.xml中配置的信息,更新某些属性的值
        Configuration conf = new Configuration();
        // 2.如果需要继续更新某些属性的值,可以在代码中更新
        conf.set("fs.defaultFS", "hdfs://192.168.215.130:9820");
        // 3.创建Job对象
        Job job = Job.getInstance(conf);  // 修正:应该是conf,不是config

        // 4.设置Mapper类型
        job.setMapperClass(WordCountMapper.class);
        // 5.设置Reducer类型
        job.setReducerClass(WordCountReducer.class);
        // 6.设置驱动类型
        job.setJarByClass(WordCountDriver.class);

        // 7.设置Map阶段输出的键值对类型
        // 如果Map阶段输出的键值对类型与Reduce阶段输出的键值对类型相同,则可以省略这个设置。
        // 例如:现在的Map阶段输出是<Text, IntWritable>类型的,与Reduce阶段的数据类型相同,因此可以省略不写。
        // job.setMapOutputKeyClass(Text.class);
        // job.setMapOutputValueClass(IntWritable.class);

        // 8.设置Reduce阶段输出的键值对类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        // 9.设置输入输出路径
        FileInputFormat.setInputPaths(job, new Path(args[0]));  // 修正:添加分号
        FileOutputFormat.setOutputPath(job, new Path(args[1]));  // 修正:添加分号

        // 10.提交Job
        System.exit(job.waitForCompletion(true) ? 0 : -1);
    }
}

4.实例分析:

对于任何一个实例,大概需要四步,定义一个新的自定义类,写出mapper,reducer,driver类

其中新的自定义类,需要根据题目自行分析出来,mapper,reducer,driver类中参数等都是相同的模板,只有void mapper/reducer()这个函数内部具体代码是根据实例来的。

相同之处还有 具体代码的最后都是:

// 输出最终结果:<单词, 总词频>
context.write(key, new IntWritable(。。。));

四.分区器设置:

**1.**概述

在MapReduce的程序中,最终⽣成多少个⽂件是由ReduceTask的数量来决定 的。

在上述的案例中,我们最终⽣成了⼀个⽂件,这是因为只有⼀个ReduceTask。那么如果需要⽣成 两个结果⽂件,可以将ReduceTask的数量设置为2。
在MapReduce程序中,⼀个MapTask会处理⼀个分⽚的数据,⽽⼀个ReduceTask 是⽤来处理⼀个分区的数据。因此如果我们需要明确某个单词的统计结果,到底该存放在哪个⽂件 中,只需要设置好分区即可。⽽Partitioner就是⼀个分区器组件,来实现数据的分区操作。默认的 分区器是HashPartitioner,如果我们需要实现⾃⼰的分区逻辑,就需要⾃定义分区器了。

HashPartitioner的逻辑为:

● 分区器计算每个Key的HashCode;
● 将计算后的HashCode % ReduceTask的数量,得到⼀个分区的编号。

二.实例代码

1.自己设立的Partitioner

java 复制代码
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;

public class WordCountPartitioner extends Partitioner<Text, IntWritable> {
    /**
     * 计算每⼀个键值对所对应的分区号,分区号从0开始
     * @param text 键
     * @param intWritable 值
     * @param i 总的ReduceTask的数量
     * @return 该键值对对应的分区
     */
    @Override
    public int getPartition(Text text, IntWritable intWritable, int i) {
    // 将键值对按照⾸字⺟分为三个分区: a-i, j-q, 其他
    // 1.获取⾸字⺟
    char firstLetter = text.toString().charAt(0);
    // 2.判断范围,分别返回不同的数值
    if (firstLetter >= 'a' && firstLetter <= 'i') {
        return 0;
    } else if (firstLetter >= 'j' && firstLetter <= 'q') {
        return 1;
    }
        return 2;
    }
} 

2.Driver

java 复制代码
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;

public class WordCountDriver {

    public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
        // 1.获取配置信息
        // 加载默认的配置,即core-default.xml、hdfs-default.xml、mapped-default.xml和yarn-default.xml中的配置信息
        // 然后读取项目中的core-site.xml、hdfs-site.xml、mapped-site.xml和yarn-site.xml中配置的信息,更新某些属性的值
        //conf:Hadoop 配置对象(通常是 Configuration 类实例)
        Configuration conf = new Configuration();
        // 2."fs.defaultFS": Hadoop 的核心配置参数,指定默认文件系统
        //"hdfs://192.168.215.130:9820": HDFS 服务的地址和端口
        conf.set("fs.defaultFS", "hdfs://192.168.215.130:9820");
        // 3.创建Job对象
        Job job = Job.getInstance(conf);  

        // 4.设置Mapper类型
        job.setMapperClass(WordCountMapper.class);
        // 5.设置Reducer类型
        job.setReducerClass(WordCountReducer.class);
        // 6.设置驱动类型
        job.setJarByClass(WordCountDriver.class);

        // 7.设置Map阶段输出的键值对类型
        // 如果Map阶段输出的键值对类型与Reduce阶段输出的键值对类型相同,则可以省略这个设置。
        // 例如:现在的Map阶段输出是<Text, IntWritable>类型的,与Reduce阶段的数据类型相同,因此可以省略不写。
        // job.setMapOutputKeyClass(Text.class);
        // job.setMapOutputValueClass(IntWritable.class);

        // 8.设置Reduce阶段输出的键值对类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        // 9.设置输入输出路径
        FileInputFormat.setInputPaths(job, new Path(args[0]));  
        FileOutputFormat.setOutputPath(job, new Path(args[1])); 

        // 10.设置分区器
        job.setPartitionerClass(WordCountPartitioner.class);
        // 11.设置ReduceTask的数量
        // ReduceTask的数量最好和分区的数量保持⼀致:
        // 如果ReduceTask的数量多于分区的数量: 会出现多余的ReduceTask空占资源,不去处理任何的数据,浪费资源,且⽣成空的结果⽂件
        // 如果ReduceTask的数量少于分区的数量: 会出现某个分区的数据暂时⽆法处理,
        //需要等待某ReduceTask任务处理结束后再处理这个分区的数据,⽆法⾼效并发
        job.setNumReduceTasks(3);
        // 12.提交Job
        System.exit(job.waitForCompletion(true) ? 0 : -1);
    }
}

3.命令后的效果

五.序列化机制

**1.**概述

在讲解为什么要序列化之前,我们要先来了解下什么是序列化和反序列化。
●序列化:序列化是指将具有结构化的内存对象,转为0和1组成的字节序列,以便进⾏⽹络传
输或持久存储到设备的过程。

● 反序列化: 反序列化指的是将字节序列,转为内存中具有结构化的对象的过程。

在基于类的编程语⾔中,数据都会被封装成对象,在内存中进⾏管理。但有些时候,如果⼀个对象

要直接存储到磁盘,或者要进⾏⽹络传输,那么该怎么做呢?这就需要将对象序列化成0和1组成的

字节序列,字节序列就可以存储到磁盘中,或者进⾏⽹络传输了。当我们需要对象时,当读取磁盘

上的字节序列,或者接收⽹络传输过来的字节序列,反序列化成我们需要的对象就可以了。

2.常⽤类型简介

Java 数据类型 Hadoop 序列化数据类型 释义
byte ByteWritable 字节型
short ShortWritable 短整型
int IntWritable 整型
long LongWritable 长整型
float FloatWritable 单精度浮点型
double DoubleWritable 双精度浮点型
boolean BooleanWritable 布尔型
String Text 使用 UTF-8 编码的字符串
array[] ArrayWritable 数组
Map<K, V> MapWritable 键值对集合
null NullWritable 空类型,常用于占位或作为 Key/Value 为空的情况

3.序列化接⼝Writable

3.1 Writable接⼝

模板:

java 复制代码
public interface Writable {

    void write(DataOutput out) throws IOException;

    void readFields(DataInput in) throws IOException;
}

例子:

java 复制代码
import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
 * @ClassName: StudentWriter
 * @Author: 壹壹哥
 * @Date: 2024-08-22 17:47
 */
public class StudentWriter implements Writable {
    private String name;
    private int age;

    public StudentWriter(String name,int age){
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public void write(DataOutput out) throws IOException {
        out.writeUTF(name);
        out.writeInt(age);
    }
    
    @Override
    public void readFields(DataInput in) throws IOException {
        name = in.readUTF();
        age = in.readInt();
    }
}

3.2 WritableComparable接⼝

java 复制代码
public interface WritableComparable<T> extends Writable, Comparable<T> {
}

3.3自定义序列化接口

如果想要把⼀个⾃定义的数据类型作为K2V2或者K3V3来使⽤,那么就必须要实现序列化的接⼝Writable。

如果这个⾃定义的数据类型是⼀个Key的数据类型,则还需要在满⾜⽐较的条件,也就是再额外实现⼀个Comparable的接⼝

或者可以直接实现WritableComparable接⼝。

java 复制代码
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Objects;

public class TextPair implements WritableComparable {

    /*一些java自定义函数.... */

    //* 序列化⽅法,将属性序列化成字节序列
    public void write(DataOutput out) throws IOException {
        //将属性写到输出流程
        name.write(out);
        info.write(out);

        //如果不是hadoop类型,⽐如是java类型
        // out.writeUTF(name);
        // out.writeUTF(info);
    }

    //* 反序列化⽅法,从流中读取字节序列进⾏反序列化。
    public void readFields(DataInput in) throws IOException {
        //要按照序列化的顺序进⾏反序列化
        name.readFields(in);
        info.readFields(in);
    }
    public int compareTo(Object o) {
        return 0;
    }
}

3.4**.**实现Serializable接⼝

java.io.Serializable 是一个标记接口。它内部没有任何需要实现的方法。

它的唯一作用是在编译时和运行时向 Java 虚拟机(JVM)声明:

"这个类的对象可以被序列化。"

如果一个类没有实现 Serializable 接口,尝试对其对象进行序列化操作(例如,使用 ObjectOutputStream)将会抛出 NotSerializableException

代码如下:

java 复制代码
import java.io.Serializable;

public class StudentSerializer implements Serializable {
}
相关推荐
电商API_180079052471 小时前
淘宝商品详情 API 性能优化秘籍:QPS 提升 5 倍的技术方案
大数据·性能优化·数据挖掘·数据分析·网络爬虫
t***26592 小时前
【大数据】MySQL与Elasticsearch的对比分析:如何选择适合的查询解决方案
大数据·mysql·elasticsearch
阳爱铭4 小时前
ClickHouse 中至关重要的两类复制表引擎——ReplicatedMergeTree和 ReplicatedReplacingMergeTree
大数据·hive·hadoop·sql·clickhouse·spark·hbase
RPA机器人就选八爪鱼4 小时前
RPA财务机器人:重塑财务效率,数字化转型的核心利器
大数据·数据库·人工智能·机器人·rpa
ITVV5 小时前
flink CDC 3.5.0
大数据·flink
Elastic 中国社区官方博客6 小时前
Elasticsearch:如何创建知识库并使用 AI Assistant 来配置 slack 连接器
大数据·人工智能·elasticsearch·搜索引擎·全文检索·信息与通信
XIAOYU6720137 小时前
中专学历,在服装设计行业真的没出路吗?
大数据
TDengine (老段)7 小时前
TDengine IDMP 赋能新能源:光伏电站智能运维实践
大数据·运维·数据库·物联网·时序数据库·tdengine·涛思数据
努力的光头强8 小时前
《智能体设计模式》从零基础入门到精通,看这一篇就够了!
大数据·人工智能·深度学习·microsoft·机器学习·设计模式·ai