mongodb分片集群搭建

1,801 阅读20分钟

mongodb分片集群搭建

前阵子心血来潮,学习如何搭建 mongodb 服务。

也借此次机会,将搭建流程,所遇问题做点小记录。

本次搭建均在 linux 服务器上进行。

如有错误,欢迎指出。

以下链接为本文借鉴之源,各位也可以去看看。(ps:写的比我好多了)

mongodb 3.4 集群搭建:分片+副本集(www.cnblogs.com/ityouknow/p…)


一、mongodb介绍

1) mongodb 特点

面向集合存储

数据结构自由

③ 支持动态查询

④ 支持索引

⑤ 可高效的二进制数据存储(可存大型对象,像视频等文件)

⑥ 文件格式为Bson(一种Json的扩展)

总结:就是一个很牛的No Sql数据库,也符合数据库系统的要求。

2) 单机劣势

① 读写并发度低

②无数据灾备。一旦挂了,使用的服务崩溃。数据无备份,服务器搞事情,数据全没了。比删库跑路还刺激,还不可控。

总结:总要给自己一点搭分片集群的理由

3) 保证高可用方式

复制集 or 副本集 (ps:二者是一样的东西)

分片集群,本文着重介绍分片集群,但文中也有涉及复制集部分。

一切请看下回分解 开玩笑开玩笑,先别走

4)分片组件介绍

集群主要组件有mongosconfig servershardreplica set

mongos 路由服务器:

集群请求入口,本质上是个请求分发中心,将对应请求转发到对应shard分片服务器上。常规会配置多个 mongos 路由服务器,确保高可用。

config server 配置服务器:

用于存储数据元信息(路由,分片)的配置。

mongos 路由服务器本身没有物理存储分片服务器和数据路由信息,只缓存在内存中。

而配置服务器则实际存储这部分信息,数据。

mongos 路由服务器启动时,就从 config server 配置服务器中加载该配置。

config server 配置服务器中任何配置信息发生变化,都会通知 mongos 更新状态。

(ps:个人认为这是分片集群核心)

shard 分片:

将库拆分,根据片键将数据拆分存储。

其基本思想:将集合分块,分散到若干片中,每个片只负责总数据的一部分,最终通过均衡器保证各分片数据量均衡。

replica set 副本集/复制集:

数据的冗余备份机制,配置子节点作为从服务器,用于存储数据副本,提高高可用。

Arbiter 仲裁者:

复制集中实例之一。使用最小的资源,且无硬件要求。应部署在与主从服务不同的服务器上。

其作用是确保该复制集中 primay 主服务器关闭时,可从各 secondary 从服务器中,选举出新的 primary 主服务器。

primary 主服务器 与 secondary 从服务器集合,也可自行选举新 primary 主服务器。

但当复制集中主从数量为偶数时,自行选举机制将会失效,此时就需要Arbiter仲裁者介入了。


二、环境准备

为了后续部署集群环境顺利,在这里就提前把所需的相关配置文件等准备好

1) 服务器列表

生产环境分三台服务器部署,本文贪图演示方便,只用了一台机器做伪集群 (ps:切记偷懒行为不可取)

2) 端口分配

1.4章节中提及到,分片集群的重要组件

这里提前把架构图端口分配等信息,准备好

shard 分片复制集两个,每个复制集以1 primary 主服务,1个 secondary 从服务,1个 Arbiter 仲裁者服务组成

config server 配置服务三个

mongos 路由服务三个

④ 架构图、端口分配(ps:略粗糙,请见怪莫怪,如果糊了可以来这看:sbimg.cn/image/2UMXk)

架构图

3) dbpath目录

用于存储 mongodb 实例数据,在配置文件中需指定数据目录。

各服务中,除了mongos路由服务外,都要进行物理存储,因此都要指定存储目录。

根据端口与职能进行分配(我本机 mongodb 目录为 /usr/local/mongodb):

# db 用于存储 分片 数据
# 复制集 rs1 端口为27017的数据目录
mkdir -p /usr/local/mongodb/data/db/rs1_27017
# 复制集 rs1 端口为27018的数据目录
mkdir -p /usr/local/mongodb/data/db/rs1_27018
# 复制集 rs1 端口为27019的数据目录
mkdir -p /usr/local/mongodb/data/db/rs1_27019
# 复制集 rs2 端口为27020的数据目录
mkdir -p /usr/local/mongodb/data/db/rs2_27020
# 复制集 rs2 端口为27021的数据目录
mkdir -p /usr/local/mongodb/data/db/rs2_27021
# 复制集 rs2 端口为27022的数据目录
mkdir -p /usr/local/mongodb/data/db/rs2_27022

