当前位置:   article > 正文

手把手教你在linux中部署hadoop以及hadoop功能介绍_linux安装hadoop

linux安装hadoop

我们在学大数据的时候hadoop是躲避不开的地方,所以如何在linux中部署hadoop,下面就是手把手教你如何部署hadoop。你只要复制粘体,然后按enter键就可以了。

准备:

  安装Linux版本的服务器。可以参考中前半部分安装服务器的内容即可:VM部署CentOS并且设置网络_ZeroMaster的博客-CSDN博客

  

设置服务器ip: 192.168.171.5

一:安装JDK

1:下载jdk

wget --no-check-certificate https://repo.huaweicloud.com/java/jdk/8u151-b12/jdk-8u151-linux-x64.tar.gz

2:解压jdk安装包

tar -zxvf jdk-8u151-linux-x64.tar.gz

3:移动并重命名jdk包

mv jdk1.8.0_151/ /usr/java8

4:配置java环境变量

  1. echo 'export JAVA_HOME=/usr/java8' >> /etc/profile
  2. echo 'export PATH=$PATH:$JAVA_HOME/bin' >> /etc/profile
  3. source /etc/profile

5:查看java是否安装成功

java -version

二:安装hadoop

1:下载hadoop包

wget --no-check-certificate https://repo.huaweicloud.com/apache/hadoop/common/hadoop-3.1.3/hadoop-3.1.3.tar.gz

2;解压hadoop包并转移到指定文件

  1. tar -zxvf hadoop-3.1.3.tar.gz -C /opt/
  2. mv /opt/hadoop-3.1.3 /opt/hadoop

3:配置hadoop环境变量

  1. echo 'export HADOOP_HOME=/opt/hadoop/' >> /etc/profile
  2. echo 'export PATH=$PATH:$HADOOP_HOME/bin' >> /etc/profile
  3. echo 'export PATH=$PATH:$HADOOP_HOME/sbin' >> /etc/profile
  4. source /etc/profile

4:修改配置文件yarn-env.sh和hadoop-env.sh

  1. echo "export JAVA_HOME=/usr/java8" >> /opt/hadoop/etc/hadoop/yarn-env.sh
  2. echo "export JAVA_HOME=/usr/java8" >> /opt/hadoop/etc/hadoop/hadoop-env.sh

5:查看hadoop是否安装成功

hadoop version

三:配置hadoop

1. 修改Hadoop配置文件 core-site.xml。
vim /opt/hadoop/etc/hadoop/core-site.xml

输入i进入编辑模式。 在<configuration></configuration>节点内插入如下内容。

  1. <property>
  2. <name>hadoop.tmp.dir</name>
  3. <value>file:/opt/hadoop/tmp</value>
  4. <description>location to store temporary files</description>
  5. </property>
  6. <property>
  7. <name>fs.defaultFS</name>
  8. <value>hdfs://localhost:9000</value>
  9. </property>
2. 修改Hadoop配置文件 hdfs-site.xml。
vim /opt/hadoop/etc/hadoop/hdfs-site.xml

 在<configuration></configuration>节点内插入如下内容

  1. <property>
  2. <name>dfs.replication</name>
  3. <value>1</value>
  4. </property>
  5. <property>
  6. <name>dfs.namenode.name.dir</name>
  7. <value>file:/opt/hadoop/tmp/dfs/name</value>
  8. </property>
  9. <property>
  10. <name>dfs.datanode.data.dir</name>
  11. <value>file:/opt/hadoop/tmp/dfs/data</value>
  12. </property>

四:配置SSH免密登录

1:创建公钥和私钥

ssh-keygen -t rsa

2. 执行以下命令,将公钥添加到authorized_keys文件中。

  1. cd ~
  2. cd .ssh
  3. cat id_rsa.pub >> authorized_keys

若报错,执行下面操作后重新执行上面两句命令;若没有报错直接进入第五步:

输入如下命令,在环境变量中添加下面的配置

vi /etc/profile

然后向里面加入如下的内容

  1. export HDFS_NAMENODE_USER=root
  2. export HDFS_DATANODE_USER=root
  3. export HDFS_SECONDARYNAMENODE_USER=root
  4. export YARN_RESOURCEMANAGER_USER=root
  5. export YARN_NODEMANAGER_USER=root

