mongodb 是最常用的 nosql 数据库,在数据库排名中已经上升到了前六。这篇文章介绍如何搭建高可用的 mongodb(分片 + 副本)集群。
在搭建集群之前,需要首先了解几个概念:路由,分片、副本集、配置服务器等。
相关概念
先来看一张图:
从图中可以看到有四个组件:mongos、config server、shard、replica set。
mongos,数据库集群请求的入口,所有的请求都通过 mongos 进行协调,不需要在应用程序添加一个路由选择器,mongos 自己就是一个请求分发中心,它负责把对应的数据请求请求转发到对应的 shard 服务器上。在生产环境通常有多 mongos 作为请求的入口,防止其中一个挂掉所有的 mongodb 请求都没有办法操作。
config server,顾名思义为配置服务器,存储所有数据库元信息(路由、分片)的配置。mongos 本身没有物理存储分片服务器和数据路由信息,只是缓存在内存里,配置服务器则实际存储这些数据。mongos 第一次启动或者关掉重启就会从 config server 加载配置信息,以后如果配置服务器信息变化会通知到所有的 mongos 更新自己的状态,这样 mongos 就能继续准确路由。在生产环境通常有多个 config server 配置服务器,因为它存储了分片路由的元数据,防止数据丢失!
shard,分片(sharding)是指将数据库拆分,将其分散在不同的机器上的过程。将数据分散到不同的机器上,不需要功能强大的服务器就可以存储更多的数据和处理更大的负载。基本思想就是将集合切成小块,这些块分散到若干片里,每个片只负责总数据的一部分,最后通过一个均衡器来对各个分片进行均衡(数据迁移)。
replica set,中文翻译副本集,其实就是 shard 的备份,防止 shard 挂掉之后数据丢失。复制提供了数据的冗余备份,并在多个服务器上存储数据副本,提高了数据的可用性, 并可以保证数据的安全性。
仲裁者(Arbiter),是复制集中的一个 MongoDB 实例,它并不保存数据。仲裁节点使用最小的资源并且不要求硬件设备,不能将 Arbiter 部署在同一个数据集节点中,可以部署在其他应用服务器或者监视服务器中,也可部署在单独的虚拟机中。为了确保复制集中有奇数的投票成员(包括 primary),需要添加仲裁节点做为投票,否则 primary 不能运行时不会自动切换 primary。
简单了解之后,我们可以这样总结一下,应用请求 mongos 来操作 mongodb 的增删改查,配置服务器存储数据库元信息,并且和 mongos 做同步,数据最终存入在 shard(分片)上,为了防止数据丢失同步在副本集中存储了一份,仲裁在数据存储到分片的时候决定存储到哪个节点。
环境准备
- 系统系统 centos7.4
- 三台服务器:192.168.31.229/230/150
- 安装包: mongodb-linux-x86_64-3.4.6.tgz
服务器规划
服务器 229 | 服务器 230 | 服务器 150 |
---|---|---|
mongos | mongos | mongos |
config server | config server | config server |
shard server1 主节点 | shard server1 副节点 | shard server1 仲裁 |
shard server2 仲裁 | shard server2 主节点 | shard server2 副节点 |
shard server3 副节点 | shard server3 仲裁 | shard server3 主节点 |
端口分配:
mongos:20000 config:21000 shard1:27001 shard2:27002 shard3:27003
集群搭建
1. 安装 mongodb
下载地址:https://www.mongodb.org/dl/linux/x86_64 下载:wget http://downloads.mongodb.org/linux/mongodb-linux-x86_64-latest.tgz #解压 tar -xzvf mongodb-linux-x86_64-latest.tgz -C /usr/local/ #改名 mv mongodb-linux-x86_64-3.4.6-289-gf1bb8e1389
分别在每台机器建立 conf、mongos、config、shard1、shard2、shard3 六个目录,因为 mongos 不存储数据,只需要建立日志文件目录即可。
mkdir -p /usr/local/mongodb/conf mkdir -p /usr/local/mongodb/mongos/log mkdir -p /usr/local/mongodb/config/data mkdir -p /usr/local/mongodb/config/log mkdir -p /usr/local/mongodb/shard1/data mkdir -p /usr/local/mongodb/shard1/log mkdir -p /usr/local/mongodb/shard2/data mkdir -p /usr/local/mongodb/shard2/log mkdir -p /usr/local/mongodb/shard3/data mkdir -p /usr/local/mongodb/shard3/log
配置环境变量
vim /etc/profile # 内容 export MONGODB_HOME=/usr/local/mongodb export PATH=$MONGODB_HOME/bin:$PATH # 使立即生效 source /etc/profile
增加用户
[root@mongodb1 local]# useradd mongo [root@mongodb1 local]# passwd mongo 更改用户 mongo 的密码 。 新的 密码:123456 无效的密码: 密码少于 8 个字符 重新输入新的 密码:123456 passwd:所有的身份验证令牌已经成功更新。 [root@mongodb1 local]# [root@mongodb1 local]# chown -R mongo:mongo mongodb/ [root@mongodb1 local]# su mongo
- config server 配置服务器
mongodb3.4 以后要求配置服务器也创建副本集,不然集群搭建不成功。
添加配置文件
vim /usr/local/mongodb/conf/config.conf ## 配置文件内容 pidfilepath = /usr/local/mongodb/config/log/configsrv.pid dbpath = /usr/local/mongodb/config/data logpath = /usr/local/mongodb/config/log/congigsrv.log logappend = true bind_ip = 0.0.0.0 port = 21000 fork = true #declare this is a config db of a cluster; configsvr = true #副本集名称 replSet=configs #设置最大连接数 maxConns=20000
启动三台服务器的 config server
mongod -f /usr/local/mongodb/conf/config.conf
登录任意一台配置服务器,初始化配置副本集
#连接 mongo --port 21000
遇到问题:
2018-03-31T23:55:17.523+0800 I CONTROL [initandlisten] 2018-03-31T23:55:17.523+0800 I CONTROL [initandlisten] ** NOTE: This is a development version (3.7.3-289-gf1bb8e1389) of MongoDB. 2018-03-31T23:55:17.523+0800 I CONTROL [initandlisten] ** Not recommended for production. 2018-03-31T23:55:17.523+0800 I CONTROL [initandlisten] 2018-03-31T23:55:17.523+0800 I CONTROL [initandlisten] ** WARNING: Access control is not enabled for the database. 2018-03-31T23:55:17.523+0800 I CONTROL [initandlisten] ** Read and write access to data and configuration is unrestricted. 2018-03-31T23:55:17.524+0800 I CONTROL [initandlisten] 2018-03-31T23:55:17.524+0800 I CONTROL [initandlisten] 2018-03-31T23:55:17.524+0800 I CONTROL [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'. 2018-03-31T23:55:17.524+0800 I CONTROL [initandlisten] ** We suggest setting it to 'never' 2018-03-31T23:55:17.524+0800 I CONTROL [initandlisten] 2018-03-31T23:55:17.524+0800 I CONTROL [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/defrag is 'always'. 2018-03-31T23:55:17.524+0800 I CONTROL [initandlisten] ** We suggest setting it to 'never' 2018-03-31T23:55:17.524+0800 I CONTROL [initandlisten] 2018-03-31T23:55:17.524+0800 I CONTROL [initandlisten] ** WARNING: soft rlimits too low. rlimits set to 4096 processes, 204800 files. Number of processes should be at least 102400 : 0.5 times number of files. 2018-03-31T23:55:17.524+0800 I CONTROL [initandlisten]
解决办法:
vim /etc/rc.d/rc.local if test -f /sys/kernel/mm/transparent_hugepage/enabled; then echo never > /sys/kernel/mm/transparent_hugepage/enabled fi if test -f /sys/kernel/mm/transparent_hugepage/defrag; then echo never > /sys/kernel/mm/transparent_hugepage/defrag fi chmod +x /etc/rc.d/rc.local vim /etc/security/limits.d/20-nproc.conf
vim /etc/security/limits.conf
#连接 mongo --port 21000 #config变量 config = { _id : "configs", members : [ {_id : 0, host : "192.168.31.229:21000" }, {_id : 1, host : "192.168.31.230:21000" }, {_id : 2, host : "192.168.31.150:21000" } ] } #初始化副本集 rs.initiate(config)
其中,”_id” : “configs”应与配置文件中配置的 replicaction.replSetName 一致,”members” 中的 “host” 为三个节点的 ip 和 port
- 配置分片副本集(三台机器)
设置第一个分片副本集
配置文件
vim /usr/local/mongodb/conf/shard1.conf #配置文件内容 #——————————————– pidfilepath = /usr/local/mongodb/shard1/log/shard1.pid dbpath = /usr/local/mongodb/shard1/data logpath = /usr/local/mongodb/shard1/log/shard1.log logappend = true bind_ip = 0.0.0.0 port = 27001 fork = true #副本集名称 replSet=shard1 #declare this is a shard db of a cluster; shardsvr = true #设置最大连接数 maxConns=20000
启动三台服务器的 shard1 server
mongod -f /usr/local/mongodb/conf/shard1.conf
登陆任意一台服务器,初始化副本集
mongo --port 27001 #使用admin数据库 use admin #定义副本集配置,第三个节点的 "arbiterOnly":true 代表其为仲裁节点。 config = { _id : "shard1", members : [ {_id : 0, host : "192.168.31.229:27001" }, {_id : 1, host : "192.168.31.230:27001" }, {_id : 2, host : "192.168.31.150:27001",arbiterOnly:true } ] } #初始化副本集配置 rs.initiate(config);
设置第二个分片副本集
配置文件
vim /usr/local/mongodb/conf/shard2.conf #配置文件内容 #——————————————– pidfilepath = /usr/local/mongodb/shard2/log/shard2.pid dbpath = /usr/local/mongodb/shard2/data logpath = /usr/local/mongodb/shard2/log/shard2.log logappend = true bind_ip = 0.0.0.0 port = 27002 fork = true #副本集名称 replSet=shard2 #declare this is a shard db of a cluster; shardsvr = true #设置最大连接数 maxConns=20000
启动三台服务器的 shard2 server
mongod -f /usr/local/mongodb/conf/shard2.conf
登陆任意一台服务器,初始化副本集
mongo --port 27002 #使用admin数据库 use admin #定义副本集配置 config = { _id : "shard2", members : [ {_id : 0, host : "192.168.31.229:27002",arbiterOnly:true }, {_id : 1, host : "192.168.31.230:27002" }, {_id : 2, host : "192.168.31.150:27002" } ] } #初始化副本集配置 rs.initiate(config);
设置第三个分片副本集
配置文件
vim /usr/local/mongodb/conf/shard3.conf #配置文件内容 #——————————————– pidfilepath = /usr/local/mongodb/shard3/log/shard3.pid dbpath = /usr/local/mongodb/shard3/data logpath = /usr/local/mongodb/shard3/log/shard3.log logappend = true bind_ip = 0.0.0.0 port = 27003 fork = true #副本集名称 replSet=shard3 #declare this is a shard db of a cluster; shardsvr = true #设置最大连接数 maxConns=20000
启动三台服务器的 shard3 server
mongod -f /usr/local/mongodb/conf/shard3.conf
登陆任意一台服务器,初始化副本集
mongo --port 27003 #使用admin数据库 use admin #定义副本集配置 config = { _id : "shard3", members : [ {_id : 0, host : "192.168.31.229:27003" }, {_id : 1, host : "192.168.31.230:27003" , arbiterOnly: true}, {_id : 2, host : "192.168.31.150:27003" } ] } #初始化副本集配置 rs.initiate(config);
- 配置路由服务器 mongos
先启动配置服务器和分片服务器,后启动路由实例:(三台机器)
vim /usr/local/mongodb/conf/mongos.conf #内容 pidfilepath = /usr/local/mongodb/mongos/log/mongos.pid logpath = /usr/local/mongodb/mongos/log/mongos.log logappend = true bind_ip = 0.0.0.0 port = 20000 fork = true #监听的配置服务器,只能有1个或者3个 configs为配置服务器的副本集名字 configdb = configs/192.168.3.229:21000,192.168.31.230:21000,192.168.31.150:21000 #设置最大连接数 maxConns=20000
启动三台服务器的 mongos server
mongos -f /usr/local/mongodb/conf/mongos.conf
启用分片
目前搭建了 mongodb 配置服务器、路由服务器,各个分片服务器,不过应用程序连接到 mongos 路由服务器并不能使用分片机制,还需要在程序里设置分片配置,让分片生效。
登陆任意一台 mongos
mongo --port 20000 #使用admin数据库 use admin #串联路由服务器与分配副本集 sh.addShard("shard1/192.168.31.229:27001,192.168.31.230:27001,192.168.31.150:27001") sh.addShard("shard2/192.168.31.229:27002,192.168.31.230:27002,192.168.31.150:27002") sh.addShard("shard3/192.168.31.229:27003,192.168.31.230:27003,192.168.31.150:27003") #查看集群状态 sh.status()
测试
目前配置服务、路由服务、分片服务、副本集服务都已经串联起来了,但我们的目的是希望插入数据,数据能够自动分片。连接在 mongos 上,准备让指定的数据库、指定的集合分片生效。
#指定testdb分片生效 db.runCommand( { enablesharding :"testdb"}); #指定数据库里需要分片的集合和片键 db.runCommand( { shardcollection : "testdb.table1",key : {id: 1} } )
我们设置 testdb 的 table1 表需要分片,根据 id 自动分片到 shard1 ,shard2,shard3 上面去。要这样设置是因为不是所有 mongodb 的数据库和表 都需要分片!
测试分片配置结果
mongo 127.0.0.1:20000 #使用testdb use testdb; #插入测试数据 for (var i = 1; i <= 100000; i++)db.table1.save({id:i,"test1":"testval1"}); #查看分片情况如下,部分无关信息省掉了 db.table1.stats(); { "sharded" : true, "ns" : "testdb.table1", "count" : 100000, "numExtents" : 13, "size" : 5600000, "storageSize" : 22372352, "totalIndexSize" : 6213760, "indexSizes" : { "_id_" : 3335808, "id_1" : 2877952 }, "avgObjSize" : 56, "nindexes" : 2, "nchunks" : 3, "shards" : { "shard1" : { "ns" : "testdb.table1", "count" : 42183, "size" : 0, ... "ok" : 1 }, "shard2" : { "ns" : "testdb.table1", "count" : 38937, "size" : 2180472, ... "ok" : 1 }, "shard3" : { "ns" : "testdb.table1", "count" :18880, "size" : 3419528, ... "ok" : 1 } }, "ok" : 1 }
可以看到数据分到 3 个分片,各自分片数量为: shard1 “count” : 42183,shard2 “count” : 38937,shard3 “count” : 18880。已经成功了!
后期运维
启动关闭
mongodb 的启动顺序是,先启动配置服务器,在启动分片,最后启动 mongos.
mongod -f /usr/local/mongodb/conf/config.conf mongod -f /usr/local/mongodb/conf/shard1.conf mongod -f /usr/local/mongodb/conf/shard2.conf mongod -f /usr/local/mongodb/conf/shard3.conf mongos -f /usr/local/mongodb/conf/mongos.conf
关闭时,直接 killall 杀掉所有进程
killall mongod killall mongos
账号密码访问集群
对副本集执行访问控制需要配置两个方面
-
副本集和共享集群的各个节点成员之间使用内部身份验证,可以使用密钥文件或 x.509 证书。密钥文件比较简单,本文介绍的也是使用密钥文件,官方推荐如果是测试环境可以使用密钥文件,但是正是环境,官方推荐 x.509 证书。原理就是,集群中每一个实例彼此连接的时候都检验彼此使用的证书的内容是否相同。只有证书相同的实例彼此才可以访问
-
使用客户端连接到 mongodb 集群时,开启访问授权。对于集群外部的访问。如通过可视化客户端,或者通过代码连接的时候,需要开启授权。
下面开始详细说明:
1. 生成密钥文件。
1.1. 在 keyfile 身份验证中,副本集中的每个 mongod 实例都使用 keyfile 的内容作为共享密码,只有具有正确密钥文件的 mongod 或者 mongos 实例可以连接到副本集。密钥文件的内容必须在 6 到 1024 个字符之间,并且在 unix/linux 系统中文件所有者必须有对文件至少有读的权限。
1.2. 可以用任何方式生成密钥文件例如:
openssl rand -base64 756 > /usr/local/mongodb/conf/keyFile.file chmod 400 /usr/local/mongodb/conf/keyFile.file
第一条命令是生成密钥文件,第二条命令是使用 chmod 更改文件权限,为文件所有者提供读权限.
2. 将密钥复制到集群中的每台机器(229,230,150)的指定位置
scp -r /usr/local/mongodb/conf/keyFile.file root@192.168.31.230:/usr/local/mongodb/conf/ 修改文件所有者 chown -R mongo:mongo keyFile.file chmod 600 /usr/local/mongodb/conf/keyFile.file
2.1. 一定要保证密钥文件一致。文件位置随便。但是为了方便查找,建议每台机器都放到一个固定的位置。我的配置文件都放在/usr/local/mongodb/conf/keyFile.file
3. 预先创建好一个管理员账号和密码然后将集群中的所有 mongod 和 mongos 全部关闭
账号可以在集群认开启认证以后添加。但是那时候添加比较谨慎。只能添加一次,如果忘记了就无法再连接到集群。建议在没开启集群认证的时候先添加好管理员用户名和密码然后再开启认证再重启
连接任意一台机器的 mongos
mongo --port 20000
添加用户
use admin //注意一定要使用admin数据库 db.createUser( { user:"admin", pwd:"123456", roles:[{role:"root",db:"admin"}] } ) db.createUser( { "user" : "mgr", "pwd": "mgr", "roles" : [ { role: "userAdminAnyDatabase", db: "admin" }, { role: "dbAdminAnyDatabase", db: "admin" } ] }) db.createUser({ user: 'root', pwd: 'root', roles: [{ "role": "root", "db": "admin" }] }); db.updateUser('zzwdev',{ user: 'zzwdev', pwd: '123456', roles: [{ "role": "readWrite", "db": "zzw_dev" },{ "role" : "dbOwner", "db" : "zzw_dev" } ]}) db.createUser({ user: 'zzwdev', pwd: '123456', roles: [{ "role": "dbAdminAnyDatabase", "db": "zzw_dev" },{ "role": "dbAdmin", "db": "zzw_dev" },{ "role": "readWrite", "db": "zzw_dev" },{ "role" : "dbOwner", "db" : "zzw_dev" } ]}) db.createUser({ user: 'zzwtest', pwd: '123456', roles: [{ "role": "dbAdminAnyDatabase", "db": "zzw_test" },{ "role": "dbAdmin", "db": "zzw_test" },{ "role": "readWrite", "db": "zzw_test" },{ "role" : "dbOwner", "db" : "zzw_test" } ]})
roles:指定用户的角色,可以用一个空数组给新用户设定空角色;在 roles 字段,可以指定内置角色和用户定义的角色。role 里的角色可以选:
Built-In Roles(内置角色): 1. 数据库用户角色:read、readWrite; 2. 数据库管理角色:dbAdmin、dbOwner、userAdmin; 3. 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager; 4. 备份恢复角色:backup、restore; 5. 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase 6. 超级用户角色:root // 这里还有几个角色间接或直接提供了系统超级用户的访问(dbOwner 、userAdmin、userAdminAnyDatabase) 7. 内部角色:__system
具体角色:
Read:允许用户读取指定数据库 readWrite:允许用户读写指定数据库 dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile userAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户 clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。 readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限 readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限 userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限 dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。 root:只在admin数据库中可用。超级账号,超级权限
然后依次连接到每一台机器上执行。
killall mongod killall mongos
3.1 可以发现。集群多少有的节点都关闭了。没开启认证的集群如果开启认证需要集群宕机几分钟。当然也有热启动的方式,官方文档中有介绍
说明:可以先开启认证重启后再添加用户。但是只能在 admin 库添加一次,所以如果忘记了,或者权限分配不恰当就无法再更改,所以建议先添加用户再开启认证重启,并且集群不建议在每个单节点添加用户,并且建议单节点关闭初始添加账号的权限,详情见 enableLocalhostAuthBypass)
4. 使用访问控制强制重新启动复制集的每个成员****
这个步骤比较重要。设置访问控制有两种方式。我选择在配置文件里面配置好。(也可以在启动命令时使用命令来指定)
4.1. 依次在每台机器上的 mongod(注意是所有的 mongod 不是 mongos)的配置文件中加入下面一段配置。如我在 10.12.40.83 上的 config server,shard1,shard2,shard3 都加入下面的配置文件
keyFile=/usr/local/mongodb/conf/keyFile.file auth=true httpinterface=true
4.2. 依次在每台机器上的 mongos 配置文件中加入下面一段配置。如我在 192.168.31.229 上的 mongos 配置文件中加入上面的一段配置
keyFile=/usr/local/mongodb/conf/keyFile.file
解释:
mongos 比 mongod 少了 authorization:enabled 的配置。原因是,副本集加分片的安全认证需要配置两方面的,副本集各个节点之间使用内部身份验证,用于内部各个 mongo 实例的通信,只有相同 keyfile 才能相互访问。所以都要开启 keyFile: /data/mongodb/testKeyFile.file
然而对于所有的 mongod,才是真正的保存数据的分片。mongos 只做路由,不保存数据。所以所有的 mongod 开启访问数据的授权 authorization:enabled。这样用户只有账号密码正确才能访问到数据
4.3. 重启每个 mongo 示例。因为我的认证配置在了配置文件里面,所以启动命令不需要再加认证的参数 (例如--auth 等)
mongod -f /usr/local/mongodb/conf/config.conf mongod -f /usr/local/mongodb/conf/shard1.conf mongod -f /usr/local/mongodb/conf/shard2.conf mongod -f /usr/local/mongodb/conf/shard3.conf mongos -f /usr/local/mongodb/conf/mongos.conf
依次重启三台机器的 mongod 和 mongos 实例
5.连接 mongodb 集群
如果用 mongo sell 脚本连接
mongo --port 20000 use admin db.auth("admin","123456")
如果返回 1 表示连接成功,然后你就可以访问自己的数据库啦~!如 use testDB
如果使用 mongodb 连接工具。我用的是 Robo3T。在连接的时候选择使用 authentization
如下图:
配置开机启动
先在/etc/rc.d/init.d 下用 vi 新建文件 mongod,内容如下:
vim /etc/rc.d/init.d/mongod #!/bin/bash # #chkconfig: 2345 80 90 #description: mongodb start() { su mongo -c "/usr/local/mongodb/bin/mongod --config /usr/local/mongodb/conf/config.conf" su mongo -c "/usr/local/mongodb/bin/mongod --config /usr/local/mongodb/conf/shard1.conf" su mongo -c "/usr/local/mongodb/bin/mongod --config /usr/local/mongodb/conf/shard2.conf" su mongo -c "/usr/local/mongodb/bin/mongod --config /usr/local/mongodb/conf/shard3.conf" su mongo -c "/usr/local/mongodb/bin/mongos --config /usr/local/mongodb/conf/mongos.conf" } stop() { su mongo -c "/usr/bin/killall mongod" su mongo -c "/usr/bin/killall mongos" } case "$1" in start) start ;; stop) stop ;; restart) stop start ;; *) echo $"Usage: $0 {start|stop|restart}" exit 1 esac
增加服务并开机启动
chmod +x /etc/rc.d/init.d/mongod chkconfig --add mongod chkconfig --level 345 mongod on chkconfig --list mongod vim /etc/rc.d/rc.local touch /var/lock/subsys/local if test -f /sys/kernel/mm/transparent_hugepage/enabled; then echo never > /sys/kernel/mm/transparent_hugepage/enabled fi if test -f /sys/kernel/mm/transparent_hugepage/defrag; then echo never > /sys/kernel/mm/transparent_hugepage/defrag fi service mongod start
参考:
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于