MapReduce,Yarn,Spark理解与执行流程

MapReduce的API理解

Mapper

  • 如果是单词计数:hello:1, hello:1, world:1
public void map(Object key, // 首字符偏移量
                    Text value, // 文件的一行内容
                    Context context) // Mapper端的上下文,与 OutputCollector 和 Reporter 的功能类似
            throws IOException, InterruptedException {

Reduce

  • 注意拿到的是value的集合
  • 如果是单词计数:hello:【1,1】,world:【1】
  public void reduce(Text key, // Map端 输出的 key 值
                       Iterable<IntWritable> values, // Map端 输出的 Value 集合(相同key的集合)
                       Context context) // Reduce 端的上下文,与 OutputCollector 和 Reporter 的功能类似
            throws IOException, InterruptedException
    {

整体架构

在这里插入图片描述

  • NameNode: 负责存储文件的元信息(如文件的大小、块信息、存储位置等)。通常会部署一个 Secondary NameNode 来周期性合并 NameNode 的 edit log 以减少恢复时间,但它并不是热备,而是辅助管理。避免单点故障
  • JobTracker:负责 Hadoop MapReduce 任务的调度和资源管理,接收作业请求并将任务分配给不同的 TaskTracker(工作节点)
  • DataNode:实际存储数据的节点,存有多个数据块(Block),128MB
  • TaskTracker:实际进行mapper和reduce工作的节点,可以看到图中TaskTracker接近DataNode,这是“移动计算比移动数据更便宜” 的设计理念,目的是减少数据传输,提高计算效率

输入阶段

  1. 文件存储到 HDFS(Hadoop Distributed File System)
  2. 当文件上传到 HDFS 时,文件会先到达 NameNodeNameNode 负责存储文件的元信息(如文件的大小、块信息、存储位置等)。
  3. 随后,文件会被分割成固定大小的数据块(Block),默认每块大小是 128 MB(可以通过配置调整)。
  4. 这些数据块会被分布式地存储到集群中的不同 DataNode 节点上,通常每个块有多个副本(默认是 3 个),以保证数据的可靠性,同步到指定数量的副本后,才向NameNode确认写操作成功,保证一致性。TiDB的底层TiKV的存储也类似这个结构,根据键值对分为多个region,且不同的节点保存奇数个副本,并有raft协议保证一致性。

Mapper阶段

  • TaskTracker 和 Mapper 的运行:
    1. 当执行 MapReduce 作业时,JobTracker会负责任务的调度。
    2. 根据 NameNode 提供的块位置信息,JobTracker 会在包含该块数据的 DataNode 上启动 Mapper,这是数据本地化优化的核心。
    3. 每个 Mapper 会处理一个或多个数据块。
    4. Mapper会将每一行的文件处理成键值对形式,也可以进行数据预处理(过滤、清洗):
// input.txt:
apple 10
banana 20
apple 5

// after mapper:
(apple, 5), (apple, 10), (banana, 20)
  1. Mapper 的输出结果存储在本地磁盘的缓存文件中或者磁盘中,并分为多个分区,每个分区对应一个 Reducer

Shuffle 阶段

什么是 Shuffle?

Shuffle 是将 Mapper 输出的中间数据(键值对)分发给 Reducer 的过程。
其主要任务包括:

  1. 对 Mapper 输出的数据进行分区。
  2. 将分区数据从 Mapper 节点移动到 Reducer 节点。
  3. 对分区数据进行排序和合并。
Shuffle 的执行角色
  • Mapper 节点执行分区:

    • 在 Mapper 阶段结束后,输出结果会被分区(默认使用 HashPartitioner)。
    • 每个分区对应一个 Reducer。Mapper 的数据会按照分区规则存储到本地磁盘的多个文件中。
  • JobTracker(或 Yarn)负责协调:

    • JobTracker 会通知各 Reducer 节点**从相应的 Mapper 节点拉取(pull)属于自己的分区数据。
  • Reducer 节点的数据迁移:

    • 每个 Reducer 从多个 Mapper 节点拉取自己的数据。
    • 拉取的数据会临时存储在 Reducer 节点上,并在内存中进行排序和合并,以便 Reducer 处理。

Shuffle 阶段会涉及到数据从 Mapper 节点到 Reducer 节点的迁移,这也是整个 MapReduce 流程中最耗时的一部分。

举个例子,更好理解如何分区以及数据传输:

Shuffle例子

输入文件内容:

File1: Hello Hadoop Hello
File2: Hadoop MapReduce Hadoop

分块:

  • Block1(File1)
  • Block2(File2)
Mapper 输出:

假设有 2 个 Mapper 和 2 个 Reducer,并用 key.hashCode() % 2 作为分区规则。

MapperKeyPartition (Reducer)Output
Mapper1Hello0{Hello: 1}
Mapper1Hadoop1{Hadoop: 1}
Mapper1Hello0{Hello: 1}
Mapper2Hadoop1{Hadoop: 1}
Mapper2MapReduce0{MapReduce: 1}
Mapper2Hadoop1{Hadoop: 1}
Shuffle 阶段:

在 Shuffle 阶段,每个 Reducer 会从多个 Mapper 拉取数据:

ReducerData SourceReceived Data
Reducer0Mapper1 + Mapper2{Hello: [1, 1], MapReduce: [1]}
Reducer1Mapper1 + Mapper2{Hadoop: [1, 1, 1]}
Reducer 聚合:

最终,Reducer 聚合数据,输出结果:

Reducer0: {Hello: 2, MapReduce: 1}
Reducer1: {Hadoop: 3}

Reduce阶段

前面提到,reduce时会向map的节点获取数据,它是如何直到那个mapper节点的呢?
具体是这样的:map任务成功完成后,它们会使用心跳机制通知它们JobTracker。因此,对于指定作业JobTracker 知道 map输出和主机位置之间的映射关系。reducer 中的一个线程定期询问 JobTracker 以便获取 map输出主机的位置,直到获得所有输出位置。

Reduce 任务执行完毕后,输出结果会直接存储到 HDFS,但是Reduce 节点不会主动通知 NameNode 数据位置,而是 HDFS 负责数据存储的元数据管理,Reduce 任务会通过 HDFS 客户端 API 将数据写入 HDFS

写数据到 HDFS 的过程(详)

  1. 客户端请求写入文件:

    客户端(例如 Reducer 或用户程序)向 HDFS 的 NameNode 发起写入请求。
    客户端需要告诉 NameNode 文件的元信息(如文件名、大小等)
    NameNode 分配数据块

    NameNode 根据文件大小、HDFS 的配置(如块大小和副本数量),分配该文件需要的 数据块(Block)

    对于每个块,NameNode 会选择多个 DataNode(通常是 3 个)作为存储目标,并将这些位置信息返回给客户端。

  2. 分配数据块:

    通常会优先选择离客户端最近的节点,或者是同一个机架的节点,来减少网络延迟。副本的存储节点也会尽量分布在不同的机架上,提高数据可靠性

    客户端直接写入 DataNode:客户端根据 NameNode 返回的块位置信息,开始向第一组目标 DataNode 写入数据。写入过程是 流式传输,数据被切分为块后,直接发送给第一个 DataNode

    DataNode 进行副本复制:第一台 DataNode 在接收到数据后,会立即将该数据块传输到下一台 DataNode,依此类推,直到完成所有副本的写入(链式复制)

  3. DataNode 汇报块信息:

    每个 DataNode 在数据写入完成后,会向 NameNode 汇报存储的块信息(如块 ID、块大小、存储位置)

  4. 写入完成:
    当所有数据块都写入成功,并且所有副本都存储完成后,HDFS 客户端通知 NameNode 文件写入完成,保证数据的一致性

    NameNode 将文件的元数据标记为 “完成状态”

MapReduce框架的Java实现

这里手写一个简易的java实现的框架,方便大家理解

import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

// 定义 Mapper 接口
interface Mapper {
    List<Pair<String, Integer>> map(String input);
}

// 定义 Reducer 接口
interface Reducer {
    Pair<String, Integer> reduce(String key, List<Integer> values);
}

// 定义 Pair 类,用于存储键值对
class Pair<K, V> {
    public final K key;
    public final V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    @Override
    public String toString() {
        return key + ": " + value;
    }
}

// 实现支持多个 Mapper 和 Reducer 的 MapReduce 框架
class ParallelMapReduceFramework {
    private List<Mapper> mappers;
    private List<Reducer> reducers;
    private int reducerCount;

    public ParallelMapReduceFramework(List<Mapper> mappers, List<Reducer> reducers) {
        this.mappers = mappers;
        this.reducers = reducers;
        this.reducerCount = reducers.size();
    }

    public Map<String, Integer> execute(List<String> inputs) throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newFixedThreadPool(mappers.size());

        // 1. Map 阶段:将输入数据分给多个 Mapper 并行处理
        List<Future<List<Pair<String, Integer>>>> mapResults = new ArrayList<>();
        int chunkSize = inputs.size() / mappers.size();
        for (int i = 0; i < mappers.size(); i++) {
            int start = i * chunkSize;
            int end = (i == mappers.size() - 1) ? inputs.size() : (i + 1) * chunkSize;
            List<String> chunk = inputs.subList(start, end);
            Mapper mapper = mappers.get(i);

            mapResults.add(executor.submit(() -> {
                List<Pair<String, Integer>> results = new ArrayList<>();
                for (String input : chunk) {
                    results.addAll(mapper.map(input));
                }
                return results;
            }));
        }

        // 收集所有 Mapper 生成的键值对
        List<Pair<String, Integer>> allMappedData = new ArrayList<>();
        for (Future<List<Pair<String, Integer>>> future : mapResults) {
            allMappedData.addAll(future.get());
        }

        // 2. Shuffle 阶段:将键值对分片,分配给不同的 Reducer
        Map<Integer, List<Pair<String, Integer>>> reducerInput = new HashMap<>();
        for (int i = 0; i < reducerCount; i++) {
            reducerInput.put(i, new ArrayList<>());
        }

        for (Pair<String, Integer> pair : allMappedData) {
            int reducerIndex = Math.abs(pair.key.hashCode() % reducerCount);
            reducerInput.get(reducerIndex).add(pair);
        }

        // 3. Reduce 阶段:每个 Reducer 处理一个分片数据
        List<Future<Map<String, Integer>>> reduceResults = new ArrayList<>();
        for (int i = 0; i < reducers.size(); i++) {
            int index = i;
            Reducer reducer = reducers.get(i);
            List<Pair<String, Integer>> inputForReducer = reducerInput.get(index);

            reduceResults.add(executor.submit(() -> {
                // 按键分组
                Map<String, List<Integer>> groupedData = new HashMap<>();
                for (Pair<String, Integer> pair : inputForReducer) {
                    groupedData.computeIfAbsent(pair.key, k -> new ArrayList<>()).add(pair.value);
                }

                // Reduce 操作
                Map<String, Integer> result = new HashMap<>();
                for (Map.Entry<String, List<Integer>> entry : groupedData.entrySet()) {
                    result.put(entry.getKey(), reducer.reduce(entry.getKey(), entry.getValue()).value);
                }
                return result;
            }));
        }

        // 收集所有 Reducer 的结果
        Map<String, Integer> finalResult = new HashMap<>();
        for (Future<Map<String, Integer>> future : reduceResults) {
            finalResult.putAll(future.get());
        }

        executor.shutdown();
        return finalResult;
    }
}

// 实现单词统计的 Mapper 和 Reducer
class WordCountMapper implements Mapper {
    @Override
    public List<Pair<String, Integer>> map(String input) {
        String[] words = input.split("\\s+");
        List<Pair<String, Integer>> result = new ArrayList<>();
        for (String word : words) {
            result.add(new Pair<>(word.toLowerCase(), 1));
        }
        return result;
    }
}

class WordCountReducer implements Reducer {
    @Override
    public Pair<String, Integer> reduce(String key, List<Integer> values) {
        int sum = values.stream().mapToInt(Integer::intValue).sum();
        return new Pair<>(key, sum);
    }
}

// 测试并行 MapReduce 框架
public class ParallelWordCountExample {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        // 输入数据
        List<String> inputs = Arrays.asList(
            "Hello world hello",
            "MapReduce is powerful",
            "Hello MapReduce world",
            "Java is great",
            "Hello from the other side"
        );

        // 创建多个 Mapper 和 Reducer 实例
        List<Mapper> mappers = Arrays.asList(new WordCountMapper(), new WordCountMapper());
        List<Reducer> reducers = Arrays.asList(new WordCountReducer(), new WordCountReducer());

        // 执行 MapReduce
        ParallelMapReduceFramework framework = new ParallelMapReduceFramework(mappers, reducers);
        Map<String, Integer> wordCounts = framework.execute(inputs);

        // 输出结果
        wordCounts.forEach((word, count) -> System.out.println(word + ": " + count));
    }
}