输入如下命令使改动生效

source /etc/profile

五:启动hadoop

1:初始化namenode

hadoop namenode -format

2:启动hadoop

start-dfs.sh

这个时候可能出现如下错误

  1. 上一次登录:二 822 20:26:37 CST 2023192.168.171.3pts/0
  2. localhost: ERROR: JAVA_HOME is not set and could not be found.
  3. Starting datanodes
  4. 上一次登录:二 822 20:27:39 CST 2023pts/0
  5. localhost: ERROR: JAVA_HOME is not set and could not be found.
  6. Starting secondary namenodes [master]
  7. 上一次登录:二 822 20:27:39 CST 2023pts/0
  8. master: ERROR: JAVA_HOME is not set and could not be found.

其实是hadoop里面hadoop-env.sh文件里面的java路径设置不对,才会导致如上问题。

进入:

  1. cd /opt/hadoop/etc/hadoop/
  2. sudo vim hadoop-env.sh
  3. 将export JAVA_HOME改成
  4. export JAVA_HOME=/usr/java8
  5. ##生效
  6. source hadoop-env.sh

最后重新执行start-dfs.sh就可以了。

若有选择Y/N的,选择Y;其他直接回车

再执行:

start-yarn.sh

3.启动成功后,执行以下命令,查看已成功启动的进程。

jps

出现如下

  1. 8177 DataNode
  2. 8802 NodeManager
  3. 8377 SecondaryNameNode
  4. 8059 NameNode
  5. 8671 ResourceManager
  6. 9279 Jps

4:打开浏览器访问http://192.168.171.5:8088和http://192.168.171.5:50070,显示如下界面则表示Hadoop伪分布式环境搭建完成

4:一些命令

(1):如果是集群查看resourcemanager的状态。在/bin下执行 
./hdfs haadmin -getAllServiceState

参考:通过命令来查看NameNode的状态(是Active还是Standby)_快乐的跳蚤的博客-CSDN博客

(2):查看运行的程序
 ./yarn application -list
(3):启动hadoop的命令行:
  1. 1、start-all.sh
  2.   启动所有的Hadoop守护进程。包括NameNode、 Secondary NameNode、DataNode、JobTracker、 TaskTrack。
  3.   2、start-dfs.sh
  4.   启动Hadoop HDFS守护进程NameNode、SecondaryNameNode和DataNode。
  5.   3、hadoop-daemons.sh start namenode
  6.   单独启动NameNode守护进程。
  7.   4、hadoop-daemons.sh start datanode
  8.   单独启动DataNode守护进程。
  9.   5、hadoop-daemons.sh start secondarynamenode
  10.   单独启动SecondaryNameNode守护进程。
  11.   6、start-mapred.sh
  12.   启动Hadoop MapReduce守护进程JobTracker和TaskTracker。
  13.   7、hadoop-daemons.sh start jobtracker
  14.   单独启动JobTracker守护进程。
  15.   8、hadoop-daemons.sh start tasktracker
  16.   单独启动TaskTracker守护进程。
  17.   9、hadoop-daemons.sh stop tasktracker
  18.   单独启动TaskTracker守护进程。
(4)发现HDFS上存储数据

./hadoop fs -du -h /

(5)发现HDFS上存储数据

./hadoop rm -r  -f /

(6)查看hadoop集群中Resourcemanager(RM)状态

查看rm 状态
yarn rmadmin -getServiceState rm

切换rm
yarn rmadmin -transitionToStandby rm

六:hadoop中配置文件说明

1:Hadoop集群特定配置文件

配置文件作用
core-site.xml特定的通用Hadoop属性配置文件。该配置文件中的配置项会覆盖core-default.xml中相同的配置项
hdfs-site.xml特定的HDFS属性配置文件。该配置文件中的配置项会覆盖hdfs-default.cml中的相同配置项
mapred-site.xml特定的MapReduce属性配置文件。该配置文件中的配置项会覆盖mapred-default.cml中的相同配置项
yarn-site.xml特定的YARN属性配置文件。该配置文件中的配置项会覆盖yarn-default.xml中的相同配置项

2:Hadoop守护进程

除了上面提到的配置文件,Hadoop管理员还可以使用下面的脚本文件来配置Hadoop集群:

  • hadoop-env.sh
  • yarn-env.sh
  • mapred-env.sh