# configs 用于存储 配置服务 数据
# 配置服务 27023 服务数据目录
mkdir -p /usr/local/mongodb/data/configs/27023
# 配置服务 27024 服务数据目录
mkdir -p /usr/local/mongodb/data/configs/27024
# 配置服务 27025 服务数据目录
mkdir -p /usr/local/mongodb/data/configs/27025

# mongos 路由服务 不需要物理存储数据,因此无须准备数据目录

本步骤已完成,建议用记事本记录下这几个目录信息,后续配置服务时需要用到

4) logpath目录

用于存储 mongodb 日志数据,在配置文件中需指定日志数据目录。

各服务都需配置。

根据端口与职能进行分配(mongodb目录为 /usr/local/mongodb ):

# db 用于存储 分片 日志数据
# 复制集 rs1 端口为27017的日志目录
mkdir -p /usr/local/mongodb/logs/db/rs1_27017
# 复制集 rs1 端口为27018的日志目录
mkdir -p /usr/local/mongodb/logs/db/rs1_27018
# 复制集 rs1 端口为27019的日志目录
mkdir -p /usr/local/mongodb/logs/db/rs1_27019
# 复制集 rs2 端口为27020的日志目录
mkdir -p /usr/local/mongodb/logs/db/rs2_27020
# 复制集 rs2 端口为27021的日志目录
mkdir -p /usr/local/mongodb/logs/db/rs2_27021
# 复制集 rs2 端口为27022的日志目录
mkdir -p /usr/local/mongodb/logs/db/rs2_27022

# configs 用于存储 配置服务 日志数据
# 配置服务 27023 服务日志目录
mkdir -p /usr/local/mongodb/logs/configs/27023
# 配置服务 27024 服务日志目录
mkdir -p /usr/local/mongodb/logs/configs/27024
# 配置服务 27025 服务日志目录
mkdir -p /usr/local/mongodb/logs/configs/27025

# router 用于存储 路由服务 日志数据
# 路由服务 27026 服务日志目录
mkdir -p /usr/local/mongodb/logs/router/27026
# 路由服务 27027 服务日志目录
mkdir -p /usr/local/mongodb/logs/router/27027
# 路由服务 27028 服务日志目录
mkdir -p /usr/local/mongodb/logs/router/27028

本步骤已完成,建议用记事本记录下这几个目录信息,后续配置服务时需要用到

5) keyFile文件

副本集成员间的内部认证机制所需文件。

开启 keyfile 身份验证后,副本集中的每个 mongod 实例都使用 keyfile 的内容作为共享密码,只有具有正确的秘钥文件的 mongod 实例或者 mongos 实例才可以连接到副本集。

切记:密钥文件的内容必须在6到1024个字符之间,并且在unix/linux系统中文件所有者必须有对文件至少有读的权限.

① 生成秘钥文件

写入随机的756位二进制码到 /usr/local/mongodb/etc/KeyFile.file 文件中(ps:文件不存在会自动创建)

openssl rand -base64 756 > /usr/local/mongodb/etc/KeyFile.file

② 修改秘钥文件权限

修改 KeyFile权限为只读

chmod 400 /usr/local/mongodb/etc/KeyFile.file

chmod指令介绍

Keyfiles是安全的最小格式,非常适合测试和开发环境。对于生产环境,推荐使用x.509 certificates

6) 服务配置文件

本处创建配置文件目录与文件,用作后续配置可直接使用

# 创建配置文件目录
mkdir -p /usr/local/mongodb/etc
# 进入配置目录
cd /usr/local/mongodb/etc

# 复制集 rs1: primary、secondary、arbiter配置文件
touch rs1_27017_primary.conf
touch rs1_27018_secondary.conf
touch rs1_27019_arbiter.conf

# 复制集 rs2: primary、secondary、arbiter配置文件
touch rs2_27020_primary.conf
touch rs2_27021_secondary.conf
touch rs2_27022_arbiter.conf