Yarn(MapReduce2)

MapReducer1的问题:

前面提到的MapReducer的模型的问题:

  1. JobTracker的负载(可扩展性):MapReduce 1 中,,obtracker 同时负责作业调度(将任务与 tasktracker 匹配)和任务进度监控(跟踪任务、重启失败或迟缓的任务;记录任务流水,如维护计数器的计数)。当任务实例过多时,会导致系统无法再扩展
  2. JobTracker的可用性:由于JobTracker管理所有应用的状态,为了实现其可用性,就要创建副本并同步内存中的数据,强一致性意味着性能的损耗,弱一致性意味着故障恢复时的数据的差异。
  3. 节点的利用率:MapReduce 1中,每个tasktracker都配置有若干固定长度和类型的slot,这些slot是静态分配的,在配置的时候就被划分为map slot和reduce slot。一个map slot仅能用于运行一个map任务,一个reduce slot仅能用于运行一个reduce任务,所以分配不当就会导致系统性能低下

针对以上几个问题,Yarn将jobTracker的工作拆分,分为资源管理器(负责作业调度),以及application Master(负责任务进度监控,一个MapperReducer应用对应一个application Master),通过合理的资源分配提高节点的利用率,每个应用交由一个master管理,可以无限扩展资源避免单点的负载过大,还可以通过zookeeper等机制分别实现资源管理器和application master的高可用(如失败后再次申请资源)。

