关于CDH和Cloudera Manager

CDH (Cloudera's Distribution, including Apache Hadoop),是Hadoop众多分支中的一种,由Cloudera维护,基于稳定版本的Apache Hadoop构建,并集成了很多补丁,可直接用于生产环境。

Cloudera Manager则是为了便于在集群中进行Hadoop等大数据处理相关的服务安装和监控管理的组件,对集群中主机、Hadoop、Hive、Spark等服务的安装配置管理做了极大简化。

系统环境

  • 实验环境:Mac下VMware虚拟机
  • 操作系统:CentOS 6.5 x64 (至少内存2G以上,这里内存不够的同学建议还是整几台真机配置比较好,将CDH的所有组件全部安装会占用很多内存,我已开始设置的虚拟机内存是1G,安装过程中直接卡死了)
  • Cloudera Manager:5.1.3
  • CDH: 5.1.3

安装说明

官方参考文档:

http://www.cloudera.com/content/cloudera/en/documentation/cloudera-manager/v5-latest/Cloudera-Manager-Installation-Guide/cm5ig_install_path_C.html

官方共给出了3中安装方式:第一种方法必须要求所有机器都能连网,由于最近各种国外的网站被墙的厉害,我尝试了几次各种超时错误,巨耽误时间不说,一旦失败,重装非常痛苦。第二种方法下载很多包。第三种方法对系统侵入性最小,最大优点可实现全离线安装,而且重装什么的都非常方便。后期的集群统一包升级也非常好。这也是我之所以选择离线安装的原因。

相关包的下载地址

Cloudera Manager下载地址:

http://archive.cloudera.com/cm5/cm/5/cloudera-manager-el6-cm5.1.3_x86_64.tar.gz

下载信息:

http://www.cloudera.com/content/cloudera/en/documentation/cloudera-manager/v5-latest/Cloudera-Manager-Version-and-Download-Information/Cloudera-Manager-Version-and-Download-Information.html#cmvd_topic_1

CDH安装包地址:http://archive.cloudera.com/cdh5/parcels/latest/,由于我们的操作系统为CentOS6.5,需要下载以下文件:

  • CDH-5.1.3-1.cdh5.1.3.p0.12-el6.parcel
  • CDH-5.1.3-1.cdh5.1.3.p0.12-el6.parcel.sha1
  • manifest.json

注意:与CDH4的不同,原来安装CDH4的时候还需要下载IMPALA、Cloudera Search(SOLR),CDH5中将他们包含在一起了,所以只需要下载一个CDH5的包就可以了。

准备工作:系统环境搭建

以下操作均用root用户操作。

1. 网络配置(所有节点)

vi /etc/sysconfig/network修改hostname:

NETWORKING=yes
HOSTNAME=n1

通过service network restart重启网络服务生效。

vi /etc/hosts,修改ip与主机名的对应关系

192.168.1.106   n1
192.168.1.107 n2
192.168.1.108 n3

注意:这里需要将每台机器的ip及主机名对应关系都写进去,本机的也要写进去,否则启动Agent的时候会提示hostname解析错误。

2.打通SSH,设置ssh无密码登陆(所有节点)

在主节点上执行ssh-keygen -t rsa一路回车,生成无密码的密钥对。

将公钥添加到认证文件中:cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys,并设置authorized_keys的访问权限:chmod 600 ~/.ssh/authorized_keys

scp文件到所有datenode节点:

scp ~/.ssh/authorized_keys root@n2:~/.ssh/

测试:在主节点上ssh n2,正常情况下,不需要密码就能直接登陆进去了。

3.安装Oracle的Java(所有节点)

CentOS,自带OpenJdk,不过运行CDH5需要使用Oracle的Jdk,需要Java 7的支持。

卸载自带的OpenJdk,使用rpm -qa | grep java查询java相关的包,使用rpm -e --nodeps 包名卸载之。

去Oracle的官网下载jdk的rpm安装包,并使用rpm -ivh 包名安装之。

由于是rpm包并不需要我们来配置环境变量,我们只需要配置一个全局的JAVA_HOME变量即可,执行命令:

echo "JAVA_HOME=/usr/java/latest/" >> /etc/environment

4.安装配置MySql(主节点)

通过yum install mysql-server安装mysql服务器。chkconfig mysqld on设置开机启动,并service mysqld start启动mysql服务,并根据提示设置root的初试密码:mysqladmin -u root password 'xxxx'

mysql -uroot -pxxxx进入mysql命令行,创建以下数据库:

#hive
create database hive DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
#activity monitor
create database amon DEFAULT CHARSET utf8 COLLATE utf8_general_ci;

设置root授权访问以上所有的数据库:

#授权root用户在主节点拥有所有数据库的访问权限
grant all privileges on *.* to 'root'@'n1' identified by 'xxxx' with grant option;
flush privileges;

官方MySql配置文档:http://www.cloudera.com/content/cloudera/en/documentation/cloudera-manager/v5-latest/Cloudera-Manager-Installation-Guide/cm5ig_mysql.html#cmig_topic_5_5

5.关闭防火墙和SELinux

注意: 需要在所有的节点上执行,因为涉及到的端口太多了,临时关闭防火墙是为了安装起来更方便,安装完毕后可以根据需要设置防火墙策略,保证集群安全。

关闭防火墙:

service iptables stop (临时关闭)
chkconfig iptables off (重启后生效)

关闭SELINUX(实际安装过程中发现没有关闭也是可以的,不知道会不会有问题,还需进一步进行验证):

setenforce 0 (临时生效)
修改 /etc/selinux/config 下的 SELINUX=disabled (重启后永久生效)

6.所有节点配置NTP服务

集群中所有主机必须保持时间同步,如果时间相差较大会引起各种问题。 具体思路如下:

master节点作为ntp服务器与外界对时中心同步时间,随后对所有datanode节点提供时间同步服务。

所有datanode节点以master节点为基础同步时间。

所有节点安装相关组件:yum install ntp。完成后,配置开机启动:chkconfig ntpd on,检查是否设置成功:chkconfig --list ntpd其中2-5为on状态就代表成功。

主节点配置

在配置之前,先使用ntpdate手动同步一下时间,免得本机与对时中心时间差距太大,使得ntpd不能正常同步。这里选用65.55.56.206作为对时中心,ntpdate -u 65.55.56.206

ntp服务只有一个配置文件,配置好了就OK。 这里只给出有用的配置,不需要的配置都用#注掉,这里就不在给出:

driftfile /var/lib/ntp/drift
restrict 127.0.0.1
restrict -6 ::1
restrict default nomodify notrap
server 65.55.56.206 prefer
includefile /etc/ntp/crypto/pw
keys /etc/ntp/keys

配置文件完成,保存退出,启动服务,执行如下命令:service ntpd start

检查是否成功,用ntpstat命令查看同步状态,出现以下状态代表启动成功:

synchronised to NTP server () at stratum 2
time correct to within 74 ms
polling server every 128 s

如果出现异常请等待几分钟,一般等待5-10分钟才能同步。

配置ntp客户端(所有datanode节点)

driftfile /var/lib/ntp/drift
restrict 127.0.0.1
restrict -6 ::1
restrict default kod nomodify notrap nopeer noquery
restrict -6 default kod nomodify notrap nopeer noquery
#这里是主节点的主机名或者ip
server n1
includefile /etc/ntp/crypto/pw
keys /etc/ntp/keys

ok保存退出,请求服务器前,请先使用ntpdate手动同步一下时间:ntpdate -u n1 (主节点ntp服务器)

这里可能出现同步失败的情况,请不要着急,一般是本地的ntp服务器还没有正常启动,一般需要等待5-10分钟才可以正常同步。启动服务:service ntpd start

因为是连接内网,这次启动等待的时间会比master节点快一些,但是也需要耐心等待一会儿。

正式开工

安装Cloudera Manager Server 和Agent

主节点解压安装

cloudera manager的目录默认位置在/opt下,解压:tar xzvf cloudera-manager*.tar.gz将解压后的cm-5.1.3和cloudera目录放到/opt目录下。

为Cloudera Manager 5建立数据库

首先需要去MySql的官网下载JDBC驱动,http://dev.mysql.com/downloads/connector/j/,解压后,找到mysql-connector-java-5.1.33-bin.jar,放到/opt/cm-5.1.3/share/cmf/lib/中。

在主节点初始化CM5的数据库:

/opt/cm-5.1.3/share/cmf/schema/scm_prepare_database.sh mysql cm -hlocalhost -uroot -pxxxx --scm-host localhost scm scm scm

Agent配置

修改/opt/cm-5.1.3/etc/cloudera-scm-agent/config.ini中的server_host为主节点的主机名。

同步Agent到其他节点

scp -r /opt/cm-5.1.3 root@n2:/opt/

在所有节点创建cloudera-scm用户

useradd --system --home=/opt/cm-5.1.3/run/cloudera-scm-server/ --no-create-home --shell=/bin/false --comment "Cloudera SCM User" cloudera-scm

准备Parcels,用以安装CDH5

将CHD5相关的Parcel包放到主节点的/opt/cloudera/parcel-repo/目录中(parcel-repo需要手动创建)。

相关的文件如下:

  • CDH-5.1.3-1.cdh5.1.3.p0.12-el6.parcel
  • CDH-5.1.3-1.cdh5.1.3.p0.12-el6.parcel.sha1
  • manifest.json

最后将CDH-5.1.3-1.cdh5.1.3.p0.12-el6.parcel.sha1,重命名为CDH-5.1.3-1.cdh5.1.3.p0.12-el6.parcel.sha,这点必须注意,否则,系统会重新下载CDH-5.1.3-1.cdh5.1.3.p0.12-el6.parcel文件。

相关启动脚本

通过/opt/cm-5.1.3/etc/init.d/cloudera-scm-server start启动服务端。

通过/opt/cm-5.1.3/etc/init.d/cloudera-scm-agent start启动Agent服务。

我们启动的其实是个service脚本,需要停止服务将以上的start参数改为stop就可以了,重启是restart。

CDH5的安装配置

Cloudera Manager Server和Agent都启动以后,就可以进行CDH5的安装配置了。

这时可以通过浏览器访问主节点的7180端口测试一下了(由于CM Server的启动需要花点时间,这里可能要等待一会才能访问),默认的用户名和密码均为admin:

可以看到,免费版本的CM5已经没有原来50个节点数量的限制了。

各个Agent节点正常启动后,可以在当前管理的主机列表中看到对应的节点。选择要安装的节点,点继续。

接下来,出现以下包名,说明本地Parcel包配置无误,直接点继续就可以了。

点击,继续,如果配置本地Parcel包无误,那么下图中的已下载,应该是瞬间就完成了,然后就是耐心等待分配过程就行了,大约10多分钟吧,取决于内网网速。

接下来是服务器检查,可能会遇到以下问题:

Cloudera 建议将 /proc/sys/vm/swappiness 设置为 0。当前设置为 60。使用 sysctl 命令在运行时更改该设置并编辑 /etc/sysctl.conf 以在重启后保存该设置。您可以继续进行安装,但可能会遇到问题,Cloudera Manager 报告您的主机由于交换运行状况不佳。以下主机受到影响:

通过echo 0 > /proc/sys/vm/swappiness即可解决。

接下来是选择安装服务:

服务配置,一般情况下保持默认就可以了(Cloudera Manager会根据机器的配置自动进行配置,如果需要特殊调整,自行进行设置就可以了):

接下来是数据库的设置,检查通过后就可以进行下一步的操作了:

下面是集群设置的审查页面,我这里都是保持默认配置的:

终于到安装各个服务的地方了,注意,这里安装Hive的时候可能会报错,因为我们使用了MySql作为hive的元数据存储,hive默认没有带mysql的驱动,通过以下命令拷贝一个就行了:

cp /opt/cm-5.1.3/share/cmf/lib/mysql-connector-java-5.1.33-bin.jar /opt/cloudera/parcels/CDH-5.1.3-1.cdh5.1.3.p0.12/lib/hive/lib/

服务的安装过程大约半小时内就可以完成:

安装完成后,就可以进入集群界面看一下集群的当前状况了。

这里可能会出现无法发出查询:对 Service Monitor 的请求超时的错误提示,如果各个组件安装没有问题,一般是因为服务器比较卡导致的,过一会刷新一下页面就好了:

测试

在集群的一台机器上执行以下模拟Pi的示例程序:

sudo -u hdfs hadoop jar /opt/cloudera/parcels/CDH/lib/hadoop-mapreduce/hadoop-mapreduce-examples.jar pi 10 100

执行过程需要花一定的时间,通过YARN的后台也可以看到MapReduce的执行状态:

MapReduce执行过程中终端的输出如下:

Number of Maps  = 10
Samples per Map = 100
Wrote input for Map #0
Wrote input for Map #1
Wrote input for Map #2
Wrote input for Map #3
Wrote input for Map #4
Wrote input for Map #5
Wrote input for Map #6
Wrote input for Map #7
Wrote input for Map #8
Wrote input for Map #9
Starting Job
14/10/13 01:15:34 INFO client.RMProxy: Connecting to ResourceManager at n1/192.168.1.161:8032
14/10/13 01:15:36 INFO input.FileInputFormat: Total input paths to process : 10
14/10/13 01:15:37 INFO mapreduce.JobSubmitter: number of splits:10
14/10/13 01:15:39 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1413132307582_0001
14/10/13 01:15:40 INFO impl.YarnClientImpl: Submitted application application_1413132307582_0001
14/10/13 01:15:40 INFO mapreduce.Job: The url to track the job: http://n1:8088/proxy/application_1413132307582_0001/
14/10/13 01:15:40 INFO mapreduce.Job: Running job: job_1413132307582_0001
14/10/13 01:17:13 INFO mapreduce.Job: Job job_1413132307582_0001 running in uber mode : false
14/10/13 01:17:13 INFO mapreduce.Job: map 0% reduce 0%
14/10/13 01:18:02 INFO mapreduce.Job: map 10% reduce 0%
14/10/13 01:18:25 INFO mapreduce.Job: map 20% reduce 0%
14/10/13 01:18:35 INFO mapreduce.Job: map 30% reduce 0%
14/10/13 01:18:45 INFO mapreduce.Job: map 40% reduce 0%
14/10/13 01:18:53 INFO mapreduce.Job: map 50% reduce 0%
14/10/13 01:19:01 INFO mapreduce.Job: map 60% reduce 0%
14/10/13 01:19:09 INFO mapreduce.Job: map 70% reduce 0%
14/10/13 01:19:17 INFO mapreduce.Job: map 80% reduce 0%
14/10/13 01:19:25 INFO mapreduce.Job: map 90% reduce 0%
14/10/13 01:19:33 INFO mapreduce.Job: map 100% reduce 0%
14/10/13 01:19:51 INFO mapreduce.Job: map 100% reduce 100%
14/10/13 01:19:53 INFO mapreduce.Job: Job job_1413132307582_0001 completed successfully
14/10/13 01:19:56 INFO mapreduce.Job: Counters: 49
File System Counters
FILE: Number of bytes read=91
FILE: Number of bytes written=1027765
FILE: Number of read operations=0
FILE: Number of large read operations=0
FILE: Number of write operations=0
HDFS: Number of bytes read=2560
HDFS: Number of bytes written=215
HDFS: Number of read operations=43
HDFS: Number of large read operations=0
HDFS: Number of write operations=3
Job Counters
Launched map tasks=10
Launched reduce tasks=1
Data-local map tasks=10
Total time spent by all maps in occupied slots (ms)=118215
Total time spent by all reduces in occupied slots (ms)=11894
Total time spent by all map tasks (ms)=118215
Total time spent by all reduce tasks (ms)=11894
Total vcore-seconds taken by all map tasks=118215
Total vcore-seconds taken by all reduce tasks=11894
Total megabyte-seconds taken by all map tasks=121052160
Total megabyte-seconds taken by all reduce tasks=12179456
Map-Reduce Framework
Map input records=10
Map output records=20
Map output bytes=180
Map output materialized bytes=340
Input split bytes=1380
Combine input records=0
Combine output records=0
Reduce input groups=2
Reduce shuffle bytes=340
Reduce input records=20
Reduce output records=0
Spilled Records=40
Shuffled Maps =10
Failed Shuffles=0
Merged Map outputs=10
GC time elapsed (ms)=1269
CPU time spent (ms)=9530
Physical memory (bytes) snapshot=3792773120
Virtual memory (bytes) snapshot=16157274112
Total committed heap usage (bytes)=2856624128
Shuffle Errors
BAD_ID=0
CONNECTION=0
IO_ERROR=0
WRONG_LENGTH=0
WRONG_MAP=0
WRONG_REDUCE=0
File Input Format Counters
Bytes Read=1180
File Output Format Counters
Bytes Written=97
Job Finished in 262.659 seconds
Estimated value of Pi is 3.14800000000000000000

检查Hue

首次登陆Hue会让设置一个初试的用户名和密码,设置好,登陆到后台,会做一次检查,一切正常后会提示:

到这里表明我们的集群可以使用了。

遇到的问题

Agent启动后,安装阶段“当前管理的主机”中显示的节点不全,每次刷新显示的都不一样。

Agent的错误日志表现如下:

[18/Nov/2014 21:12:56 +0000] 22681 MainThread agent ERROR Heartbeating to master:7182 failed.
Traceback (most recent call last):
File "/home/opt/cm-5.2.0/lib64/cmf/agent/src/cmf/agent.py", line 820, in send_heartbeat
response = self.requestor.request('heartbeat', dict(request=heartbeat))
File "/home/opt/cm-5.2.0/lib64/cmf/agent/build/env/lib/python2.6/site-packages/avro-1.6.3-py2.6.egg/avro/ipc.py", line 139, in request
return self.issue_request(call_request, message_name, request_datum)
File "/home/opt/cm-5.2.0/lib64/cmf/agent/build/env/lib/python2.6/site-packages/avro-1.6.3-py2.6.egg/avro/ipc.py", line 255, in issue_request
return self.read_call_response(message_name, buffer_decoder)
File "/home/opt/cm-5.2.0/lib64/cmf/agent/build/env/lib/python2.6/site-packages/avro-1.6.3-py2.6.egg/avro/ipc.py", line 235, in read_call_response
raise self.read_error(writers_schema, readers_schema, decoder)
File "/home/opt/cm-5.2.0/lib64/cmf/agent/build/env/lib/python2.6/site-packages/avro-1.6.3-py2.6.egg/avro/ipc.py", line 244, in read_error
return AvroRemoteException(datum_reader.read(decoder))
File "/home/opt/cm-5.2.0/lib64/cmf/agent/build/env/lib/python2.6/site-packages/avro-1.6.3-py2.6.egg/avro/io.py", line 444, in read
return self.read_data(self.writers_schema, self.readers_schema, decoder)
File "/home/opt/cm-5.2.0/lib64/cmf/agent/build/env/lib/python2.6/site-packages/avro-1.6.3-py2.6.egg/avro/io.py", line 448, in read_data
if not DatumReader.match_schemas(writers_schema, readers_schema):
File "/home/opt/cm-5.2.0/lib64/cmf/agent/build/env/lib/python2.6/site-packages/avro-1.6.3-py2.6.egg/avro/io.py", line 379, in match_schemas
w_type = writers_schema.type
AttributeError: 'NoneType' object has no attribute 'type'

这是由于在主节点上启动了Agent后,又将Agent scp到了其他节点上导致的,首次启动Agent,它会生成一个uuid,路径为:/opt/cm-5.1.3/lib/cloudera-scm-agent/uuid,这样的话每台机器上的Agent的uuid都是一样的了,就会出现紊乱的情况。

解决方案:

删除/opt/cm-5.1.3/lib/cloudera-scm-agent/目录下的所有文件。

清空主节点CM数据库。

最佳实践

在内网单独搭建yum的repo,然后CM的管理界面安装Agent,这样安装比较快,也不会出现什么问题。

如果手动添加添加Agent的话,需要注意启动之前删除相关的标示文件,否则Server端在数据库中记录Agent的信息,还得清理数据库中的信息,就麻烦了。

离线安装Cloudera Manager 5和CDH5(最新版5.1.3) 完全教程的更多相关文章

  1. 离线安装Cloudera Manager 5和CDH5(最新版5.9.3) 完全教程(六)CM的安装

    一.角色分配 Cloudera Manager Agent:向server端报告当前机器服务状态. Cloudera Manager Server:接受agent角色报告服务状态,以视图界面展现,方便 ...

  2. 离线安装Cloudera Manager 5和CDH5(最新版5.9.3) 完全教程(一)环境说明

    关于CDH和Cloudera Manager CDH (Cloudera's Distribution, including Apache Hadoop),是Hadoop众多分支中的一种,由Cloud ...

  3. 离线安装Cloudera Manager 5和CDH5(最新版5.9.3) 完全教程(七)界面安装

    一.安装过程 1.1 登录 1.2 接受许可协议 1.3 选择免费版本 1.4 选择下一步 1.5 选择当前管理的主机 1.6 选择使用Parcel安装,选择CDH版本,点击继续 1.7 等待安装 此 ...

  4. 离线安装Cloudera Manager 5和CDH5(最新版5.9.3) 完全教程(二)基础环境安装

    一.安装CentOS 6.5 x64 具体安装过程自行百度 1.1 修改IP地址 [root@master ~]# vi /etc/sysconfig/network DEVICE=eth0 TYPE ...

  5. 离线安装Cloudera Manager 5和CDH5(最新版5.9.3) 完全教程(四)数据库安装(单节点)

    一.卸载CentOS自带的MySQL 1.1 查看之前是否安装过mysql [root@master mysql]# rpm -qa|grep -i mysql mysql-libs--.el6.x8 ...

  6. 离线安装Cloudera Manager 5和CDH5(最新版5.9.3) 完全教程(五)数据库安装(双节点)

    一.方案选择 通过Lvs+keepalived+mysql(主主同步)实现数据库层面的高可用方案,需要两台服务器作为数据库提供业务数据的存储,应用服务器通过vip访问数据库,允许同一时间内一台数据库服 ...

  7. 离线安装Cloudera Manager 5和CDH5(最新版5.9.3) 完全教程(三)重新分配磁盘空间(可选)

    一.查看文件系统 [root@master ~]# df -h Filesystem Size Used Avail Use% Mounted on /dev/mapper/vg_master-lv_ ...

  8. 离线安装 Cloudera Manager 5 和 CDH5.10

    关于CDH和Cloudera Manager CDH (Cloudera's Distribution, including Apache Hadoop),是Hadoop众多分支中的一种,由Cloud ...

  9. 离线安装Cloudera Manager 5和CDH5

    关于CDH和Cloudera Manager CDH (Cloudera's Distribution, including Apache Hadoop),是Cloudera 完全开源的Hadoop  ...

随机推荐

  1. IOS第二天多线程-02一次性代码

    ********** #import "HMViewController.h" #import "HMImageDownloader.h" @interface ...

  2. EmguCV+Win7+Visual C# 2012 配置

    一.下载与安装OpenCV 安装包版本:OpenCV2.4.2 X86 下载地址:http://sourceforge.net/projects/opencvlibrary/files/opencv- ...

  3. NFS服务器搭建

    1. 安装nfs-kernel-server,然后编辑/etc/exports. /sambadata/nfsserver    10.0.0.0/255.255.255.0(fsid=0,all_s ...

  4. 基于php5.6 php.ini详解

    PHP中auto_prepend_file与auto_append_file的用法 第一种方法:在所有页面的顶部与底部都加入require语句.例如:?123require('header.php') ...

  5. ios-实现项目在开发、测试、正式环境快速部署

    快速部署:简单的来说,就是不用更改开发.测试.正式环境下的 url ,来实现在同一台测试手机快速部署三种项目状态. Bundle ID一样只会出现一个app:不一样会出现三个app: 具体步骤: 一. ...

  6. Any changes made by a writer will not be seen by other users of the database until the changes have been completed

    https://en.wikipedia.org/wiki/Multiversion_concurrency_control Multiversion concurrency control (MCC ...

  7. 转:EntityFramework查询--联合查询(Join,GroupJoin)

    首先我们先看一下Join public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(this ...

  8. GIS简单计算Helper类

    using System; using ESRI.ArcGIS.Client.Geometry; namespace GISProject.Extensions { /// <summary&g ...

  9. cat *.txt | grep '>' | wc -l

    find the line where the '>' located, and make a statistic.

  10. [原创]java WEB学习笔记98:Spring学习---Spring Bean配置及相关细节:如何在配置bean,Spring容器(BeanFactory,ApplicationContext),如何获取bean,属性赋值(属性注入,构造器注入),配置bean细节(字面值,包含特殊字符,引用bean,null值,集合属性list map propert),util 和p 命名空间

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...