# 复制集 configs:config server 配置文件
touch configs_27023.conf
touch configs_27024.conf
touch configs_27025.conf

# mongos 路由服务 配置文件
touch router_27026.conf
touch router_27027.conf
touch router_27028.conf

7) 环境配置

建议把环境变量配置好,方便后续命令使用

① 修改 profile 文件

vi /etc/profile

PATH变量 添加 mongodbbin 目录路径

export MONGODB_HOME=/usr/local/mongodb
export PATH=$MONGODB_HOME/bin:$PATH

② 重新加载 profile 文件

source /etc/profile

③ 校验

# 查看mongod的指令提示列表,可正常调用,则表明配置生效
mongod --help

三、配置服务复制集搭建

根据服务启动顺序,先从配置服务开始搭建

1)配置文件解析

以下配置可作用于 configs_27023.conf、configs_27024.conf、configs_27025.conf,此处以 configs_27023.conf 为例

# 监听端口(27023,27024,27025),根据不同配置文件,更改该端口
port=27023
# 数据存储目录(27023,27024,27025),根据不同配置文件,更改该目录
dbpath = /usr/local/mongodb/data/configs/27023
# 日志存储文件地址(27023,27024,27025),根据不同配置文件,更改该目录
logpath = /usr/local/mongodb/logs/configs/27023/congigsrv.log
# 日志文件是否以日期划分(每日一个)
logappend=true
# 是否后台运行
fork=true
# 声明这是个配置服务集群
configsvr=true
# 复制集名称
replSet=configs

本处只提供27023配置样本,另外两个需自行修改

2) 启动

config server 配置服务实例和 mongod 分片实例的启动方式一致,使用 mongod 命令启动。

mongos 路由服务实例启动则用 mongos 命令进行。

该步骤,需要将三台 配置服务 都启动完成

# mongod -f 启动时,加载指定的配置文件
mongod -f /usr/local/mongodb/etc/configs_27023.conf
mongod -f /usr/local/mongodb/etc/configs_27024.conf
mongod -f /usr/local/mongodb/etc/configs_27025.conf

3)初始化复制集

① 登陆其中一台配置服务

mongo -port 27023

② 初始化 primary 服务

登录后键入:

use admin
rs.initiate()
rs.status()

③ 添加 secondary 服务

congfig server 复制集不需要 Arbiter 仲裁者,因此 27024 与 27025 都作为复制集中的从服务使用

# 添加 secondary 节点,此处ip未必填写为localhost,可参照rs.status()中name属性
rs.add('localhost:27024')
rs.add('localhost:27025')

(ps:配置服务复制集已搭建完毕)

四、分片服务器复制集搭建

1) 配置文件解析

复制集 rs1 配置,以27017为例(27018与27019自行配置)

# 以下配置已在config server介绍过了
port=27017
dbpath=/usr/local/mongodb/data/db/rs1_27017
logpath=/usr/local/mongodb/logs/db/rs1_27017/27017.log
logappend=true
fork=true
replSet=rs1

# 声明这是一个分片复制集
shardsvr = true

复制集 rs2 配置,以27020为例(27021与27022自行配置)

port=27020
dbpath=/usr/local/mongodb/data/db/rs2_27020
logpath=/usr/local/mongodb/logs/db/rs2_27020/27020.log
logappend=true
fork=true
replSet=rs2

# 声明这是一个分片复制集
shardsvr = true

2) 启动

本处以 rs1 复制集搭建为案例,rs2复制集自行搭建

mongod -f /usr/local/mongodb/etc/rs1_27017_primary.conf
mongod -f /usr/local/mongodb/etc/rs1_27018_secondary.conf
mongod -f /usr/local/mongodb/etc/rs1_27019_arbiter.conf

3) 初始化复制集

① 登陆其中一台配置服务

mongo -port 27017

② 初始化 primary 服务

登录后键入:

use admin
rs.initiate()
rs.status()

③ 添加 secondary 服务

# 添加 secondary 节点,此处ip未必填写为localhost,可参照rs.status()中name属性
rs.add('localhost:27018')

④ 添加 Arbiter 服务

# 添加 arbiter 节点
rs.addArb('localhost:27019')

(ps:shard 服务复制集已搭建完毕)

五、路由服务搭建

1) 配置文件解析

以27026为例