这些脚本文件主要负责设置如下属性

  • Java主目录
  • 不同的日志文件位置
  • 用于各种守护进程的JVM选项
守护进程配置变量

守护进程    环境变量
名称节点    HADOOP_NAMENODE_OPTS
数据节点    HADOOP_DATANODE_OPTS
辅助名称节点    HADOOP_SECONDARYNAMENODE_OPTS
资源管理器    YARN_RESOURCEMANAGER_OPTS
节点管理器    YARN_NODEMANAGER_OPTS

Hadoop配置文件的优先级

Hadoop集群中的每个节点必须要有配置文件的一个拷贝,包括集群的客户端节点。这些配置文件以如下次序应用到系统中(优先级由高到低):
1)MapRduce任务初始化的时候,由JobConf或者Job对象指定的值
2)客户端节点的 *-site.xml
3)从节点的 *-site.xml
4) *-default.xml 文件中的默认值,这个文件是集群中的所有节点都相同

3:主要配置文件说明

core-site.xml一些通用属性
参数作用
hadoop-tmp-dir其他所有临时目录的根目录。其默认值是/tmp/hadoop-${usern.name}。在hdfs-site.xml文件中有几个属性,其配置值都是路径名,它们的根目录就是在hadoop-tmp-dir中配置的
fs.defaultFs在没有特殊配置的情况下,HDFS客户端使用的默认路径前缀由该属性配置。
io.file.buffer-size该属性指定了文件流的缓冲大小。这个缓冲的大小应该是硬件页面大小的整数倍(Intel x86中是4096),它决定了数据读取和写入操作过程中缓冲了多少数据,其默认值为4096
io.bytes.per.checksum

Hadoop系统在数据写入时会计算校验和(checksums),并在读取数据的时候进行校验,这个过程对用户是透明的。该参数定义了对多大的数据量进行一次校验和计算。其默认值是512字节,其CRC-32校验和结果为4字节。因此,在默认情况下,每存储512字节的数据就会占用大约1%的开销来存储其校验和结果。该参数的值一定要比io.file.buffer-size设置的值大,因为在HDFS读写数据过程中,数据流在内存中的时候就会计算其校验和。当读取HDFS上的数据时,会计算该数据的校验和,并将其与写入数据时计算并保存下来的校验和进行比对验证。

