一、概述
- Mongodb是非关系型数据库,存储bson格式数据 ,数据格式灵活。
- 相比课程管理等核心数据CMS数据不重要,且没有事务管理要求。
MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。 MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的,它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。 MongoDB最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
基本概念:
| SQL术语/概念 | MongoDB术语/概念 | 解释/说明 |
|---|---|---|
| database | database | 数据库 |
| table | collection | 数据库表/集合 |
| row | document | 数据记录行/文档 |
| column | field | 数据字段/域 |
| index | index | 索引 |
| table joins | 表连接,MongoDB不支持 | |
| primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 |
1.1 数据模型
MongoDB的最小存储单位就是文档(document)对象。文档(document)对象对应于关系型数据库的行。数据在MongoDB中以 BSON(Binary-JSON)文档的格式存储在磁盘上。
BSON(Binary Serialized Document Format)是一种类json的一种二进制形式的存储格式,简称Binary JSON。BSON和JSON一样,支持 内嵌的文档对象和数组对象,但是BSON有JSON没有的一些数据类型,如Date和BinData类型。
BSON采用了类似于 C 语言结构体的名称、对表示方法,支持内嵌的文档对象和数组对象,具有轻量性、可遍历性、高效性的三个特点,可 以有效描述非结构化数据和结构化数据。这种格式的优点是灵活性高,但它的缺点是空间利用率不是很理想。
Bson中,除了基本的JSON类型:string,integer,boolean,double,null,array和object,mongo还使用了特殊的数据类型。这些类型包括 date,object id,binary data,regular expression 和code。每一个驱动都以特定语言的方式实现了这些类型,查看你的驱动的文档来获取详 细信息。
| 数据类型 | 描述 | 举例 |
|---|---|---|
| 字符串 | UTF-8字符串都可标识为字符串类型得数据 | {"x" : "foobar"} |
| 对象id | 对象id是文档的12字节的唯一 ID | {"X" :ObjectId() } |
| 布尔值 | 真或者假:true或者false | {"x":true} |
| 数组 | 值的集合或者列表可以表示成数组 | {"x" : ["a", "b", "c"]} |
| 32位整数 | 类型不可用。JavaScript仅支持64位浮点数,所以32位整数会被 自动转换。 | shell是不支持该类型的,shell中默认会转换成64 位浮点数 |
| 64位整数 | 不支持这个类型。shell会使用一个特殊的内嵌文档来显示64位 整数 | shell是不支持该类型的,shell中默认会转换成64 位浮点数 |
| 64位浮点数 | shell中的数字就是这一种类型 | {"x":3.14159,"y":3} |
| null | 表示空值或者未定义的对象 | {"x":null} |
| undefined | 文档中也可以使用未定义类型 | {"x":undefined} |
| 符号 | shell不支持,shell会将数据库中的符号类型的数据自动转换成 字符串 | |
| 正则表达式 | 文档中可以包含正则表达式,采用JavaScript的正则表达式语法 | {"x" : /foobar/i} |
| 代码 | 文档中还可以包含JavaScript代码 | {"x" : function() { /* …… */ }} |
| 二进制数据 | 二进制数据可以由任意字节的串组成,不过shell中无法使用 | |
| 最大值/最 小值 | BSON包括一个特殊类型,表示可能的最大值。shell中没有这个 类型。 |
shell默认使用64位浮点型数值。{“x”:3.14}或{“x”:3}。对于整型值,可以使用NumberInt(4字节符号整数)或NumberLong(8字节符 号整数),{“x”:NumberInt(“3”)}{“x”:NumberLong(“3”)}
1.2 特点
高性能
MongoDB提供高性能的数据持久性。特别是, 对嵌入式数据模型的支持减少了数据库系统上的I/O活动。 索引支持更快的查询,并且可以包含来自嵌入式文档和数组的键。(文本索引解决搜索的需求、TTL索引解决历史数据自动过期的需求、地 理位置索引可用于构建各种 O2O 应用)
mmapv1、wiredtiger、mongorocks(rocksdb)、in-memory 等多引擎支持满足各种场景需求。 Gridfs解决文件存储的需求。
高可用性
MongoDB的复制工具称为副本集(replica set),它可提供自动故障转移和数据冗余。
高拓展性
MongoDB提供了水平可扩展性作为其核心功能的一部分。 分片将数据分布在一组集群的机器上。(海量数据存储,服务能力水平扩展) 从3.4开始,MongoDB支持基于片键创建数据区域。在一个平衡的集群中,MongoDB将一个区域所覆盖的读写只定向到该区域内的那些 片。
查询
MongoDB支持丰富的查询语言,支持读和写操作(CRUD),比如数据聚合、文本搜索和地理空间查询等。
无模式(动态模式)、灵活的文档模型
1.3 命名规范
数据库命名
数据库命名:不能是空字符串("")。 不得含有' '(空格)、.、$、/、\和\0 (空字符)。 应全部小写。 最多64字节。
保留得数据库名:
admin: 从权限的角度来看,这是"root"数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特 定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。
local: 这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合
config: 当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。
集合命名
集合名不能是空字符串""。 集合名不能含有\0字符(空字符),这个字符表示集合名的结尾。 集合名不能以"system."开头,这是为系统集合保留的前缀。 用户创建的集合名字不能含有保留字符。有些驱动程序的确支持在集合名里面包含,这是因为某些系统生成的集合中包含该字符。除 非你要访问这种系统创建的集合,否则千万不要在名字里出现$。
二、java使用示例
2.1 与springboot整合
依赖
用于操作MongoDB的持久层框架,封装了底层的mongodb-driver。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐data‐mongodb</artifactId>
</dependency>配置
server:
port: 31001
spring:
application:
name: xc‐service‐manage‐cms
data:
mongodb:
host:127.0.0.1 # 主机地址,与uri配置一个即可。
port: 27017 # 默认值
#uri: mongodb://root:123@localhost:27017
# 副本集的连接字符串 uri后可跟 用户名:密码@ip:端口
# uri:mongodb://180.76.159.126:27017,180.76.159.126:27018,180.76.159.126:27019/articledb?connect=replicaSet&slaveOk=true&replicaSet=myrs
# 连接分片集群 其余配置均不使用
# uri: mongodb://180.76.159.126:27017,180.76.159.126:27117/articledb
database: xc_cms连接uri配置的?后缀参数
| 选项 | 描述 |
|---|---|
| replicaSet=name | 验证replica set的名称。 Impliesconnect=replicaSet |
| slaveOk=true|false | true:在connect=direct模式下,驱动会连接第一台机器,即使这台 服务器不是主。在connect=replicaSet模式下,驱动会发送所有的写 请求到主并且把读取操作分布在其他从服务器。false: 在 connect=direct模式下,驱动会自动找寻主服务器. 在 connect=replicaSet 模式下,驱动仅仅连接主服务器,并且所有的 读写命令都连接到主服务器。 |
| safe=true|false | true: 在执行更新操作之后,驱动都会发送getLastError命令来确保 更新成功。(还要参考 wtimeoutMS).false: 在每次更新之后,驱动不 会发送getLastError来确保更新成功。 |
| w=n | 驱动添加 { w : n } 到getLastError命令. 应用于safe=true。 |
| wtimeoutMS=ms | 驱动添加 { wtimeout : ms } 到 getlasterror 命令. 应用于 safe=true. |
| fsync=true|false | true: 驱动添加 { fsync : true } 到 getlasterror 命令.应用于 safe=true.false: 驱动不会添加到getLastError命令中。 |
| journal=true|false | 如果设置为 true, 同步到 journal (在提交到数据库前写入到实体中). 应用于 safe=true |
| connectTimeoutMS=ms | 可以打开连接的时间。 |
| socketTimeoutMS=ms | 发送和接受sockets的时间。 |
启动类配置
@SpringBootApplication
@EntityScan("com.xuecheng.framework.domain.cms")//扫描实体类
@ComponentScan(basePackages={"com.xuecheng.api"})//扫描接口
@ComponentScan(basePackages={"com.xuecheng.manage_cms"})//扫描本项目下的所有类
public class ManageCmsApplication {
public static void main(String[] args) {
SpringApplication.run(ManageCmsApplication.class,args);
}
}model
//@Document:是Spring Data mongodb提供的注解,
@Document(collection = "cms_page")
public class CmsPage {
/**
* 页面名称、别名、访问地址、类型(静态/动态)、页面模版、状态
*/
//站点ID
private String siteId;
//页面ID
@Id
private String pageId;
}dao,使用MongoTemplate
import cn.itcast.mongodb.Person;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class PersonDao {
@Autowired
private MongoTemplate mongoTemplate;
public void savePerson(Person person) {
this.mongoTemplate.save(person);
}
public List<Person> queryPersonListByName(String name) {
Query query = Query.query(Criteria.where("name").is(name));
return this.mongoTemplate.find(query, Person.class);
}
public List<Person> queryPersonListByName(Integer page, Integer rows) {
Query query = new Query().limit(rows).skip((page - 1) * rows);
return this.mongoTemplate.find(query, Person.class);
}
public UpdateResult update(Person person) {
Query query = Query.query(Criteria.where("id").is(person.getId()));
Update update = Update.update("age", person.getAge());
return this.mongoTemplate.updateFirst(query, update, Person.class);
}
public DeleteResult deleteById(String id) {
Query query = Query.query(Criteria.where("id").is(id));
return this.mongoTemplate.remove(query, Person.class);
}
}dao,实现通用接口
//创建Dao,继承MongoRepository,并指定实体类型和主键类型。
//,在MongoRepository中定义了很多现成的方法,如save、delete等
public interface CmsPageRepository extends MongoRepository<CmsPage,String> {
//自定义Dao方法 (添加为自定义)
//按照findByXXX,findByXXXAndYYY、countByXXXAndYYY等规则定义方法,实现查询操作。
//根据页面名称查询
CmsPage findByPageName(String pageName);
//根据页面名称和类型查询
CmsPage findByPageNameAndPageType(String pageName,String pageType);
//根据站点和页面类型查询记录数
int countBySiteIdAndPageType(String siteId,String pageType);
//根据站点和页面类型分页查询
Page<CmsPage> findBySiteIdAndPageType(String siteId,String pageType, Pageable pageable);
}service
@Service
public class PageService {
@Autowired
CmsPageRepository cmsPageRepository;
//添加
public void testInsert(){
//定义实体类
CmsPage cmsPage = new CmsPage();
cmsPage.setSiteId("s01");
cmsPageRepository.save(cmsPage);
System.out.println(cmsPage);
}
//删除
public void testDelete() {
cmsPageRepository.deleteById("5b17a2c511fe5e0c409e5eb3");
}
//修改
public void testUpdate() {
Optional<CmsPage> optional = cmsPageRepository.findOne("5b17a34211fe5e2ee8c116c9");
//Optional是jdk1.8引入的类型,Optional是一个容器对象,它包括了我们需要的对象,
//使用isPresent方法判断所包 含对象是否为空,isPresent方法返回false则表示Optional包含对象为空,
//否则可以使用get()取出对象进行操作。
//Optional的优点是: 1、提醒你非空判断。 2、将对象非空检测标准化。
if(optional.isPresent()){
CmsPage cmsPage = optional.get();
cmsPage.setPageName("测试页面01");
cmsPageRepository.save(cmsPage);
}
}
}2.2 GridFS
GridFS是MongoDB提供的用于持久化存储文件的模块,CMS使用MongoDB存储数据,使用GridFS可以快速集成开发。
工作原理:
在GridFS存储文件是将文件分块存储,文件会按照256KB的大小分割成多个块进行存储,GridFS使用两个集合(collection)存储文件,一个集合是chunks, 用于存储文件的二进制数据;一个集合是fifiles,用于存储文件的元数 据信息(文件名称、块大小、上传时间等信息)。
从GridFS中读取文件要对文件的各各块进行组装、合并。
测试代码
@Autowired
GridFsTemplate gridFsTemplate;
@Autowired
GridFSBucket gridFSBucket;
// 存储
//向测试程序注入GridFsTemplate。
//文件存储成功得到一个文件id
//此文件id是fs.files集合中的主键。
//可以通过文件id查询fs.chunks表中的记录,得到文件的内容
public void testGridFs() throws FileNotFoundException {
//要存储的文件
File file = new File("d:/index_banner.html");
//定义输入流
FileInputStream inputStram = new FileInputStream(file);
//向GridFS存储文件
ObjectId objectId = = gridFsTemplate.store(inputStram, "轮播图测试文件01", "");
//得到文件ID
String fileId = objectId.toString();
System.out.println(file);
}
// 读取
//GridFSBucket用于打开下载流对象
@Configuration
public class MongoConfig {
@Value("${spring.data.mongodb.database}")
String db;
@Bean
public GridFSBucket getGridFSBucket(MongoClient mongoClient){
MongoDatabase database = mongoClient.getDatabase(db);
GridFSBucket bucket = GridFSBuckets.create(database);
return bucket;
}
}
public void queryFile() throws IOException {
String fileId = "5b9c54e264c614237c271a99";
//根据id查询文件
GridFSFile gridFSFile = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(fileId)));
//打开下载流对象
GridFSDownloadStream gridFSDownloadStream = gridFSBucket.openDownloadStream(gridFSFile.getObjectId());
//创建gridFsResource,用于获取流对象
GridFsResource gridFsResource = new GridFsResource(gridFSFile,gridFSDownloadStream);
//获取流中的数据
String s = IOUtils.toString(gridFsResource.getInputStream(), "UTF‐8");
System.out.println(s);
}
// 删除文件
public void testDelFile() throws IOException {
//根据文件id删除fs.files和fs.chunks中的记录
gridFsTemplate.delete(Query.query(Criteria.where("_id").is("5b32480ed3a022164c4d2f92")));
}2.3 mongodb原生操作
依赖
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>3.9.1</version>
</dependency>基于Document对象Demo
import com.mongodb.client.*;
import org.bson.Document;
import java.util.function.Consumer;
public class MongoDBDemo {
public static void main(String[] args) {
// 建立连接
MongoClient mongoClient = MongoClients.create("mongodb://172.16.55.185:27017");
// 选择数据库
MongoDatabase mongoDatabase = mongoClient.getDatabase("testdb");
// 选择表
MongoCollection<Document> userCollection = mongoDatabase.getCollection("user");
// 查询数据
userCollection.find().limit(10).forEach((Consumer<? super Document>)
document -> {
System.out.println(document.toJson());
});
// 查询数据
// userCollection.find().limit(10).forEach(new Consumer<Document>() {
// @Override
// public void accept(Document document) {
// System.out.println(document.toJson());
// }
// });
// 查询
this.mongoCollection.find(and(lte("age", 50),gte("id", 100))).sort(Sorts.descending("id"))
.projection(Projections.fields(Projections.include("id","age"),Projections.excludeId()))
.forEach((Consumer<? super Document>) document ->
{System.out.println(document.toJson());}
);
// 新增
Document document = new Document("id",10001).append("name", "张三").append("age", 30);
this.mongoCollection.insertOne(document);
// 更新
UpdateResult updateResult = this.mongoCollection.updateOne(eq("id", 10001), Updates.set("age", 25));
//删除
DeleteResult deleteResult = this.mongoCollection.deleteOne(eq("id",10001));
// 关闭连接
mongoClient.close();
}
}基于对象Demo
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Updates;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.codecs.pojo.PojoCodecProvider;
import org.bson.types.ObjectId;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class TestPerson {
MongoCollection<Person> personCollection;
@Before
public void init() {
//定义对象的解码注册器
CodecRegistry pojoCodecRegistry = CodecRegistries.
fromRegistries(MongoClientSettings.getDefaultCodecRegistry(),
CodecRegistries.fromProviders(PojoCodecProvider.builder().automatic(true).build()));
// 建立连接
MongoClient mongoClient =MongoClients.create("mongodb://172.16.55.185:27017");
// 选择数据库 并且 注册解码器
MongoDatabase mongoDatabase = mongoClient.getDatabase("testdb").withCodecRegistry(pojoCodecRegistry);
// 选择表
this.personCollection = mongoDatabase.getCollection("person", Person.class);
}
@Test
public void testInsert() {
Person person = new Person(ObjectId.get(), "张三", 20,new Address("人民路", "上海市", "666666"));
this.personCollection.insertOne(person);
System.out.println("插入数据成功");
}
@Test
public void testInserts() {
List<Person> personList = Arrays.asList(new Person(ObjectId.get(), "张三",20, new Address("人民路", "上海市", "666666")),
new Person(ObjectId.get(), "李四", 21, new Address("北京西路", "上海市", "666666")),
new Person(ObjectId.get(), "王五", 22, new Address("南京东路", "上海市", "666666")),
new Person(ObjectId.get(), "赵六", 23, new Address("陕西南路", "上海市", "666666")),
new Person(ObjectId.get(), "孙七", 24, new Address("南京西路", "上海市", "666666")));
this.personCollection.insertMany(personList);
System.out.println("插入数据成功");
}
@Test
public void testQuery() {
this.personCollection.find(Filters.eq("name", "张三"))
.forEach((Consumer<? super Person>) person -> {
System.out.println(person);
});
}
@Test
public void testUpdate() {
UpdateResult updateResult = this.personCollection.updateMany(Filters.eq("name", "张三"), Updates.set("age",22));
System.out.println(updateResult);
}
@Test
public void testDelete() {
DeleteResult deleteResult = this.personCollection.deleteOne(Filters.eq("name", "张三"));
System.out.println(deleteResult);
}
}2.4 地理位置索引
在MongoDB中,支持存储位置的经纬度,可以对其索引,通过算子操作,进行查找附近的数据。如:查找附近的人、附近的餐馆等。
测试数据
#进入容器
docker exec -it mongodb /bin/bash
use testdb
db.house.createIndex({loc:'2d'}) #为house表的loc字段创建地理2d索引
db.house.createIndex({hid:1},{unique:true}) #为house表的hid字段创建唯一索引
#通过百度api查询地址的经纬度
http://api.map.baidu.com/geocoder/v2/?address=上海xxxx&ak=jpfEH2etB2gutGyHpxVdWy8ZrTxbu0qj&output=json
#插入测试数据
db.house.insert({hid:1,title:'整租 · 南丹大楼 1居室 7500',loc:[121.4482236974557,31.196523937504549]})
db.house.insert({hid:2,title:'陆家嘴板块,精装设计一室一厅,可拎包入住诚意租。',loc:[121.51804613891443,31.238878702131506]})
db.house.insert({hid:3,title:'整租 · 健安坊 1居室 4050',loc:[121.4148310693774,31.16507733043528]})
db.house.insert({hid:4,title:'整租 · 中凯城市之光+视野开阔+景色秀丽+拎包入住',loc:[121.43528282056717,31.198687949417815]})
db.house.insert({hid:5,title:'整租 · 南京西路品质小区 21213三轨交汇 配套齐* 拎包入住',loc:[121.43528282056717,31.198687949417815]})
db.house.insert({hid:6,title:'祥康里 简约风格 *南户型 拎包入住 看房随时',loc:[121.47521508401232,31.23859308719981]})
#查询上海人民广场附近5公里的房源,人民广场的坐标为:121.48130241985999,31.235156971414239
db.house.find({loc:{$near:[121.48130241985999,31.235156971414239],$maxDistance:5/111.12 }})
#注意距离要除以111.2(1度=111.2km),跟普通查找的区别仅仅是多了两个算子$near和$maxDistance
#查询上海中山公园附近2公里的房源,中山公园的坐标为:121.42261657004589,31.229111410235285
db.house.find({loc:{$near:[121.42261657004589,31.229111410235285],$maxDistance:2/111.12 }})代码实现
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>3.9.1</version>
</dependency>配置
spring.data.mongodb.uri=mongodb://172.16.55.185:27017/testdb代码
import cn.itcast.haoke.dubbo.api.pojo.MongoHouse;
import cn.itcast.haoke.dubbo.api.vo.map.MapHouseDataResult;
import cn.itcast.haoke.dubbo.api.vo.map.MapHouseXY;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class MongoHouseService {
public static final Map<Integer, Double> BAIDU_ZOOM = new HashMap<>();
static {
BAIDU_ZOOM.put(19, 20d / 1000); //单位为km
BAIDU_ZOOM.put(18, 50d / 1000);
BAIDU_ZOOM.put(17, 100d / 1000);
BAIDU_ZOOM.put(16, 200d / 1000);
BAIDU_ZOOM.put(15, 500d / 1000);
BAIDU_ZOOM.put(14, 1d);
BAIDU_ZOOM.put(13, 2d);
BAIDU_ZOOM.put(12, 5d);
BAIDU_ZOOM.put(11, 10d);
BAIDU_ZOOM.put(10, 20d);
BAIDU_ZOOM.put(9, 25d);
BAIDU_ZOOM.put(8, 50d);
BAIDU_ZOOM.put(7, 100d);
BAIDU_ZOOM.put(6, 200d);
BAIDU_ZOOM.put(5, 500d);
BAIDU_ZOOM.put(4, 1000d);
BAIDU_ZOOM.put(3, 2000d);
BAIDU_ZOOM.put(2, 5000d);
BAIDU_ZOOM.put(1, 10000d);
}
@Autowired
private MongoTemplate mongoTemplate;
public MapHouseDataResult queryHouseData(Float lng, Float lat, Integer zoom) {
double distance = BAIDU_ZOOM.get(zoom) * 1.5 / 111.12; //1.5倍距离范围,根据实际需求调整
Query query = Query.query(Criteria.where("loc").near(new Point(lng,
lat)).maxDistance(distance));
List<MongoHouse> mongoHouses = this.mongoTemplate.find(query, MongoHouse.class);
List<MapHouseXY> list = new ArrayList<>();
for (MongoHouse mongoHouse : mongoHouses) {
list.add(new MapHouseXY(mongoHouse.getLoc()[0], mongoHouse.getLoc()[1]));
}
return new MapHouseDataResult(list);
}
}三、命令
3.1 数据库集合相关
mongo
#或
mongo --host=127.0.0.1 --port=27017
# 退出
<NolebasePageProperties />
exit
# 帮助
mongo --help
-- 查看所有的数据库
show dbs
-- 通过use关键字切换数据库
use admin
switched to db admin
-- 创建数据库
-- 说明:在MongoDB中,数据库是自动创建的,通过use切换到新数据库中,进行插入数据即可自动创建数据库
use testdb
-- 查看正在使用得数据库
db
-- 集合操作,集合类似关系型数据库中的表。
-- 显示创建 通常隐式创建
db.createCollection(name)
-- 插入数据
db.user.insert({id:1,name:'zhangsan'})
-- 查看表
show tables
-- 或
show collections
-- 删除集合(表)如果成功删除选定集合,则 drop() 方法返回 true,否则返回 false。
db.user.drop()
-- 删除数据库 先切换到要删除的数据中
db.dropDatabase() #删除数据库3.2 数据操作相关
-- 插入数据
-- 语法:db.COLLECTION_NAME.insert(document or array,{writeConcern: <document>,ordered: <boolean>}) 可以是文档或文档数组。
-- ordered 可选。如果为真,则按顺序插入数组中的文档,如果其中一个文档出现错误,MongoDB将返回而不处理数组中的其余文档。如果为假,则执行无序插入,如果其中一个文档出现错误,则继续处理数组中的主文档。在版本2.6+中默认为true
db.user.insert({id:1,username:'zhangsan',age:20})
-- 保存或更新,以_id判断
-- 如果使用save插入数据的时候,如果存在id,那么此时save方法就会变为修改,新值将原来的旧值覆盖。而insert会报错
db.user.save({})
-- 查询全部数据
db.user.find()
-- 查询第一条数据
db.user.findOne()
-- 查询数据
/*
格式db.user.find([1 query],[fields])
query :可选,使用查询操作符指定查询条件
fields :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。
如果你需要以易读的方式来读取数据,可以使用 pretty() 方法,语法格式如下:
>db.col.find().pretty()
pretty() 方法以格式化的方式来显示所有文档。
*/
-- 只查询id与username字段
db.user.find({},{id:1,username:1})
-- 查询数据条数
db.user.find().count()
-- 查询id为1的数据
db.user.find({id:1})
-- 查询小于等于21的数据
db.user.find({age:{$lte:21}})
-- and查询,age小于等于21并且id大于等于2
db.user.find({age:{$lte:21}, id:{$gte:2}})
-- 查询id=1 or id=2
db.user.find({$or:[{id:1},{id:2}]})
-- 分页查询:Skip()跳过几条,limit()查询条数
-- 跳过1条数据,查询2条数据
db.user.find().limit(2).skip(1)
-- 按照age倒序排序,-1为倒序,1为正序
db.user.find().sort({id:-1})
-- skip(), limilt(), sort()三个放在一起执行的时候,执行的顺序是先 sort(), 然后是 skip(),最后是显示的 limit(),和命令编写顺序无关。
-- 正则表达式查询
db.collection.find({field:/正则表达式/})
-- 包含查询 查询集合中userid字段包含1003或1004的文档 查询不包含为 $nin
db.comment.find({userid:{$in:["1003","1004"]}})
-- 条件连接查询
-- and $and:[ { },{ },{ } ]
-- or $or:[ { },{ },{ } ]
-- 更新数据
/*
db.collection.update(
<query>,
<update>,
[
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>
]
)
参数说明
query : update的查询条件,类似sql update查询内where后面的。
update : update的对象和一些更新的操作符(如inc...)等,也可以理解为sql update查询内set后面的
upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
writeConcern :可选,抛出异常的级别
*/
-- 局部修改 多个符合条件会只更新第一条,更新多条需要修改multi为true 列值增长还可用 $inc
db.user.update({id:1},{$set:{age:22}})
--注意:如果这样写,会删除掉其他的字段 更新不存在的字段,会新增字段 更新不存在的数据,默认不会新增数据
db.user.update({id:1},{age:25})
-- 删除数据
/*
db.collection.remove(
<query>,
{
justOne: <boolean>,
writeConcern: <document>
}
)
参数说明:
query :(可选)删除的文档的条件。
justOne : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配条件的文档。
writeConcern :(可选)抛出异常的级别。
*/
db.user.remove({age:25})
-- 删除所有数据
db.user.remove({})
--说明:为了简化操作,官方推荐使用deleteOne()与deleteMany()进行删除数据操作。
-- 删除单个
db.user.deleteOne({id:1})
-- 删除所有数据
db.user.deleteMany({})拓展 数据插入时注意事项
db.comment.insert({"articleid":"100000","content":"今天天气真好,阳光明媚","userid":"1001","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null})注意:
- comment集合如果不存在,则会隐式创建
- mongo中的数字,默认情况下是double类型,如果要存整型,必须使用函数NumberInt(整型数字),否则取出来就有问题了。
- 插入当前日期使用 new Date()
- 插入的数据没有指定 _id ,会自动生成主键值
- 如果某字段没值,可以赋值为null,或不写该字段。
- 文档中的键/值对是有序的。
- 文档中的值不仅可以是在双引号里面的字符串,还可以是其他几种数据类型(甚至可以是整个嵌入的文档)。
- MongoDB区分类型和大小写。
- MongoDB的文档不能有重复的键。
- 文档的键是字符串。除了少数例外情况,键可以使用任意UTF-8字符。
- 以下划线"_"开头的键是保留的(不是严格要求的)。
- 批量插入时,如果某条数据插入失败,将会终止插入,但已经插入成功的数据不会回滚掉。
3.3 条件查询示例
| 操作 | 格式 | 示例 | RDBMS中的类似语句 |
|---|---|---|---|
| 等于 | {<key>:<value> } | db.col.find({"by":"黑马程序员"}).pretty() | where by = '黑马程序员' |
| 小于 | {<key>:{$lt:<value>}} | db.col.find({"likes":{$lt:50}}).pretty() | where likes < 50 |
| 小于或等于 | {<key>:{$lte:<value>}} | db.col.find({"likes":{$lte:50}}).pretty() | where likes <=50 |
| 大于 | {<key>:{$gt:<value>}} | db.col.find({"likes":{$gt:50}}).pretty() | where likes > 50 |
| 大于或等于 | {<key>:{$gte:<value>}} | db.col.find({"likes":{$gte:50}}).pretty() | where likes >=50 |
| 不等于 | {<key>:{$ne:<value>}} | db.col.find({"likes":{$ne:50}}).pretty() | where likes !=50 |
四、索引
4.1 概述
引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。
这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。
索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构
4.2 索引分类
单字段索引
对于单个字段索引和排序操作,索引键的排序顺序(即升序或降序)并不重要,因为MongoDB可以在任何方向上遍历索引。
复合索引
复合索引中列出的字段顺序具有重要意义。例如,如果复合索引由 { userid: 1, score: -1 } 组成,则索引首先按userid正序排序,然后 在每个userid的值内,再在按score倒序排序。
地理空间索引(Geospatial Index)
为了支持对地理空间坐标数据的有效查询,MongoDB提供了两种特殊的索引:返回结果时使用平面几何的二维索引和返回结果时使用球面 几何的二维球面索引。
文本索引(Text Indexes)
MongoDB提供了一种文本索引类型,支持在集合中搜索字符串内容。这些文本索引不存储特定于语言的停止词(例如“the”、“a”、“or”), 而将集合中的词作为词干,只存储根词。
哈希索引(Hashed Indexes)
为了支持基于散列的分片,MongoDB提供了散列索引类型,它对字段值的散列进行索引。这些索引在其范围内的值分布更加随机,但只支 持相等匹配,不支持基于范围的查询。
4.3 索引相关命令
MongoDB在创建集合的过程中,在 _id 字段上创建一个唯一的索引,默认名字为 id ,该索引可防止客户端插入两个具有相同值的文 档,您不能在_id字段上删除此索引。 注意:该索引是唯一索引,因此值不能重复,即 _id 值不能重复的。在分片集群中,通常使用 _id 作为片键。
注意在 3.0.0 版本前创建索引方法为 db.collection.ensureIndex() ,之后的版本使用了 db.collection.createIndex() 方法, ensureIndex() 还能用,但只是 createIndex() 的别名。
-- 查看索引
-- 说明:1表示升序创建索引,-1表示降序创建索引。
db.user.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
}
]
-- 创建索引 后续可跟 options 选项,创建索引的选项
db.user.createIndex({'age':1})
--删除索引
db.user.dropIndex("age_1")
--删除除了_id之外的索引
db.user.dropIndexes()
-- 创建联合索引
db.user.createIndex({'age':1, 'id':-1})
-- 查看索引大小,单位:字节
db.user.totalIndexSize()| arameter | Type | Description |
|---|---|---|
| background | Boolean | 建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 "background" 可选参数。 "background" 默认值为false。 |
| unique | Boolean | 建立的索引是否唯一。指定为true创建唯一索引。默认值为false. |
| name | string | 索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名 称。 |
| dropDups | Boolean | 3.0+版本已废弃。在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引。默认值为 false. |
| sparse | Boolean | 对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索 引字段中不会查询出不包含对应字段的文档.。默认值为 false. |
| expireAfterSeconds | integer | 指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。 |
| v | index | version 索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。 |
| weights | document | 索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。 |
| default_language | string | 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语 |
| language_override | string | 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language. |
4.4 执行计划分析
MongoDB 查询分析可以确保我们建议的索引是否有效,是查询语句性能分析的重要工具。
db.user.find({age:{$gt:100},id:{$lt:200}}).explain()
/* 取值示例
"winningPlan" : { #最佳执行计划
"stage" : "FETCH", #查询方式,常见的有COLLSCAN/全表扫描、IXSCAN/索引扫描、
FETCH/根据索引去检索文档、SHARD_MERGE/合并分片结果、IDHACK/针对_id进行查询
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"age" : 1,
"id" : -1
},
*/五、安全
默认情况下,MongoDB实例启动运行时是没有启用用户访问权限控制的,也就是说,在实例本机服务 器上都可以随意连接到实例进行各种操作,MongoDB不会对连接客户端进行用户验证。
保障安全方式:
1)使用新的端口,默认的27017端口如果一旦知道了ip就能连接上,不太安全。
2)设置mongodb的网络环境,最好将mongodb部署到公司服务器内网,这样外网是访问不到的。公 司内部访问使用vpn等。
3)开启安全认证。认证要同时设置服务器之间的内部认证方式,同时要设置客户端连接到集群的账号 密码认证方式。需要在MongoDB实例启动时使用选项 --auth 或在指定启动 配置文件中添加选项 auth=true 。
5.1 相关概念
访问控制
MongoDB使用的是基于角色的访问控制(Role-Based Access Control,RBAC)来管理用户对实例的访问。 通过对用户授予一个或多个角色来控制用户访问数据库资源的权限和数据库操作的权限,在对用户分配 角色之前,用户无法访问实例。 在实例启动时添加选项 --auth 或指定启动配置文件中添加选项 auth=true 。
角色
在MongoDB中通过角色对用户授予相应数据库资源的操作权限,每个角色当中的权限可以显式指定, 也可以通过继承其他角色的权限,或者两都都存在的权限。
权限
权限由指定的数据库资源(resource)以及允许在指定资源上进行的操作(action)组成。
- 资源(resource)包括:数据库、集合、部分集合和集群;
- 操作(action)包括:对资源进行的增、删、改、查(CRUD)操作。
在角色定义时可以包含一个或多个已存在的角色,新创建的角色会继承包含的角色所有的权限。在同一 个数据库中,新创建角色可以继承其他角色的权限,在 admin 数据库中创建的角色可以继承在其它任意 数据库中角色的权限。
5.2 角色权限
角色权限命令查询
# 查询所有角色权限(仅用户自定义角色)
db.runCommand({ rolesInfo: 1 })
# 查询所有角色权限(包含内置角色)
db.runCommand({ rolesInfo: 1, showBuiltinRoles: true })
# 查询当前数据库中的某角色的权限
db.runCommand({ rolesInfo: "<rolename>" })
# 查询其它数据库中指定的角色权限
db.runCommand({ rolesInfo: { role: "<rolename>", db: "<database>" } }
# 查询多个角色权限
db.runCommand(
{
rolesInfo: [
"<rolename>",
{ role: "<rolename>", db: "<database>" },
...
]
}
)常用的内置角色:
- 数据库用户角色:read(可以读取指定数据库中任何数据。)、readWrite(可以读写指定数据库中任何数据,包括创建、重命名、删除集合。);
- 所有数据库用户角色:readAnyDatabase(可以读取所有数据库中任何数据(除了数据库config和local之外)。)、readWriteAnyDatabase(可以读写所有数据库中任何数据(除了数据库config和local之外)。)、 userAdminAnyDatabase(可以在指定数据库创建和修改用户(除了数据库config和local之外)。)、dbAdminAnyDatabase(可以读取任何数据库以及对数据库进行清理、修改、压缩、获取统 计信息、执行检查等操作(除了数据库config和local之外)。)
- 数据库管理角色:dbAdmin(可以读取指定数据库以及对数据库进行清理、修改、压缩、获取统 计信息、执行检查等操作。)、dbOwner()、userAdmin(可以在指定数据库创建和修改用户。);
- 集群管理角色:clusterAdmin(可以对整个集群或数据库系统进行管理操作。)、clusterManager、clusterMonitor、hostManager;
- 备份恢复角色:backup(备份MongoDB数据最小的权限。)、restore(从备份文件中还原恢复MongoDB数据(除了system.profile集合)的 权限。);
- 超级用户角色:root (超级账号,超级权限)
- 内部角色:system
5.3 创建用户
创建管理员
# 切换到admin库
use admin
# 创建系统超级用户 myroot,设置密码123456,设置角色root
db.createUser({user:"myroot",pwd:"123456",roles:[ { "role" : "root", "db" :"admin" } ]})
# 或
db.createUser({user:"myroot",pwd:"123456",roles:["root"]})
# 创建专门用来管理admin库的账号myadmin,只用来作为用户权限的管理
db.createUser({user:"myadmin",pwd:"123456",roles:[{role:"userAdminAnyDatabase",db:"admin"}]})
# 查看已经创建了的用户的情况:
db.system.users.find()将用户和权限信息保存到数据库对 应的表中。Mongodb存储所有的用户信息在admin 数据库的集合system.users中,保存用户名、密码 和数据库信息。
如果不指定数据库,则创建的指定的权限的用户在所有的数据库上有效,如 {role: "userAdminAnyDatabase", db:""}
创建普通用户
创建普通用户可以在没有开启认证的时候添加,也可以在开启认证之后添加,但开启认证之后,必须使 用有操作admin库的用户登录认证后才能操作。底层都是将用户信息保存在了admin数据库的集合 system.users中。
如果开启了认证后,登录的客户端的用户必须使用admin库的角色,如拥有root角色的myadmin用 户,再通过myadmin用户去创建其他角色的用户
# 创建(切换)将来要操作的数据库articledb,
use articledb
# 创建用户,拥有articledb数据库的读写权限readWrite,密码是123456
db.createUser({user: "bobo", pwd: "123456", roles: [{ role: "readWrite", db:"articledb" }]})
# db.createUser({user: "bobo", pwd: "123456", roles: ["readWrite"]})5.4 开启认证
服务端开启认证
- 方式一:服务端启动添加
--auth - 方式二:在mongod.conf配置文件中加入
security.authorization=enabled
客户端连接:
# 连接后
db.auth("myroot","123456")
# 连接时认证
/usr/local/mongodb/bin/mongo --host 180.76.159.126 --port 27017 --authenticationDatabase admin -u myroot -p 1234565.5 副本集环境开启验证
集群中每一个实例彼此连接的时候都检验彼此使用的证书的内容是否相同。 只有证书相同的实例彼此才可以访问,在keyfile身份验证中,副本集中的每个mongod实例都使用keyfile的内容作为共享密码,只有具有正确 密钥文件的mongod或者mongos实例可以连接到副本集。密钥文件的内容必须在6到1024个字符之 间,并且在unix/linux系统中文件所有者必须有对文件至少有读的权限。
使用客户端连接到mongodb集群时,开启访问授权。对于集群外部的访问。如通过可视化客户端, 或者通过代码连接的时候,需要开启授权。
集群关闭
# 客户端登录服务,注意,这里通过localhost登录,如果需要远程登录,必须先登录认证才行。
mongo --port 27017
# 告知副本集说本机要下线
rs.stepDown()
#切换到admin库
use admin
# 关闭服务
db.shutdownServer()分片集群是通过mongos添加的账号信息,只会保存到配置节点的服务中,具体的数据节点不保存账号信息,因 此,分片中的账号信息不涉及到同步问题。
在主节点或mongos添加管理员账户,副本集会自动同步。
创建副本集认证的key文件
所有副本集节点都必须要用同一份keyfile,一般是在一台机器上生成,然后拷贝到其他机器上,且必须 有读的权限,否则将来会报错,配置中指定keyFilesecurity.keyFile: keyFile路径
如果是分片集群,mongos的配置中不需要开启鉴权的(authorization:enabled的)配置,mongod是真正的保存数据的分片。mongos只做路由,不保存数据。所以所有的 mongod开启访问数据的授权authorization:enabled。这样用户只有账号密码正确才能访问到数据。
openssl rand -base64 90 -out ./mongo.keyfile
chmod 400 ./mongo.keyfile主节点或mongos添加普通用户。
六、集群
6.1 复制集
一组Mongodb复制集,就是一组mongod进程,这些进程维护同一个数据集合。复制集提供了数据冗余和高等级的可靠性,这是生产部署的基础。
目的
- 保证数据在生产部署时的冗余和可靠性,通过在不同的机器上保存副本来保证数据的不会因为单点损坏而丢失。能够随时应对数据丢失、机器损坏带来的风险。
- 还能提高读取能力,用户的读取服务器和写入服务器在不同的地方,而且,由不同的服务器为不同的用户提供服务,提高整个系统的负载。
机制
- 一组复制集就是一组mongod实例掌管同一个数据集,实例可以在不同的机器上面。实例中包含一个主导(Primary),接受客户端所有的写入操作,其他都是副本实例(Secondary),从主服务器上获得数据并保持同步。
- 主服务器很重要,包含了所有的改变操作(写)的日志。但是副本服务器集群包含有所有的主服务器数据,因此当主服务器挂掉了,就会在副本服务器上重新选取一个成为主服务器。
- 每个复制集还有一个仲裁者(Arbiter),仲裁者不存储数据,只是负责通过心跳包来确认集群中集合的数量,并在主服务器选举的时候作为仲裁决定结果。
原理
mongodb的复制至少需要两个节点。其中一个是主节点,负责处理客户端请求,其余的都是从节点(后备节点),负责复制主节点上的数据。mongodb各个节点常见的搭配方式为:一主一从、一主多从。主节点记录在其上的所有操作oplog(oplog,操作日志的简称,是local库下一个特殊的固定集合,它保存了与数据库中数据存储相关的所有操作记录。从节点就是通过查看主节点 的oplog这个集合来进行复制的。每个节点都有oplog,记录着从主节点复制过来的信息,这样每个成员都可以作为同步源给其他节点。),从节点定期查询主节点获取这些操作,然后对自己的数据副本执行这些操作,从而保证从节点的数据与主节点一致。
架构
基本的架构由3台服务器组成,一个三成员的复制集,由三个有数据,或者两个有数据,一个作为仲裁者。
- 三个存储数据的复制集
一个主,两个从库组成,主库宕机时,这两个从库都可以被选为主库。

当主库宕机后,两个从库都会进行竞选,其中一个变为主库,当原主库恢复后,作为从库加入当前的复制集群即可。

- 存在arbiter节点的复制集
一个主库,一个从库,可以在选举中成为主库,一个arbiter节点,在选举中,只进行投票,不能成为主库。

说明:由于arbiter节点没有复制数据,因此这个架构中仅提供一个完整的数据副本。arbiter节点只需要更少的资源,代价是更有限的冗余和容错。
当主库宕机时,将会选择从库成为主,主库修复后,将其加入到现有的复制集群中即可。

Primary选举
复制集通过replSetInitiate命令(或mongo shell的rs.initiate())进行初始化,初始化后各个成员间开始发送心跳消息,并发起Priamry选举操作,获得『大多数』成员投票支持的节点,会成为Primary,其余节点成为Secondary。
『大多数』的定义
假设复制集内投票成员数量为N,则大多数为 N/2 + 1,当复制集内存活成员数量不足大多数时,整个复制集将无法选举出Primary,复制集将无法提供写服务,处于只读状态。若票数相同,且都获得了“大多数”成员的投票支持的,数据新的节点获胜。 数据的新旧是通过操作日志oplog来对比的。票数可通过设置优先级(priority)来设置额外票数。优先级多少可以增加多少的票数。
成员说明
| 成员 | 说明 |
|---|---|
| Primary | Priamry的作用是接收用户的写入操作,将自己的数据同步给其他的Secondary。 |
| Secondary | 正常情况下,复制集的Seconary会参与Primary选举(自身也可能会被选为Primary),并从Primary同步最新写入的数据,以保证与Primary存储相同的数据。Secondary可以提供读服务,增加Secondary节点可以提供复制集的读服务能力,同时提升复制集的可用性。另外,Mongodb支持对复制集的Secondary节点进行灵活的配置,以适应多种场景的需求。 |
| Arbiter | Arbiter节点只参与投票,不能被选为Primary,并且不从Primary同步数据。比如你部署了一个2个节点的复制集,1个Primary,1个Secondary,任一节点宕机,复制集将不能提供服务了(无法选出Primary),这时可以给复制集添加一个Arbiter节点,即使有节点宕机,仍能选出Primary。Arbiter本身不存储数据,是非常轻量级的服务,当复制集成员为偶数时,最好加入一个Arbiter节点,以提升复制集可用性。 |
| Priority0 | Priority0节点的选举优先级为0,不会被选举为Primary。比如你跨机房A、B部署了一个复制集,并且想指定Primary必须在A机房,这时可以将B机房的复制集成员Priority设置为0,这样Primary就一定会是A机房的成员。(注意:如果这样部署,最好将『大多数』节点部署在A机房,否则网络分区时可能无法选出Primary) |
| Vote0 | Mongodb 3.0里,复制集成员最多50个,参与Primary选举投票的成员最多7个,其他成员(Vote0)的vote属性必须设置为0,即不参与投票。 |
| Hidden | Hidden节点不能被选为主(Priority为0),并且对Driver不可见。因Hidden节点不会接受Driver的请求,可使用Hidden节点做一些数据备份、离线计算的任务,不会影响复制集的服务。 |
| Delayed | Delayed节点必须是Hidden节点,并且其数据落后与Primary一段时间(可配置,比如1个小时)。因Delayed节点的数据比Primary落后一段时间,当错误或者无效的数据写入Primary时,可通过Delayed节点的数据来恢复到之前的时间点。 |
搭建复制集
#创建3个mongo容器
docker create --name mongo01 -p 27017:27017 -v mongo-data-01:/data/db mongo:4.0.3 --
replSet "rs0" --bind_ip_all
docker create --name mongo02 -p 27018:27017 -v mongo-data-02:/data/db mongo:4.0.3 --
replSet "rs0" --bind_ip_all
docker create --name mongo03 -p 27019:27017 -v mongo-data-03:/data/db mongo:4.0.3 --
replSet "rs0" --bind_ip_all
#启动容器
docker start mongo01 mongo02 mongo03#进入容器操作
docker exec -it mongo01 /bin/bash
#登录到mongo服务
mongo 172.16.55.185:27017
#初始化复制集集群
rs.initiate( {
_id : "rs0",
members: [
{ _id: 0, host: "172.16.55.185:27017" },
{ _id: 1, host: "172.16.55.185:27018" },
{ _id: 2, host: "172.16.55.185:27019" }
]
})
#响应
{
"ok" : 1, #成功
"operationTime" : Timestamp(1551619334, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1551619334, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
# 查看副本集的配置内容 configuration可选,如果没有配置,则使用默认主节点配置。
rs.conf(configuration)
# 查看副本集状态
rs.status()
# 添加副本从节点 host:地址,arbiterOnly,为true,表明添加的为仲裁者
rs.add(host, arbiterOnly)
# 添加仲裁从节点
rs.addArb(host)
# 开启从节点的读权限
rs.slaveOk()
#或
rs.slaveOk(true)故障转移
- 测试一:从节点宕机
- 集群依然可以正常使用,可以读写操作。
- 测试二:主节点宕机
- 选举出新的主节点继续提供服务
- 测试三:停止集群中的2个节点
- 当前集群无法选举出Priamry,无法提供写操作,只能进行读操作
增加arbiter节点
当集群中的节点数为偶数时,如一主一从情况下,任意一节点宕机都无法选举出Priamry,无法提供写操作,加入arbiter节点后即可解决该问题。
docker create --name mongo04 -p 27020:27017 -v mongo-data-04:/data/db mongo:4.0.3 --
replSet "rs0" --bind_ip_all
docker start mongo04
#在主节点执行
rs0:PRIMARY> rs.addArb("172.16.55.185:27020")
{
"ok" : 1,
"operationTime" : Timestamp(1551627454, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1551627454, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
#查询集群状态
rs.status()通过测试,添加arbiter节点后,如果集群节点数不满足N/2+1时,arbiter节点可作为“凑数”节点,可以选出主节点,继续提供服务。
6.2 分片集群
分片(sharding)是MongoDB用来将大型集合分割到不同服务器(或者说一个集群)上所采用的方法。尽管分片起源于关系型数据库分区,但MongoDB分片完全又是另一回事。
和MySQL分区方案相比,MongoDB的最大区别在于它几乎能自动完成所有事情,只要告诉MongoDB要分配数据,它就能自动维护数据在不同服务器之间的均衡。
简介
高数据量和吞吐量的数据库应用会对单机的性能造成较大压力,大的查询量会将单机的CPU耗尽,大的数据量对单机的存储压力较大,最终会耗尽系统的内存而将压力转移到磁盘IO上。
为了解决这些问题,有两个基本的方法: 垂直扩展和水平扩展。
- 垂直扩展:增加更多的CPU和存储资源来扩展容量。
- 水平扩展:将数据集分布在多个服务器上。水平扩展即分片。
分片为应对高吞吐量与大数据量提供了方法。使用分片减少了每个分片需要处理的请求数,因此,通过水平扩展,集群可以提高自己的存储容量和吞吐量。举例来说,当插入一条数据时,应用只需要访问存储这条数据的分片.
使用分片减少了每个分片存储的数据。例如,如果数据库1tb的数据集,并有4个分片,然后每个分片可能仅持有256GB的数据。如果有40个分片,那么每个切分可能只有25GB的数据。
优势
- 对集群进行抽象,让集群“不可见”
- MongoDB自带了一个叫做mongos的专有路由进程。mongos就是掌握统一路口的路由器,其会将客户端发来的请求准确无误的路由到集群中的一个或者一组服务器上,同时会把接收到的响应拼装起来发回到客户端。
- 保证集群总是可读写
- MongoDB通过多种途径来确保集群的可用性和可靠性。
- 将MongoDB的分片和复制功能结合使用,在确保数据分片到多台服务器的同时,也确保了每分数据都有相应的备份,这样就可以确保有服务器换掉时,其他的从库可以立即接替坏掉的部分继续工作。
- 使集群易于扩展
当系统需要更多的空间和资源的时候,MongoDB使我们可以按需方便的扩充系统容量。
架构
| 组件 | 说明 |
|---|---|
| Config Server | 存储集群所有节点、分片数据路由信息。默认需要配置3个Config Server节点。 |
| Mongos | 提供对外应用访问,所有操作均通过mongos执行。一般有多个mongos节点。数据迁移和数据自动平衡。 |
| Mongod | 存储应用数据记录。一般有多个Mongod节点,达到数据分片目的。 |
Mongos本身并不持久化数据,Sharded cluster所有的元数据都会存储到Config Server,而用户的数据会分散存储到各个shard。Mongos启动后,会从配置服务器加载元数据,开始提供服务,将用户的请求正确路由到对应的分片。
当数据写入时,MongoDB Cluster根据分片键设计写入数据。当外部语句发起数据查询时,MongoDB根据数据分布自动路由至指定节点返回数据。
集群中数据分布
在一个shard server内部,MongoDB会把数据分为chunks,每个chunk代表这个shard server内部一部分数据。chunk的产生,会有以下两个用途:
Splitting:当一个chunk的大小超过配置中的chunk size时,MongoDB的后台进程会把这个chunk切分成更小的chunk,从而避免chunk过大的情况
Balancing:在MongoDB中,balancer是一个后台进程,负责chunk的迁移,从而均衡各个shard server的负载,系统初始1个chunk,chunk size默认值64M,生产库上选择适合业务的chunk size是最好的。mongoDB会 自动拆分和迁移chunks。
chunk分裂及迁移
随着数据的增长,其中的数据大小超过了配置的chunk size,默认是64M,则这个chunk就会分裂成两个。数据的增长会让chunk分裂得越来越多。
这时候,各个shard 上的chunk数量就会不平衡。mongos中的一个组件balancer 就会执行自动平衡。把chunk从chunk数量最多的shard节点挪动到数量最少的节点。

- chunksize
chunk的分裂和迁移非常消耗IO资源;chunk分裂的时机:在插入和更新,读数据不会分裂。
- 小的chunksize:数据均衡是迁移速度快,数据分布更均匀。数据分裂频繁,路由节点消耗更多资源。
- 大的chunksize:数据分裂少。数据块移动集中消耗IO资源。
适合业务的chunksize是最好的。
chunkSize 对分裂及迁移的影响
- MongoDB 默认的 chunkSize 为64MB,如无特殊需求,建议保持默认值;chunkSize 会直接影响到 chunk 分裂、迁移的行为。
- chunkSize 越小,chunk 分裂及迁移越多,数据分布越均衡;反之,chunkSize 越大,chunk 分裂及迁移会更少,但可能导致数据分布不均。
- chunk 自动分裂只会在数据写入时触发,所以如果将 chunkSize 改小,系统需要一定的时间来将 chunk 分裂到指定的大小。
- chunk 只会分裂,不会合并,所以即使将 chunkSize 改大,现有的 chunk 数量不会减少,但 chunk 大小会随着写入不断增长,直到达到目标大小。
搭建集群
#创建3个config节点
docker create --name configsvr01 -p 17000:27019 -v mongoconfigsvr-data-01:/data/configdb mongo:4.0.3 --configsvr --replSet "rs_configsvr" --bind_ip_all
docker create --name configsvr02 -p 17001:27019 -v mongoconfigsvr-data-02:/data/configdb mongo:4.0.3 --configsvr --replSet "rs_configsvr" --bind_ip_all
docker create --name configsvr03 -p 17002:27019 -v mongoconfigsvr-data-03:/data/configdb mongo:4.0.3 --configsvr --replSet "rs_configsvr" --bind_ip_all
#启动服务
docker start configsvr01 configsvr02 configsvr03
#进去容器进行操作
docker exec -it configsvr01 /bin/bash
mongo 172.16.55.185:17000#集群初始化
rs.initiate(
{
_id: "rs_configsvr",
configsvr: true,
members: [
{ _id : 0, host : "172.16.55.185:17000" },
{ _id : 1, host : "172.16.55.185:17001" },
{ _id : 2, host : "172.16.55.185:17002" }
]
}
)
#创建2个shard集群,每个集群都有3个数据节点
#集群一
docker create --name shardsvr01 -p 37000:27018 -v mongoshardsvr-data-01:/data/db
mongo:4.0.3 --replSet "rs_shardsvr1" --bind_ip_all --shardsvr
docker create --name shardsvr02 -p 37001:27018 -v mongoshardsvr-data-02:/data/db
mongo:4.0.3 --replSet "rs_shardsvr1" --bind_ip_all --shardsvr
docker create --name shardsvr03 -p 37002:27018 -v mongoshardsvr-data-03:/data/db
mongo:4.0.3 --replSet "rs_shardsvr1" --bind_ip_all --shardsvr
#集群二
docker create --name shardsvr04 -p 37003:27018 -v mongoshardsvr-data-04:/data/db
mongo:4.0.3 --replSet "rs_shardsvr2" --bind_ip_all --shardsvr
docker create --name shardsvr05 -p 37004:27018 -v mongoshardsvr-data-05:/data/db
mongo:4.0.3 --replSet "rs_shardsvr2" --bind_ip_all --shardsvr
docker create --name shardsvr06 -p 37005:27018 -v mongoshardsvr-data-06:/data/db
mongo:4.0.3 --replSet "rs_shardsvr2" --bind_ip_all --shardsvr
#启动容器
docker start shardsvr01 shardsvr02 shardsvr03
docker start shardsvr04 shardsvr05 shardsvr06
#进去容器执行
docker exec -it shardsvr01 /bin/bash
mongo 172.16.55.185:37000
#初始化集群
rs.initiate(
{
_id: "rs_shardsvr1",
members: [
{ _id : 0, host : "172.16.55.185:37000" },
{ _id : 1, host : "172.16.55.185:37001" },
{ _id : 2, host : "172.16.55.185:37002" }
]
}
)
#初始化集群二
mongo 172.16.55.185:37003
rs.initiate(
{
_id: "rs_shardsvr2",
members: [
{ _id : 0, host : "172.16.55.185:37003" },
{ _id : 1, host : "172.16.55.185:37004" },
{ _id : 2, host : "172.16.55.185:37005" }
]
}
)
#创建mongos节点容器,需要指定config服务
docker create --name mongos -p 6666:27017 --entrypoint "mongos" mongo:4.0.3 --
configdb rs_configsvr/172.16.55.185:17000,172.16.55.185:17001,172.16.55.185:17002 --
bind_ip_all
docker start mongos
#进入容器执行
docker exec -it mongos bash
mongo 172.16.55.185:6666
#添加shard节点
sh.addShard("rs_shardsvr1/172.16.55.185:37000,172.16.55.185:37001,172.16.55.185:37002
")
sh.addShard("rs_shardsvr2/172.16.55.185:37003,172.16.55.185:37004,172.16.55.185:37005
")
#启用分片
sh.enableSharding("test")
# 分片状态
sh.status()
#设置分片规则,按照_id的hash进行区分 ,namespace 库名.集合名 key 引用分片的索引规范, unique,为true标识唯一索引 默认false。
sh.shardCollection(namespace, key, unique)
sh.shardCollection("test.order", {"_id": "hashed" })
#插入测试数据
use test
for (i = 1; i <= 1000; i=i+1){
db.order.insert({'id':i , 'price': 100+i})
}
#分别在2个shard集群中查询数据进行测试
db.order.count()
#集群操作(在mongos中执行)
use config
db.databases.find() #列出所有数据库分片情况
db.collections.find() #查看分片的片键
sh.status() #查询分片集群的状态信息
# 移除分片
db.runCommand( { removeShard: "myshardrs02" }七、安装
7.1 docker安装
#拉取镜像
docker pull mongo:4.0.3
#创建容器
docker create --name mongodb -p 27017:27017 -v /data/mongodb:/data/db mongo:4.0.3
#启动容器
docker start mongodb
#进入容器
docker exec -it mongodb /bin/bash
#使用MongoDB客户端进行操作
mongo
show dbs #查询所有的数据库7.2 普通安装(win)
MongoDB 预编译二进制包下载地址: https://www.mongodb.com/download-center#community
MongoDB的版本命名规范如:x.y.z; y为奇数时表示当前版本为开发版,如:1.5.2、4.1.13; y为偶数时表示当前版本为稳定版,如:1.6.3、4.0.10; z是修正版本号,数字越大越好。 详情:http://docs.mongodb.org/manual/release-notes/#release-version-numbers
启动方式
默认端口是27017,可以通过--port来指定端口。
mongod --dbpath=..\data\db配置文件方式启动服务
配置中如果使用双引号,会自动将内容转义,需要将对
\ 换成 / 或 \\如果路径中没有空格,则无需加引号。 不能以Tab分隔字段,可以将其转换为空格
# 在解压目录中新建 config 文件夹,该文件夹中新建配置文件 mongod.conf
# 更多配置 https://www.mongodb.com/docs/manual/reference/configuration-options/
storage:
#The directory where the mongod instance stores its data.Default Value is "\data\db" on Windows.
dbPath: D:\02_Server\DBServer\mongodb-win32-x86_64-2008plus-ssl-4.0.1\data7.3 普通安装(linux)
解压压缩包后
#数据存储目录
mkdir -p /mongodb/single/data/db
#日志存储目录
mkdir -p /mongodb/single/log
# 新建并修改配置文件
vi /mongodb/single/mongod.conf
systemLog:
#MongoDB发送所有日志输出的目标指定为文件
# #The path of the log file to which mongod or mongos should send all diagnostic logging information
destination: file
#mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
path: "/mongodb/single/log/mongod.log"
#当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
logAppend: true
storage:
#mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
##The directory where the mongod instance stores its data.Default Value is "/data/db".
dbPath: "/mongodb/single/data/db"
journal:
#启用或禁用持久性日志以确保数据文件保持有效和可恢复。
enabled: true
processManagement:
#启用在后台运行mongos或mongod进程的守护进程模式。
fork: true
net:
#服务实例绑定的IP,默认是localhost
bindIp: localhost,192.168.0.2
#bindIp
#绑定的端口,默认是27017
port: 27017
replication:
#副本集的名称
replSetName: myshardrs01
sharding:
#分片角色 shardsvr 和 configsvr
clusterRole: shardsvr
#指定配置节点副本集
configDB:myconfigrs/180.76.159.126:27019,180.76.159.126:27119,180.76.159.126:27219
# 启动服务 successfully则成功,失败查看配置文件格式
/usr/local/mongodb/bin/mongod -f /mongodb/single/mongod.conf
# 关闭服务
# 客户端登录服务,注意,这里通过localhost登录,如果需要远程登录,必须先登录认证才行。
mongo --port 27017
#切换到admin库
use admin
# 关闭服务
# 必须是在admin库下执行该关闭服务命令。
#如果没有开启认证,必须是从localhost登陆的,才能执行关闭服务命令。
#非localhost的、通过远程登录的,必须有登录且必须登录用户有对admin操作权限才可以。
db.shutdownServer()
# 数据损坏修复
# 删除lock文件:
rm -f /mongodb/single/data/db/*.lock
# 修复数据
/usr/local/mongdb/bin/mongod --repair --dbpath=/mongodb/single/data/db7.4 图形化界面客户端
下载地址:https://www.mongodb.com/download-center/v2/compass?initial=true