Hadoop之MapReduce 详细教程

MapReduce仅作了解,生产上很少使用该计算程序

1、MapReduce介绍

MapReduce 思想在生活中处处可见。或多或少都曾接触过这种思想。MapReduce的思想核心是“分而治之”,适用于大量复杂的任务处理场景(大规模数据处理场景)。即使是发布过论文实现分布式计算的谷歌也只是实现了这种思想,而不是自己原创。

1、Map 负责“分”,即把复杂的任务分解为若干个“简单的任务”来并行处理。可以进行拆分的前提是这些小任务可以并行计算,彼此间几乎没有依赖关系。

2、Reduce 负责“合”,即对 map 阶段的结果进行全局汇总。

3、MapReduce 运行在 yarn 集群

  • ResourceManager
  • NodeManager

这两个阶段合起来正是 MapReduce 思想的体现。
在这里插入图片描述
还有一个比较形象的语言解释 MapReduce:
我们要数图书馆中的所有书。你数 1 号书架,我数 2 号书架。这就是“Map”。我们人越多,数书就更快。现在我们到一起,把所有人的统计数加在一起。这就是“Reduce”。

1、MapReduce 设计构思

MapReduce 是一个分布式运算程序的编程框架,核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在 Hadoop集群上。

既然是做计算的框架,那么表现形式就是有个输入(input),MapReduce 操作这个输入(input),通过本身定义好的计算模型,得到一个输出(output)。对许多开发者来说,自己完完全全实现一个并行计算程序难度太大,而MapReduce 就是一种简化并行计算的编程模型,降低了开发并行应用的入门门槛。

Hadoop MapReduce 构思体现在如下的三个方面:

1、如何对付大数据处理:分而治之
对相互间不具有计算依赖关系的大数据,实现并行最自然的办法就是采取分而治之的策略。并行计算的第一个重要问题是如何划分计算任务或者计算数据以便对划分的子任务或数据块同时进行计算。不可分拆的计算任务或相互间有依赖关系的数据无法进行并行计算!

2、构建抽象模型:Map 和 Reduce
MapReduce 借鉴了函数式语言中的思想,用 Map 和 Reduce 两个函数提供了高层的并行编程抽象模型。

Map: 对一组数据元素进行某种重复式的处理;
Reduce: 对 Map 的中间结果进行某种进一步的结果整理。

MapReduce 中定义了如下的 Map 和 Reduce 两个抽象的编程接口,由用户去编程实现:

  • map: (k1; v1) → [(k2; v2)]
  • reduce: (k2; [v2]) → [(k3; v3)]

Map 和 Reduce 为程序员提供了一个清晰的操作接口抽象描述。通过以上两个编程接口,大家可以看出 MapReduce 处理的数据类型是<key,value>键值对。

3、MapReduce 框架结构 一个完整的 mapreduce 程序在分布式运行时有三类实例进程:

  • MR AppMaster:负责整个程序的过程调度及状态协调;
  • MapTask:负责 map 阶段的整个数据处理流程;
  • ReduceTask:负责 reduce 阶段的整个数据处理流程。
    在这里插入图片描述

2、MapReduce 编程规范

MapReduce 的开发一共有八个步骤, 其中 Map 阶段分为 2 个步骤,Shuffle 阶段
4 个步骤,Reduce 阶段分为 2 个步骤

1、Map 阶段 2 个步骤:

  • 1.1 设置 InputFormat 类, 将数据切分为 Key-Value(K1 和 V1)对, 输入到第二步
  • 1.2 自定义 Map 逻辑, 将第一步的结果转换成另外的 Key-Value(K2 和 V2) 对, 输出结果

2、Shuffle 阶段 4 个步骤:

  • 2.1 对输出的 Key-Value 对进行分区
  • 2.2 对不同分区的数据按照相同的 Key 排序
  • 2.3 (可选) 对分组过的数据初步规约, 降低数据的网络拷贝
  • 2.4 对数据进行分组, 相同 Key 的 Value 放入一个集合中

3、Reduce 阶段 2 个步骤:

  • 3.1 对多个 Map 任务的结果进行排序以及合并, 编写 Reduce 函数实现自己的逻辑, 对输入的 Key-Value 进行处理, 转为新的 Key-Value(K3 和 V3)输出
  • 3.2 设置 OutputFormat 处理并保存 Reduce 输出的 Key-Value 数据