io.compression.codecs这个参数值是一个由逗号分割的可用压缩编码类的列表,这些类会被用来压缩/解压数据
hdfs-site.xml一些通用配置
参数作用
dfs.namenode.name.dir名称节点本地文件系统中存放元数据文件表(fsimage文件)的目录。这个文件中存储的是HDFS元数据的最近快照。如果该属性值是一个逗号分隔的目录列表,文件会被复制到所有的目录中,用作数据冗余。该属性默认值为:file://${hadoop.tmp.dir}/dfs/name。
dfs.namenode.edits.dir名称节点本地文件系统中存储元数据事务处理文件(edits文件)的目录。如果该属性值是一个逗号分隔的目录列表,该事务处理文件会被复制到所有的目录中,用作数据冗余。其默认值与dfs.namenode.name.dir相同
dfs.namenode.checkpoint.dir该属性决定了辅助名称节点中存放临时fs image文件的目录,该临时fs image文件用来在名称节点可访问的本地/网络文件系统中进行合并,该文件用来与从名称节点拷贝过来的edits文件合并。如果这是一个以逗号分隔的目录列表,镜像文件会复制到所有目录中,用作数据冗余。该属性的默认值为file://${hadoop.tmp.dir}/dfs/namesecondary 
dfs.namenode.checkpoint.edits.dir该属性决定了辅助名称节点中存放从名称节点拷贝过来的edits文件的目录,该文件用来与已经拷贝到由dfs.namenode.checkpoint.dir属性决定的目录中的fsimage文件合并,该合并在辅助名称节点可访问的本地/网络文件系统中进行。如果这是一个以逗号分隔的目录列表,edits文件会复制到所有目录中,用作数据冗余。该属性的默认值与dfs.namenode.checkpoint.dir相同
dfs.namenode.checkpoint.period两个检查点之间的间隔秒数。经过该属性配置的时间之后,检查点操作就开始执行,该操作会合并从名称节点拷贝过来的edits文件和fsimage文件
dfs.blocksize指定新文件的默认数据块大小,单位是字节,其默认值是128MB.需要注意的是数据块的大小不是一个系统全局参数,这个参数可以针对单个文件指定
dfs.replication默认的数据块备份数量。该参数也可以针对单个文件纪念性指定,如果没有特殊指定,就会以参数值作为文件的备份数量。其默认值为3
dfs.namenode.handler.count该参数决定了名称节点与数据节点通信的服务器线程数。其默认值为10,但是推荐其值为集群节点数量的10%。最小值为10.如果该值设置过低,会在数据节点的日志中发现很多告警信息,这些告警信息显示了当数据节点与名称节点进行心跳信息通信的时候被拒绝了
dfs.datanode.du.reserved该参数为每卷磁盘中的保留存储空间(单位为字节),该存储空间保留供非HDFS使用。其默认值为0,但是建议其值为10GB和整个磁盘空间大小的25%两者之间的较小值
dfs.hosts该属性指定了指向一个文件的完整路径名,指向的文件包含了一个允许与名称节点通信的主机列表。如果没有设置该属性,集群中的所有节点都允许与名称节点通信
mapred-site.xml一些关键属性
参数作用
mapreduce.framework.name决定MapReduce作业时提交到YARN集群还是使用本地作业执行器来本地运行。该属性的合法值为yarn或者local
mapred.child.java.opts运行Map或者Reduce任务的JVM堆大小。其默认值是-Xmx200m(堆空间大小为200M)设置的值应该小于或者等于在mapreduce.map.memory.mb和mapreduce.reduce.memory.mb属性中设置的值,这些属性值是在应用程序管理器与资源管理器协商资源时使用的。节点管理器负责按照这些属性中设定的JVM堆大小来启动容器。Map和Reduce任务按照mapred.child.opts属性设定的JVM堆大小在容器中执行。如果mapred.child.opts属性值大于mapreduce.*.memory.mb属性值,任务会失败
mapreduce.map.memory.mb    设定分配给Map人物的容器内存大小,默认值是1024MB

mapreduce.reduce.memory.mb
设定分配给Reduce任务的容器的内存大小,默认值是1024MB
mapreduce.cluster.local.dir MapReduce存储中间数据文件的本地目录。它可以是逗号分隔的存在于不同设备的目录的列表,这样可以提高磁盘I/O。比如存放Mapper过程的数据输出的文件和存放shuffle-sort过程中排序的中间数据结果的文件。其默认值是${hadoop.tmp.dir}/mapred/local,这与value dfs.du.reserved属性值(core-site.xml中指定)相关。一般来说,建议设置不超过75%的硬盘空间来存放HDFS数据,25%的硬盘空间用于存放中间数据文件
mapreduce.jobtracker.handler.count作业跟踪器(相当于YARN)的服务器进程数量。该属性值应大约为集群中从节点数量的4%,其最小值为10,该属性默认值为10
mapreduce.job.reduce.slowstart.completedmaps 在作业中调度Reducer之前,作业中Map完成百分比。默认值是0.05,但是建议值是0.5到0.8之间
mapreduce.jobtracker.taskscheduler 该类负责人物的调度。其默认值为FIFO的调度策略,其属性值为org.apache.hadoop.mapredJobQueueTaskScheduler。但是推荐设置公平调度器或者计算能力调度器
mapreduce.map.maxattempts 
   每个Map任务重试的最大次数。框架在放弃执行Map任务并使作业失败之前,重复尝试执行一个Map任务的次数。其默认值为4