# 路由服务没有物理存储数据信息,因此无须配置dbpath
logpath = /usr/local/mongodb/logs/router/27026/mongos.log
logappend = true
port = 27026
#bind_ip=0.0.0.0 开放任意访问,分片集群入口为路由服务,要想外网可以访问该端口,此处必须放开注释,或者指定对应ip
fork = true

# configs 为 配置服务的复制集名称,必须一一对应
configdb = configs/localhost:27025,localhost:27024,localhost:27023

2) 启动

以27026为例

mongos -f /usr/local/mongodb/router_27026.conf
mongos -f /usr/local/mongodb/router_27027.conf
mongos -f /usr/local/mongodb/router_27028.conf

3) 串联分片服务

配置中,已将路由服务与配置服务绑定,但路由与分片服务未进行绑定

① 登录其中一台路由服务,以27026为例

mongo -port 27026

② 串联分片

sh.addShard("rs1/localhost:27017,localhost:27018,localhost:27019")
sh.addShard("rs2/localhost:27020,localhost:27021,localhost:27022")
# 查看集群状态
sh.status()

搭建完成啦,分片集群搭建基本完成,大家可以开始愉快的造bug了

完成此步后,分片集群基本搭建完毕,以下步骤是开启鉴权操作。

感兴趣的朋友可以继续观看呦

六、配置用户

目前集群在 bind_ip 配置允许的ip内,谁都可以访问。

这样显然是不合理的,应该限制对应用户才可使用,且每个用户可操作权限也不同。

1)连接路由

连接至其中一台路由,进行用户配置。

创建用户后,该数据会通过 config server 配置服务进行数据共享。

mongo -port 27026

2)创建用户

① 使用 admin 库。切记:创建用户时,一定要连接 admin 库,否则新用户会被新增至 test 库中!!!。

use admin

② 创建超级用户(黑帮老大,任我行)

db.createUser({
'user':'xxxx',
'pwd':'xxx',
'roles':[{'role':'root','db':'admin'}]
})

③ 分析命令

此处借鉴:Mongodb设置用户权限(整理版)

user:用户名

pwd:密码

roles:指定用户的角色,可以用一个空数组给新用户设定空角色;

db:指定该用户的数据库,admin 是用于权限控制的数据库

roles 字段,可以指定内置角色和用户定义的角色。role 里的角色可以选

Built-In Roles(内置角色):

1.数据库用户角色:read、readWrite

2.数据库管理角色:dbAdmin、dbOwner、vuserAdmin

3.集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager

4.备份恢复角色:backup、restore

5.所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase

6.超级用户角色:root

7.内部角色:__system

// 这里还有几个角色间接或直接提供了系统超级用户的访问(dbOwner 、userAdmin、userAdminAnyDatabase

具体角色的功能:

Read:允许用户读取指定数据库

readWrite:允许用户读写指定数据库

dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问 system.profile

userAdmin:允许用户向 system.users 集合写入,可以找指定数据库里创建、删除和管理用户

clusterAdmin:只在 admin 数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。

readAnyDatabase:只在 admin 数据库中可用,赋予用户所有数据库的读权限

readWriteAnyDatabase:只在 admin 数据库中可用,赋予用户所有数据库的读写权限

userAdminAnyDatabase:只在 admin 数据库中可用,赋予用户所有数据库的 userAdmin 权限

dbAdminAnyDatabase:只在 admin 数据库中可用,赋予用户所有数据库的 dbAdmin 权限。

root:只在 admin 数据库中可用。超级账号,超级权限

④ 检验用户

show users

# 创建成功则返回如下信息.
# 如果_id 显示为 test.root,则说明创建用户时,未选中admin库。
{
"_id" : "admin.root",
"userId" : UUID("05a5a272-ea28-438b-b4fc-f81e48c1c968"),
"user" : "root",
"db" : "admin",
"roles" : [
	{
		"role" : "root",
		"db" : "admin"
	}
]
}

七、开启鉴权

1) 关闭所有服务

① 方案一:逐台关闭1(该方案中,路由服务/配置服务/分片服务都可用)

# 1.登录服务
mongo -port 27017

# 2.选中 admin
use admin

# 3.登录账号,此步骤在开启鉴权前可省略
db.auth('user','pwd')

# 4.关闭服务
db.shutdownServer()

