【Hadoop_06】MapReduce的概述与wc案例

这篇具有很好参考价值的文章主要介绍了【Hadoop_06】MapReduce的概述与wc案例。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、MapReduce概述

1.1 MapReduce定义

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

1.2 MapReduce优点

1)MapReduce易于编程

它简单的实现一些接口,就可以完成一个分布式程序, 这个分布式程序可以分布到大量廉价的PC机器上运行。也就是说你写一个分布式程序,跟写一个简单的串行程序是一模一样的。就是因为这个特点使得MapReduce编程变得非常流行。

2)良好的扩展性

当你的计算资源不能得到满足的时候,你可以通过简单的增加机器来扩展它的计算能力。

3)高容错性

MapReduce设计的初衷就是使程序能够部署在廉价的PC机器上,这就要求它具有很高的容错性。比如其中一台机器挂了,它可以把上面的计算任务转移到另外一个节点上运行,不至于这个任务运行失败,而且这个过程不需要人工参与,而完全是由Hadoop内部完成的。

4)适合PB级以上海量数据的离线处理

可以实现上千台服务器集群并发工作,提供数据处理能力。

1.3 MapReduce缺点

1)不擅长实时计算

MapReduce无法像MySQL一样,在毫秒或者秒级内返回结果。

2)不擅长流式计算

流式计算的输入数据是动态的,而MapReduce的输入数据集是静态的,不能动态变化。这是因为MapReduce自身的设计特点决定了数据源必须是静态的。

3)不擅长DAG(有向无环图)计算

多个应用程序存在依赖关系,后一个应用程序的输入为前一个的输出。在这种情况下,MapReduce并不是不能做,而是使用后,每个MapReduce作业的输出结果都会写入到磁盘,会造成大量的磁盘IO,导致性能非常的低下。

1.4 MapReduce核心思想

现在有一个需求:要统计一个文件当中每一个单词出现的总次数(并将查询结果a-p字母保存一个文件,q-z字母保存一个文件),则可以按照图示步骤

【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce
(1)分布式的运算程序往往需要分成至少2个阶段。map+reduce
(2)第一个阶段的MapTask并发实例,完全并行运行,互不相干。统计次数,形成键值对,<H,1>、<S,1>、<H,1>,但是次数之间不相加。
(3)第二个阶段的ReduceTask并发实例互不相干,但是他们的数据依赖于上一个阶段的所有MapTask并发实例的输出。将统计的次数相加求和。
(4)MapReduce编程模型只能包含一个Map阶段和一个Reduce阶段,如果用户的业务逻辑非常复杂,那就只能多个MapReduce程序,串行运行。

总结:分析WordCount数据流走向深入理解MapReduce核心思想。

1.5 MapReduce进程

mr、job、任务指的都是一个应用程序。例如:跑一个wordcount,可以说这是一个job或者任务。

未来在运行MapReduce程序的时候,会启动哪些进程呢?

一个完整的MapReduce程序在分布式运行时有三类实例进程:
(1)MrAppMaster:负责整个程序的过程调度及状态协调。
(2)MapTask:负责Map阶段的整个数据处理流程。
(3)ReduceTask:负责Reduce阶段的整个数据处理流程。

1.6 常用数据序列化类型

Java类型 Hadoop Writable类型
Boolean BooleanWritable
Byte ByteWritable
Int IntWritable
Float FloatWritable
Long LongWritable
Double DoubleWritable
String Text
Map MapWritable
Array ArrayWritable
Null NullWritable
  • 除了string,其他的都是在java类型的基础上加上writable

1.7 源码与MapReduce编程规范

【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce

用户编写的程序分成三个部分:Mapper、Reducer和Driver。

【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce

【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce

源码如下:

package org.apache.hadoop.examples;

import java.io.IOException;
import java.io.PrintStream;
import java.util.StringTokenizer;
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.Mapper;
import org.apache.hadoop.mapreduce.Mapper.Context;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.Reducer.Context;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;

public class WordCount
{
  public static void main(String[] args)
    throws Exception
  {
    Configuration conf = new Configuration();
    String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
    if (otherArgs.length < 2) {
      System.err.println("Usage: wordcount <in> [<in>...] <out>");
      System.exit(2);
    }
    Job job = Job.getInstance(conf, "word count");
    job.setJarByClass(WordCount.class);
    job.setMapperClass(TokenizerMapper.class);
    job.setCombinerClass(IntSumReducer.class);
    job.setReducerClass(IntSumReducer.class);
    job.setOutputKeyClass(Text.class);
    job.setOutputValueClass(IntWritable.class);
    for (int i = 0; i < otherArgs.length - 1; i++) {
      FileInputFormat.addInputPath(job, new Path(otherArgs[i]));
    }
    FileOutputFormat.setOutputPath(job, new Path(otherArgs[(otherArgs.length - 1)]));

    System.exit(job.waitForCompletion(true) ? 0 : 1);
  }