mapreduce.reduce.maxattempts每个Reduce任务重试最大次数。框架在放弃执行Reduce任务并使作业失败之前,重复尝试执行一个Reduce任务的次数。其默认值为4
yarn-site.xml文件中的一些关键属性
参数 作用
yarn.resourcemanager.hostname资源管理器所在节点的主机名
yarn.resourcemanager.address 运行资源管理器服务的主机名及其端口号。其默认值为http://{yarn.resourcemanager.hostname}:8032
yarn.nodemanager.local-dirs 一个逗号分隔的本地文件目录列表。节点管理器初始化而成的容器,在容器运行过程中存储文件的目录。此类文件通常是附加配置文件,通过分布式缓存分发每个计算节点的作业运行需要的数据文件函数库。当程序结束时,这些文件都会被删除
yarn.nodemanager.aux-services一个逗号分隔的辅助服务列表,这些服务由节点管理执行。该属性默认为空。
yarn.nodemanager.resource.memory-mb  可分配给容器的物理内存总和,这些容器是由在节点上运行的节点管理器来初始化的。其默认值是8192.大多数节点的内存远大于8GB,在为运行操作系统和其他Hadoop守护进程预留足够的内存之后,可以适当的增大该属性值
yarn.nodemanager.vmem-pmem-ratio 在配置容器的内存限制时,虚拟内存与物理内存的比值。分配给容器的内存用物理内存来表示,其虚拟内存使用量允许按这个比值超出其所分配的物理内存大小。如果yarn.scheduler.maximum属性设置为其默认值8192,比值设定为2.1,那么每个容器允许使用的虚拟内存上限为8GB*2.1=16.2GB如果超过这一比例,YARN框架会启动容器失败
yarn.scheduler.minimum-allocation-mb启动每个容器,需要向资源管理器申请的最小内存量,单位是MB。低于该配置值得内存请求会被忽略,并且按照该设定值来请求内存使用量。其默认值为1024,即1GB
 yarn.scheduler.maximum-allocation-vcores  启动每个容器,需要向资源管理器申请得最小虚拟CPU内核数。低于该配置值得请求不会生效,并且会按照该设定值来分配。其默认值为1
yarn.scheduler.maximum-allocation-vcores 启动每个容器,需要向资源管理器申请的最大虚拟CPU内核数。高于该配置值得请求不会生效,并且会按照该设定来分配,其默认值为32

参考:Hadoop中配置文件重要属性释义_hadoop配置文件详解_盛者无名的博客-CSDN博客

七:hadoop功能介绍

一 :namenode:
  管理文件系统的命名空间,他维护着文件系统树及整棵树上所有的文件和目录,这些信息以两个文件形式永久的保存在本地磁盘上,命名空间镜像文件(fsimage)和(Editlogs)
    fsimage:namenode启动时,对整个文件系统的快照
    editlogs:namenode启动以后,对文件系统的改动序列
namenode也记录着每个文件中各个块所在的数据节点信息(包括副本数,用户对hdfs的操作),但它并不永久保存块的位置信息,因为这些信息会在系统启动时根据数据节点信息重建。  

启动命令:

hadoop-daemon.sh start namenode

二:datanode
  datanode负责提供来自文件系统客户端读和写的请求,受客户端或者namenode的调度,并且定期向namenode发送(通过心跳机制存储的块的列表) 

启动命令:

hadoop-daemon.sh start datanode

三:journalNode
  namenode之间为了数据同步,会通过一组称作JournalNodes的独立进程相互通信,当active状态的NameNode的命名空间有任何修改时,会告知大部分的JournalNodes进程。standby状态的NameNode有能力读取JournalNodes中的变更信息,并且一直监控editlog的变化,把变化应用与自己的命名空间。standby可以确保在集群出错时,命名空间状态已经完全同步了。为了确保快速切换,standby状态的NameNode有必要知道集群中所有数据块的位置。为了做到这点,所有的DataNodes必须配置两个NameNode的地址,发送数据块位置信息和心跳给他们两个。
    对于HA集群而言,确保同一时刻只有一个NameNode处于active状态是至关重要的。否则,两个NameNode的数据状态就会产生分歧,可能丢失数据,或者产生错误的结果。为了保证这点,JournalNodes必须确保同一时刻只有一个NameNode可以向自己写数据。

启动命令:

hadoop-daemon.sh start journalnode

四:nodemanager
   yarn中的每一台节点服务器都运行一个NodeManager,NodeManager相当于管理当前机器的一个代理,负责本台机器的程序运行,并且对本台机器资源进行管理和监控,NodeManager定时向ResourceManager汇报本节点的资源(cpu,内存,磁盘)等使用情况,启动并监控Container(容器)。

启动命令:

yarn-daemon.sh start nodemanager

五:ResourceManager
管理集群资源,负责全局资源的监控,分配和管理。接收来自NodeManager的心跳信息,进行整体资源的汇总,监控ApplicationMaster的开启和创建。

启动命令:

yarn-daemon.sh start resourcemanager

./yarn --daemon start resourcemanager