还有一个优点就是实现了多租户,对于资源的抽象和分配机制,可以在Yarn上构建不同的应用,如Spark等
![[Pasted image 20250126181445.png]]

MapReducer2的工作流程

申明几个概念

  1. Yarn的资源管理器,申请的资源即为一个container,可以指定其计算机的资源数量(内存和CPU),可以理解为之前版本的DataNode拆分成了多个容器
  2. Map,reduce的执行是容器中的进程,而前面提到的Application Master实际上也是容器中的进程,只是功能较为特殊
  3. 一个MapReduce应用对应一个Application Master
  4. 一个节点对应一个Node Manager,负责管理该节点上的所有容器和心跳

![[Pasted image 20250126182139.png]]

  • 1-5的步骤较为简单,就是创建一个container用于生成application master。具体是资源管理器收到调用它的submitApplication()消息后,便将请求传递给 YARN调度器(scheduler)。调度器分配一个容器,然后资源管理器在节点管理器的管理下在容器中启动application master的进程
  • 接下来,它接受来自共享文件系统的、在客户端计算的输入分片(步骤7)。然后对每一个分片创建一个 map任务对象以及由mapreduce.job.reduces 属性(通过作业的 setNumReduceTasks()方法设置),根据配置确定多个reduce任务对象
  • application master就会为该作业中的所有map任务和reduce任务向资源管理器请求执行具体任务的容器
  • 一旦资源管理器的调度器为任务分配了一个特定节点上的容器,application master就通过与节点管理器通信来启动容器(步骤9a和9b)。该任务由主类为YarnChild的一个 Java 应用程序执行。在它运行任务之前,首先将任务需要的资源本地化,包括作业的配置、JAR 文件和所有来自分布式缓存的文件(步骤 10)。最后,运行map任务或reduce任务(步骤11)。

