摘要
组长让我分享下mongo的技术,因为这块的功能是我自己做的,别人都没有涉及。
将这篇文章也作为博客吧。
正文
公司远程服务器版本4.2.14,本地版本4.4.6
目前主要使用MongoDb做一些非实时的大量的数据的统计功能。集中在接口日志-接口API通用、日志管理、统计功能
MongoDb是一个非关系型数据库,主要用来存储非结构化的数据。
Mongo将数据存储成一个文档,数据结构由key、value组成,类似于json,value值可以是一个对象(基本类型、List等)
MongoDb内部是基于V8引擎(2.4版本以后),通过JavaScript代码单线程执行操作。
遇事不决,就看官网,看不明白,不怪官网,自己太菜!
实际使用参考的所有文档
- 菜鸟教程
- 官方中文文档
- MongoDB设置登录账号和密码_西瓜游侠的博客-CSDN博客_mongodb设置用户名和密码
- Mongo 创建数据库_太阳上的雨天的博客-CSDN博客_mongo 创建数据库
- MongoDB设置登录账号和密码_西瓜游侠的博客-CSDN博客_mongodb设置用户名和密码
- mongodb开启权限验证、超级管理员、用户权限管理 - 古墩古墩 - 博客园
- User Management Methods — MongoDB Manual
命令行获取帮助
- 命令
db.help获取db下的所有方法 db.表名.find().help()获取find下的所有方法
一、安装
1.1 windows
官网下载安装以Windows为例。
详细安装教程
1.2 linux
官网选择4.4.11,centos7.0,server,下载rpm包。
1
| wget https://repo.mongodb.org/yum/redhat/7/mongodb-org/4.4/x86_64/RPMS/mongodb-org-server-4.4.11-1.el7.x86_64.rpm
|
进入服务器,关闭防火墙。
1
2
| systemctl status firewalld
systemctl stop firewalld
|
安装mongo
1
| rpm -ivh mongodb-org-server-4.4.11-1.el7.x86_64.rpm
|
提示创建关联mongo.service,表示成功。
修改mongo的配置文件,默认是/etc/mongod.conf,绑定ip127.0.0.1改为0.0.0.0
1
2
3
4
| # network interfaces
net:
port: 27017
bindIp: 127.0.0.1 # Enter 0.0.0.0,:: to bind to all IPv4 and IPv6 addresses or, alternatively, use the net.bindIpAll setting.
|
启动mongo,并查看结果
1
2
3
| sudo systemctl start mongod
sudo systemctl status mongod
sudo systemctl enable mongod #设置自启动
|
或者用命令
二、基本操作
2.1 图形界面工具
mongoDbCompress
robot3T
2.2 命令行
shell安装
shell有两种,一种是mongo shell,一种是mongosh shell。就官网描述。后者比前者更强大。不过正常使用还是推荐mongo shell,因为轻量。
mongosh shell
如果是windows,在安装server时,就已经带了命令行操作的工具。
但是上面Linux安装时,只是单纯安装了一个server而已,mongoshell还需要单独安装。
Install mongosh — MongoDB Shell
以Centos7为例,创建一个仓库,然后配置地址,以便于方便直接通过yum下载
1
2
3
4
5
6
7
8
9
| vim /etc/yum.repos.d/mongodb-org-5.0.repo
# 添加下面内容
[mongodb-org-5.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/5.0/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-5.0.asc
|
执行命令进行安装
1
| sudo yum install -y mongodb-mongosh
|
进行连接
1
| mongosh "mongodb://localhost:27017"
|
mongo shell
1
2
3
| wget https://repo.mongodb.org/yum/redhat/7/mongodb-org/4.4/x86_64/RPMS/mongodb-org-shell-4.4.11-1.el7.x86_64.rpm
rpm -ivh mongodb-org-shell-4.4.11-1.el7.x86_64.rpm
|
进行连接
连接
以windows为例,执行mongo.exe,或者cmd命令行执行
cmd执行的话,如果没有配置环境变量,需要去mongo的有mongo.exe的bin目录下执行。
连接到mongo数据库
1
2
3
4
| mongo 127.0.0.1:27017/comm -u jack -p 123456
# mongo ip:port/库名 -u 用户名 -p 密码
# 如果没有库名、用户密码,可以不写,如下
mongo 127.0.0.1:27017
|
获取当前的连接
获取当前版本
查看下面的所有数据库
查看下面的所有的集合
1
2
3
| show collections;
# 或者
show tables;
|
查看当前所使用的数据库
鉴权
开启鉴权,只需要在mongo配置文件/etc/mongod.conf中追加
1
2
3
| # 开启鉴权
security:
authorization: enabled
|
在开启鉴权之前,首先,要在未开启状态下,创建一个最高级用户。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 进入mongo
mongo 127.0.0.1:27017
# 在admin表里创建最高级用户
# 创建一个用户,管理admin库时root权限,在admin下创建的管理其他库时也有root权限
use admin
db.createUser({
user: 'admin',
pwd: 'admin',
roles: [{
role: 'root',
db: 'admin'
}]
})
# 创建完成之后退出
exit
|
修改mongo配置文件,开启鉴权。上面有记录该操作。
然后重启mongo
1
| systemctl restart mongod
|
以admin用户登录,创建一个test库,并在test库里创建三个用户,只能登录、只能登录+查询、只能登录+查询+修改。
以上三个用户,如果适用于所有表,就使用use admin,在admin表里操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| # 登录
mongo 127.0.0.1:27017 -u admin -p admin
# 创建一个测试库test,并在测试库test的aaa表里插入一条数据
use test
db.aaa.insert({_id:1})
# 创建只能登录用户
db.createUser({
user: 'loginOnly',
pwd: 'loginOnly',
roles: []
})
# 创建登录+读取用户
db.createUser({
user: 'readOnly',
pwd: 'readOnly',
roles: [{
role: 'read',
db: 'test'
}]
})
# 创建登录+读写用户
db.createUser({
user: 'readWriteOnly',
pwd: 'readWriteOnly',
roles: [{
role: 'readWrite',
db: 'test'
}]
})
|
如果想要给一个用户,多个表权限,只需要在roles里面,再添加一个对象即可
其他一些操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 查看当前库下所有用户
show users
# 删除用户
db.dropUser('testadmin')
# 修改用户,后面对象不仅仅可传pwd,其他也可。看自己需求
db.updateUser('admin', {pwd: '654321'})
db.updateUser('admin',{
pwd: 'admin',
roles: [{
role: 'root',
db: 'admin'
}]
})
# 切换到admin用户
db.auth('admin', 'admin')
|
普通CRUD
更详细的参考
查询所有
1
2
3
4
5
6
| // 法一
// 语法:db.表名.find();
db.interfaceCountEveryday.find();
// 法二
// 语法:db.getCollection("表名").find();
db.getCollection("hotProject").find();
|
以下所有例子都使用法一
插入数据
1
2
| // 语法:db.表名.insert(json字符串)
db.ccc.insert({"name":"ccc","_id":"ccc"})
|
删除表
1
2
| // 语法:db.表名.drop();
db.ccc.drop();
|
删除某条数据
1
2
| // 语法:db.表名.remove(json字符串)
db.ccc.remove({"_id":"ccc"});
|
更新数据
1
2
3
| // 法一
db.col.update({'title':'MongoDB'},{$set:{'title':'Mongo'}})
db.col.update({'title':'MongoDB','author':'ccc'},{$set:{'title':'MongoDB'},'author':'sldt'},{multi:true})
|
根据条件查询
1
2
| // 语法:
db.interfaceCountEveryday.find({"_id":"2021-05-09"});
|
常用的查询关键字
- $gt:greater than 大于
- $lt:less than 小于
- $gte:greater than and equals 大于等于
- $lte:less than and equals 小于等于
- $ne:no equals 不等于
- $in
- $nin
- $all:$all与$in的区别是,all必须匹配到里面所有,in匹配其中之一即可
- $exists:判断元素是否存在
1
2
3
4
5
6
7
8
9
10
11
12
13
| // 多条件联合查询
db.track.find({
"bindingId": "渝A18287D",
"saveTime": {
$gt: 1676246400000,
$lt: 1676253600000
},
"spd": {
$gt: 120
}
});
// 表示查询number为2或4或6的所有数据
db.collection.find({"number":{$in: [2,4,6]}});
|
复杂聚合查询
查询总条数
查询取第6-10条
1
2
| db.ccc.find().skip(5).limit(5);
db.ccc.find().skip(5).limit(5).count(true);//加true表示统计skip、limit里面的内容
|
查询排序
1
| db.ccc.find().sort({"createTimie":-1})// -1表示从大到小降序,1表示从小到大升序
|
mapReduce
mapreduce分为两个阶段
- map:Map阶段并行处理输入数据
- reduce:Reduce阶段对Map结果进行汇总
例子:从interfaceLogMongo表中查询出interfaceCallType为2的数据,以interfaceCode分组统计出调用的总次数以及它的类型,并存入test数据表中。
mapreduce两种方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| //法一
db.interfaceLogMongo.mapReduce(
function() {
emit(this.interfaceCode, {count:1,type:this.interfaceType});
},
function(key, values) {
//统计的时候,如果想要统计出正确的次数,必须要用for循环
//使用total+=values[0].count()不会统计
var total=0;
for(var i in values){
total+=values[i].count;
}
return {count:total,type:values[1].type}
},
{
query: {
interfaceCallType: 2
},
out: "test" //删表重存
}
)
//法二
db.runCommand({
"mapreduce": "interfaceLogMongo",
"map": function () {
emit(this.interfaceCode, {count: 1, type: this.interfaceType});
},
"reduce": function (id, emits) {
var total = 0;
for(var i in emits)
total+=emits[i].count;
var type = emits[1].type;
return {count: total, type: emits[1].type};
},
"query": {
interfaceCallType: 2
},
"out": {merge: "test"}//merge表示追加
})
|
注意:由于mongo是基于js,单线程,单个节点跑mapreduce效率反而不如aggregate(agg framework使用c++)来的快。所以后续所有使用mapreduce的都换成了aggregate
mongo中MR与agg的比较,以及如何优化MR
aggregate
例子:从interfaceLogMongo中查出interfaceCallType为2的数据,以interfaceCode、interfaceName、interfaceType为主键分组,取他们的最大的interfaceResponseTime,取前5条并按倒序(由大到小)排序
aggregation中参数介绍
- $match:相当于SQL中的where
- $group:相当于SQL中的group by
- $project:相当于SQL中的select
- $sort:相当于SQL中的order by
- $limit:相当于SQL中的limit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| db.interfaceLogMongo.aggregate([
{
$match: {
"interfaceCallType": 2
}
},
{
$group: {
_id: {
"code": "$interfaceCode",
"name": "$interfaceName",
"type": "$interfaceType"
},
max: {$max: "$interfaceResponseTime"}
}
}, {
$sort: {
max: -1
}
}, {
$limit: 5
}
]);
|
索引CRD
以interfaceLogMongo这张表为例
索引值,1为按升序创建索引,-1为按降序创建索引
查询表的索引
1
| db.interfaceLogMongo.getIndexes();
|
创建单字段索引
1
2
| // 按照interfaceResponseTime的降序创建索引
db.interfaceLogMongo.createIndex({"interfaceResponseTime":-1})
|
创建复合索引
1
| db.interfaceLogMongo.createIndex({"interfaceResponseTime":-1,"interfaceCallType":1})
|
数据量多大,创建索引也会比较耗时,可以让索引在后台执行
1
| db.interfaceLogMongo.createIndex({"interfaceResponseTime":-1,"interfaceCallType":1},{background:true})
|
删除索引
1
2
| db.interfaceLogMongo.dropIndex("索引名称");// 删除单个
db.interfaceLogMongo.dropIndexes(); //删除所有索引
|
索引优化
mongodb性能优化
查看执行过程,在语句上添加explain()即可。
如
1
| db.interfaceLogMongo.explain().find().limit(5).sort({"interfaceResponseTime":-1});
|
stage类型
- COLLSCAN:全表扫描
- IXSCAN:索引扫描
- FETCH:根据索引去检索指定document
- SHARD_MERGE:将各个分片返回数据进行merge
- SORT:表明在内存中进行了排序
- LIMIT:使用limit限制返回数
- SKIP:使用skip进行跳过
- IDHACK:针对_id进行查询
- SHARDING_FILTER:通过mongos对分片数据进行查询
- COUNT:利用db.coll.explain().count()之类进行count运算
- COUNTSCAN:count不使用Index进行count时的stage返回
- COUNT_SCAN:count使用了Index进行count时的stage返回
- SUBPLA:未使用到索引的$or查询的stage返回
- TEXT:使用全文索引进行查询时候的stage返回
- PROJECTION:限定返回字段时候stage的返回
使用索引前
耗时
使用索引后
耗时
注意:并非所有的都可以使用索引来优化。
特别复杂的聚合查询,使用单字段索引会变慢,使用聚合索引效果也不明显,最后是通过分表的方式来处理的。
三、MongoTemplate
3.1 普通CRUD
mongoTemplate中提供的常用方法
- find
- findOne
- findAll
- findDistinct:如果是多个字段distinct的话,还是使用aggregate比较好用。
- insert:数据重复就会抛异常
- save:数据重复则覆盖旧的数据
- upsert:与save类似,有重复数据就会进行修改
- remove
3.2 mapReduce
语法:mongoTemplate.mapReduce(Query对象,表名或者实体类的字节码,map的js语句,reduce的js语法,返回实体的字节码)
以下面这个为例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| db.interfaceLogMongo.mapReduce(
function() {
emit(this.interfaceCode, {count:1,type:this.interfaceType});
},
function(key, values) {
//统计的时候,如果想要统计出正确的次数,必须要用for循环
//使用total+=values[0].count()不会统计
var total=0;
for(var i in values){
total+=values[i].count;
}
return {count:total,type:values[1].type}
},
{
query: {
interfaceCallType: 2
},
out: "test" //删表重存
}
)
|
java代码
1
2
3
4
5
6
7
8
9
10
| //法一
mongoTemplate.mapReduce(Query查询对象, 表名或者实体类的字节码,
"function () {emit(this.interfaceCode, {count:1,type:this.interfaceType});}",
"function (id, emits) {var total=0;for(var i in values){total+=values[i].count;}return {count:total,type:values[1].type}}",
返回实体的字节码);
//法二
mongoTemplate.mapReduce(Query查询对象, 表名或者实体类的字节码,
"classpath:map.js",
"classpath:reduce.js",
返回实体的字节码);
|
3.3 aggregate
语法:mongoTemplate.aggregate(aggregation对象,表的名称或者表的字节码,返回对象的字节码)
获取aggregation对象:Aggregation.newAggregation(一串可变数组,可以是match、project、group、sort、limit等等)
以下面这个为例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| db.interfaceLogMongo.aggregate([
{
$group: {
_id: {
"code":"$interfaceCode",
"type":"$interfaceType",
"name":"$interfaceName"
},
count: {
$sum: 1
}
}
}, {
$sort: {
count: -1// 1表示由小到大,-1表示由大到小
}
}
]);
|
java代码
1
2
3
4
5
6
| Aggregation agg = Aggregation.newAggregation(
Aggregation.project("interfaceCode","interfaceType","interfaceName"),
Aggregation.group("interfaceCode","interfaceType","interfaceName").count().as("count"),
Aggregation.project("count"),
Aggregation.sort(Sort.by("count").descending()));
mongoTemplate.aggregate(agg,表的名称或者表的字节码,返回对象的字节码)
|