NoSQL概念和MongoDB

NoSQL

NoSQL => NOT ONLY SQL

现在我们来了解一下NoSQL的概念. 为了引入NoSQL, 我们先从现在的大数据来说说. 互联网公司每分钟甚至每秒都产生巨大的数据量. 并且数据量的增长速度还在越来越快, 因此, 数据的存储, 处理分析都会出现各种各样问题.

因此, 衍生出的数据库系统, 我们有:

  • 并行数据库: 水平切分, 分区查询
  • NoSQL数据库管理系统: 非关系模型, 分布式, 可能不支持ACID数据库设计范式
    • 数据模型简单
    • 弱一致性 (分布式通用概念)
    • 元数据和数据分离
    • 高吞吐量
    • 水平扩展能力和低端硬件集群
  • NewSQL数据库管理系统 (Combination of ACID and scalability of NoSQL)
    • Azure Cosmos DB
  • 云数据管理系统

我们现在很流行的大数据分析处理方案就是MapReduce, 例如Hadoop. 而Hadoop的数据库软件, HBase也是一个NoSQL数据库系统.

典型NoSQL数据存储模型有这些:

  • 列式存储: 以”列”为中心进行存储, 将同一列数据存储在一起
    • 应用场景: 在分布式文件系统上提供支持随机读写的分布式数据存储
    • 典型产品: HBase, Cassandra
    • 优点: 快速查询, 高扩展性, 易于实现分布式扩展.
  • 文档数据: 键值模型, 但是存储为文档
    • 应用场景: 非强事务需求的Web应用.
    • 典型产品: MongoDB, Elastic
    • 优点: 数据模型无需事先定义.
  • 键值数据: 基于哈希表实现的K-V
    • 应用场景: 内容缓存, 用于大量并行数据访问的高负载场景
    • 典型产品: Redis
    • 优点: 查询迅速
  • 图式数据: 图式结构
    • 应用场景: 社交网络, 推荐系统, 关系图谱
    • 典型数据: Neo4j
    • 优点: 图式计算

MongoDB

接下来我们就来说说MongoDB, 今天的主角.

MongoDB, 刚刚也已经提到, 是一个文档数据模型的NoSQL数据库. 在我们的epel源中就有mongodb的包 但是版本是2.6, 如果需要新版本就还是使用mongodb官方提供的yum仓库就好.

官方的文档写的很详细了, 熟悉一下CRUD操作直接来一遍就行. 这里做一个简单总结

插入操作:
db.coll.insert()

查找操作:

db.coll.find(cond, specified field) -> cond: { field: {operator}, ... }

其中, 比较操作使用$gt $gte $lt $lte $ne $in $nin 逻辑操作使用: $or $and $not

存在性判断使用$exists 值类型判断$type

MongoDB的查询语法还是很有意思的, 都是使用{}来描述一个操作, 通过operator: value来指明.

接下来我们再来说说说谈到数据库的时候, 就一定绕不开的一个话题, 那就是索引.

对于特别大的数据集, 索引就十分重要了. 索引会对一个特定字段来实现, 这个字段一般都是查找的条件. 我们将这些查找条件抽取出来, 放在一个专门的数据结构, 然后进行排序. 索引也是需要增删查改的, 当我们的原始数据的数据发生变化, 索引也需要发生变化. 因此我们说索引加速的查询操作, 但是加重了IO操作. 不过对于大量数据而言, 索引还是利远远大于弊的.

我们知道索引可以降序也可以升序. 另外, 根据索引查找的结果不是直接结果, 而是一个指向结果的位置. 索引是个数据结构, 因此也有不同的类型, 其中对于MySQL我们之前说过是B+ Tree. MongoDB同样, 也是使用的B+ Tree.

另外一种索引就是哈希索引. 哈希索引把对应字段每一个值当做键, 构建哈希, 使用这个哈希值来查找. 一般来说, 哈希索引会构建哈希桶, 现检索哈希桶, 再在桶里面查找对应的值.

另外还有空间索引和全文索引.

对于MongoDB而言, 它支持多种索引形式.

  • 单字段索引
  • 组合索引(多字段索引)
  • 多键索引 - 用于数组
  • 基于位置的索引, 空间索引
  • 对于文本的搜索, 全文索引
  • 哈希索引

MongoDB创建索引很简单, 直接对应的collection中使用createIndex就可以了. 这个过程我们也可以理解成是一个排序过程, MongoDB使用1来表示升序, -1表示降序. 创建各种索引的方式也很简单:

1
2
db.addresses.createIndex( { "xmpp_id": 1 }, { sparse: true } ) // 创建一个稀疏索引
db.members.createIndex( { "user_id": 1 }, { unique: true } ) // 创建一个unique约束

接下来我们回到服务端程序, mongod. 来看一下它的一些常用选项吧.

fork={true|false} mongod是否运行到后台

bind_ip=IP 监听地址

port=PORT 指定监听的端口