Spark

适用场景

Spark 最突出的表现在于它能将作业与作业之间产生的大规模的工作数据集存储在内存中。MapReduce的数据集始终需要从磁盘上加载。从Spark 处理模型中获益最大的两种应用类型分别为迭代算法(即对一个数据集重复应用某个函数,直至满足退出条件)和交互式分析(用户向数据集发出一系列专用的探索性查询,比如查出数据后,根据数据在进行多次筛选分析)。

相关概念

RDD

RDD(Resilient Distributed Dataset)是 Spark 的核心抽象,用于表示分布式、不变、容错的数据集。

RDD的分区(Partition) 是 Spark 对数据的基本并行处理单元。RDD会被分割成多个分区,并在多个节点上并行处理,以实现高效的分布式计算。分区的大小就是HDFS 文件默认块大小(HDFS block size),通常为 128MB。可以理解为从HDFS中读取block到内存中,并且对这个block进行计算的抽象

举个例子,我们在 Spark 中使用 textFile(“hdfs://path/to/my_data.txt”) 读取时,RDD 会被划分为2 个分区,分别对应:

Partition 1 在 Node A 处理 Block 1 数据
Partition 2 在 Node B 处理 Block 2 数据

RDD的操作

RDD的生成,一个是读取文件,在不同的block中生成分区,还有一个就是对现有的RDD进行转换

JavaRDD<String> rdd = sc.textFile("hdfs://path/to/my_data.txt");
JavaRDD<String> filteredRDD = rdd.filter(line -> line.contains("error"));
JavaPairRDD<String, Integer> counts = filteredRDD
                                      .mapToPair(line -> new Tuple2<>(line, 1))
                                      .reduceByKey((a, b) -> a + b);
// 触发RDD开始转换,foreach函数也可以触发
counts.saveAsTextFile("hdfs://path/to/output");

以上的代码中的filter,mapToPair,reduceByKey就是一系列动作,类似于响应式编程中的订阅发布,当实际订阅(也就是这里的saveAsTextFile执行时),才会触发发布(RDD的开始转换),即惰性转换

RDD的持久化

spark的特性就是能够保存中间数据在内存默认RDD不会保存到内存中,当我们需要某部分数据时,可以手动将其保存到内存中,方便下一次计算,以下调用cache缓存
![[Pasted image 20250126230342.png]]

当执行RDD转换时,提示已经保存:
![[Pasted image 20250126230428.png]]

当下一次对该RDD重新进行不同的转换时,提示用到了缓存:
![[Pasted image 20250126230507.png]]

DAG和Stage

多个RDD的转换形成一个有向无环图,当一些可以基于本地的RDD的操作进行的转换的执行链,即每个分区的数据只依赖于上游 RDD (在本地)的一个分区的话(如 map(), filter()),我们当然可以在同一个节点中进行这个转换操作,这称为窄依赖

如果当前 RDD 的分区需要依赖多个上游 RDD 分区(如 reduceByKey(), groupBy()),那么会发生 Shuffle,相当于触发了一次reduce操作,这成为宽依赖

而这个DAG会因为出现宽依赖而进行stage的划分,将执行链拆分成不同的stage部分,每一个stage交给一个节点运行

这个很好理解,相当于上游RDD的reduceByKey需要进行一个类似于mapreducer中的shuffle操作,下游RDD的reduceByKey需要进行一个类似于mapreducer中的reduce操作,而reduce的数据非本地的,且对应的所需要的reduce的任务数量也不等同于map阶段的任务数,所以重新分配

![[Pasted image 20250126232041.png|475]]

执行过程

![[Pasted image 20250126224535.png]]

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

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

相关文章

如何让Dev-C++支持C++11及以上标准

目录 问题描述解决方案步骤1&#xff1a;打开编译选项 问题描述 在Dev-C中使用C11/17/20新特性&#xff08;如pop_back()等&#xff09;时&#xff0c;可能出现编译错误&#xff1a; #include <iostream> #include<string> using namespace std; int main() {str…

springBoot 整合ModBus TCP

ModBus是什么&#xff1a; ModBus是一种串行通信协议&#xff0c;主要用于从仪器和控制设备传输信号到主控制器或数据采集系统&#xff0c;例如用于测量温度和湿度并将结果传输到计算机的系统。&#xff08;百度答案&#xff09; ModBus 有些什么东西&#xff1a; ModBus其分…

linux常用加固方式

目录 一.系统加固 二.ssh加固 三.换个隐蔽的端口 四.防火墙配置 五.用户权限管理 六.暴力破解防护 七.病毒防护 八.磁盘加密 九.双因素认证2FA 十.日志监控 十一.精简服务 一.系统加固 第一步&#xff1a;打好系统补丁 sudo apt update && sudo apt upgra…

const的用法

文章目录 一、C和C中const修饰变量的区别二、const和一级指针的结合const修饰的量常出现的错误是:const和一级指针的结合总结&#xff1a;const和指针的类型转换公式 三、const和二级指针的结合 一、C和C中const修饰变量的区别 C中&#xff1a;const必须初始化&#xff0c;叫常…

Arduino大师练成手册 -- 控制 MH-SD 卡模块

要在 Arduino 上控制 MH-SD 卡模块&#xff0c;你可以按照以下步骤进行&#xff1a; 硬件连接 VCC&#xff1a;连接到 Arduino 的 3.3V 或 5V 引脚&#xff08;根据模块的要求&#xff09;。 GND&#xff1a;连接到 Arduino 的 GND 引脚。 CS&#xff1a;连接到 Arduino 的…

Android OpenGL(六) 纹理

纹理 纹理是一个2D图片&#xff08;甚至也有1D和3D的纹理&#xff09;&#xff0c; 它可以用来添加物体的细节&#xff1b;你可以想象纹理是一张绘有砖块的纸&#xff0c;无缝折叠贴合到你的3D的 房子上&#xff0c;这样你的房子看起来就像有砖墙外表了 纹理环绕方式 纹理坐…

一文大白话讲清楚webpack基本使用——18——HappyPack

文章目录 一文大白话讲清楚webpack基本使用——18——HappyPack1. 建议按文章顺序从头看&#xff0c;一看到底&#xff0c;豁然开朗2. 啥是HappyPack3. 怎么使用HappyPack 一文大白话讲清楚webpack基本使用——18——HappyPack 1. 建议按文章顺序从头看&#xff0c;一看到底&a…

深入 Rollup:从入门到精通(二)Rollup简介

1. 简介 对于将rollup描述为一个打包工具&#xff0c;我觉得并不是很精准&#xff0c;稍有些笼统&#xff0c;未能完全展示它的功能。 我更倾向于将它定义为&#xff1a; 以模块化的方式加载你的 js 文件&#xff08;如果使用插件&#xff0c;也可以支持其他静态资源文件&…

【数据结构】_C语言实现不带头非循环单向链表

目录 1. 链表的概念及结构 2. 链表的分类 3. 单链表的实现 3.1 SList.h头文件 3.2 SList.c源文件 3.3 Test_SList.c测试文件 关于线性表&#xff0c;已介绍顺序表&#xff0c;详见下文&#xff1a; 【数据结构】_顺序表-CSDN博客 本文介绍链表&#xff1b; 基于顺序表…

HTML5 Web Worker 的使用与实践

引言 在现代 Web 开发中&#xff0c;用户体验是至关重要的。如果页面在执行复杂计算或处理大量数据时变得卡顿或无响应&#xff0c;用户很可能会流失。HTML5 引入了 Web Worker&#xff0c;它允许我们在后台运行 JavaScript 代码&#xff0c;从而避免阻塞主线程&#xff0c;保…

Excel 技巧21 - Excel中整理美化数据实例,Ctrl+T 超级表格(★★★)

本文讲Excel中如何整理美化数据的实例&#xff0c;以及CtrlT 超级表格的常用功能。 目录 1&#xff0c;Excel中整理美化数据 1-1&#xff0c;设置间隔行颜色 1-2&#xff0c;给总销量列设置数据条 1-3&#xff0c;根据总销量设置排序 1-4&#xff0c;加一个销售趋势列 2&…

手撕Diffusion系列 - 第九期 - 改进为Stable Diffusion(原理介绍)

手撕Diffusion系列 - 第九期 - 改进为Stable Diffusion&#xff08;原理介绍&#xff09; 目录 手撕Diffusion系列 - 第九期 - 改进为Stable Diffusion&#xff08;原理介绍&#xff09;DDPM 原理图Stable Diffusion 原理Stable Diffusion的原理解释Stable Diffusion 和 Diffus…

倍频增量式编码器--角度插值法输出A,B(Aangular Interpolation)

问题是&#xff1a; 最大速度&#xff0c;周期刻度&#xff0c;最小细分刻度&#xff0c;可以计算得到&#xff1a; 结论&#xff1a; 按照最高速度采样&#xff1b;数字A,B输出间隔时间&#xff1a;按照计算角度 插入细分角度运算算时间&#xff08;最快速度&#xff09;&a…

基于微信小程序的助农扶贫系统设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

嵌入式C语言:结构体对齐

目录 一、对齐的原因 1.1. 硬件访问效率 1.2. 内存管理简化 1.3. 编译器优化 1.4. 代码示例 二、对齐规则 2.1. 基本数据类型对齐 2.2. 结构体成员对齐 2.3. 结构体整体对齐 2.4. 代码示例 三、对齐控制 3.1. 使用 #pragma pack 3.2. 使用 __attribute__((packed)…

网络工程师 (1)数据的表示

一、R进制的表示及互转 R进制是一种数制表示方法&#xff0c;其中R代表基数&#xff0c;采用R个基本符号&#xff08;0, 1, 2, …, R-1&#xff09;表示各位上的数字&#xff0c;并遵循“逢R进一”的运算规则。在R进制中&#xff0c;每一位上的数字均不超过R-1。R进制与十进制之…

coffee销售数据集分析:基于时间趋势分析的实操练习

**文章说明&#xff1a;**对coffee销售数据集的简单分析练习&#xff08;时间趋势分析练习&#xff09;&#xff0c;主要是为了强化利用python进行数据分析的实操能力。属于个人的练习文章。 **注&#xff1a;**这是我第一次使用md格式编辑博客文章&#xff0c;排版上还是不是很…

SQL在DBA手里-改写篇

背景 最近运营需要做月报汇总交易情况&#xff0c;之前一直是他们手工出的数据&#xff0c;他们想做成月初自动发送邮件&#xff0c;从而减轻他们的工作量。于是他们提供SQL我们在邮件服务器配置做定时发送任务。 表介绍&#xff08;表及字段已做脱敏处理&#xff09; trans…

vue3入门基础学习之搭建登录验证功能

环境准备&#xff1a;node.js、Visual Studio Code&#xff08;也可以是其他开发工具&#xff0c;选自己熟悉的就行&#xff09; 下载地址&#xff1a;https://nodejs.p2hp.com/https://code.visualstudio.com/ 新建一个vue3的项目&#xff0c;选一个文件夹执行以下命令 使用…

Scrapy如何设置iP,并实现IP重用, IP代理池重用

前置知识 1/3乐观锁 2/3 Scrapy流程(非全部) 3/3 关于付费代理 我用的"快代理", 1000个ip, 每个ip1min的有效期, 你用的时候, 把你的链接, 用户名填上去就行 设置代理IP &#x1f512; & 帮助文档: ①meta ②meta#proxy$ 语法: ①proxy的设置: Request对象中…