• Hadoop权威指南读书笔记


    本书中提到的Hadoop项目简述

    1. Common:一组分布式文件系统和通用I/O的组件与接口(序列化、javaRPC和持久化数据结构)。

    2. Avro:一种支持高效、跨语言的RPC以及永久存储数据的序列化系统。

    3. MapReduce:分布式数据处理模型和执行环境,执行于大型商业集群。

    4. HDFS:分布式文件系统,执行于大型商用机集群。

    5. Pig:一种数据流语言和执行环境,用以检索很大的数据集。

      Pig执行在MapReduceHDFS的集群上。

    6. Hive:一个分布式、按列存储的数据仓库。Hive管理HDFS中存储的数据,并提供基于SQL的查询语言(由执行时引擎翻译成MapReduce作业)用以查询数据。

    7. HBase:一个分布式、按列存储数据库。

      HBase使用HDFS作为底层存储。同一时候支持MapReduce的批量式计算和点查询(随机读取)。

    8. ZooKeeper:一个分布式、可用性高的协调服务。ZooKeeper提供分布式锁之类的基本服务,用于构建分布式应用。

    9. Sqoop:在数据库和HDFS之间高效数据传输的工具。

    内容笔记


    mapreduce数据本地化(核心特征)、无共享。
       1  HDFS的设计
    • 为了存储超大文件
    • 流式訪问(一次写入多次读取)
    • 商用硬件(不须要超豪华的机器)
       2  数据快(block)
    • 磁盘一般是512字节
    • HDFS默认是64M
    • 但与其它文件系统不同的是小于一个块大小的文件不会占用整个块的空间。

    • 块为什么设置这么大?(最小化寻址开销)
    • 块抽象带来的优点(一个文件的大小能够大于网络中随意磁盘的大小,由于文件的全部的块并不须要存储在同一个磁盘中)第二个优点,使用块抽象大大简化了存储子系统的设计。

      第三。很适合数据备份,添加了数据容错能力。

         3  NameNode和DataNode
    • HDFS中有一个NN多个DN,NN管理文件系统的命名空间。它维护这文件系统树及整棵书内全部文件和文件夹。这写信息保存在命名空间镜像文件(fsimage)和编辑日志文件(edits)
    • DN是文件系统的工作节点。存储并检索数据块。并定期想NN发送他们所存储的块的列表。
    • NN很重要。所以容错很重要。hadoop提供了两中机制。

      第一种是备份那写组成文件系统元数据持久状态的文件。

      Hadoop能够配置使NN在多个文件系统上保存元数据的持久状态。这写操作是实时同步的。是原子操作。一般的配置是,将持久状态写入本地磁盘的同一时候。写入一个远程挂载的网络文件系统(NFS)。还有一种是执行一个辅助NN,但它不能被用作NN,这个辅助NN的重要作用是定期通过编辑日志合并命名空间镜像。以防止编辑日志过大。

      这个SNN一般在还有一台单独的无力计算机上执行,以为他须要占用大量的cpu和NN同样容量的内存来执行合并操作。

      它会保存合并后的命名空间镜像的副本。并在NN发生问题时启用。可是SNN滞后与NN,所以假设NN全部失效,难免会丢失部分数据。在这样的情况下。一般把存储在NFS上的NN元数据拷贝到SNN并作为新的NN执行。

    关于MapReduce

    · Hadoopmapreduce的输入划分成等长的小数据快。成为输入切分(input split)或简称分片。对于大多数作业来说,一个合理的分片大小趋向于HDFS的一个块的大小,默认是64M

    · 最核心的思想:数据本地最优化(data locality optimization),map任务将其输出写入到本地硬盘。而非HDFS

    · 

     
     
    reduce任务的数量并非由输入数据的大小决定。而是特别指定的。假设有多个reduce
     
     
    任务。则每一个map任务都会对其输出进行分区(partition)。即为每一个reduce任务建立一个分区。

    上图显示就知道,mapreduce之间的数据流为什么被称为shuffle(混洗)

    · 集群上的可用宽带限制了MR作业的数量,因此最重要的一点是尽量避免MR之间的数据传输。H同意用户针对M的输出指定一个合并函数,合并函数的输出作为R的输入。可是在MR中使用C是须要慎重考虑的。有可能造成reduce输出的结果不对。

    Hadoop分布式文件系统

    · HDFS以流式数据訪问模式来存储超大文件,执行于商用硬件集群上。

    构建思路是这样的:一次写入、多次读取是最高效的訪问模式。HDFS的块默觉得64MB。与其它文件系统不同的是,HDFS中小于一个块大小的文件不会占领整个块的空间。为何HDFS中的块如此之大?(HDFS的块比磁盘块大,其目的是为了最小化寻址开销)

    · HDFS集群有两类节点,并以管理者-工作者模式执行,即一个namenode和多个datanodenamenode管理文件系统的命名空间。它维护着文件系统树及整颗树内全部的文件和文件夹。

    这些信息以两个文件形式永久保存在本地磁盘上:命名空间镜像文件和编辑日志文件。namenode也记录着每一个文件里各个块所在的数据节点信息。但他并不永久保存块的位置信息,由于这写信息会在系统启动时由数据节点重建。dataname是文件系统的工作节点,他们依据须要存储并检索数据块,而且定期向namenode发送他们所存储的块的列表。

    · namenode实现容错很常使用药。有两种机制:一、备份那写组成文件系统元数据持久状态的文件。

    二、执行一个辅助namenode,但它不能被用作namenode。这个辅助namenode的重要作用是定期通过编辑日志合并命名空间镜像,以防止编辑日志过大。

    · Hadoop URL中读取数据

    · 通过FileSystemAPI读取数据,关键代码FileSystem fs=FileSytem.get(URI,conf),FileSystem对象的open方法返回的是FSDataInputStream对象,它继承了DataInputStream

    他支持随机訪问,能够从流的任何位置读取数据。

    · 创建文件夹:FileSystemmkdirs方法

    · 查询文件系统:FileSystemFileStatus,列出文件:listStatus,通配符查询:globStatus。能够配合PathFilter对象使用正則表達式,精确定位文件。

    · 删除文件:FileSystemdelete方法。

     

    文件读取剖析

     
     
     
     
     

     

    文件写入剖析

     
     


    副本的布局

     
     
     

    · 文件系统的一致模型描写叙述了对文件读/写的数据可见性。须要调用flushsync方法后,才干同步数据。这个一致模型和你设计应用程序的详细方法息息相关。须要找到一个合适的使用sync方法的频率。

    · distcp并行复制。在两个HDFS集群之间数据传输。比如同样版本号的HDFS%hadoop distcp hdfs://namenode1/foo hdfs://namenode2/bar

    不同版本号的HDFS%hadoop distcp hftp://namenode1:50070/foo hdfs://namenode2/bar这里注意:源路径必须是绝对路径。

    · Hadoop存档工具。

    %hadoop archive -archiveName files.har /my/files /myHAR文件一旦创建,存档文件便不能改动,要想从中增减文件。必须吃哦够耐心创建存档文件。


    Hadoop I/O

    · 数据完整性。

    检測数据是否损坏的常见措施是,在数据第一次引入系统时计算校验和(checksum),并在数据通过一个不可靠的通道进行栓书时再次计算校验和。这样就能发现数据是否损坏。经常使用的错误检測码是CRC-32(循环冗余校验),不论什么大小的数据输入均计算得到一个32位证书校验和。datanode负责在验证收到的数据后存储数据及其校验和。每一个datanode会在后台线程中执行一个DataBlockScanner,从而定期验证存储在这个datanode上的全部数据快。

    · 压缩:文件压缩两个优点,能够较少存储文件所须要的磁盘空间;能够加速数据在网络和磁盘上的传输。压缩格式:DEFLATE(不可切分)。Gzip(不可切分),bzip2(可切分),LZO(不可切分)。

    选项-1为优化压缩速度,-9为优化压缩空间。可切分压缩格式bzip2尤其适合MapReduce

    codec实现了一种压缩-解压算法。

    MR会在读取文件时自己主动解压缩文件

    · 序列化。

    在进程间通信和永久存储中使用。Hadoop使用writable序列化。Writable有两个方法:writereadFields.

     
     
     

    · AVRO

    是一个独立与编程语言的序列化系统。一般是用JSON编写,而数据一般是二进制编码。Avro文件支持压缩。

    而且是可切分的。Avro还能够用于RPC

    · Sequence file(写入sequencefile。读取sequencefile

     
     
    MapFile。(写入Mapfile,读取MapfileMapFile是已经排序的SequenceFile,它已添加用于搜索键的索引。

    能够将MapFile视为java.util.Map的持久化形式。

     
     
    SequenceFile转换为MapFile。对SequenceFile排序和建立索引。MapFilefix()静态方法能够为MapFile重建索引。


    MapReduce应用开发

    · Mr有一个特定的流程。

    首先写map函数和reduce函数,最好使用单元測试来确保函数的执行符合预期。然后,写一个驱动程序来执行作业。要看这个驱动程序能否够执行。能够从本地IDE用一个小的数据集来执行它。假设驱动程序不能正确执行,就用本地IDE调试。

    一旦程序通过了小的数据集測试。就能够准备执行在集群上了。

    · 配置API

    H中通过Configuration实例代表配置属性及其取值的一个集合。能够把多个配置文件合并在一起。可是后面的配置文件里的属性会覆盖前面的同样属性值。假设属性指定为final则不会覆盖。

    · MapReduceWeb界面。http://jobtracker-host:50030

    · 察看MR输出结果。1、合并MR输出结果hadoop fs -getmerge …....2hadoop fs -cat output/*

    · 使用远程调试器。首先配置属性keep.failed.task.files的值为true,以便在任务失败时。tasktracker能保留足够的信息让人物在同样的输入数据上又一次执行。然后再次执行作业,并使用WebUI察看故障节点和task attempt ID。接着,须要使用前面保留的文件作为输入,执行一个特殊的作业执行器,即IsolationRunner

    · 作业调优。

    mapper的数量。reducer数量。combiner

    中间值的压缩。自己定义序列。调整shuffle

    · 分析任务。

    HPROF分析工具(是JDK自带的分析工具)。


    MapReduce的工作机制

    · 

     
     
    上图包括4个独立的实体。

    1、client:提交MR作业。2jobtracker:协调作业的执行。

    jobtracker是一个java应用程序,他的主类是JobTracker3tasktracker:执行作业划分后的人物。tasktrackerjava应用程序。主类为TaskTracker4、分布式文件系统(HDFS)。

    用来在其它实体间共享作业文件。

     

    · 

     
     
    告知jobtracker作业准备执行。

     

     
     
     

     

     

     

    · 

     
     
     
     

    · StreamingPipes

    · 

     
     
    状态更新

    · 

     
     
    任务失败。1MR任务中用户代码一场。子任务进程会在退出之前向其父tasktracker发送错误报告。最后被计入用户日志。

    Tasktracker会将此次task attempt标记为failed,释放一个任务槽执行另外一个任务。2、一旦tasktracker注意到JVM有一段时间没有更新(默认是10分钟),则JVM子进程将被自己主动杀死。3jobtracker知一个task attempt失败后,将又一次调度该任务的执行。一般超过4此,将不会再重试。

    · 作业的调度。

    1FIFO

    2、优先队列。

    3Fair Scheduler4Capacity Scheduler.

    · shuffle和排序。MR确保每一个reducer的输入都按键排序。系统执行排序的过程——map输出作为输入传给reducer,称为shuffleshuffleMR的心脏,是奇迹发生的地方。

    · 

     
     
    配置的调优。

    总的原则是给shuffle过程尽量多提供内存空间。在map端,要便面多次溢出写磁盘来获得最佳性能。在Reduce端,中间数据全部驻留在内存时。获得最佳性能。H使用默觉得4kb的缓冲区,这是很低的,因此应该在集群上添加这个值。

    尝试JVM重用。跳过坏记录。


    MapReduce的类型与格式

    · MR的类型。1Map:(k1,v1)——list(k2,v2).reduce(k2list(v2))——list(k3v3)

    2map:(k1,v1)——list(k2,v2).combine:(k2,listk2))——list(k2,v2).reduce:(k2,list(v2))——list(k3,v3)

    combinereduce函数一般是一样的,在这样的情况下。k3k2类型同样,v3v2类型同样。Partition函数将中间的key/value(k2,v2)进行处理。而且返回一个分区索引。实际上是单独由key决定(value被忽略)。

    Partition:(k2,v2)——integer.

    · 输入分片与记录。一个输入分片是由一个map处理的输入块。

    每一个map操作仅仅处理一个输入分片。每一个分片被划分为若干个记录,每一个记录就是一个key/value对。map一个接一个的处理每条记录。

    注意,灭个分片并不包括数据本身,而是指向数据的引用(reference)

    · FileInputFormat类。

    FileInputFormat提供四种静态方法来设定JobConf的输入路径,addInputPath,addInputPaths,setInputPaths,setInputPaths。一条路径能够表示一个文件,一个文件夹或是一个glob,即一个文件和文件夹的集合。

    · 文本输入:Hadoop很擅优点理非结构化文本数据。TextInputFormat:是默认的InputFormat,每条记录是一行输入。键是LongWritable类型。存储该行在整个文件里的字节偏移量。

    值是这行的内容,是Text类型。

    KeyValueTextInputFormat:文件里的每一行是一个键/值对。使用某个分界符进行切割。分隔符前面是key,后面是value。分隔符能够通过key.value.separator.in.input.line属性指定分隔符。

    默认是制表符。

    NLineInputFormat:控制mapper收到固定行数的输入。键是文件里行的字节偏移量,值是行本身。N是每一个mapper受到的输入行数。N通过mapred.ine.input.format.linespermap控制。

     
     

    · 二进制输入:SequenceFileInputFormat:以顺序文件格式存储二进制的键/值对的序列。

    SequenceFileAsTextInputFormat将顺序文件的键和值转换为Text对象。SequenceFileAsBinaryInputFormat获取顺序文件的键和值作为二进制对象。(BytesWritable)

    · FileInputFormat类的输入分片。给定一组文件。FileInputFormat是怎样把他们转换为输入分片?FileInputFormat仅仅切割大文件。这里的指的是超过HDFS快的大小。分片统称与HDFS块大小一样。这个值也能够通过设置不同的Hadoop属性改变。分片的大小由公式max(minimumSize,min(maximumSize,blockSize))计算。

    · 小文件与CombineFileInputFormat

    · 避免切分。1、添加最小分片大小。大于要处理的最大文件大小。2、使用FileInputFormat详细子类,而且重载isSplitable()方法,把返回值设置为false

    · 输出格式FileOutputFormat

    · 

     
     
    有时可能须要对输出的文件名称进行控制,或让每一个reducer输出多个文件。MR为此提供了两个库:MultipleOutputFormatMultipleOutput类。

    · 计数器。

    Hadoop为每一个作业维护若干内置计数器,以描写叙述该作业的各项指标。

    比如。记录已处理的字节数和记录数。计数器由其关联人物维护,并定期传到tasktracker。再由tasktracker传给jobtracker。因此,计数器能够被全局的聚集。与其它计数器不同。内置的作业计数器实际上由jobtracker维护。不必在整个网络中传送。

    · 排序。

    · 连接。map端连接。在两个大规模输入数据集之间的map端连接会在数据到达map函数之间就执行连接操作。为达到这个目的,各map的输入数据必须先区分而且以特定方式排序。

    各个输入数据集被划分成同样数量的分区。而且均按同样的健排序(连接健)。

    同一健的全部记录均会放在同一分区之中。利用org.apache.hadoop.mapred.join包中的CompositeInputFormat类来执行一个map端连接。reduce端连接,reduce端连接并不要求输入数据集符合特定结构,因而reduce连接比map端连接更为经常使用。

    · 分布式缓存。

    · 构建Hadoop集群

    · 管理Hadoop


    PIG

    · Pig为大型数据集的处理提供了更高层次的抽象。是一种探索大规模数据集的脚本语言。Pig被设计为可扩展的。处理路径中的每一个部分,载入、存储、过滤、分组、连接,都是能够定制的。Pig并不适合全部的数据处理任务,它是为数据批处理而设计的。

    · 执行类型(1)本地模式(2MapReduce模式。

    执行Pig程序:(1)脚本(2Grunt3)嵌入式方法。

    · 与数据库比較:Pig Latin是一种数据留编程语言。而SQL是一种描写叙述型编程语言。

    换句话说,一个Pig Latin程序是相对于输入的一步步操作。

    当中每一歩都是对数据的一个简单的变换。相反,SQL语句是一个约束的集合。

    这些约束在一起。定义了输出。RDBMS把数据存储在严格定了模式的表内。Pig对他处理的数据要求则宽松了许多:能够在执行时定义模式。而且这是可选的。几个支持在线和低延迟查询的特性是RDBMS有但Pig没有的,比如事务和索引。

    Hive介于Pig和传统的RDBMS之间。

    Pig一样,Hive也被设计为用HDFS作为存储。可是他们之间有着显著的差别。Hive的查询语言HiveQL,是基于SQL的。

    RDBMS同样。Hive要求全部数据必须存储在表中,表必须有模式,而模式有Hive进行管理。

    可是Hive同意在预先存在于HDFS的数据关联一个模式。所以数据的载入步骤是可选的。

    · PIG的关系操作(1)载入和存储:LOADSTOREDUMP2)过滤:FILTERDISTINGCTFOREACH...GENERATESTREAMSAMPLE3)分组和连接JOINCOGROUPGROUPCROSS4)排序:ORDERLIMIT5)合并与切割UNIONSPLIT6UDFREGISTERDEFINE。(7 HADOOPMapReduce相关命令。


    HIVE

    · Hive是一个构建在Hadoop上的数据仓库框架。Hive是应Facebook每天产生的海量新兴社会网络数据进行管理和(机器)学习的需求而产生和发展的。Hive的设计目的是让精通SQL技能(但Java编程技能相对较弱)的分析师能够在Facebook存放在HDFS的大规模数据集上执行查询。当然,SQL并非全部的大数据问题的理想工具。可是他对许多分析任务很实用,Hive有条件和这些产品进行集成。

    · Hive外壳环境。外壳环境是我们和Hive进行交互。发出HiveQL命令的主要方式。命令语法很像MySQL

    · Hive的体系结构

    · 

     
     
    metastore。是Hive云数据的集中存放地。包括两部分:服务和后台数据的存储。默认情况下。metastore服务和Hive服务执行在同一个JVM中。

    metastore的配置例如以下图。假设要支持多绘画(以及多用户),须要使用一个独立的数据库。这样的配置称本地metastore”,由于metastore服务仍然和Hive服务执行在同一个进程中,但连接的却是在还有一个进程中执行的数据库,在同一台机器上或在远程机器上。MySQL是一种很受欢迎的独立metastore的选择。

    · 

     
     
    RDBMS比較。Hive在许多方面和传统数据库相似,可是它底层对HDFSMapReduce的依赖意味着他的体系结构有别于传统数据库。

    在传统数据库里,表的模式是在数据载入时强制确定的。这一设计有时被成为写时模式Hive对数据的验证并不再载入数据时进行,而在查询时进行。这称为读时模式。读时模式能够使数据载入很迅速。写时模式有利于提升查询性能。更新、事务和索引都是传统数据库最重要的特性。可是Hive也还没有考虑支持这些特性。

    · HiveQL:能够勉强看作是对MySQL方言的模仿。

    · 托管表和外部表。在Hive中创建表时。默认情况下Hive负责管理数据。这意味着Hive把数据移入它的仓库文件夹

    还有一种选择是创建一个外部表。这会在Hive到仓库文件夹以外的位置訪问数据。这两种表的差别表如今LOADDROP命令的语义上。托管表:DROP这个表(包括他的元数据和数据)会被一起删除。外部表(EXTERNALkeyword):DROP时,Hive不会碰数据,而仅仅会删除元数据。最为一个经验法则,假设全部处理都有Hive完毕,应该使用托管表。但假设要用Hive和其它工具来处理同一个数据集,应该使用外部表。普遍的使用方法是把存放在HDFS(由其它进程创建)的初始数据集用作外部表使,然后用Hive的变换功能把数据移动到托管的Hive表。这一方法反之也成立——外部表(未必在HDFS中)能够用于从Hive到处数据供其应用程序使用。须要使用外部表的还有一个原因是你想为同一个数据集关联不同的模式。

    · 分区和桶。

    Hive把表组织成分区,这是一种依据分区列的值对表进行粗略划分的机制。使用分区能够加快数据分片的查询速度。

    表或分区能够进一步分为。他会为数据提供额外的结构以获得更高效的查询处理。


    HBase

    · Hbase是一个HDFS上开发的面向列的分布式数据库。

    假设须要实时的随机读/写超大规模数据集,就能够使用HBaseHBase从还有一个方向攻克了可伸缩性的问题。它自底向上的进行构建,能够简单的通过添加节点来达到线性扩展。

    HBase并不关系型数据库。不支持SQL。可是在特定的问题空间里,他能够做RDBMS不能做的问题:在便宜的硬件构成的集群上管理超大规模的稀疏表。

    · 区域。

    HBase自己主动把表水平划分成区域。每一个区域由表中行的子集构成。每一个区域由它所属的表、他所包括的第一行以及最后一行来表示。区域是在HBase集群分布数据的最小单位。

    · Hbase採用一个Master节点协调管理一个或多个Regionserver从属机。

    HBasemaster负责启动和全新的安装、把区域分配给注冊的Regionserver,回复Regionserver的故障。Master的负载很轻。Regionserver负责零个或多个区域的管理以及响应client的读写请求。Regionserver还负责区域的划分。并通知Master有了新的子区域,这样master就能够把父区域设为离线,并用子区域替换父区域。

    · 

     
     
    HBase依赖于ZooKeeper

    默认情况下。它管理一个ZooKeeper实例。

    · HbaseRDBMS的比較。

    HBase是一个分布式的、面向列的数据存储系统。它通过在HDFS上提供随机读写来解决Haoop不能处理的问题。HBase自底向上设计即聚焦于各种可伸缩行问题。


    ZooKeeper

    · ZooKeepersHadoop的分布式协调应用,它提供一组工具。使得在构建分布式应用时能够对部分失败进行正确处理。

    · ZooKeeper能够看作是一个具有高可用性的文件系统。可是这个文件系统中没有文件和文件夹。而是统一使用节点node)的概念。称为znodeznode既能够作为保存数据的容器(如同文件),也能够作为保存其它znode的容器(如同文件夹)。

    全部的znode构成一个层次化的命名空间。

    · 

     
     
    创建组、添加组、列出组成员、删除组(不支持递归删除操作)

    · ZooKeeper命令行工具。zkCli.sh -server localhost cmd args 

    · zooKeeper维护一个树形层次结构,树中的节点被称为znodeznode能够用于存储数据。而且有一个与之相关联的ACL

    ZooKeeper被设计用来实现协调服务,而不是用于存储大数据。一次一个znode能存储的数据被限制在1MB以内。ZooKeeper的数据訪问具有原子性。znode有两中类型:短暂的和持久的。

    对于那写须要知道特定时刻有哪些分布式资源可用的应用来说,使用短暂znode是一种理想的选择。ZooKeeper服务的操作:createdeleteexistsgetACLsetACLgetChildrengetDatasetDatasync(将client的znode视图与ZooKeeper同步。

    · Zookeeper服务有两种不同的执行模式。一种是独立模式,即仅仅有一个ZooKeeper服务器。

    在生产环境中通常以复制模式执行与一个计算机集群上,这个计算机集群被称为一个集合体ZooKeeper通过复制来实现高可用性,仅仅要集合体中半数以上的及其处于可用状态,他就能够提供服务。从概念上来说,ZooKeeper是很easy的:它所做的就是确保对znode树的每一个改动都会被拷贝到集合体中超过半数的机器上。


    Sqoop

    · sqoop是一个开源工具,他同意用户将数据从关系型数据库抽取到Haoop中,用于进一步的处理。

    抽取出的数据能够被MapReduce程序使用,也能够被其它相似鱼Hive的工具使用。

    一旦形成分析结果,Sqoop便能够将这些结果导回数据库,使其它client使用。

    · 数据导入:

    · 

     
     
    数据导出:

    · 

     
     
    进程的并行特性,导致导出操作往往不是原子操作。Sqoop会生成多个并行执行的任务,分别导出数据的一部分。数据库系统经常使用固定大小的缓冲区来存储事务数据。这使一个任务中的全部操作不可能在一个事务中完毕。Sqoop每导入几千条记录便执行一次提交。以确保不会出现内存不足的情况。假设任务失败,他们会从头開始又一次导入自己负责的那部分数据,因此可能会插入反复的记录。
  • 相关阅读:
    centos 7 配置ip
    Linux下安装jmeter
    eclipse 高效快捷键大全
    eclipse中不能找到dubbo.xsd报错”cvc-complex-type.2.4.c“的 两种解决方法
    大型网站系统架构演化之路(转)
    程序员技术练级攻略
    JSP和servlet之间的传值(总结的很全面)
    正则表达式笔记
    cenos 安装nginx并添加到service
    mac os重装php
  • 原文地址:https://www.cnblogs.com/ldxsuanfa/p/10018878.html
Copyright © 2020-2023  润新知