在这里插入图片描述

3、Mapper 以及 Reducer 抽象类介绍

为了开发我们的 MapReduce 程序,一共可以分为以上八个步骤,其中每个步骤都是一个 class 类,我们通过 job 对象将我们的程序组装成一个任务提交即可。为了简化我们的 MapReduce 程序的开发,每一个步骤的 class 类,都有一个既定的父类,让我们直接继承即可,因此可以大大简化我们的 MapReduce 程序的开发难度,也可以让我们快速的实现功能开发。

MapReduce 编程当中,其中最重要的两个步骤就是我们的 Mapper 类和 Reducer类

Mapper 抽象类的基本介绍
在 hadoop2.x 当中 Mapper 类是一个抽象类,我们只需要覆写一个 java 类,继承自 Mapper 类即可,然后重写里面的一些方法,就可以实现我们特定的功能,接下来我们来介绍一下 Mapper 类当中比较重要的四个方法

  1. setup 方法: 我们 Mapper 类当中的初始化方法,我们一些对象的初始化工作都可以放到这个方法里面来实现
  2. map 方法: 读取的每一行数据,都会来调用一次 map 方法,这个方法也是我们最重要的方法,可以通过这个方法来实现我们每一条数据的处理
  3. cleanup 方法: 在我们整个 maptask 执行完成之后,会马上调用 cleanup方法,这个方法主要是用于做我们的一些清理工作,例如连接的断开,资源的关闭等等
  4. run 方法: 如果我们需要更精细的控制我们的整个 MapTask 的执行,那么我们可以覆写这个方法,实现对我们所有的 MapTask 更精确的操作控制

Reducer 抽象类基本介绍
同样的道理,在我们的 hadoop2.x 当中,reducer 类也是一个抽象类,抽象类允许我们可以继承这个抽象类之后,重新覆写抽象类当中的方法,实现我们的逻辑的自定义控制。接下来我们也来介绍一下 Reducer 抽象类当中的四个抽象方法

  1. setup 方法: 在我们的 ReduceTask 初始化之后马上调用,我们的一些对象的初始化工作,都可以在这个类当中实现
  2. reduce 方法: 所有从 MapTask 发送过来的数据,都会调用 reduce 方法,这个方法也是我们 reduce 当中最重要的方法,可以通过这个方法实现我们的数据的处理
  3. cleanup 方法: 在我们整个 ReduceTask 执行完成之后,会马上调用cleanup 方法,这个方法主要就是在我们 reduce 阶段处理做我们一些清理工作,例如连接的断开,资源的关闭等等
  4. run 方法: 如果我们需要更精细的控制我们的整个 ReduceTask 的执行,那么我们可以覆写这个方法,实现对我们所有的 ReduceTask 更精确的操作控制

4、WordCount 示例编写

需求:在一堆给定的文本文件中统计输出每一个单词出现的总次数
node01 服务器执行以下命令,准备数,数据格式准备如下

cd /export/servers
vim wordcount.txt
#添加以下内容:
hello hello
world world
hadoop hadoop
hello world
hello flume
hadoop hive
hive kafka
flume storm
hive oozie

将数据文件上传到 hdfs 上面去

hdfs dfs -mkdir /wordcount/
hdfs dfs -put wordcount.txt /wordcount/

1、定义一个 mapper 类

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
// mapper 程序: 需要继承 mapper 类, 需要传入 四个类型:
/* 在 hadoop 中, 对 java 的类型都进行包装, 以提高传输的效率 writable
keyin : k1 Long ---- LongWritable
valin : v1 String ------ Text
keyout : k2 String ------- Text
valout : v2 Long -------LongWritable
*/
public class MapTask extends Mapper<LongWritable,Text,Text,LongWritable> {
	/**
	*
	* @param key : k1
	* @param value v1
	* @param context 上下文对象 承上启下功能
	* @throws IOException
	* @throws InterruptedException
	*/
	@Override
	protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
		//1. 获取 v1 中数据
		String val = value.toString();
		//2. 切割数据
		String[] words = val.split(" ");
		Text text = new Text();
		LongWritable longWritable = new LongWritable(1);
		//3. 遍历循环, 发给 reduce
		for (String word : words) {
			text.set(word);
			context.write(text,longWritable);
		}
	}
}