  public static class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable>
  {
    private IntWritable result = new IntWritable();

    public void reduce(Text key, Iterable<IntWritable> values, Reducer<Text, IntWritable, Text, IntWritable>.Context context)
      throws IOException, InterruptedException
    {
      int sum = 0;
      for (IntWritable val : values) {
        sum += val.get();
      }
      this.result.set(sum);
      context.write(key, this.result);
    }
  }

  public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable>
  {
    private static final IntWritable one = new IntWritable(1);
    private Text word = new Text();

    public void map(Object key, Text value, Mapper<Object, Text, Text, IntWritable>.Context context) throws IOException, InterruptedException
    {
      StringTokenizer itr = new StringTokenizer(value.toString());
      while (itr.hasMoreTokens()) {
        this.word.set(itr.nextToken());
        context.write(this.word, one);
      }
    }
  }
}
  • 上面一共有三个方法,分别是main方法,map方法和reduce方法。
  • 定义一个类,继承mapper,之后重写里面的mapper方法,实现自己的业务逻辑。

【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce
【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce

MapReduce的编程规范如下:

【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce
【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce

2、WordCount案例实操

2.1 本地测试

1)需求

在给定的文本文件中统计输出每一个单词出现的总次数
(1)输入数据

(2)期望输出数据

wenxin	2
banzhang	1
cls	2
hadoop	1
jiao	1
ss	2
xue	1
  • 可以发现上面的数据涉及首字母排序的问题。

2)需求分析

按照MapReduce编程规范,分别编写Mapper,Reducer,Driver。

【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce

(1)创建maven工程,MapReduceDemo

(2)在pom.xml文件中添加如下依赖

<dependencies>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-client</artifactId>
        <version>3.1.3</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.30</version>
    </dependency>
</dependencies>

(2)在项目的src/main/resources目录下,新建一个文件,命名为“log4j.properties”,在文件中填入。

log4j.rootLogger=INFO, stdout  
log4j.appender.stdout=org.apache.log4j.ConsoleAppender  
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout  
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n  
log4j.appender.logfile=org.apache.log4j.FileAppender  
log4j.appender.logfile.File=target/spring.log  
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout  
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

(3)创建包名:com.wenxin.mapreduce.wordcount

【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce

Mapper的源码:

@Public
@Stable
public class Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
    public Mapper() {
    }

    protected void setup(Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
    }

    protected void map(KEYIN key, VALUEIN value, Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
        context.write(key, value);
    }

    protected void cleanup(Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
    }

    public void run(Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
        this.setup(context);

        try {
            while(context.nextKeyValue()) {
                this.map(context.getCurrentKey(), context.getCurrentValue(), context);
            }
        } finally {
            this.cleanup(context);
        }

    }

    public abstract class Context implements MapContext<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
        public Context() {
        }
    }
}

【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce
【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce

4)编写程序

(1)编写Mapper类

package com.wenxin.mapreduce.wordcount;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;

/**
 * @author Susie-Wen
 * @version 1.0
 * @description:
 * @date 2023/12/13 9:56
 */
/*
KEYIN,map阶段输入的key的类型:LongWritable
VALUEINT,map阶段输入的value的类型:Text
KEYOUT,map阶段输出的Key的类型:Text
VALUEOUT,map阶段输出的value类型:IntWritable
 */
public class WordCountMapper<map> extends Mapper<LongWritable, Text,Text, IntWritable> {
    //
    private Text outK=new Text();
    private IntWritable outV=new IntWritable(1);
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        /*
        LongWritable key,输入的key,偏移量
        Text value,输入的value
        Context context,对应的上下文
         */
        //1.获取一行
        String line = value.toString();
        
        //2.对一行数据进行切割(因为原始数据使用的是空格,因此这里使用空格切割)
        String[] words = line.split(" ");
        
        //3.循环写出
        for(String word:words){
            //封装outK
            outK.set(word);
            //写出
            context.write(outK,outV);
        }
    }
}

【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce
【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce
【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce

(2)编写Reducer类

package com.wenxin.mapreduce.wordcount;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
 * @author Susie-Wen
 * @version 1.0
 * @description:
 * @date 2023/12/13 9:56
 */