六:ZKFC
  定期对本地的namenode发起health—check命令,如果nameNode正确返回,那么这个namenode被认为是ok的,否则被认为是失效节点,zookeeper提供了一个简单的机制来保证只有一个namenode是活动的,如果当前的namenode失效,那么另一个namenode将获取zookeeper的独占锁,表明自己是活动节点
   作为一个zookeeper集群的客户端,用来监控namnode的状态信息,每个namenode的节点必须要运行一个zkfc

启动命令:

hadoop-daemon.sh start zkfc

七:SecondaryNameNode(检查节点)
  namenode重启时,editlogs才会合并到fsimage文件中,从而得到一个文件系统的最新快照,但是在集群中,namenode是很少重启的,这也就意味着当namenode运行了很长一段时间后,edit-logs文件会非常大,这种情况就出现了以下问题:
    edit-logs文件非常大,怎么去管理
    namenode重启需要花费很长的时间,因为有很多改动要合并到fsimage文件中
    如果namenode挂掉了,那么就丢失了很多改动,此时的fsimage中,在这种情况下,丢失的改动不会特别多,因为丢失的改动是在内存中但是没有写到editlogs的这部分
    
secondary nameNode就是解决这个问题的,他的职责是合并namenode的editlogs文件到fsimage文件中
    secondary nameNode定时查询namenode上的editlogs,并更新到fsimage上(是他自己的fsimage)
    一旦他有了新的faimage文件,他将其拷贝到namenode中
    namenode下次重启的时候,会使用这个新的fsimage,从而减少重启时间
    
关于NameNode是什么时候将改动写到edit logs中的?这个操作实际上是由DataNode的写操作触发的,当我们往DataNode写文件时,DataNode会跟NameNode通信,告诉NameNode什么文件的第几个block放在它那里,NameNode这个时候会将这些元数据信息写到edit logs文件中。

参考:NameNode的HA机制_namenode ha_拾荒路上的开拓者的博客-CSDN博客

八:hadoop常见异常

1:hadoop报错,ERROR: Invalid HADOOP_COMMON_HOME)

首先确定hadoop的安装没有错误。

进入hadoop安装路径,cd (hadoop安装路径)

进入安装路径中的 etc/hadoop

修改该目录下的hadoop-env.sh文件

在该目录下添加java,hadoop环境换量(主要是hadoop环境变量),在里面输入下列代码

  1. export JAVA_HOME=/home/ys/opt/jdk1.8.0_221 (自己的java安装路径)
  2. export HADOOP_HOME=/home/ys/opt/disk/hadoop-3.1.2

2:错误Name node is in safe mode的解决方法

这是因为在分布式文件系统启动的时候,开始的时候会有安全模式,当分布式文件系统处于安全模式的情况下,文件系统中的内容不允许修改也不允许删除,直到安全模式结束。安全模式主要是为了系统启动的时候检查各个DataNode上数据块的有效性,同时根据策略必要的复制或者删除部分数据块。运行期通过命令也可以进入安全模式。在实践过程中,系统启动的时候去修改和删除文件也会有安全模式不允许修改的出错提示,只需要等待一会儿即可。

可以通过以下命令来手动离开安全模式:

  1. bin/hadoop dfsadmin -safemode leave
  2. //在bin下执行
  3. //若配置环境变量,使用以下命令
  4. hadoop dfsadmin -safemode leave

用户可以通过dfsadmin -safemode value 来操作安全模式,参数value的说明如下:
enter - 进入安全模式
leave - 强制NameNode离开安全模式
get - 返回安全模式是否开启的信息
wait - 等待,一直到安全模式结束。

3:HA高可用下,NameNode切换active和standby状态

A为active B为standby

要互相切换

在A的namenode下执行

hdfs --daemon stop zkfc

可能需要等待几秒

hdfs --daemon start zkfc

再次启动,原来的active就变为standby了,原来的standby就变为active了
 

  1. bin/hdfs dfsadmin -report 查看hdfs的各节点状态信息
  2. bin/hdfs haadmin -getServiceState nn1 获取一个namenode节点的HA状态
  3. sbin/hadoop-daemon.sh start namenode 单独启动一个namenode进程
  4. sbin/hadoop-daemon.sh start zkfc 单独启动一个zkfc进程

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号