该方案需要逐台进入关闭,略麻烦

② 方案二:逐台关闭2(该方案中,只适用于配置服务/分片服务)

# 1.指定对应配置文件关闭对应服务,其原理是关闭对dbpath的使用
mongod --shutdown -f /usr/local/mongodb/etc/rs1_27017_primary.conf\

# 2.路由服务要用 mongos 命令,但 mongos 只是一个路由节点,没有物理存储结构,只用内存进行部分数据映射。
# 因此路由服务也无需配置 dbpath,也就没提供 --shutdown 命令

③ 方案三:全部关闭

# 关闭所有路由节点
killall mongos
# 关闭所有mongod实例
killall mongod

mongod 实例不建议使用此方式

非正常关闭时,dbpath 中会有 lock 文件遗留,有几率导致无法重新启动。所以 mongod 实例不建议使用这种方式

mongos 建议用这种方式,方便且快捷。也不用担心 dbpathlock 文件导致的不能启动问题。( mongos 没有dbpath)

④ 方案四:取各种方案的优点

配置服务/分片服务使用方案二关闭。

路由服务使用方案三关闭。

# 方案二
mongod --shutdown -f rs1_27017_primary.conf
mongod --shutdown -f rs1_27018_secondary.conf
mongod --shutdown -f rs1_27019_arbiter.conf
mongod --shutdown -f rs2_27020_primary.conf
mongod --shutdown -f rs2_27021_secondary.conf
mongod --shutdown -f rs2_27022_arbiter.conf
mongod --shutdown -f configs_27023.conf
mongod --shutdown -f configs_27024.conf
mongod --shutdown -f configs_27025.conf
# 方案三
killall mongos

2) 更改配置文件

官方推荐x509证书(但我还不会)

集群中妹妹一个实例,彼此连接的时候,都校验对方使用的证书是否相同

shardconfig server 的所有配置文件中添加

# 开启用户鉴权,需要登录对应用户,才可使用用户角色允许的操作。(比如我们新建的超级用户,拥有全部权限)
auth=true
# 副本集成员间的 内部认证机制 所需文件,指向2.5章节中准备的验证文件。
# 只用拥有相同秘钥的 mongod 或 mongos 实例可连接复制集
keyFile=/usr/local/mongodb/etc/KeyFile.file

mongos 路由服务配置文件中添加

keyFile=/usr/local/mongodb/etc/KeyFile.file
# 如果需要允许外网访问,记住加上 bind_ip=0.0.0.0 配置

八、重启集群

1)启动顺序

启动顺序应该是: config server 配置服务 ——》 shard 分片服务 ——》 mongos 路由服务

2)重启

使用命令启动所有服务。(每次启动都要输入这么多人,有点繁琐。文章结尾将部分快捷操作分享给大家,有兴趣的朋友可以看看)

重启时,一定要先启动配置服务。

如果还有异常,一定原因是服务非正常关闭,dbpath中有lock文件存在,启动时检索到该文件,就抛出异常。

大家遇到问题可以,百度编程

因为遇到的异常情况很多,本处就不贴优秀范文了。

mongod -f configs_27023.conf
mongod -f configs_27024.conf
mongod -f configs_27025.conf
mongod -f rs1_27017_primary.conf
mongod -f rs1_27018_secondary.conf
mongod -f rs1_27019_arbiter.conf
mongod -f rs2_27020_primary.conf
mongod -f rs2_27021_secondary.conf
mongod -f rs2_27022_arbiter.conf
mongos -f router_27026.conf
mongos -f router_27027.conf
mongos -f router_27028.conf

3)测试集群

# 登录路由
mongo -port 27026
# 选择admin库
use admin
# 提示需要登录
show users
# 提示登录成功,则集群搭建完毕
db.auth('user','pwd')

完成以上操作,一个基本集群就搭建完成了。

大家可以继续深入了解 mongodb 的特性,也祝愿朋友们造Bug开心。

九、小分享

在实验搭建的途中,发现部分操作有点繁琐。

决定写这篇文章时,就想着能不能把一些小Tips分享给大家。

希望对大家有帮助。

1)mongodb 启动、关闭命令

前几章中,有看到过关闭和启动 mongodb 示例的情况。

当时在想这也太麻烦了。