/*
KEYIN,reduce阶段输入的key的类型:Text
VALUEINT,reduce阶段输入的value的类型:IntWritable
KEYOUT,reduce阶段输出的Key的类型:Text
VALUEOUT,reduce阶段输出的value类型:IntWritable
 */
public class WordCountReducer extends Reducer<Text, IntWritable,Text,IntWritable> {
    IntWritable outV=new IntWritable();
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
        int sum =0;//定义一个变量,进行累加
        //传进来的值:wenxin,(1,1)
        for(IntWritable value:values){
            sum +=value.get();//累加,不能直接加上value,因为value是IntWritable类型,要使用get方法
        }
        outV.set(sum);
        //写出
        context.write(key,outV);
    }
}

【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce

(3)编写Driver驱动类

  • driver当中有7步,都是固定的;其次需要注意不要导错包了!
package com.atguigu.mapreduce.wordcount;
import java.io.IOException;
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;

public class WordCountDriver {

	public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

		// 1 获取配置信息以及获取job对象
		Configuration conf = new Configuration();
		Job job = Job.getInstance(conf);

		// 2 关联本Driver程序的jar
		job.setJarByClass(WordCountDriver.class);

		// 3 关联Mapper和Reducer的jar
		job.setMapperClass(WordCountMapper.class);
		job.setReducerClass(WordCountReducer.class);

		// 4 设置Mapper输出的kv类型
		job.setMapOutputKeyClass(Text.class);
		job.setMapOutputValueClass(IntWritable.class);

		// 5 设置最终输出kv类型
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(IntWritable.class);
		
		// 6 设置输入和输出路径
		FileInputFormat.setInputPaths(job, new Path(args[0]));
		FileOutputFormat.setOutputPath(job, new Path(args[1]));

		// 7 提交job
		boolean result = job.waitForCompletion(true);
		System.exit(result ? 0 : 1);
	}
}

【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce
【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce
【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce
【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce
【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce
【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce
【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce

  • 可以看到hadoop默认会对数据进行排序
  • 如果此时再次点击运行的话,会报错,显示输出路径存在;因此对于mapreduce程序,如果输出路径存在了,就会报错。

5)本地测试

(1)需要首先配置好HADOOP_HOME变量以及Windows运行依赖

(2)在IDEA/Eclipse上运行程序

【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce

【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce

2.2 提交到集群测试

集群上测试

(1)用maven打jar包,需要添加的打包插件依赖

<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.6.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

注意:如果工程上显示红叉。在项目上右键->maven->Reimport刷新即可。

(2)将程序打成jar包
【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce

【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce
【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce
【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce

(3)修改不带依赖的jar包名称为wc.jar,并拷贝该jar包到Hadoop集群的/home/wenxin/module/hadoop-3.1.3路径。

(4)启动Hadoop集群

[root@hadoop102 hadoop-3.1.3]sbin/start-dfs.sh
[root@hadoop103 hadoop-3.1.3]$ sbin/start-yarn.sh

【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce

【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce

(5)执行WordCount程序
【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce
【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce
【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce
【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce

[root@hadoop102 hadoop-3.1.3]$ hadoop jar  wc.jar
 com.wenxin.mapreduce.wordcount.WordCountDriver /user/wenxin/input /user/wenxin/output

【Hadoop_06】MapReduce的概述与wc案例,【大数据】,mapduce文章来源地址https://www.toymoban.com/news/detail-757378.html