maxConns 最大连接数

logPath / syslog 可以指定是mongo自己来管理日志或者交给rsyslog来管理日志.

等等等…

MongoDB的复制功能

和MySQL一样, MongoDB也需要在某一个数据服务器离线之后, 仍然需要通过冗余继续提供数据服务. 因此需要复制功能. MongoDB有两种类型的复制功能, 其一就是主从复制master/slave, 另一个就是replica set复制集. 更常用的, 是复制集.

我们可以把replica set理解成是服务于同一个数据集的多个mongodb实例. 在MySQL中, 我们是使用的二进制日志来实现复制, 从节点去获取主节点的日志来进行重放. 而MongoDB的主节点将数据修改操作保存到了oplog中.

对于mongodb来讲, 当主节点不可用的时候, 两个从节点会对主节点进行心跳检测, 当一段时间内收不到心跳回复后, 这两个从节点就会触发选举操作, 谁的优先级高, 谁就会自动成为主节点. 这种模式下, 最好是存在一个第三节点, 用来参与仲裁过程. 这样当从节点联系不到主节点的时候, 就会去联系仲裁节点. 这个仲裁节点甚至可以是一个web服务, 只要安装mongodb服务即可, 并不需要参与到操作日志的保存和数据操作. 因此对于mongodb的复制过程, 应该至少三个节点, 且应该是奇数个节点.

在复制集中, 节点有这些分类:

第一种, 0优先级的节点, 冷备节点, 不会被选举成为主节点, 但可以参与选举.

基于这一种, 我们有被隐藏的从节点, 它也是一个0优先级的从节点, 且对客户端不可见.

第三种, 延迟复制的从节点, 同样, 也是一个0优先级的从节点, 且复制时间落后于主节点一个固定时长(数据一直是过期数据)

还有就是我们说过的仲裁节点.

说完这些节点, 我们就来说下MongoDB的复制架构, 一个就是oplog, 另一个是heartbeat.

我们先来说这个oplog, 他是一个大小固定的文件. 通常这个文件位置在local数据库中.

1
2
3
4
> use local
switched to db local
> show collections
startup_log

我们说过这个大小是固定的, 因此在一开始的时候, 这个空间(5%)就会预留出来了. (最小是990MB, 最大是50G, 当然这个大小是可以修改的) 另外, 这个日志文件是幂等性的. 不过当然, 既然大小是固定的, 我们就不能从一开始进行存储数据.

那么如何应用这个文件呢. 我们的从节点一般有这些方式来应用:

  • 初始同步
  • 回滚后追赶
  • 切片块迁移

local数据库存放了副本集的所有元数据和oplog.

当从节点联系不到主节点的时候, 就会触发选举, 除此之外, 还有这些事件可以触发选举机制:

  • 新副本集初始化时
  • 主节点收到stepDown()命令时
  • 某从节点有更高的优先级且已经满足成为主节点的其他所有条件
  • 主节点无法联系到副本集的”多数方”

MongoDB的分片功能

随着我们业务发展, 数据量会越来越大. 渐渐地, 单机资源CPU. MEM…会不够, 为了扩展MongoDB, 我们就可以将数据进行切片. MongoDB可以自己做分片, 而MySQL就需要使用额外的分片框架来实现了.
Diagram of a sample sharded cluster for production purposes.  Contains exactly 3 config servers, 2 or more ``mongos`` query routers, and at least 2 shards. The shards are replica sets.

在MongoDB内部, 有三个组件:

  • mongos: 这是一个router, 接受用户的查询请求, 相当于代理
  • config server: router需要知道查询的数据在哪一个分片上, 需要一个元数据服务器, 存放了索引的mapping
  • shard: 数据分片, 也成为mongod实例

当我们的sharding有节点挂掉了的时候, 就会出现数据不完整的情况, 因此, 我们需要做replica, 同样的, 作为元数据服务器, 如果它挂掉了, 整个集群就完蛋了, 所以这个也需要做复制冗余.

接下来我们来看一下分片的策略, 显然每一个分片应该保证尽量平均才行, MongoDB使用shard key来进行集合中文档的分片分布. 这个shard key是一个被索引的域, 也就是说, MongoDB通过索引排序来进行分片的划分.

当我们进行分片操作的时候, 我们可以选择shard key, 文档中的这个shard key的值就会决定它在不同分片中的分布. 我们之前说过, 索引十分类型的对不, 所以同理, 不同类型的索引, 也会决定不同类型的分片.

首先第一种是基于范围的切片:

Diagram of the shard key value space segmented into smaller ranges or chunks.

此时我们的分片是连续的, 在这种分片模式下, shard key越接近的文档就越有可能会被分在同样的chunk或者shard中. 这种分片模式是默认的.

另一种是基于哈希的分片:

Diagram of the hashed based segmentation.

显然, 哈希所以不存在排序, 但是在这种模式下, 我们可以使得数据写操作更分散.