# 1. 导入包管理系统使用的公钥
# Ubuntu 的软件包管理工具(即dpkg和APT)要求软件包的发布者通过GPG密钥
# 签名来确保软件包的一致性和真实性。通过以下命令导入MongoDB公共GPG密钥:
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6
# 2. 创建list file
# 根据 Ubuntu 的版本使用适当的命令创建 list file: /etc/apt/sources.list.d/mongodb-org-3.4.list
# ubuntu14.04
echo "deb [ arch=amd64 ] http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.4.list
# ubuntu16.04
echo "deb [ arch=amd64,arm64 ] http://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/3.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.4.list
# 3. 重新下载本地包数据库索引
sudo apt-get update
# 4. 安装 MongoDB
sudo apt-get install -y mongodb-org
# 5. 运行MongoDB
sudo service mongod start
# 或者
sudo systemctl start mongod
# 6. 命令行运行命令:
mongo
# 7. 卸载mongodb
# 关闭MongoDB
sudo service mongod stop
# 删除所有相关软件包
sudo apt-get purge mongodb-org*
# 删除数据和日志目录
sudo rm -r /var/log/mongodb
sudo rm -r /var/lib/mongodb
# 下载解压软件包
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1404-3.4.4.tgz
tar -zxvf mongodb-linux-x86_64-ubuntu1404-3.4.4.tgz -C .
# 创建数据库数据、配置文件、日志文件的存放目录
mkdir -p data conf logs
# 创建和编写mongodb配置文件
touch mongod.conf
vim mongod.conf
-------------------
#指定mongod端口
port = 27017
#指定数据存放目录
dbpath = /home/ubuntu/mongodb-3.4.4/data
#指定日志文件
logpath = /home/ubuntu/mongodb-3.4.4/logs/mongod.log
#指定启动的是后台进程,这个参数在windows下无效
fork = true
#是都启用权限认证
auth = false
-------------------
# 启动mongod进程,-f等价于--config
./bin/mongod -f conf/mongod.conf
# 加入开机启动项
echo "xxx/mongod -f xxx/mongodb.conf;" >> /etc/rc.local
#关闭命令,前提是关闭了前端程序
xxx/mongod --shutdown -f xxx/mongodb.conf
#启动客户端交互
#在~/.mongorc.js中可以编写mongod启动时运行的js脚本,如这里打印的 "hello kinglyjn!"
xxx/mongo localhost:27017/test
------------------------------
MongoDB shell version v3.4.4
connecting to: mongodb://localhost:27017/test
MongoDB server version: 3.4.4
hello kinglyjn!
Server has startup warnings:
2017-06-08T18:38:52.632+0800 I CONTROL [initandlisten]
2017-06-08T18:38:52.632+0800 I CONTROL [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'.
2017-06-08T18:38:52.632+0800 I CONTROL [initandlisten] ** We suggest setting it to 'never'
------------------------------
#我们发现有警告,按照提示修改即可,脚本如下(在root用户下执行 sudo -i):
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
#数据库和集合
show dbs
use dbname
db.getName()
db.stats()
db.getMongo()
show collections
db.dropDatabase()
db.cname.drop()
db.copyDatabase("src_dbname", "target_dbname", "127.0.0.1")
db.repairDatabase()
#数据库状态分析
mongostat -h 127.0.0.1:27017
# 0:profile操作记录功能关闭
# 1:记录操作值大于slowms的操作
# 2:记录任何操作记录
# 一般测试阶段使用2,而生产环境profilingLevel的值为0
db.getProfilingLevel()
db.setProfilingLevel(2)
db.getProfilingStatus() { "was" : 0, "slowms" : 100 }
# 通过mongodb的配置文件/etc/mongo.conf的 verbose参数可以配置日志的详细程序(一个v~5个v)
#配置文件开启权限模式:
--------------------------
security:
authorization: enabled
或者
auth = true
--------------------------
#在相应的库下创建和删除用户:
#数据库管理角色
#read、readWrite、dbAdmin、dbOwner、userAdmin
read:该角色只有读取数据的权限
readWrite:该角色有数据的读写权限,但不能创建删除索引,也不能创建和删除数据库
dbAdmin:该角色拥有数据库管理权限(比如更改数据库类型)
dbOwner:是read、readWrite、dbAdmin这三者角色的集合体
userAdmin:该角色可以对其他角色权限进行管理
#集群管理角色
clusterAdmin:提供了最大的集群管理功能。相当于clusterManager, clusterMonitor,
and hostManager和dropDatabase的权限组合
clusterManager:提供了集群和复制集管理和监控操作。拥有该权限的用户可以操作config
和local数据库(即分片和复制功能)
clusterMonitor:仅仅监控集群和复制集
hostManager:提供了监控和管理服务器的权限,包括shutdown节点,logrotate, repairDatabase等。
#所有数据库角色
readAnyDatabase:具有read每一个数据库权限。但是不包括应用到集群中的数据库。
readWriteAnyDatabase:具有readWrite每一个数据库权限。但是不包括应用到集群中的数据库。
userAdminAnyDatabase:具有userAdmin每一个数据库权限,但是不包括应用到集群中的数据库。
dbAdminAnyDatabase:提供了dbAdmin每一个数据库权限,但是不包括应用到集群中的数据库。
#超级管理员权限
root: dbadmin到admin数据库、useradmin到admin数据库以及UserAdminAnyDatabase。
但它不具有备份恢复、直接操作system.*集合的权限,但是拥有root权限的超级用户
可以自己给自己赋予这些权限。
#backup、restore:备份角色权限
#客户端创建用户:
var user = {
user:"usernamexx",
pwd:"passwordxx",
customData:"xxxx",
roles:[{role:"userAdmin", db:"admin"}, {role:"read", db:"response"}]
}
db.createUser(user);
db.dropUser("usernamexxx");
#使用新创建的用户登录:
mongo 127.0.0.1:27017/test -u usernamexxx -p passwordxxx
或 db.auth('usernamexxx','passwordxxx')
#添加数据
#insert添加时如果主键重复则报错,而save添加时主键重复则覆盖
db.cname.insert({name:"zhangsan", age:23})
db.cname.save({name:"zhangsan", age:23})
#修改数据
#定位语句,更新语句,不存在是否更新插入,是否全部更新
db.cname.update(criteria, objNew, isUpsert, isMulti)
#局部修改
db.cname.update({age:25}, {$set:{name:"lisi"}}, false, false)
db.cname.update({age:25}, {$inc:{age:2}}, false, true)
db.cname.update({age:25}, {$push:{scores:{$each:[1,2,3,4]}}})
db.cname.update({age:25}, {$addToSet:{tags:{$each:["aaa", "bbb"]}}})
#整体修改
db.cname.update({age:25}, new_user)
#修改集合名称
db.cname.renameCollection("target_name")
#删除数据
db.cname.remove({name:"zhangsan"})
#查询数据
db.cname.count()
db.cname.dataSize()
db.cname.totalSize()
db.cname.storageSize()
db.cname.getShardVersion()
db.cname.stats()
db.cname.distinct("name") #select distict name from user
db.cname.find()
db.cname.find({name:"zhangsan"})
db.cname.find({name:"zhangsan"}).pretty() #友好显示数据
db.cname.find({name:"zhangsan"}).explain() #性能分析函数
db.cname.find({name:"zhangsan", age:23})
db.cname.find({age:{$gt:22}}, {name:1, age:1}) #显示name和age字段
db.cname.find({age:{$gte:22}})
db.cname.find({age:{$gt:23, $lt:26}}) #查询age >= 23 并且 age <= 26
db.cname.find({name:/mongo/}) #查询name中包含 mongo的数据
db.cname.find({"data.map.key1":/regexxxx/}) #注意这里必须加引号
db.cname.find().skip(3).limit(5).sort({age:-1})
db.cname.find().skip(3).limit(5).sort({$natural:-1}) #$natural一般按写入顺序排序
db.cname.find({$or:[{age:22}, {age:25}]})
db.cname.find({age:{$exists:true}}) #查询存在age字段的记录
db.cname.find({age:{$ne:23}}) #不等于
db.cname.find({age:{$in:[23,24,25]}})
db.cname.find({name:{$in:[null], $exists:true}}) #可以匹配 {name:null,sex:1,age:18}
db.cname.find({name:{$nin:["zhangsan"]}})
db.cname.find({x:{$all:[1,5]}}) #查询x数组中全部包含1和5的记录
db.cname.find({x:[1,2,3,4]}) #精确查询为该数组的记录
db.cname.find({"x.0":1}) #匹配数组中指定下标元素的值
db.cname.find({x:{$size:4}}) #匹配x数组长度为4的记录
#分组查询
db.user.group({
key:{name:true},
initial:{users:[]}, #每组都分享的“初始化函数”,可以在此处初始化一些变量供每组进行使用
$reduce:function(current, previor){previor.users.push(current.name);},
condition:{name:{$exists:true}}, #过滤条件
finalize:function(out){out.count=out.users.length}
})
#索引数据
#_id索引
#单建索引
#多键索引
#复合索引
#过期索引
#全文索引
#地理信息索引
db.cname.getIndexes()
db.cname.ensureIndex({name:1, age:-1})
#删除索引
db.cname.dropIndex("name_1_age_-1")
db.cname.dropIndexes()
#创建唯一索引(以下创建的索引只允许集合中存在唯一一个name-address记录)
db.cname.ensureIndex({name:1, adddress:1}, {unique:true})
#创建稀疏索引(不存在name的记录不创建name索引,注意不能使用稀疏索引查找不存在name字段的记录)
db.cname.ensureIndex({name:1}, {spare:true})
#加过期索引的字段必须为ISODate或者ISODate的数组,超时后记录被自动删除(TTL)
#删除的过程是不精确的,删除过程是每60s跑一次,而且删除也需要一定的时间,所以存在误差
db.cname.ensureIndex({createTime:1}, {expireAfterSeconds:50})
db.cname.ensureIndex({name:1, age:-1}, {name:"name_age_idx", unique:true, sparse:true, expireAfterSeconds:50})
#强制使用索引查找
db.cname.find({name:"zs", age:23}).hint("name_1_age_-1");
#全文检索索引
db.cname.ensureIndex({name:"text"})
db.cname.ensureIndex({"$**":"text"})
#全文检索查询
db.cname.find({$text:{$search:"aaa"}})
db.cname.find({$text:{$search:"aa bb cc"}}) #查询包含aa或bb或cc的记录
db.cname.find({$search:"\"aa\" \"bb\" \"cc\""}}) #查询既包含aa又包含bb又包含cc的记录
db.cname.find({$text:{$search:"aa bb -cc"}}) #查询包含aa或bb,但是不包含cc的记录
db.cname.find({$text:{$search:"/AA/i"}}) #正则匹配
db.cname.find({$text:{$search:"aaa"}, score:{$meta:"textScore"}})
db.cname.find({$text:{$search:"aaa"}}, {score:{$meta:"textScore"}}).sort({score:{$meta:"textScore"}}) #根据匹配度排序
#地理位置索引
#平面地理位置索引 经纬度范围 -180~180 -90~90
db.cname.ensureIndex({p:"2d"})
#球面地理位置索引
db.cname.ensureIndex({p:"2dsphere"})
db.cname.insert({p:[0,0]})
db.cname.insert({p:[-180,-90]})
db.cname.insert({p:[180,90]})
#默认返回100个最近的点
db.cname.find({p:{$near:[0,0]}})
db.cname.find({p:{$near:[0,0]}, $maxDistance:10})
#查找圆内的点
db.cname.find({p:{$geoWithin:{$center:[[0,0], 5]}}})
#查找方块内的点
db.cname.find({p:{$geoWithin:{$box:[[0,0], [3,3]]}}})
#查找多边形内的点
db.cname.find({p:{$polygon:[[0,0],[1,3],[4,5]]}})
#准备三台机器
172.16.127.129
172.16.127.130
172.16.127.131
#配置文件信息
port = 27017
dbpath = /home/ubuntu/mongodb-3.4.4/data
logpath = /home/ubuntu/mongodb-3.4.4/logs/mongod.log
logappend = true
fork = true
auth = false
#分别在这三台机器上启动mongodb
mongodb-3.4.4/bin/mongod -f mongodb-3.4.4/conf/mongod.conf --replSet rs01
mongodb-3.4.4/bin/mongod -f mongodb-3.4.4/conf/mongod.conf --replSet rs01
mongodb-3.4.4/bin/mongod -f mongodb-3.4.4/conf/mongod.conf --replSet rs01
#启动以后我们发现副本集还没有初始化配置信息,接下来便是初始化副本集
#在三台机器上任意一台登陆mongodb(我这里在129上登陆)
#使用admin数据库
use admin
#输入如下内容:
#注:红色字体的repset要和上面参数 “--replSet rs01” 保持一致.
config=
{_id:"rs01",members:[
...{_id:0,host:"172.16.127.129:27017"},
...{_id:1,host:"172.16.127.130:27017"},
...{_id:2,host:"172.16.127.131:27017"}]
}
#初始化副本集配置
#输出成功{"ok",1}
rs.initiate(config)
#查看日志,副本集启动成功后,130为主节点PRIMARY,129、131为副本节点SECONDARY。(选举机制似乎类似于zk)
#查看集群节点的状态
rs.status()
rs.isMaster()
#至此整个副本集就搭建成功了,接下来测试副本集数据复制功能
#回到130主节点连接进入终端
#建立test数据库
use test
#往user测试表中插入数据
db.user.insert({name:"zhangsan", age:23})
#在副节点129,131上查看数据是否复制过来
use test
show tables
"errmsg" : "not master and slaveOk=false",
#mongodb默认是从主节点读写数据的,副本节点上不允许读,需要设置副本节点可以读(主节点可读可写,从节点可读)
db.getMongo().setSlaveOk()
#可以看到数据已经复制到了副本集
db.user.find()
#容灾测试
#此时如果主节点130宕机(关闭主节点)
mongodb-3.4.4/bin/mongod --shutdown -f mongodb-3.4.4/conf/mongod.conf
#查看129和131节点状态发现,此时129节点变成了主节点,向主节点写入数据ok
use test
db.user.insert({name:"lisi", age:24})
#查看131从节点,发现数据副本已经生成
db.user.find()
#在主库130中添加从库
rs.add("172.16.127.132:27017")
{ "ok" : 1 }
注意:MongoDB 3.4, config servers must be deployed as a replica set (CSRS).
也就是说mongodb3.4以后【config节点】强制使用副本集。
什么是分片?
分片是将数据进行拆分,将数据水平地分散到不同服务器的技术。
为什么要分片?
架构上:读写均衡,去中心化。
硬件上:解决单机内存和硬盘的限制。
总的来说,分片能够改善单台机器数据的存储以及数据吞吐性能,提高在大量数据下随机访问的性能。
分片和副本集的比较
replication(副本集) | shard(分片) |
---|---|
实现意义 | 数据冗余、实现读写分离(主写副读),提升读的性能 |
架构上 | 中心化 |
实现原理上 | 数据镜像 |
维护成本 | 相对容易 |
分片节点介绍
app—>mongos---->CONFIG_RS[config]---->RS01[shard01、shard02、shard03...]
---->RS02[shard01、shard02、shard03...]
---->RS03[shard01、shard02、shard03...]
分片测试细节
分片节点信息:
# 分片节点信息:
172.16.127.128 mongos
172.16.127.129 [config_rs]configsvr0,configsvr1,configsvr2
172.16.127.130 shardsvr01
172.16.127.131 shardsvr02
# 配置信息
----------------------------
#shardsvr配置文件mongod.conf
port = 27017
dbpath = /home/ubuntu/mongodb-3.4.4/data
logpath = /home/ubuntu/mongodb-3.4.4/logs/mongod.log
logappend = true
fork = true
auth = false
#config_rs配置文件mongod.conf
port = 27017
dbpath = /home/ubuntu/mongodb-3.4.4/data
logpath = /home/ubuntu/mongodb-3.4.4/logs/mongod.log
logappend = true
fork = true
auth = false
port = 27018
dbpath = /home/ubuntu/mongodb-3.4.4-01/data
logpath = /home/ubuntu/mongodb-3.4.4-01/logs/mongod.log
logappend = true
fork = true
auth = false
port = 27019
dbpath = /home/ubuntu/mongodb-3.4.4-02/data
logpath = /home/ubuntu/mongodb-3.4.4-02/logs/mongod.log
logappend = true
fork = true
auth = false
#mongos配置文件mongod.conf
port = 27017
logpath = /home/ubuntu/mongodb-3.4.4/logs/mongod.log
logappend = true
fork = true
启动各个分片节点
# shardsvr01 & shardsvr02
mongodb-3.4.4/bin/mongod -f mongodb-3.4.4/conf/mongod.conf --shardsvr
mongodb-3.4.4/bin/mongod -f mongodb-3.4.4/conf/mongod.conf --shardsvr
# configsvr(正常应该配置至少三个configsvr)
mongodb-3.4.4/bin/mongod -f mongodb-3.4.4/conf/mongod.conf --configsvr --replSet config_rs
mongodb-3.4.4-01/bin/mongod -f xxx/conf/mongod.conf --configsvr --replSet config_rs
mongodb-3.4.4-02/bin/mongod -f xxx/conf/mongod.conf --configsvr --replSet config_rs
# 初始化CONFIG_RS
use admin
var config = {_id:"config_rs", configsvr:true,members:[{_id:0, host:"172.16.127.129:27017"}]}
rs.initiate(config)
rs.add({_id:1, host:"172.16.127.129:27018"})
rs.add({_id:2, host:"172.16.127.129:27019"})
# mongos
mongodb-3.4.4/bin/mongos -f mongodb-3.4.4/conf/mongod.conf --configdb config_rs/172.16.127.129:27017,172.16.127.129:27018,172.16.127.129:27019
给具体的库表添加分片:
#1. 连接到mongos
mongodb-3.4.4/bin/mongo
use admin
#2. add shard [需要在admin数据库下操作]
#注意如果你的mongos和shard在同一台机器上,添加分片不能使用localhost,建议使用ip
#对于单个数据库实例,maxSize表示分片节点最大存储大小
#db.runCommand({addShard:"<:port>" , maxSize:, name:"" })
#对于副本集群
#db.runCommand({addShard:"/<:port>" ,
# maxSize:, name:"" })
db.runCommand({addShard:"172.16.127.130:27017"})
db.runCommand({addShard:"172.16.127.131:27017"})
#或者
sh.addShard('172.16.127.130:27017')
sh.addShard('172.16.127.131:27017')
sh.status()
sh.status({verbose:true})
#3. enable sharding [admin,这里只是标识这个数据库可以启用分片,但实际上并没有进行分片]
db.runCommand({enableSharding:"" })
#或者(启用test库的分片)
sh.enableSharding('test')
#4. 对一个集合进行分片(key为片件,指定为片件的字段需要索引,punique表示对shard-key的唯一性的约束)
db.user.ensureIndex({name:1})
db.runCommand({shardcollection:"dbname.cname", key:{"userid":1}, unique:<true/false>})
#或者
db.user.ensureIndex({name:1})
sh.shardCollection("test.user", {name:1})
#5. 如果要加验证,官网说只要在mongos上操作即可,简单地添加一个用户:
use test
db.createUser({
user:'test',
pwd:'123456',
roles:[
{role:'readWrite',db:'test'}
]
})
#分片测试
#1.查看集合状态
db.shard_cname.stats()
#2.查看分片状态
db.printShardingStatus()
#3.在mongos节点插入数据
#for(var i=0; i<1000; i++) db.user.insert({name:String.fromCharCode(i%26+97), age:i})
for (var i=0; i<1000; i++) db.user.insert({name:"zhangsan"+i, age:i})
for (var i=0; i<1000; i++) db.user.insert({name:"lisi"+i, age:i})
#4.查看shard01和shard02两个节点,可以看到数据已经均摊到这两个节点上了(mongos节点上是不保存插入数据的)
db.user.find()
删除分片
#==========================================================
#删除分片(在mongos节点操作)
use admin
#下面这句会立即返回,实际在后台执行
db.runCommand({removeshard:"172.16.127.130:27017"})
#我们可以反复执行上面语句,查看执行结果
db.runCommand({removeshard:"172.16.127.130:27017"})
{ msg: "draining ongoing" , state: "ongoing", remaining: { chunks: 42, dbs : 1 }, ok: 1 }
#从上面可以看到,正在迁移,还剩下42块没迁移完,当remain为0之后,这一步就结束了,因为我们有两个分片节点,shard01从集群被移除之后,shard01(1002条)的分片数据全部被迁往到了shard02(1001+1002条),但是shard01物理节点上的数据背不会被删除
#有一些分片上保存上一些unsharded data,需要迁移到其他分片上,
#可以用sh.status()查看分片上是否有unsharded data,如果有则显示:
{ "_id" : "test", "primary" : "shard0000", "partitioned" : true }
#用下面的命令迁移(只有全部迁移完上面的命令才会返回,执行完毕后shard01的未分片的1002条数据也被删除了)
mongos> db.runCommand({movePrimary:"test", to:"shard0001"})
shards:
{"_id":"shard0000", "host":"172.16.127.130:27017", "state":1, "draining": true }
{"_id":"shard0001", "host":"172.16.127.131:27017", "state":1 }
databases:
{ "primary" : "shard0001:172.16.127.131:27017", "ok" : 1 }
#最后运行命令进行彻底删除
db.runCommand({removeshard:"172.16.127.130:27017"})
sh.status()
shards:
{"_id":"shard0001", "host":"172.16.127.131:27017", "state":1}
databases:
{"_id":"test", "primary":"shard0001", "partitioned":true }
添加被删除的分片
#重新添加刚才删除的分片,将遇到以下错误,提示test数据库已经存在
db.runCommand({addShard:"172.16.127.130:27017"})
{
"code" : 96,
"ok" : 0,
"errmsg" : "can't add shard '172.16.127.130:27017'
"because a local database 'test' exists in another shard0001"
}
#这时,就需要通过MongoDB shell连接到shard01实例,删除test数据库,然后重新添加
use test
db.dropDatabase()
#回到mongos节点
use admin
mongos> sh.addShard('172.16.127.130:27017')
{
"code" : 11000,
"ok" : 0,
"errmsg" : "E11000 duplicate key error collection:
"admin.system.version index: _id_ dup key: { : \"shardIdentity\" }"
}
#这时候再连接到shard01节点,删除admin.system.version集合中记录
db.system.version.remove({})
db.system.version.remove({"_id":"shardIdentity"})
WriteResult({
"nRemoved" : 0,
"writeError" : {
"code" : 40070,
"errmsg" : "cannot delete shardIdentity document while in --shardsvr mode"
}
})
#删除时报错,意思是说不能在分片模式下删除这张表中的这条记录,然后我们关闭shard01,然后以非shardsvr的方式启动,删除这条记录后,再以shardsvr方式启动
db.shutdownServer()
mongodb-3.4.4/bin/mongod -f mongodb-3.4.4/conf/mongod.conf
use admin
db.system.version.remove({})
db.shutdownServer()
mongodb-3.4.4/bin/mongod -f mongodb-3.4.4/conf/mongod.conf --shardsvr
#最后添加之前被删除的分片shard01,大功告成
db.runCommand({addShard:"172.16.127.130:27017"})
{ "shardAdded" : "shard0002", "ok" : 1 }
关于两个概念的说明:
片件(shardid):
当设置分片时,需要从集合里面选择一个或几个键,把选择出来的键作为数据拆分的依据,这个键叫做片键或复合片键。选好片键后,MongoDB将不允许插入没有片键的文档,但是允许不同文档的片键类型不一样。
块(chunk):
在一个shard server内部,MongoDB还是会把数据分为chunks,每个chunk(默认64m)代表这个shard server内部一部分数据。chunk的产生,会有以下两个用途:
- Splitting:当一个chunk的大小超过配置中的chunk size时,MongDB的后台
进程会把这个chunk切分成更小的chunk,从而避免chunk过大的情况
- Balancing:在MongoDB中,balancer是一个后台进程,负责chunk的迁移,
从而均衡各个shard server的负载
数据块的拆分(split)和均衡(balance):