我的做法是,把相应的指令放在sh文件中,用时直接调用即可。

① sh命令创建

我的sh文件,也是放在etc目录下

# 创建 sh 脚本文件
touch xxxx.sh
# 编辑 sh 文件
vi xxx.sh

# sh文件开头写入下面这行文本,标记是bash命令
#!/bin/bash
# 写入想要的linux 指令
# esc :wq 保存退出

# 声明该文件有可执行权限
chmod 700 xxxx.sh

# 之后运行该sh文件直接通过
sh xxxx.sh
# 或者
./xxxx.sh

② 关闭脚本

# 创建 sh 脚本文件
touch shutdownAllServer.sh
# 编辑 sh 文件
vi shutdownAllServer.sh

# sh文件开头写入下面这行文本,标记是bash命令
#!/bin/bash
mongod -f configs_27023.conf
mongod -f configs_27024.conf
mongod -f configs_27025.conf
mongod -f rs1_27017_primary.conf
mongod -f rs1_27018_secondary.conf
mongod -f rs1_27019_arbiter.conf
mongod -f rs2_27020_primary.conf
mongod -f rs2_27021_secondary.conf
mongod -f rs2_27022_arbiter.conf
mongos -f router_27026.conf
mongos -f router_27027.conf
mongos -f router_27028.conf
# esc :wq 保存退出

# 声明该文件有可执行权限
chmod 700 shutdownAllServer.sh

③ 启动脚本

# 创建 sh 脚本文件
touch startAllServer.sh
# 编辑 sh 文件
vi startAllServer.sh

# sh文件开头写入下面这行文本,标记是bash命令
#!/bin/bash
mongod -f configs_27023.conf
mongod -f configs_27024.conf
mongod -f configs_27025.conf
mongod -f rs1_27017_primary.conf
mongod -f rs1_27018_secondary.conf
mongod -f rs1_27019_arbiter.conf
mongod -f rs2_27020_primary.conf
mongod -f rs2_27021_secondary.conf
mongod -f rs2_27022_arbiter.conf
mongos -f router_27026.conf
mongos -f router_27027.conf
mongos -f router_27028.conf

# 声明该文件有可执行权限
chmod 700 startAllServer.sh

本处请注意,该脚本只考虑了所有服务在一台服务器上的情况。

因此,应该在有 mongodb 实例的服务器都应创建一个对应的sh文件。

本处分享的未必是最优方案,大家有好的建议,欢迎教导。

2)目录跳转快捷方式

由于我使用 Xshell 连接我的阿里云服务器。

会有偶尔断开的情况,每次重新登录上服务器,都要键入层层的目录。

程序员嘛,就是为了更快捷的生活。而提供偷懒的更优方案。

本处分享的是如何创建 “目录的快捷方式”。

① 新增 sh 文件

# 进入用户家目录
cd 
# 创建sh文件
touch intoMongodb.sh
# 键入sh内容
vi intoMongodb.sh
# sh内容
#!/bin/bash
cd /usr/local/mongodb/etc
# 保存

② 试验

./intoMongodb.sh

键入命令后,发现目录压根没变化。

这时是不是想着,这人不是在坑我吧。

先别急,有了bug就要耐心修复。

③ 分析原因

sh 文件中的 linux 命令,的的确确是执行。

为什么没有效果呢?

这是因为,sh 命令是一个后台操作。相当于起了个线程,跳转到 mongodb 目录。

等线程结束后,并没有影响主线程的状态

④ 优化方案

# 更改intoMongodb文件,这一步仅仅是想要调用美观,没有.sh的小尾巴
mv intoMongodb.sh intoMongodb
# 使用 .或者source调用,指明用父bash来执行
. intoMongodb
source intoMongodb
# 目录发生更改

以上就是文章的全部内容。

原本想要拆开几篇来发的,但感觉分开又不成体系。所以篇幅略长,大家多多包涵。

这是我首次写文,有很多没考虑的地方。大家多多指导。

特别是知识点,文章中知识点/理论可能和大家的有出入,或不对。欢迎大家指出。

感谢各平台中,技术分享的各位朋友。本文编写时,内容借鉴处有很多,有许多重复处,大家海涵(也可联系,进行文章修改或删除)。

也感谢大家抽空看这篇文章。

最后,祝大家每日欢乐写bug,技术水平日益提升。