到了这里,关于【Hadoop_06】MapReduce的概述与wc案例的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • Hadoop之MapReduce概述

    Hadoop之MapReduce概述

    MapReduce定义 MapReduce是一个分布式运算程序的编程框架,是用户开发“基于Hadoop的数据分析应用”的核心框架。 MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个Hadoop集群上。 MapReduce优缺点 优点 1)MapReduce易于编

    2024年02月08日
    浏览(9)
  • 【Hadoop】- MapReduce概述[5]

    【Hadoop】- MapReduce概述[5]

    目录 前言 一、分布式计算框架 - MapReduce 二、MapReduce执行原理 MapReduce是一种 分布式计算框架 ,由Google开发。它的设计目标是将大规模数据集的处理和生成任务分布到一个由廉价计算机组成的集群中。 在MapReduce模型中,输入数据被分割成若干小块,并在集群中的多个节点上并

    2024年04月25日
    浏览(7)
  • Hadoop快速入门+MapReduce案例(赠送17到23年往年真题答案+MapReduce代码文件)-----大数据与人工智能比赛

    Hadoop快速入门+MapReduce案例(赠送17到23年往年真题答案+MapReduce代码文件)-----大数据与人工智能比赛

    Hadoop的核心就是HDFS和MapReduce HDFS为海量数据提供了 存储 而MapReduce为海量数据提供了 计算框架 一.HDFS 整个HDFS有三个重要角色: NameNode (名称节点)、 DataNode (数据节点)和 Client (客户机) NameNode :是Master节点(主节点) DataNode : 是Slave节点(从节点),是文件存储的基本

    2024年02月20日
    浏览(5)
  • Hadoop系统应用之MapReduce相关操作【IDEA版】---经典案例“倒排索引、数据去重、TopN”

    Hadoop系统应用之MapReduce相关操作【IDEA版】---经典案例“倒排索引、数据去重、TopN”

      倒排索引是文档检索系统中最常用的数据结构,被广泛应用于全文搜索引擎。倒排索引主要用来存储某个单词(或词组)在一组文档中的存储位置的映射,提供了可以根据内容来查找文档的方式,而不是根据文档来确定内容,因此称为倒排索引(Inverted Index)。带有倒排索引

    2024年02月07日
    浏览(13)
  • MapReduce入门(一)—— MapReduce概述 + WordCount案例实操

    MapReduce入门(一)—— MapReduce概述 + WordCount案例实操

    MapReduce知识点总览图 MapReduce 是 一个分布式运算程序的编程框架 ,是用户开发“基于 Hadoop 的数据分析应用”的核心框架。 MapReduce 核心功能是 将用户编写的业务逻辑代码 和 自带默认组件 整合成一个 完整的分布式运算程序 ,并发运行在一个 Hadoop 集群上。 1.2.1 优点 1 )M

    2023年04月21日
    浏览(11)
  • Hadoop3 - MapReduce COVID-19 案例实践

    Hadoop3 - MapReduce COVID-19 案例实践

    上篇文章对 MapReduce 进行了介绍,并编写了 WordCount 经典案例的实现,本篇为继续加深 MapReduce 的用法,实践 COVID-19 新冠肺炎案例,下面是上篇文章的地址: https://blog.csdn.net/qq_43692950/article/details/127195121 COVID-19,简称“新冠肺炎”,世界卫生组织命名为“2019冠状病毒病” [1-

    2024年02月08日
    浏览(8)
  • hadoop学习:mapreduce入门案例二:统计学生成绩

    hadoop学习:mapreduce入门案例二:统计学生成绩

    这里相较于 wordcount,新的知识点在于学生实体类的编写以及使用 数据信息: 1. Student 实体类 2.  mapper 阶段,StudentMapper 类 3. reduce 阶段,StudentReduce 类 4. 驱动类,studentDriver 类

    2024年02月11日
    浏览(8)
  • 虚拟机+Hadoop下MapReduce的Wordcount案例

    虚拟机+Hadoop下MapReduce的Wordcount案例

    环境:ubuntu18.04 前提:Hadoop已经搭建好 抄作业记得改标题 输入内容(可以自定义,抄作业别写一样的) yarn-site.xml 内容如下,注意第一个property要改: ·输入hadoop classpath(任意路径下均可),将返回的内容复制在第一个property的value中 *修改配置文件之后要重启hadoop(关了又

    2024年02月07日
    浏览(8)
  • 第三节 Hadoop学习案例——MapReduce课程设计 好友推荐功能

    第三节 Hadoop学习案例——MapReduce课程设计 好友推荐功能

    提示:文章内容主要以案例为主 目录 前言 项目说明 一,程序需求 1.需求 2.数据 二,编码操作 1.项目建包目录 2.FriendsRecommend.java  3.FriendsRecommendMapper.java 4.FriendsRecommendReduce.java 三,Xshell运行的步骤 1.创建目录 2.上传程序  3.分布式文件系统上传测试数据  4.执行程序 5. 查看结

    2024年02月07日
    浏览(14)
  • Hadoop3教程(十九):MapReduce之ETL清洗案例

    ETL,即 Extract-Transform-Load 的缩写,用来描述数据从源端,经过抽取(Extract)、转换(transform),最后加载(load)到目标端的处理过程。 ETL主要应用于数据仓库,但不只是应用于数据仓库,毕竟这个更像是一类思想。 在运行核心的MR程序之前,往往要对数据进行清理,清除掉

    2024年02月06日
    浏览(8)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包