2、定义一个 reducer 类

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
	/**
	* KEYIN : k2 -----Text
	* VALUEIN : v2 ------LongWritable
	* KEYOUT : k3 ------ Text
	* VALUEOUT : v3 ------ LongWritable
	*/
	public class ReducerTask extends Reducer<Text, LongWritable, Text, LongWritable> {
	@Override
	protected void reduce(Text key, Iterable<LongWritable> values, Context context)throws IOException, InterruptedException {
	//1. 遍历 values 获取每一个值
	long v3 = 0;
		for (LongWritable longWritable : values) {
			v3 += longWritable.get(); //1
		}
	//2. 输出
	context.write(key,new LongWritable(v3));
	}
}

3、定义一个主类,用来描述 job 并提交 job

import com.sun.org.apache.bcel.internal.generic.NEW;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.nativeio.NativeIO;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
// 任务的执行入口: 将八步组合在一起
public class JobMain extends Configured implements Tool {
	// 在 run 方法中编写组装八步
	@Override
	public int run(String[] args) throws Exception {
		Job job = Job.getInstance(super.getConf(), "JobMain");
		//如果提交到集群操作. 需要添加一步 : 指定入口类
		job.setJarByClass(JobMain.class);
		//1. 封装第一步: 读取数据
		job.setInputFormatClass(TextInputFormat.class);
		TextInputFormat.addInputPath(job,new Path("hdfs://node01:8020/wordcount.txt
	"));
		//2. 封装第二步: 自定义 map 程序
		job.setMapperClass(MapTask.class);
		job.setMapOutputKeyClass(Text.class);
		job.setMapOutputValueClass(LongWritable.class);
		//3. 第三步 第四步 第五步 第六步 省略
		//4. 第七步: 自定义 reduce 程序
		job.setReducerClass(ReducerTask.class);
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(LongWritable.class);
		//5) 第八步 : 输出路径是一个目录, 而且这个目录必须不存在的
		job.setOutputFormatClass(TextOutputFormat.class);
		TextOutputFormat.setOutputPath(job,new Path("hdfs://node01:8020/output"));
		//6) 提交任务:
		boolean flag = job.waitForCompletion(true); // 成功 true 不成功 false
		return flag ? 0 : 1;
	}
		
		public static void main(String[] args) throws Exception {
			Configuration configuration = new Configuration();
			
			JobMain jobMain = new JobMain();
			int i = ToolRunner.run(configuration, jobMain, args); //返回值 退出码
			System.exit(i); // 退出程序 0 表示正常 其他值表示有异常 1
		}
}

提醒:代码开发完成之后,就可以打成 jar 包放到服务器上面去运行了,实际工作当中,都是将代码打成 jar 包,开发 main 方法作为程序的入口,然后放到集群上面去运行

5、MapReduce 程序运行模式

本地运行模式

  1. mapReduce 程序是被提交给 LocalJobRunner 在本地以单进程的形式运行
  2. 而处理的数据及输出结果可以在本地文件系统,也可以在 hdfs 上
  3. 怎样实现本地运行?写一个程序,不要带集群的配置文件本质是程序的 conf 中是否有 mapreduce.framework.name=local 以及yarn.resourcemanager.hostname=local 参数
  4. 本地模式非常便于进行业务逻辑的 debug,只要在 idea 中打断点即可【本地模式运行代码设置】
    configuration.set(“mapreduce.framework.name”,“local”);
    configuration.set(“yarn.resourcemanager.hostname”,“local”);
    以上两个是不需要修改的,如果要在本地目录测试, 可有修改 hdfs 的路径

TextInputFormat.addInputPath(job,new Path(“file:///D:\wordcount\input”));
TextOutputFormat.setOutputPath(job,new Path(“file:///D:\wordcount\output”));

集群运行模式

  1. 将 mapReduce 程序提交给 yarn 集群,分发到很多的节点上并发执行
  2. 处理的数据和输出结果应该位于 hdfs 文件系统
  3. 提交集群的实现步骤:
    将程序打成 JAR 包,然后在集群的任意一个节点上用 hadoop 命令启动
    yarn jar hadoop_hdfs_operate-1.0-SNAPSHOT.jar cn.itcast.hdfs.demo1.JobMain

6、MapReduce 的运行机制详解

1、MapTask工作机制

在这里插入图片描述整个 Map 阶段流程大体如上图所示。

简单概述:
inputFile 通过 split 被逻辑切分为多个 split 文件,通过 Record按行读取内容给 map(用户自己实现的)进行处理,数据被 map 处理结束之后交给 OutputCollector 收集器,对其结果 key 进行分区(默认使用 hash 分区),然后写入 buffer,每个 map task 都有一个内存缓冲区(环形缓冲区),存储着 map 的输出结果,当缓冲区快满的时候需要将缓冲区的数据以一个临时文件的方式存放到磁盘,当整个 map task 结束后再对磁盘中这个 map task 产生的所有临时文件做合并,生成最终的正式输出文件,然后等待 reduce task 来拉数据

总结:加载文件,文件切割,按行读取,map拆分转换,map结果输出mapTask的环形缓冲区(100m)(临时存放),80m触发线程溢写到磁盘,在此期间会进行 排序,预combiner(可选)(结果预合并,减少写磁盘数据量),溢写文件聚合

详细步骤

  1. 读取数据组件 InputFormat (默认 TextInputFormat) 会通过 getSplits 方法
    对输入目录中文件进行逻辑切片规划得到 block, 有多少个 block 就对应启动
    多少个 MapTask

  2. 将输入文件切分为 block 之后, 由 RecordReader 对象 (默认是LineRecordReader) 进行读取, 以 \n 作为分隔符, 读取一行数据, 返回 <key,value>. Key 表示每行首字符偏移值, Value 表示这一行文本内容

  3. 读取 block 返回 <key,value>, 进入用户自己继承的 Mapper 类中,执行用户重写的 map 函数, RecordReader 读取一行这里调用一次

  4. Mapper 逻辑结束之后, 将 Mapper 的每条结果通过 context.write 进行collect 数据收集. 在 collect 中, 会先对其进行分区处理
    默认使用=HashPartitionerMapReduce 提供 Partitioner 接口, 它的作用就是根据 Key 或 Value 及Reducer 的数量来决定当前的这对输出数据最终应该交由哪个 Reduce task 处理, 默认对 Key Hash 后再以 Reducer 数量取模. 默认的取模方式只是为了平均Reducer 的处理能力, 如果用户自己对 Partitioner 有需求, 可以订制并设置到Job 上

  5. 接下来, 会将数据写入内存, 内存中这片区域叫做环形缓冲区, 缓冲区的作用是批量收集 Mapper 结果, 减少磁盘 IO 的影响. 我们的 Key/Value 对以及Partition 的结果都会被写入缓冲区. 当然, 写入之前,Key 与 Value 值都会被序列化成字节数组
    环形缓冲区(其实是一个数组, 数组中存放着 Key, Value 的序列化数据和Key,Value 的元数据信息, 包括 Partition, Key 的起始位置, Value 的起始位置以及Value 的长度. 环形结构是一个抽象概念)。
    缓冲区是有大小限制, 默认是 100MB. 当 Mapper 的输出结果很多时, 就可能会撑爆内存, 所以需要在一定条件下将缓冲区中的数据临时写入磁盘, 然后重新利用这块缓冲区. 这个从内存往磁盘写数据的过程被称为 Spill, 中文可译为溢写. 这个溢写是由单独线程来完成, 不影响往缓冲区写 Mapper 结果的线程. 溢写线程启动时不应该阻止 Mapper 的结果输出, 所以整个缓冲区有个溢写的比例spill.percent. 这个比例默认是0.8, 也就是当缓冲区的数据已经达到阈值buffer size * spill percent = 100MB * 0.8 = 80MB, 溢写线程启动, 锁定这 80MB的内存, 执行溢写过程. Mapper 的输出结果还可以往剩下的 20MB 内存中写, 互不影响

  6. 当溢写线程启动后, 需要对这 80MB 空间内的 Key 做排序 (Sort). 排序是MapReduce 模型默认的行为, 这里的排序也是对序列化的字节做的排序如果 Job 设置过 Combiner,那么现在就是使用 Combiner 的时候了. 将有相同Key 的 Key/Value 对的 Value 合并在起来, 减少溢写到磁盘的数据量.

  7. Combiner会优化 MapReduce 的中间结果, 所以它在整个模型中会多次使用 \ 那哪些场景才能使用 Combiner 呢?从这里分析, Combiner 的输出是 Reducer 的输入,Combiner 绝不能改变最终的计算结果. Combiner 只应该用于那种 Reduce 的输入Key/Value 与输出 Key/Value 类型完全一致, 且不影响最终结果的场景. 比如累加, 最大值等. Combiner 的使用一定得慎重, 如果用好, 它对 Job 执行效率有帮助, 反之会影响 Reducer 的最终结果

  8. 合并溢写文件, 每次溢写会在磁盘上生成一个临时文件 (写之前判断是否有Combiner), 如果 Mapper 的输出结果真的很大, 有多次这样的溢写发生, 磁盘上相应的就会有多个临时文件存在. 当整个数据处理结束之后开始对磁盘中的临时文件进行 Merge 合并, 因为最终的文件只有一个, 写入磁盘, 并且为这个文件提供了一个索引文件, 以记录每个 reduce 对应数据的偏移量

mapTask 的一些基础设置配置:

在这里插入图片描述
在这里插入图片描述

2、ReduceTask 工作机制

在这里插入图片描述Reduce 大致分为 copy、sort、reduce 三个阶段,重点在前两个阶段。

copy 阶段
包含一个 eventFetcher 来获取已完成的 map 列表,由 Fetcher 线程去copy 数据,在此过程中会启动两个 merge 线程,分别为 inMemoryMerger 和onDiskMerger,分别将内存中的数据 merge 到磁盘和将磁盘中的数据进行合并merge。

待数据 copy 完成之后,copy 阶段就完成了,开始进行 sort 阶段,sort阶段主要是执行 finalMerge 操作,纯粹的 sort 阶段,完成之后就是 reduce阶段,调用用户定义的 reduce 函数进行处理

详细步骤

  1. Copy 阶段,简单地拉取数据。Reduce 进程启动一些数据 copy 线程(Fetcher),通过 HTTP 方式请求 maptask 获取属于自己的文件。

  2. Merge 阶段。这里的 merge 如 map 端的 merge 动作,只是数组中存放的是不同map 端 copy 来的数值。Copy 过来的数据会先放入内存缓冲区中,这里的缓冲区大小要比 map 端的更为灵活。merge 有三种形式:内存到内存;内存到磁盘;磁盘到磁盘。默认情况下第一种形式不启用。当内存中的数据量到达一定阈值,就启动内存到磁盘的 merge。与 map 端类似,这也是溢写的过程,这个过程中如果你设置有 Combiner,也是会启用的,然后在磁盘中生成了众多的溢写文件。第二种 merge方式一直在运行,直到没有 map 端的数据时才结束,然后启动第三种磁盘到磁盘的merge 方式生成最终的文件。

  3. 合并排序。把分散的数据合并成一个大的数据后,还会再对合并后的数据排序。

  4. 对排序后的键值对调用 reduce 方法,键相等的键值对调用一次 reduce 方法,每次调用会产生零个或者多个键值对,最后把这些输出的键值对写入到 HDFS 文件中。

3、Shuffle过程

map 阶段处理的数据如何传递给 reduce 阶段,是 MapReduce 框架中最关键的一个流程,这个流程就叫 shuffle

shuffle: 洗牌、发牌 ——(核心机制:数据分区,排序,分组,规约,合并等
过程)
在这里插入图片描述shuffle 是 Mapreduce 的核心,它分布在 Mapreduce 的 map 阶段和 reduce阶段。一般把从 Map 产生输出开始到 Reduce 取得数据作为输入之前的过程称作 shuffle。

处理流程:

  1. Collect 阶段:将 MapTask 的结果输出到默认大小为 100M 的环形缓冲区,保存的是 key/value,Partition 分区信息等。

  2. Spill 阶段:当内存中的数据量达到一定的阀值的时候,就会将数据写入本地磁盘,在将数据写入磁盘之前需要对数据进行一次排序的操作,如果配置了 combiner,还会将有相同分区号和 key 的数据进行排序。

  3. Merge 阶段:把所有溢出的临时文件进行一次合并操作,以确保一个 MapTask 最终只产生一个中间数据文件。

  4. Copy 阶段:ReduceTask 启动 Fetcher 线程到已经完成 MapTask 的节点上复制一份属于自己的数据,这些数据默认会保存在内存的缓冲区中,当内存的缓冲区达
    到一定的阀值的时候,就会将数据写到磁盘之上。

  5. Merge 阶段:在 ReduceTask 远程复制数据的同时,会在后台开启两个线程对内存到本地的数据文件进行合并操作。

  6. Sort 阶段:在对数据进行合并的同时,会进行排序操作,由于 MapTask 阶段已经对数据进行了局部的排序,ReduceTask 只需保证 Copy 的数据的最终整体有效性即可。

  7. Shuffle 中的缓冲区大小会影响到 mapreduce 程序的执行效率,原则上说,缓
    冲区越大,磁盘 io 的次数越少,执行速度就越快
    缓冲区的大小可以通过参数调整, 参数:mapreduce.task.io.sort.mb 默认 100M

转载自:五分钟学大数据

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

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

相关文章

54.网游逆向分析与插件开发-游戏增加自动化助手接口-项目需求与需求拆解

内容来源于&#xff1a;易道云信息技术研究院VIP课 项目需求&#xff1a; 为游戏增加VIP功能-自动化助手。自动化助手做的是首先要说一下背景&#xff0c;对于授权游戏来讲它往往年限都比较老&#xff0c;老游戏和新游戏设计理念是不同的&#xff0c;比如说老游戏基本上在10年…

用户规模破亿!基于文心一言的创新应用已超4000个

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

《MySQL》事务篇

事务特性 ACID Atomicity原子性&#xff1a;事务中的操作要么全部完成&#xff0c;要么全部失败。 Consistency一致性&#xff1a;事务操作前后&#xff0c;数据满足完整性约束。 Isolation隔离性&#xff1a;允许并发执行事务&#xff0c;每个事务都有自己的数据空间&…

MySQL事务、四大原则、执行步骤、四种隔离级别、锁、脏读、脏写等

MySQL事务 MySQL事务1.什么是事务&#xff1f;2.事务的四大原则3.事务执行的步骤4、事务的隔离性5、MySQL中的锁 MySQL事务 模拟一个转账业务&#xff1a; 上图中的sql语句&#xff1a; update from table set money mongey - 100 where name A; update from table set mone…

SEO中的实体:它们是什么以及为什么它们很重要?

从了解搜索历史到区分实体与关键字&#xff0c;真正了解实体是什么&#xff0c;以便获得更有针对性的搜索流量。 关于SEO专业人士应该如何理解&#xff0c;更重要的是&#xff0c;如何利用SEO中的“实体”&#xff0c;存在很多困惑。 我明白这是从哪里来的&#xff0c;尤其是…

免费代理IP:如何获取?有什么风险?有什么性价比高的代理IP?

您可能已经知道&#xff0c;生活中没有什么是真正免费的。代理IP虽然用于保护隐私&#xff0c;但也有非常多代理都是免费的&#xff0c;这对于不想使用付费替代方案的用户来说是一个巨大的优势。在这篇博文中&#xff0c;我们将深入研究免费代理的细节&#xff0c;并评估这把双…

磁盘管理 :逻辑卷、磁盘配额

一 LVM可操作的对象&#xff1a;①完成的磁盘 ②完整的分区 PV 物理卷 VG 卷组 LV 逻辑卷 二 LVM逻辑卷管理的命令 三 建立LVM逻辑卷管理 虚拟设置-->一致下一步就行-->确认 echo "- - -" > /sys/class/scsi_host/host0/scan;echo "- -…

Redis缓存雪崩、缓存击穿、缓存穿透

1. 什么是缓存雪崩 当我们提到缓存系统中的问题&#xff0c;缓存雪崩是一个经常被讨论的话题。缓存雪崩是指在某一时刻发生大量的缓存失效&#xff0c;导致瞬间大量的请求直接打到了数据库&#xff0c;可能会导致数据库瞬间压力过大甚至宕机。尤其在高并发的系统中&#xff0c;…

Springboot使用log4j2日志框架

文章目录 1.pom.xml引入依赖2.配置文件引入log4j2的配置文件3.导入log4j2配置文件4.通过Slf4j注解来使用log.info()等最后 1.pom.xml引入依赖 提示&#xff1a;lombok用于Slf4j注解 <dependency><groupId>org.springframework.boot</groupId><artifactId&…

机器人中的数值优化之牛顿共轭梯度法

欢迎大家关注我的B站&#xff1a; 偷吃薯片的Zheng同学的个人空间-偷吃薯片的Zheng同学个人主页-哔哩哔哩视频 (bilibili.com) 本文ppt来自深蓝学院《机器人中的数值优化》 如何解决Hessian矩阵非正定的情况 求解线性系统需要很精确么 引入截断的机制&#xff0c;如果Hessia…

遥感技术应用于作物类型种植面积估算实例

1.农作物遥感分类 1.1 利用多时相环境星 CCD 数据作物分类识别实验 采用支持向量机分类器进行基于象素遥感影像分类方法。在分类过程中&#xff0c;分别对不同日期的单景环境星数据以及不同日期环境星数据的组合进行分类&#xff0c;以评价环境星在作物分类中的应用潜力&#x…

【C语言】分支与循环语句

什么是语句&#xff1f; C语句可分为以下五类&#xff1a; 表达式语句函数调用语句控制语句 &#xff08;本篇重点介绍&#xff09;复合语句空语句 控制语句用于控制程序的执行流程&#xff0c;以实现程序的各种结构方式。C语言支持三种结构&#xff1a; 顺序结构选择结构循…

MidJourney笔记(9)-daily_theme-docs-describe

/daily_theme 切换 #daily-theme 频道更新的通知。 但我发现在对话框那里,是没有这个命令的: 但官网是有介绍,不知道是不是版本问题还是这个命令已经无效。 但后来,我发现这个命令是要在Midjourney服务对话框那里才有,在我们后面添加的Mid

哈希桶的模拟实现【C++】

文章目录 哈希冲突解决闭散列 &#xff08;开放定址法&#xff09;开散列 &#xff08;链地址法、哈希桶&#xff09;开散列实现&#xff08;哈希桶&#xff09;哈希表的结构InsertFindErase 哈希冲突解决 闭散列 &#xff08;开放定址法&#xff09; 发生哈希冲突时&#xf…

MySQL数据库多版本并发控制(MVCC)

在数据库中&#xff0c;并发控制是确保多个事务能够同时执行&#xff0c;而不会导致数据不一致或冲突的关键机制。多版本并发控制(MVCC)是一种流行的并发控制方法&#xff0c;它可以允许多个事务同时读取同一数据项的不同版本&#xff0c;而不会相互阻塞。本文将讨论MVCC的原理…

【每日一题】LeetCode206.反转链表

个人主页&#xff1a;白日依山璟 专栏&#xff1a;Java|数据结构与算法|每日一题 文章目录 1. 题目描述示例1示例2示例3提示 2. 思路3.代码 1. 题目描述 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例1 输入&#xff1a;head [1…

FreeRTOS任务调度

开启任务调度器 vTaskStartScheduler(); 无参数, 无返回值. 作用是用于启动任务调度器&#xff0c;任务调度器启动后&#xff0c; FreeRTOS 便会开始进行任务调度 . 如果允许了静态创建任务, 则创建空闲任务和创建定时器任务都会变为需要程序员手动实现创建. 1.创建空闲任务(…

成为一名成功的互联网产品经理需要掌握哪些技能?

在现代互联网产业中&#xff0c;产品经理扮演着至关重要的角色。作为产品经理&#xff0c;需要不断地学习和提高自己的技能&#xff0c;以便为用户提供更佳的产品体验。以下是成为一名成功的互联网产品经理所需要掌握的技能&#xff1a; 1.产品设计技能 产品设计是产品经理的…

Linux 线程概念

文章目录 前言线程的概念线程的操作操作的原理补充与说明 前言 ① 函数的具体说明被放在补充与说明部分 ② 只说些基础概念和函数使用 线程的概念 网络回答&#xff1a;Linux 线程是指在 Linux 操作系统中创建和管理的轻量级执行单元。线程是进程的一部分&#xff0c;与进程…

使用数组创建链表的解决方案

因为在创建链表时用到了这种方法&#xff0c;后面发现这种方法创建链表做删除操作时不是很好&#xff0c;就打算删除&#xff0c;但是觉得这种方法可能对部分读者有参考意义&#xff0c;就基于数组创建链表的方法单独发了一篇&#xff0c;完整的~链表五大基础操作的实现方法可参…