1 HDFS中的一些概念
HDFS(Hadoop Distributed File System):分布式文件系统,将一个文件分成多个块,分别存储(拷贝)到不同的节点上,它是Hadoop体系中数据存储管理的基础。它是一个高度容错的系统,能检测和应对硬件故障,用于在低成本的通用硬件上运行。HDFS简化了文件的一致性模型,通过流式数据访问,提供高吞吐量应用程序数据访问功能,适合带有大型数据集的应用程序。
11 数据块
每个磁盘都有数据块的概念,在HDFS中也有数据块的概念,HDFS中的所有文件都是分割成块存储在Datanode上的,每个块默认64M。。每个块都有多个副本存储在不同的机器上:默认有3个副本,3个副本不可能存放在同一个机器上。
HDFS副本存放策略
以下是HDFS文件存储架构图
:表示每台机器
绿色:文件被分割出的块
例如:
上图中part-0文件,有2个块。块1和块3只在2个机器上分别出现过2次。
上图中part-1文件,有3个块。块2,4,5分别在不同的机器上各出现3次
HDFS中也可以显示块信息,使用fsck命令
例如:下面的命令将列出文件系统中各个文件由哪些块构成
$ hadoop fsck / -files -blocks
12 NameNode和DataNode
HDFS的设计是主(Master)从(Slave)结构的。也就是,一个管理者(NameNode)和多个工作者(DataNode)组成。
121 管理者:Namenode
NameNode是主节点,它是一个中心服务器,负责管理整个文件系统的命名空间和控制着客户端对文件的访问。它不保存文件的内容,而是保存着文件的元数据(文件名称,所在目录,文件权限,文件拥有者,文件有多少块,每个块有多少副本,块都存在哪些节点上)。
Namenode负责文件的元数据 *** 作,Datanode处理文件内容的读写请求。
跟文件相关的流不经过Namenode,只会询问该文件跟哪个Datanode有关系。
副本存放在哪些Datanode上是由Namenode来控制。读取文件时,Namenode尽量让用户先读取最近的副本。
Namenode全权管理数据块的复制,周期性的从集群中的每个Datanode接收心跳信号和块状态报告。
Namenode和Datanode就是通过这两种方式来进行通信:
心跳信号:意味着该Datanode节点工作正常
块状态报告:包含了一个该Datanode上所有数据块的列表
元数据保存在内存中
Namenode维护着整个文件系统树以及树内的所有文件。这些信息以两个文件的形式永久保存在磁盘上。命名空间镜像文件(fsimage)和 *** 作日志(fsedits)文件
1 fsimage是什么?
fsimage是元数据镜像文件:Namenode启动后,文件的元数据被加载到内存中,加载到内存后也会把这些元数据写入到本地的磁盘中,这个文件就是fsimage文件。
元数据镜像在内存中保存一份最新的,内存中的镜像=fsimage+fsedit
2 fsedits是什么?
fsedits是元数据 *** 作日志文件:客户端要对文件进行读写 *** 作,在这些 *** 作产生的日志就存在了fsedit文件中。
121 工作者:Datanode
DataNode是从节点,它的作用很简单,就是存储文件的块数据。以及块数据的校验和。
一个数据块在Ddtanode以文件存储在磁盘上,包括两个文件:数据本身和元数据(数据块的长度,块数据的校验和,时间戳)
Datanode启动后向Namenode注册,通过后,周期性(1小时)的向Namenode上报所有块信息。
心跳是3秒一次,如果超过10分钟没有收到某个Datanode的心跳。则认为该节点不可用。
13 Secondary Namenode
Secondary Namenode:Secondary表示助手的意思,也就是说Secondary Namenode表示NameNode的助手,辅助NameNode工作的一个节点。要了解Secondary Namenode节点都辅助NameNode做了哪些工作,我们需要先回顾下NameNode是做什么的?
NameNode是HDFS中的一个主节点,主要是来管理其他DataNode从节点。它存储了HDFS系统的namespace和控制着客户端对HDFS文件系统的访问。NameNode在维护整个文件系统树的时候是以两个文件的形式永久保存在磁盘上。镜像文件(fsimage)和 *** 作日志文件(fsedits)。考虑以下,这两个文件一直这样运行存在着有什么问题?
fsedits *** 作日志文件会越来越大,因为它保存着客户端对HDFS文件系统的访问日志。
只有在NameNode重启后,edits logs才会合并到fsimage中,产生一个新的文件系统快照。但是NameNode是很少重启的。
为了保证edit logs文件不会太大和fsimage是一个最新的文件,此时需要一个节点来备份这些文件。定期的合并这两个文件然后再推送给NameNode,这样就减轻NameNode工作的压力,同时也保证了假如Namenode节点宕机后数据无法恢复问题。虽然可能不会把所有的数据全部恢复出来,但是至少丢失的很少。
所以,Secondary Namenode做的就是这些辅助工作
Secondary NameNode所做的不过是在文件系统中设置一个检查点来帮助NameNode更好的工作。它不是要取代掉NameNode也不是NameNode的备份。
SecondaryNameNode有两个作用,一是镜像备份,二是日志与镜像的定期合并。两个过程同时进行,称为checkpoint
在core-sitexml配置文件中有2个参数可配置,但一般来说我们不做修改。fscheckpointperiod表示多长时间记录一次hdfs的镜像。默认是1小时。fscheckpointsize表示一次记录多大的size,默认64M。
<property> <name>fscheckpointperiod</name> <value>3600</value> <description>The number of seconds between two periodic checkpoints </description></property><property> <name>fscheckpointsize</name> <value>67108864</value> <description>The size of the current edit log (in bytes) that triggers a periodic checkpoint even if the fscheckpointperiod hasn’t expired </description></property>可以参考这篇博客,写的还是很详细的Secondary NameNode:它究竟有什么作用?
14 HDFS的优缺点
优点:
高容错性
数据自动保存多个副本
副本丢失后,自动恢复
适合批处理
移动计算而非数据
数据位置暴露给计算框架
适合大数据处理
GB、TB、甚至PB级数据
百万规模以上的文件数量
10K+节点规模
流式文件访问
一次性写入,多次读取
保证数据一致性
可构建在廉价机器上
通过多副本提高可靠性
提供了容错和恢复机制
缺点:
低延迟与高吞吐率的数据访问 ,比如毫秒级
小文件存取
占用NameNode大量内存
寻道时间超过读取时间
并发写入、文件随机修改
一个文件同一个时间只能有一个写者
仅支持append
2 HDFS的架构
HDFS以流式数据访问(一次写入,多次读取)模式来存储超大文件,运行于商用硬件集群上。超大文件是指GB,TB,PB的文件。目前已经有存储到PB级别的Hadoop集群了。
计算机字节关系
Hadoop1x HDFS官方架构图
21 HDFS架构之NameNode和DataNode
HDFS架构图
客户端(HDFS Client):如果想对文件进行读写的话,首先需要通过Namenode来获取一些信息。Namenode存储着命名空间(namespace)和元数据(metadata)
客户端有如下工作:
文件的切分
与Namenode交互,获取文件位置信息
与Datanode交互,读取或者写入数据
管理HDFS
访问HDFS(浏览器,Shell命令,JavaAPI)
辅助节点(Secondary):用于辅助Namenode工作,分担其工作量。主要工作是nameSpace的冷备份工作,并非热备份。定期将Namenode的镜像文件(fsimage)和 *** 作日志(fsedit)进行合并,然后推送给Namenode
1 fsimage是什么?
是元数据镜像文件:Namenode启动后,文件的元数据被加载到内存中,加载到内存后也会把这些元数据写入到本地的磁盘中,这个文件就是fsimage文件。
元数据镜像在内存中保存一份最新的,内存中的镜像=fsimage+fsedit
2 fsedits是什么?
是元数据 *** 作日志文件:客户端要对文件进行读写 *** 作,在这些 *** 作产生的日志就存在了fsedit文件中。
数据节点(Datanode):Namenode和Datanode通信是通过心跳和块报告。每个文件被分割成不同的块,存在不同的机器的本地磁盘上。
22 Namenode和Secondary Namenode运行关系
Secondarynamenode工作原理
日志与镜像的定期合并总共分以下五步:
Secondary Namenode通知Namenode切换editlog
Secondary Namenode通过>
Secondary Namenode将fsimage载入内存,然后开始合并editlog
Secondary Namenode将新的fsimage发回给Namenode
Namenode收到fsiamge后将新的fsimage替换旧的fsimage
3 HDFS文件的读写流程
31 HDFS文件的读取
HDFS文件读取:
首先调用FileSystem对象的open方法,其实是一个DistributedFileSystem的实例
DistributedFileSystem通过rpc获得文件的第一批个block的locations,同一block按照重复数会返回多个locations,这些locations按照hadoop拓扑结构排序,距离客户端近的排在前面
前两步会返回一个FSDataInputStream对象,该对象会被封装成DFSInputStream对象,DFSInputStream可以方便的管理datanode和namenode数据流。客户端调用read方法,DFSInputStream最会找出离客户端最近的datanode并连接。
数据从datanode源源不断的流向客户端。
如果第一块的数据读完了,就会关闭指向第一块的datanode连接,接着读取下一块。这些 *** 作对客户端来说是透明的,客户端的角度看来只是读一个持续不断的流。
如果第一批block都读完了,DFSInputStream就会去namenode拿下一批blocks的location,然后继续读,如果所有的块都读完,这时就会关闭掉所有的流。
32 HDFS文件的写入
HDFS文件写入:
客户端通过调用DistributedFileSystem的create方法创建新文件
DistributedFileSystem通过RPC调用namenode去创建一个没有blocks关联的新文件,创建前,namenode会做各种校验,比如文件是否存在,客户端有无权限去创建等。如果校验通过,namenode就会记录下新文件,否则就会抛出IO异常
前两步结束后会返回FSDataOutputStream的对象,和读文件的时候相似,FSDataOutputStream被封装成DFSOutputStream,DFSOutputStream可以协调namenode和datanode。客户端开始写数据到DFSOutputStream,DFSOutputStream会把数据切成一个个小packet,然后排成队列data quene。
DataStreamer会去处理接受data quene,他先问询namenode这个新的block最适合存储的在哪几个datanode里,比如重复数是3,那么就找到3个最适合的datanode,把他们排成一个pipelineDataStreamer把packet按队列输出到管道的第一个datanode中,第一个datanode又把packet输出到第二个datanode中,以此类推。
DFSOutputStream还有一个对列叫ack quene,也是有packet组成,等待datanode的收到响应,当pipeline中的所有datanode都表示已经收到的时候,这时akc quene才会把对应的packet包移除掉。
客户端完成写数据后调用close方法关闭写入流
DataStreamer把剩余得包都刷到pipeline里然后等待ack信息,收到最后一个ack后,通知datanode把文件标示为已完成。
Hadoop 生态圈中的框架包括以下主要组件,除了以下组件之外的都不属于Hadoop 生态圈。
1)HDFS:一个提供高可用的获取应用数据的分布式文件系统。
2)MapReduce:一个并行处理大数据集的编程模型。
3)HBase:一个可扩展的分布式数据库,支持大表的结构化数据存储。是一个建立在 HDFS 之上的,面向列的 NoSQL 数据库,用于快速读/写大量数据。
4)Hive:一个建立在 Hadoop 上的数据仓库基础构架。它提供了一系列的工具;可以用来进行数据提取转化加载(ETL),这是一种可以存储、查询和分析存储在 Hadoop 中的大规模数据的机制。Hive 定义了简单的类 SQL 查询语言,称为 HQL,它允许不熟悉 MapReduce 的开发人员也能编写数据查询语句,然后这些语句被翻译为 Hadoop 上面的 MapReduce 任务。
5)Mahout:可扩展的机器学习和数据挖掘库。它提供的 MapReduce 包含很多实现方法,包括聚类算法、回归测试、统计建模。
6)Pig:一个支持并行计算的高级的数据流语言和执行框架。它是 MapReduce 编程的复杂性的抽象。Pig 平台包括运行环境和用于分析 Hadoop 数据集的脚本语言(PigLatin)。其编译器将 PigLatin 翻译成 MapReduce 程序序列。
7)Zookeeper:—个应用于分布式应用的高性能的协调服务。它是一个为分布式应用提供一致性服务的软件,提供的功能包括配置维护、域名服务、分布式同步、组服务等。
8)Amban:一个基于 Web 的工具,用来供应、管理和监测 Hadoop 集群,包括支持 HDFS、MapReduceAHive、HCatalog、HBase、ZooKeeperAOozie、Pig 和 Sqoop 。Ambari 也提供了一个可视的仪表盘来查看集群的健康状态,并且能够使用户可视化地查看 MapReduce、Pig 和 Hive 应用来诊断其性能特征。
9)Sqoop:一个连接工具,用于在关系数据库、数据仓库和 Hadoop 之间转移数据。Sqoop 利用数据库技术描述架构,进行数据的导入/导出;利用 MapReduce 实现并行化运行和容错技术。
10)Flume:提供了分布式、可靠、高效的服务,用于收集、汇总大数据,并将单台计算机的大量数据转移到 HDFS。它基于一个简单而灵活的架构,并提供了数据流的流。它利用简单的可扩展的数据模型,将企业中多台计算机上的数据转移到 Hadoop。
安装流程
我们先来回顾上一篇我们完成的单节点的Hadoop环境配置,已经配置了一个CentOS 68 并且完成了java运行环境的搭建,Hosts文件的配置、计算机名等诸多细节。
其实完成这一步之后我们就已经完成了Hadoop集群的搭建的一半的工作了,因为我们知道通过虚拟机搭建所搭建的好处就是直接拷贝机器。多台同步进行 *** 作,减少分别配置的时间消耗浪费。这也是虚拟化技术所带来的优势。
下面,咱们进去分布式系统的详细 *** 作过程。
1、首先需要在VMWare中将之前创建的单实例的计算机进行拷贝。
这里根据之前第一篇文章的规划,我们至少需要再克隆出三台计算机,作为DataNode数据节点的数据存储。之前的上一台机器作为Master主节点进行管理。
这里先来梳理一下整个Hadoop集群的物理架构图,大家有一个直接的观念和认识,上表中已经和明确了,总共需要5台服务器来使用,四台用来搭建Hadoop集群使用,另外一台(可选)作为MySQL等外围管理Hadoop集群来使用。
大数据平台架构设计沿袭了分层设计的思想,将平台所需提供的服务按照功能划分成不同的模块层次,每一模块层次只与上层或下层的模块层次进行交互(通过层次边界的接口),避免跨层的交互,这种设计的好处是:各功能模块的内部是高内聚的,而模块与模块之间是松耦合的。这种架构有利于实现平台的高可靠性,高扩展性以及易维护性。比如,当我们需要扩容Hadoop集群时,只需要在基础设施层添加一台新的Hadoop节点服务器即可,而对其他模块层无需做任何的变动,且对用户也是完全透明的。整个拉卡拉大数据平台按其职能划分为五个模块层次,从下到上依次为:
运行环境层:
运行环境层为基础设施层提供运行时环境,它由2部分构成,即 *** 作系统和运行时环境。
(1) *** 作系统我们推荐安装REHL50以上版本(64位)。此外为了提高磁盘的IO吞吐量,避免安装RAID驱动,而是将分布式文件系统的数据目录分布在不同的磁盘分区上,以此提高磁盘的IO性能。
(2)运行时环境的具体要求如下表:
名称
版本
说明
JDK
16或以上版本
Hadoop需要Java运行时环境,必须安装JDK。
gcc/g++
3x或以上版本
当使用Hadoop
Pipes运行MapReduce任务时,需要gcc编译器,可选。
python
2x或以上版本
当使用Hadoop
Streaming运行MapReduce任务时,需要python运行时,可选。
基础设施层:
基础设施层由2部分组成:Zookeeper集群和Hadoop集群。它为基础平台层提供基础设施服务,比如命名服务、分布式文件系统、MapReduce等。
(1)ZooKeeper集群用于命名映射,做为Hadoop集群的命名服务器,基础平台层的任务调度控制台可以通过命名服务器访问Hadoop集群中的NameNode,同时具备failover的功能。
(2)Hadoop集群是大数据平台的核心,是基础平台层的基础设施。它提供了HDFS、MapReduce、JobTracker和TaskTracker等服务。目前我们采用双主节点模式,以此避免Hadoop集群的单点故障问题。
基础平台层:
基础平台层由3个部分组成:任务调度控制台、HBase和Hive。它为用户网关层提供基础服务调用接口。
(1)任务调度控制台是MapReduce任务的调度中心,分配各种任务执行的顺序和优先级。用户通过调度控制台提交作业任务,并通过用户网关层的Hadoop客户端返回其任务执行的结果。其具体执行步骤如下:
任务调度控制台接收到用户提交的作业后,匹配其调度算法;
请求ZooKeeper返回可用的Hadoop集群的JobTracker节点地址;
提交MapReduce作业任务;
轮询作业任务是否完成;
如果作业完成发送消息并调用回调函数;
继续执行下一个作业任务。
作为一个完善的Hadoop集群实现,任务调度控制台尽量自己开发实现,这样灵活性和控制力会更加的强。
(2)HBase是基于Hadoop的列数据库,为用户提供基于表的数据访问服务。
(3)Hive是在Hadoop上的一个查询服务,用户通过用户网关层的Hive客户端提交类SQL的查询请求,并通过客户端的UI查看返回的查询结果,该接口可提供数据部门准即时的数据查询统计服务数据库与hadoop与分布式文件系统的区别和联系
1 用向外扩展代替向上扩展
扩展商用关系型数据库的代价是非常昂贵的。它们的设计更容易向上扩展。要运行一个更大
的数据库,就需要买一个更大的机器。事实上,往往会看到服务器厂商在市场上将其昂贵的高端机
标称为“数据库级的服务器”。不过有时可能需要处理更大的数据集,却找不到一个足够大的机器。
更重要的是,高端的机器对于许多应用并不经济。例如,性能4倍于标准PC的机器,其成本将大大
超过将同样的4台PC放在一个集群中。Hadoop的设计就是为了能够在商用PC集群上实现向外扩展
的架构。添加更多的资源,对于Hadoop集群就是增加更多的机器。一个Hadoop集群的标配是十至
数百台计算机。事实上,如果不是为了开发目的,没有理由在单个服务器上运行Hadoop。
2 用键/值对代替关系表
关系数据库的一个基本原则是让数据按某种模式存放在具有关系型数据结构的表中。虽然关
系模型具有大量形式化的属性,但是许多当前的应用所处理的数据类型并不能很好地适合这个模
型。文本、和XML文件是最典型的例子。此外,大型数据集往往是非结构化或半结构化的。
Hadoop使用键/值对作为基本数据单元,可足够灵活地处理较少结构化的数据类型。在hadoop中,
数据的来源可以有任何形式,但最终会转化为键/值对以供处理。
3 用函数式编程(MapReduce)代替声明式查询(SQL )
SQL 从根本上说是一个高级声明式语言。查询数据的手段是,声明想要的查询结果并让数据库引擎
判定如何获取数据。在MapReduce中,实际的数据处理步骤是由你指定的,它很类似于SQL
引擎的一个执行计划。SQL 使用查询语句,而MapReduce则使用脚本和代码。利用MapReduce可
以用比SQL 查询更为一般化的数据处理方式。例如,你可以建立复杂的数据统计模型,或者改变
图像数据的格式。而SQL 就不能很好地适应这些任务。
4
分布式文件系统(dfs)和分布式数据库都支持存入,取出和删除。但是分布式文件系统比较暴力,
可以当做key/value的存取。分布式数据库涉及精炼的数据,传统的分布式关系型数据库会定义数据元
组的schema,存入取出删除的粒度较小。
分布式文件系统现在比较出名的有GFS(未开源),HDFS(Hadoop distributed file system)。
分布式数据库现在出名的有Hbase,oceanbase。其中Hbase是基于HDFS,而oceanbase是自己内部
实现的分布式文件系统,在此也可以说分布式数据库以分布式文件系统做基础存储。
分布式文件系统(Distributed File System,DFS)
如果局域网中有多台服务器,并且共享文件夹也分布在不同的服务器上,这就不利于管理员的管理和用户的访问。而使用分布式文件系统,系统管理员就可以把不同服务器上的共享文件夹组织在一起,构建成一个目录树。这在用户看来,所有共享文件仅存储在一个地点,只需访问一个共享的DFS根目录,就能够访问分布在网络上的文件或文件夹,而不必知道这些文件的实际物理位置。
换个思路,使用mount --bind把目录加载过来就可以了 先将数据盘挂载 mount /dev/sdb1 /mnt/d 在ftp目录下建一个文件夹data mount --bind /mnt/d data
FTP server和分布式文件系统的区别, 分布式文件系统和分布式数据库有什么不同 分布式文件系统(dfs)和分布式数据库都支持存入,取出和删除。但是分布式文件系统比较暴力,可以当做key/value的存取。分布式数据库涉及精炼的数据,传统的分布式关系型数据库会定义数据元组的schema,存入取出删除的粒度较小。
分布式文件系统现在比较出名的有GFS(未开源),HDFS(Hadoop distributed file system)。分布式数据库现在出名的有Hbase,oceanbase。其中Hbase是基于HDFS,而oceanbase是自己内部实现的分布式文件系统,在此也可以说分布式数据库以分布式文件系统做基础存储。
是的
Hadoop分布式文件系统(HDFS)是一种被设计成适合运行在通用硬件上的分布式文件系统。HDFS是一个高度容错性的系统,适合部署在廉价的机器上。它能提供高吞吐量的数据访问,非常适合大规模数据集上的应用。要理解HDFS的内部工作原理,首先要理解什么是分布式文件系统。
1分布式文件系统
多台计算机联网协同工作(有时也称为一个集群)就像单台系统一样解决某种问题,这样的系统我们称之为分布式系统。
分布式文件系统是分布式系统的一个子集,它们解决的问题就是数据存储。换句话说,它们是横跨在多台计算机上的存储系统。存储在分布式文件系统上的数据自动分布在不同的节点上。
分布式文件系统在大数据时代有着广泛的应用前景,它们为存储和处理来自网络和其它地方的超大规模数据提供所需的扩展能力。
2分离元数据和数据:NameNode和DataNode
存储到文件系统中的每个文件都有相关联的元数据。元数据包括了文件名、i节点(inode)数、数据块位置等,而数据则是文件的实际内容。
在传统的文件系统里,因为文件系统不会跨越多台机器,元数据和数据存储在同一台机器上。
为了构建一个分布式文件系统,让客户端在这种系统中使用简单,并且不需要知道其他客户端的活动,那么元数据需要在客户端以外维护。HDFS的设计理念是拿出一台或多台机器来保存元数据,并让剩下的机器来保存文件的内容。
NameNode和DataNode是HDFS的两个主要组件。其中,元数据存储在NameNode上,而数据存储在DataNode的集群上。NameNode不仅要管理存储在HDFS上内容的元数据,而且要记录一些事情,比如哪些节点是集群的一部分,某个文件有几份副本等。它还要决定当集群的节点宕机或者数据副本丢失的时候系统需要做什么。
存储在HDFS上的每份数据片有多份副本(replica)保存在不同的服务器上。在本质上,NameNode是HDFS的Master(主服务器),DataNode是Slave(从服务器)。
文件系统与数据库系统的区别和联系
其区别在于:
(1)
文件系统用文件将数据长期保存在外存上,数
据库系统用数据库统一存储数据。
(2)
文件系统中的程序和数据有一
定的联系,数据库系统中的程序和数据分离。
(3)
文件系统用 *** 作系
统中的存取方法对数据进行管理,数据库系统用
DBMS
统一管理和控
制数据。
(4)
文件系统实现以文件为单位的数据共享,数据库系统实
现以记录和字段为单位的数据共享。
其联系在于:
(1)
均为数据组织的管理技术。
(2)
均由数据管理软
件管理数据,程序与数据之间用存取方法进行转换。
(3)
数据库系统
是在文件系统的基础上发展而来的。
文件系统和数据库系统之间的区别:
(1) 文件系统用文件将数据长期保存在外存上,数据库系统用数据库统一存储数据;
(2) 文件系统中的程序和数据有一定的联系,数据库系统中的程序和数据分离;
(3) 文件系统用 *** 作系统中的存取方法对数据进行管理,数据库系统用DBMS统一管理和控制数据;
(4) 文件系统实现以文件为单位的数据共享,数据库系统实现以记录和字段为单位的数据共享。
文件系统和数据库系统之间的联系:
(1) 均为数据组织的管理技术;
(2) 均由数据管理软件管理数据,程序与数据之间用存取方法进行转换;
(3) 数据库系统是在文件系统的基础上发展而来的。
分布式文件系统(Distributed File System)是指文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点相连。
Hadoop是Apache软件基金会所研发的开放源码并行运算编程工具和分散式档案系统,与MapReduce和Google档案系统的概念类似。
HDFS(Hadoop 分布式文件系统)是其中的一部分。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)