0.MongoDB缺点

MongoDB的锁机制和一般关系数据库如 MySQL(InnoDB), Oracle 有很大的差异,InnoDB 和 Oracle 能提供行级粒度锁,而 MongoDB 只能提供 库级粒度锁,这意味着当 MongoDB 一个写锁处于占用状态时,其它的读写操作都得干等。

初看起来库级锁在大并发环境下有严重的问题,但是 MongoDB 依然能够保持大并发量和高性能,这是因为 MongoDB 的锁粒度虽然很粗放,但是在锁处理机制和关系数据库锁有很大差异,主要表现在:

MongoDB 没有完整事务支持,操作原子性只到单个 document 级别,所以通常操作粒度比较小;
MongoDB 锁实际占用时间是内存数据计算和变更时间,通常很快;
MongoDB 锁有一种临时放弃机制,当出现需要等待慢速 IO 读写数据时,可以先临时放弃,等 IO 完成之后再重新获取锁。

通常不出问题不等于没有问题,如果数据操作不当,依然会导致长时间占用写锁,比如下面提到的前台建索引操作,当出现这种情况的时候,整个数据库就处于完全阻塞状态,无法进行任何读写操作,情况十分严重。

解决问题的方法,尽量避免长时间占用写锁操作,如果有一些集合操作实在难以避免,可以考虑把这个集合放到一个单独的 MongoDB 库里,因为 MongoDB 不同库锁是相互隔离的,分离集合可以避免某一个集合操作引发全局阻塞问题。

nosql 数据库总结_高负载



1.Mongodb概念

MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统。

(1)在高负载的情况下,添加更多的节点,可以保证服务器性能。

(2)MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。

(3)MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。

(4)MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。

2.Mongodb特性

1、存储方式:虚拟内存+持久化。

2、查询语句:是独特的MongoDB的查询方式。

3、适合场景:事件的记录,内容管理或者博客平台等等。

4、架构特点:可以通过副本集,以及分片来实现高可用。

5、数据处理:数据是存储在硬盘上的,只不过需要经常读取的数据会被加载到内存中,将数据存储在物理内存中,从而达到高速读写。

6、成熟度与广泛度:新兴数据库,成熟度较低,Nosql数据库中最为接近关系型数据库,比较完善的DB之一,适用人群不断在增长。

高性能:无论规模大小,NoSQL (MongoDB) 数据库都旨在在吞吐量和延迟方面提供出色的性能。

灵活的数据模型:MongoDB 中的文档数据格式使存储和聚合任何类型的数据变得简单,而无需牺牲复杂的验证规则、数据访问或广泛的索引功能。

1. 面向文档

MongoDB是面向文档存储的数据库,它存储的是类似JSON的BSON格式的文档。文档是多个字段的键值对集合,可以包含多个值,如下面的示例:

{

"_id": ObjectId("541680b2c0e20c2cc5f5cf31"),

"name": "MongoDB",

"type": "document-oriented",

"count": 1,

"info": {

"x": 203,

"y": 102

}

}

2. 动态Schema

MongoDB是一种无模式的数据库,它允许存储的文档结构可以随意改变,为数据建模带来了很大的灵活性。例如,可以将一个文档中的文本字段替换为另一个文档的二进制对象,而不需要对现有数据进行修改。

3. 支持多种查询和索引

MongoDB支持丰富的查询语言,包括匹配、范围查询、正则表达式匹配等等。同时,它还支持多种类型的索引,如单键、复合键、全文索引等等,能够支持更加灵活的数据库查询。

4. 高可扩展性

MongoDB可以通过对集群进行分片和副本集的方式,来扩展数据库的存储容量和读写性能。MongoDB的副本集是将数据复制到多个服务器的数据库,而分片则是将数据分散到多个服务器上的数据库。



3.Mongodb使用场景

High performance - 对数据库高并发读写的需求。

Huge Storage - 对海量数据的高效率存储和访问的需求。

High Scalability && High Availability- 对数据库的高可扩展性和高可用性的需求

【适用场景】

(1)社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能。

(2)游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、高效率存储和访问。

(3)物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来。

(4)物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析。

(5)视频直播,使用 MongoDB 存储用户信息、点赞互动信息等。

适用场景特点:

数据量大

写入操作频繁(读写都很频繁)

价值较低的数据,对事务性要求不高

应用不需要事务及复杂 join 支持

新应用,需求会变,数据模型无法确定,想快速迭代开发

应用需要2000-3000以上的读写QPS(更高也可以)

应用需要TB甚至 PB 级别数据存储

应用发展迅速,需要能快速水平扩展

应用要求存储的数据不丢失

应用需要99.999%高可用

应用需要大量的地理位置查询、文本查询

4.Mongodb支持的数据类型

1. 字符串(String):存储文本。

2. 数字(Number):整数或浮点数。

3. 布尔值(Boolean):只有两个值,即true和false。

4. 对象ID(Object ID):12字节的文档唯一标识符。

5. 日期时间(Date):存储日期和时间。

6. 正则表达式(Regular Expression):存储正则表达式。

7. 数组(Array):存储值的序列。

8. 内嵌文档(Embedded Document):嵌套在其他文档中的文档。

9. Null:用于表示空或缺失的值。

10. 二进制数据(Binary data):存储二进制数据,如图像、音频和视频等。

11. 代码(Code):存储JavaScript代码。

12. 代码(Code with Scope):与Code类似,但也存储代码作用域。

13. 时间戳(Timestamp):用于内部使用,通常与操作日志一起使用。

除了基本数据类型,MongoDB还支持地理位置(GeoJSON)和文本(Text)数据类型,可以实现空间数据存储和全文索引

5.Mongodb索引机制

1:单字段索引

单字段索引是最基本的索引类型,它可以把集合中的某个字段(如_id)的值与该字段所对应文档的位置关联起来。

例如,以下代码用于在集合students中创建一个_id字段为单字段索引:

db.students.ensureIndex({_id: 1})

2:复合索引

复合索引是将多个字段的索引合并到一起,形成一个新的索引。复合索引的查找速度比单字段索引更快。

例如,以下代码用于在集合students中创建一个名字和年龄字段为复合索引:

db.students.ensureIndex({name: 1, age: -1})

在使用复合索引时,需要注意以下几点:

索引键的顺序非常重要,它会直接影响索引的效率。

复合索引包含了多个字段,查询时需要完全满足索引顺序才能进行查找。

3:唯一索引

唯一索引可以保证集合中某个或某些字段的值是唯一的。

例如,以下代码用于在集合students中创建一个_id字段为唯一索引:

db.students.ensureIndex({_id: 1}, {unique: true})

4:稀疏索引

稀疏索引只包含有索引键的文档,它可以跳过未包含索引键的文档。在某些场景下使用稀疏索引可以大大降低索引建立和查询所需空间。

例如,以下代码用于在集合students中创建一个address字段为稀疏索引:

db.students.ensureIndex({address: 1}, {sparse: true})

5:全文本索引

全文本索引是特殊的文本索引,它可以帮助MongoDB在文本数据中匹配出最相关的文档。

例如,以下代码用于在集合articles中创建一个content字段为全文本索引:

db.articles.ensureIndex({content: "text"})

单字段索引(Single Field Indexes)

复合索引(Compound Indexes)

多键索引(Multikey Indexes)

全文索引(text Indexes)

Hash 索引(Hash Indexes)

通配符索引(Wildcard Index)

2dsphere索引(2dsphere Indexes)


6.Mongodb查询优化

nosql 数据库总结_数据存储_02

为查询字段创建索引:在查询频繁的字段上创建索引,能够提高查询速度。

限制查询结果的数量:可以使用skip()和limit()方法分页,减少查询结果大小。

只查询需要的字段:使用projection或者特定字段查询的方式,避免查询所有字段,减少网络传输带宽。

使用聚合查询:使用聚合查询代替多个单独的查询语句,可以提高性能。

为数据结构优化设计:为了优化结构设计,尽可能减少重复的数据。

7.Mongodb查询过程

MongoDB使用db.collection.find()方法来执行查询操作。这个方法可以接受一系列的选项,以匹配需要查询的数据。

具体来说,我们可以使用以下操作符来执行匹配操作:

$eq:等于

$ne:不等于

$gt:大于

$gte:大于等于

$lt:小于

$lte:小于等于

$in:在某个列表中

$regex:匹配一个正则表达式

db.students.find().sort({age: 1}).skip(2).limit(2)  分页查询

nosql 数据库总结_数据存储_03

聚合操作能够处理数据记录并返回计算结果。聚合操作能将多个文档中的值组合起来,对成组数据执行各种操作,返回单一的结果。它相当于 SQL 中的 count(*) 组合 group by。对于 MongoDB 中的聚合操作,应该使用aggregate()方法。

db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)

8.Mongodb高可用

主从复制

nosql 数据库总结_高负载_04

MongoDB复制是将数据同步在多个服务器的过程。

复制提供了数据的冗余备份,并在多个服务器上存储数据副本,提高了数据的可用性, 并可以保证数据的安全性。

复制还允许您从硬件故障和服务中断中恢复数据。

mongodb的复制至少需要两个节点。其中一个是主节点,负责处理客户端请求,其余的都是从节点,负责复制主节点上的数据。

mongodb各个节点常见的搭配方式为:一主一从、一主多从。

主节点记录在其上的所有操作oplog,从节点定期轮询主节点获取这些操作,然后对自己的数据副本执行这些操作,从而保证从节点的数据与主节点一致。

MongoDB Oplog是MongoDB Primary和Secondary在复制建立期间和建立完成之后的复制介质,就是Primary中所有的写入操作都会记录到MongoDB Oplog中,然后从库会来主库一直拉取Oplog并应用到自己的数据库中

初始同步(Initial Sync):当一个新的从节点加入到主节点集群中时,它需要进行初始同步,即将主节点的数据集复制到自己的数据集上。初始同步会将主节点的全量数据复制到从节点,确保从节点与主节点的数据一致。

增量同步(Incremental Sync):在初始同步完成后,从节点会持续读取Oplog,将主节点的写操作应用到自己的数据集上,以保持与主节点的数据一致。通过增量同步,从节点不断追赶主节点的数据更新。

心跳机制:从节点会定期发送心跳信号给主节点,用于检测主节点是否存活。如果主节点出现故障,从节点会通过选举机制选择新的主节点,并继续复制数据。

复制集

副本集只能有一个主节点能够确认写入操作来接收所有写操作,并记录其操作日志中的数据集的所有更改(记录在oplog中)。在集群中,当主节点(master)失效,Secondary节点会变为master。

nosql 数据库总结_数据存储_05

1 优先级0型(Priority 0)节点

2 隐藏型(Hidden)节点

3 延迟型(Delayed)节点

4 投票型(Vote)节点以及不可投票节点

9.Mongodb选举

MongoDB在副本集中,会自动进行主节点的选举,主节点选举的触发条件:

主节点故障

主节点网络不可达(默认心跳信息为10秒)

人工干预(rs.stepDown(600))

一旦触发选举,就要根据一定规则来选主节点。

“大多数”的定义为:假设复制集内投票成员数量为N,则大多数为 N/2 + 1。例如:3个投票成员,则大多数的值是2。当复制集内存活成员数量不足大多数时,整个复制集将无法选举出Primary,复制集将无法提供写服务,处于只读状态。

若票数相同,且都获得了“大多数”成员的投票支持的,数据新的节点获胜

数据的新旧是通过操作日志oplog来对比的。

在获得票数的时候,优先级(priority)参数影响重大

可以通过设置优先级(priority)来设置额外票数。优先级即权重,取值为0-1000,相当于可额外增加0-1000的票数,优先级的值越大,就越可能获得多数成员的投票(votes)数。指定较高的值可使成员更有资格成为主要成员,更低的值可使成员更不符合条件。

10.Mongodb的GridFS机制

GridFS是一种将大型文件存储在MongoDB中的文件规范。使用GridFS可以将大文件分隔成多个小文档存放,这样我们能够有效的保存大文档,而且解决了BSON对象有限制的问题。

11.Mongodb分片

a.机器的磁盘不够用了

b.单个mongod已经不能满足些数据的性能需要了

c.想将大量数据放在内存中提高性能

分片是指将数据拆分,将其分散存在不同机器上的过程.有时也叫分区.将数据分散在不同的机器上,不需要功能强大的大型计算机就可以存储更多的数据,处理更大的负载.

使用几乎所有数据库软件都能进行手动分片,应用需要维护与若干不同数据库服务器的连接,每个连接还是完全独立的.应用程序管理不同服务器上的不同数据,存储查村都需要在正确的服务器上进行.这种方法可以很好的工作,但是也难以维护,比如向集群添加节点或从集群删除节点都很困难,调整数据分布和负载模式也不轻松.

MongoDB支持自动分片,可以摆脱手动分片的管理.集群自动切分数据,做负载均衡

MongoDB 分片是基于区域(range)的。所以一个集合(collection)中的所有的对象都被存放到一个块(chunk)中,默认块的大小是 64Mb。当数据容量超过64 Mb,才有可能实施一个迁移,只有当存在不止一个块的时候,才会有多个分片获取数据的选项。


12.Mongodb命名空间

MongoDB存储BSON对象在集(collection)中。数据库名字和集名字以句点连结起来叫做名字空间(namespace)。

13.Mongdb空值null

对象成员而言,是的。但是用户不能够添加空值(null)到数据库集(collection)由于空值不是对象。但是用户能够添加空对象{}。

14.Mongodb的fsync

更新操作不会立刻fsync到磁盘,磁盘写操作默认是延迟执行的。

写操作可能在两三秒(默认在60秒内)后到达磁盘。例如,如果一秒内数据库收到一千个对一个对象递增的操作,仅刷新磁盘一次。

15.Mongodb外键

默认MongoDB不支持主键和外键关系。 用Mongodb本身的API需要硬编码才能实现外键关联,不够直观且难度较大。

16.Mongod功能

mongod是处理MongoDB系统的主要进程。它处理数据请求,管理数据存储,和执行后台管理操作。当我们运行mongod命令意味着正在启动MongoDB进程,并且在后台运行。

MongoDB命令:

use database_name

切换数据库

db.myCollection.find().pretty()

格式化打印结果

db.getCollection(collectionName).find()

修改Collection名称

17.Mongodb事务

在4.0版本中,MongoDB支持多文档事务 副本集。

在4.2版本中,MongoDB引入了分布式事务, 增加了对分片上的多文档事务的支持 集群并包含对 副本集上的多文档事务。这种方式事务的实现主要是借助于两阶段提交协议(2PC)实现的。


nosql 数据库总结_数据存储_06

MongoDB 中的 WiredTiger 存储引擎是目前使用最广泛的,WiredTiger 存储引擎支持 read-uncommitted 、read-committed 和 snapshot 3 种事务隔离级别,MongoDB 启动时默认选择 snapshot 隔离;

  • Read-Uncommited:读未提交,一个事务还没提交时,它的变更就能被别的事务看到,读取未提交的数据也叫做脏读,WiredTiger 引擎在实现这个隔方式时,就是将事务对象中的 snap_object.snap_array 置为空即可,那么在读取 MVCC list 中的版本值时,总是读取到 MVCC list 链表头上的第一个版本数据,这样就总是能读取到最新的数据了;
  • read-committed:一个事务提交之后,它的变更才能被其他的事务看到;这种对于一个长事务可能存在多次读取,读取到的值不一样,因为每次读取都是读取的最新提交的数据,WiredTiger 引擎在实现该事务隔离级别,就是在事务在每次执行之前,都对系统机型一次快照,然后在这个事务快照中读取最新提交的数据;
  • snapshot:快照隔离方式,一个事务开始时,就进行一次快照,并且只会进行一次快照,这样事务看到的值提交版本,这个值在整个事务过程中看到的都是一样;

   WiredTiger 中对于事务的实现也是基于 MVCC 实现的,MVCC 可以提供基于某个时间点的快照,有了这个快照,就能确定当前事务能看到的数据了,通过这个来实现对应的事务隔离级别,这点也个人感觉和 mysql 中的 Read View 类似,不展开分析了;

18.Mongodb的chunk

MongoDB中,在使用到分片的时候,常常会用到chunk的概念,chunk是指一个集合数据中的子集,也可以简单理解成一个数据块,每个chunk都是基于片键的范围取值,区间是左闭右开。例如,我们的片键是姓名的第二个字母,包含了A-Z这26中可能,理想情况下,划分为26个chunk,其中每个字母开头的姓名记录即为一个chunk。

在数据写入的时候,mongos根据片键shard key的值来写入对应的chunk中,chunk可以表示的最小范围是单个唯一的shard key的值,只包含具体的单个片键值文档的chunk不能被分割,这个也比较容易理解,如果某个chunk只包含一个片键的值,如果对它进行分割,则代表一个片键值映射了2个chunk,下次遇到这个片键的文档时,mongos就不知道应该存放在哪个chunk当中了。

chunk的大小如何确定???

在MongoDB中,chunk的默认大小是64MB,可以增加或者减少chunk的大小。

chunk的大小不宜过小,如果chunk过小,好处是可以让数据更加均匀的分布,但是会导致chunk之间频繁的迁移,有一定的性能开销;同样的,chunk的大小不宜过大,过大的chunk size会导致数据分布不均匀,

chunk的分裂

当某个chunk的值达到了chunk所能表示的最大值的时候,这个时候chunk不能无限增长,需要通过分割的方法来减少chunk的大小,例如一个64MB的chunk分割成2个32MB的chunk,这样虽然增加了chunk的数量,但是带来的收益是单个chunk的缩小。

nosql 数据库总结_数组_07

更新一个正在被迁移的块(Chunk)上的文档时会发生什么?

更新操作会立即发生在旧的块(Chunk)上,然后更改才会在所有权转移前复制到新的分片上。

19 Mongodb的ObjectId

MongoDB中存储的文档必须有一个"_id"键。这个键的值可以是任何类型的,默认是个ObjectId对象。

在一个集合里面,每个文档都有唯一的"_id"值,来确保集合里面每个文档都能被唯一标识。

MongoDB采用ObjectId,而不是其他比较常规的做法(比如自动增加的主键)的主要原因,因为在多个 服务器上同步自动增加主键值既费力还费时。

20 Mongodb固定集合

MongoDB 固定集合(Capped Collections)是性能出色且有着固定大小的集合,对于大小固定,我们可以想象其就像一个环形队列,当集合空间用完后,再插入的元素就会覆盖最初始的头部的元素

21 Mongodb排序超过内存限制

2.1 限制排序数据量

一种解决方法是限制排序数据量。我们可以使用 limit 和 skip 命令来分页获取数据,然后对每页数据进行排序。这种方法可以避免将所有数据加载到内存中进行排序,从而减少内存消耗。

2.2利用索引进行排序

另一种解决方法是利用索引进行排序。我们可以在集合上创建一个排序所需的索引,从而避免将所有数据加载到内存中进行排序。

++++++++++++++++++++++++++++++++++++++

1.HBase数据库概念

HBase是一个分布式的、面向列的开源数据库;HBase不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库。另一个不同的是HBase基于列的而不是基于行的模式。

(1) Hbase一个分布式的基于列式存储的数据库,基于Hadoop的hdfs存储,zookeeper进行管理。

(2) Hbase适合存储半结构化或非结构化数据,对于数据结构字段不够确定或者杂乱无章很难按一个概念去抽取的数据。

(3) Hbase为null的记录不会被存储。

(4)基于的表包含rowkey,时间戳,和列族。新写入数据时,时间戳更新,同时可以查询到以前的版本。

(5) hbase是主从架构。hmaster作为主节点,hregionserver作为从节点。

2.HBase特性

1)容量巨大:一个表可以有数十亿行,上百万列;

2)无模式:每行都有一个可排序的主键和任意多的列,列可以根据需要动态的增加,同一张表中不同的行可以有截然不同的列;

3)面向列:面向列(族)的存储和权限控制,列(族)独立检索;

4)稀疏:空(null)列并不占用存储空间,表可以设计的非常稀疏;

5)数据多版本:每个单元中的数据可以有多个版本,默认情况下版本号自动分配,是单元格插入时的时间戳;

6)数据类型单一:Hbase 中的数据都是字符串,没有类型。

1. 数据类型

HBase 只有简单的字符串类型,所有的类型都是交由用户自己处理,它只保存字符串。而 RDBMS 有丰富的类型选择,如数值类型、字符串类型、时间类型等。

2. 数据操作

HBase 只有很简单的插入、査询、删除、清空等操作,表和表之间是分离的,没有复杂的表和表之间的关系,所以不能,也没有必要实现表和表之间的关联等操作。而 RDBMS 通常有各种各样的函数、连接操作等,表与表之间的关系也有多神。

3. 存储模式

HBase 是基于列(簇)存储的,每个列都由几个文件保存,不同列的文件是分离的。而 RDBMS 是基于表结构和行模式保存的。

4. 数据维护

确切地说, HBase 的更新操作不应该叫作更新,虽然一个主键或列对应新的版本,但它的旧版本仍然会保留、所以它实际上是插入了新的数据,而不是 RDBMS 中的替换修改。

5. 可伸缩性

因为 HBase 分布式数据库就是为了此目的而开发出来的,所以它能够轻松地増加或减少硬件数量,并且对错误的兼容性比较高。而 RDBMS 通常需要增加中间层才能实现类似的功能。

6. 具体应用

RDBMS 具有 ACID 特性,拥有丰富的 SQL。

HBase中有两个主要目录表,分别是ROOT和META。ROOT表的目的是跟踪META表,并且META表用于在HBase系统中存储区域。

Hbase的特性

基于列式存储模型,对于数据实现了高度压缩,节省存储成本

采用 LSM 机制而不是B(+)树,这使得HBase非常适合海量数据实时写入的场景

高可靠,一个数据会包含多个副本(默认是3副本),这得益于HDFS的复制能力,由RegionServer提供自动故障转移的功能

高扩展,支持分片扩展能力(基于Region),可实现自动、数据均衡

强一致性读写,数据的读写都针对主Region上进行,属于CP型的系统

易操作,HBase提供了Java API、RestAPI/Thrift API等接口

查询优化,采用Block Cache 和 布隆过滤器来支持海量数据的快速查找


3.HBase和Hive区别

hbase与hive都是架构在hadoop之上的,都是用hadoop作为底层存储

Hive是建立在Hadoop之上为了减少MapReduce jobs编写工作的批处理系统,HBase是为了支持弥补Hadoop对实时操作的缺陷的项目 。

3.想象你在操作RMDB数据库,如果是全表扫描,就用Hive+Hadoop,如果是索引访问,就用HBase+Hadoop 。

4.Hive query就是MapReduce jobs可以从5分钟到数小时不止,HBase是非常高效的,肯定比Hive高效的多。

5.Hive本身不存储和计算数据,它完全依赖于HDFS和MapReduce,Hive中的表纯逻辑。

6.hive借用hadoop的MapReduce来完成一些hive中的命令的执行。

7.hbase是物理表,不是逻辑表,提供一个超大的内存hash表,搜索引擎通过它来存储索引,方便查询操作。

8.hbase是列存储。所以Hbase可以对数据进行增改删等操作,但Hive是行的,只能追加数据。

9.hdfs作为底层存储,hdfs是存放文件的系统,而Hbase负责组织文件。

10.hive需要用到hdfs存储文件,需要用到MapReduce计算框架。

4.HBase适用场景

① 半结构化或非结构化数据

对于数据结构字段不够确定或杂乱无章很难按一个概念去进行抽取的数据适合用HBase。以上面的例子为例,当业务发展需要存储author的email,phone,address信息时RDBMS需要停机维护,而HBase支持动态增加。

② 记录非常稀疏

RDBMS的行有多少列是固定的,为null的列浪费了存储空间。而如上文提到的,HBase为null的Column不会被存储,这样既节省了空间又提高了读性能。

③ 多版本数据

如上文提到的根据Row key和Column key定位到的Value可以有任意数量的版本值,因此对于需要存储变动历史记录的数据,用HBase就非常方便了。比如上例中的author的Address是会变动的,业务上一般只需要最新的值,但有时可能需要查询到历史值。

④ 超大数据量

采用HBase就简单了,只需要加机器即可,HBase会自动水平切分扩展,跟Hadoop的无缝集成保障了其数据可靠性(HDFS)和海量数据分析的高性能(MapReduce)。

5.HBase中的rowkey

RowKey是HBase中的唯一标识符,用于在逻辑上对表单元进行分组,以确保所有具有相似RowKey的单元都放在同一服务器上。但是,内部RowKey是字节数组。

① Rowkey 长度原则

Rowkey 是一个二进制码流,Rowkey 的长度被很多开发者建议说设计在 10~100 个字节,不过建议是越短越好,不要超过 16 个字节。

(1)数据的持久化文件 HFile 中是按照 KeyValue 存储的,如果 Rowkey 过长比如 100个字节,1000 万列数据光 Rowkey 就要占用 100*1000 万=10 亿个字节,将近 1G 数据,这会极大影响 HFile 的存储效率;

(2)MemStore 将缓存部分数据到内存,如果 Rowkey 字段过长内存的有效利用率会降低,系统将无法缓存更多的数据,这会降低检索效率。因此 Rowkey 的字节长度越短越好。

(3)目前操作系统是都是 64 位系统,内存 8 字节对齐。控制在 16 个字节,8 字节的整数倍利用操作系统的最佳特性。

② Rowkey 散列原则

如果Rowkey 是按时间戳的方式递增,不要将时间放在二进制码的前面,建议将Rowkey的高位作为散列字段,由程序循环生成,低位放时间字段,这样将提高数据均衡分布在每个Regionserver 实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息将产生所有新数据都在一个 RegionServer 上堆积的热点现象,这样在做数据检索的时候负载将会集中在个别 RegionServer,降低查询效率。

③ Rowkey 唯一原则

必须在设计上保证其唯一性。

6.HBase的scan和get

HBase 的查询实现只提供两种方式:

1)按指定 RowKey 获取唯一一条记录,get 方法(org.apache.hadoop.hbase.client.Get)Get 的方法处理分两种 : 设置了 ClosestRowBefore 和没有设置 ClosestRowBefore 的rowlock。主要是用来保证行的事务性,即每个 get 是以一个 row 来标记的。一个 row 中可以有很多 family 和 column。

2)按指定的条件获取一批记录,scan 方法(org.apache.Hadoop.hbase.client.Scan)实现条件查询功能使用的就是 scan 方式。

7.HBase中的compact

在 hbase 中每当有 memstore 数据 flush 到磁盘之后,就形成一个 storefile,当 storeFile 的数量达到一定程度后,就需要将 storefile 文件来进行 compaction 操作。

Compact 的作用:

1>.合并文件

2>.清除过期,多余版本的数据

3>.提高读写数据的效率

HBase 中实现了两种 compaction 的方式:minor and major。

这两种 compaction 方式的区别是:

1、 Minor 操作只用来做部分文件的合并操作以及包括 minVersinotallow=0,并且设置 ttl 的过期版本清理,不做任何删除数据、多版本数据的清理工作。

2、 Major 操作是对 Region 下的 HStore 下的所有 StoreFile 执行合并操作,最终的结果是整理合并出一个文件。

8.HBase的架构

nosql 数据库总结_高负载_08

nosql 数据库总结_高负载_09

nosql 数据库总结_高负载_10

nosql 数据库总结_数据存储_11

其中 Master 节点是允许存在多个的,当多个 Master 节点共存时,只有一个 Master 是提供服务的,这种主备角色的"仲裁"由 ZooKeeper 实现。

      RegionServer是直接负责存储数据的服务器,RegionServer保存的表数据直接存储在Hadoop的HDFS上。RegionServer非常依赖ZooKeeper服务。ZooKeeper管理了HBase中所有的RegionServer的信息,包括具体的数据段存放在哪个 RegionServer上。 客户端每次与HBase连接,其实都是先与ZooKeeper通信,查询出具体需要连接哪个RegionServer,然后再连接到RegionServer。

nosql 数据库总结_数组_12

    RegionServer就是存放Region的容器,直观上说就是服务器上的一 个服务。当客户端从ZooKeeper获取RegionServer的地址后,它会直接从 RegionServer获取数据。

HMaster

  1. 为Region server分配region
  2. 负责region server的负载均衡
  3. 发现失效的region server并重新分配其上的region
  4. HDFS上的垃圾文件回收
  5. 处理schema更新请求

   HBase遵守主从架构的技术,由主HMaster和若干个从HRegionServer组成。HBase中的一张表的数据由若干个HRegion组成,也就是说每一个HRegion负责管理一张表中的一段数据,HRegion都是分布式的存在于HRegionServer中,所以说HRegion是HBase表中数据分布式存储的单位。

    那么一个HRegion中又是由若干个column family的数据组成;在HRegion中每个column family数据由一个store管理,每个store包含了一个memory store和若干个HFile组成,HFile的数据最终会落地到HDFS文件中,所以说HBase依赖HDFS。

    HBase中还有一部分元数据信息,比如HMaster的状态信息、HRegionServer的状态信息以及HRegion的状态信息等,这些信息都是存储在zookeeper集群中。

      HBase是一个分布式列式数据库,最基本的存储单位是列(column),一个列或者多个列形成一行(row)。在HBase中,这一行有三个列a、b、 c,下一个行也许是有4个列a、e、f、g。行跟行的列可以完全不一样,这个行的数据跟另外一个行的数据也可以存储在不同的机器上,甚至同一行内的列也可以存储在完全不同的机器上!

    每个行(row)都拥有唯一的行键(row key)来标定这个行的唯一性。每个列都有多个版本,多个版本的值存储在单元格(cell)中。

nosql 数据库总结_数组_13

nosql 数据库总结_数组_14

HBase的所有数据属性都是定义在列族上的。同一个表的不同列族可以定义完全不同的两套属性,所以从这个意义上来说,列族更像是传统关系数据库中的表,而表本身反倒变成只是存放列族的空壳了。

RegionServer的架构

nosql 数据库总结_数据存储_15

一个WAL:

    预写日志,WAL是Write-Ahead Log的缩写。从名字就可以看出它的用途,就是:预先写入。当操作到达Region的时候,HBase先不管三七二十一把操作写到WAL里面去。HBase会先把数据放到基于内存实现的Memstore里,等数据达到一定的数量 时才刷写(flush)到最终存储的HFile内。而如果在这个过程中 服务器宕机或者断电了,那么数据就丢失了。WAL是一个保险机 制,数据在写到Memstore之前,先被写到WAL了。这样当故障恢复的时候可以从WAL中恢复数据。

    数据到达 Region 的时候是先写入 WAL,然后再被加载到 Memstore,就算 Region 的机器宕掉了,由于 WAL 的数据是存储在 HDFS 上的,所以数据并不会丢失。

多个Region:

Region相当于一个数据分片。每一个Region都有起始rowkey和结束rowkey,代表了它所存储的row范围。

nosql 数据库总结_高负载_16

MemStore

由于 HDFS 上的文件不可修改,为了让数据顺序存储从而提高读取效率,HBase 使用了 LSM 树结构来存储数据,数据会先在 Memstore 中整理成 LSM 树,最后再刷写到 HFile 上。

优化数据的存储,比如一个数据添加后就马上删除了,这样在刷写的时候就可以直接不把这个数据写到 HDFS 上。

可以使用内存先把数据整理成顺序存放,然后再一起写入硬盘,这就是 Memstore 存在的意义。虽然 Memstore 是存储在内存中的,HFile 和 WAL 是存储在 HDFS 上的,但由于数据在写入 Memstore 之前,要先被写入 WAL,所以增加 Memstore 的大小并不能加速写入速度。

不要想当然地认为读取也是先读取 Memstore 再读取磁盘哟!读取的时候是有专门的缓存叫 BlockCache,这个 BlockCache 如果开启了,就是先读 BlockCache,读不到才是读 HFile+Memstore。


9.HBase中cell的结构

cell:由{row key, column(= + ), version}唯一确定的单元,cell中的数据是没有类型的,全部是字节码形式存储。

1. rowkey

rowkey是HBase中最重要的一个概念,它是每一行数据的唯一标识,它是一个由字节数组组成的字符串,它可以由用户自定义,也可以由系统自动生成。rowkey是HBase存储和检索数据的依据,它决定了HBase中数据的存储顺序,因此,rowkey的设计对于HBase的性能有着重要的影响。

2. column family

column family是HBase中的一个概念,它是一组由列名称组成的集合,用于描述一组相关的数据。在HBase中,一个表可以有多个column family,每个column family可以有多个column qualifier,它们是HBase中数据的最小存储单元。

3. column qualifier

column qualifier是HBase中的一个概念,它是column family中的一个列,它是column family中数据的最小存储单元,它可以有多个版本,每个版本都有一个时间戳。column qualifier用于描述column family中的一个列,它是HBase中数据的最小存储单元。

4. timestamp

timestamp是HBase中的一个概念,它是column family中每个column qualifier的一个版本,它是column family中数据的最小存储单元,它用于描述column family中每个column qualifier的一个版本,它是用于跟踪数据变化的一个版本号,它可以用于实现多版本控制。

Cell 由 {rowkey, column Family:column Qualifier, time Stamp} 唯一确定的单元。cell 中的数据是没有类型的,全部是字节码形式存储。

10.HBase的关键组件

1)Table:可理解为传统数据库中的一个表,但因为SchemaLess的设计,它较之传统数据库的表而言,在设计上更加灵活。

2)Region:将表横向切割为一个个子表,子表在HBase中被称之为Region。

3)RegionServer:数据服务进程,Region必须部署在某一个RegionServer上才可以提供读写服务。

4)HFile:HBase数据库在底层分布式文件系统中的文件组织形式。

5)Column Family:一些列的集合。不同的Column Family数据被存储在不同的路径中。MemStore:用来在内存中缓存一定大小的数据,达到设定的阈值后批量写入到底层文件系统中。数据是有序的

6)Zookeeper:HBase集群的调度器,可以用于将HBase RegionServer信息注册到zookeeper中,查询HBase RegionServer状态信息,HMaster启动时会将HBase系统表-ROOT-加载到zookeeper集群中,通过zookeeper集群可以获取当前系统表.META.的存储所对应的RegionServer信息。

7)Master,负责管理表,分配Region到各个RegionServer以及RegionServer Failover的处理。

11.HBase读写流程

nosql 数据库总结_数组_17

nosql 数据库总结_数据存储_18

读:

① HRegionServer 保存着 meta 表以及表数据,要访问表数据,首先 Client 先去访问zookeeper,从 zookeeper 里面获取 meta 表所在的位置信息,即找到这个 meta 表在哪个HRegionServer 上保存着。

② 接着 Client 通过刚才获取到的 HRegionServer 的 IP 来访问 Meta 表所在的HRegionServer,从而读取到 Meta,进而获取到 Meta 表中存放的元数据。

③ Client 通过元数据中存储的信息,访问对应的 HRegionServer,然后扫描所在HRegionServer 的 Memstore 和 Storefile 来查询数据。

④ 最后 HRegionServer 把查询到的数据响应给 Client。

写:

① Client 先访问 zookeeper,找到 Meta 表,并获取 Meta 表元数据。

② 确定当前将要写入的数据所对应的 HRegion 和 HRegionServer 服务器。

③ Client 向该 HRegionServer 服务器发起写入数据请求,然后 HRegionServer 收到请求并响应。

④ Client 先把数据写入到 HLog,以防止数据丢失。

⑤ 然后将数据写入到 Memstore。

⑥ 如果 HLog 和 Memstore 均写入成功,则这条数据写入成功

⑦ 如果 Memstore 达到阈值,会把 Memstore 中的数据 flush 到 Storefile 中。

⑧ 当 Storefile 越来越多,会触发 Compact 合并操作,把过多的 Storefile 合并成一个大的 Storefile。

⑨ 当 Storefile 越来越大,Region 也会越来越大,达到阈值后,会触发 Split 操作,将Region 一分为二。

nosql 数据库总结_数据存储_19

12.HBase客户端读写性能

1 开启 bloomfilter 过滤器,开启 bloomfilter 比没开启要快 3、4 倍

2 Hbase 对于内存有特别的需求,在硬件允许的情况下配足够多的内存给它

3 通过修改 hbase-env.sh 中的export HBASE_HEAPSIZE=3000 #这里默认为 1000m

4 增大 RPC 数量

通过修改 hbase-site.xml 中的 hbase.regionserver.handler.count 属性,可以适当的放大RPC 数量,默认值为 10 有点小

13.HBase实时查询

实时查询,可以认为是从内存中查询,一般响应时间在 1 秒内。HBase 的机制是数据先写入到内存中,当数据量达到一定的量(如 128M),再写入磁盘中, 在内存中,是不进行数据的更新或合并操作的,只增加数据,这使得用户的写操作只要进入内存中就可以立即返回,保证了 HBase I/O 的高性能。

14.HBase优化

1 减少调整

减少调整这个如何理解呢?HBase中有几个内容会动态调整,如region(分区)、HFile,所以通过一些方法来减少这些会带来I/O开销的调整。

Region

如果没有预建分区的话,那么随着region中条数的增加,region会进行分裂,这将增加I/O开销,所以解决方法就是根据你的RowKey设计来进行预建分区,减少region的动态分裂。

HFile

HFile是数据底层存储文件,在每个memstore进行刷新时会生成一个HFile,当HFile增加到一定程度时,会将属于一个region的HFile进行合并,这个步骤会带来开销但不可避免,但是合并后HFile大小如果大于设定的值,那么HFile会重新分裂。为了减少这样的无谓的I/O开销,建议估计项目数据量大小,给HFile设定一个合适的值。

2 减少启停

数据库事务机制就是为了更好地实现批量写入,较少数据库的开启关闭带来的开销,那么HBase中也存在频繁开启关闭带来的问题。

关闭Compaction,在闲时进行手动Compaction

因为HBase中存在Minor Compaction和Major Compaction,也就是对HFile进行合并,所谓合并就是I/O读写,大量的HFile进行肯定会带来I/O开销,甚至是I/O风暴,所以为了避免这种不受控制的意外发生,建议关闭自动Compaction,在闲时进行compaction

批量数据写入时采用BulkLoad

如果通过HBase-Shell或者JavaAPI的put来实现大量数据的写入,那么性能差是肯定并且还可能带来一些意想不到的问题,所以当需要写入大量离线数据时建议使用BulkLoad。

3 减少数据量

虽然我们是在进行大数据开发,但是如果可以通过某些方式在保证数据准确性同时减少数据量,何乐而不为呢?

开启过滤,提高查询速度

开启BloomFilter,BloomFilter是列族级别的过滤,在生成一个StoreFile同时会生成一个MetaBlock,用于查询时过滤数据

使用压缩:一般推荐使用Snappy和LZO压缩

4 合理设计

在一张HBase表格中RowKey和ColumnFamily的设计是非常重要,好的设计能够提高性能和保证数据的准确性

RowKey设计:应该具备以下几个属性

散列性:散列性能够保证相同相似的rowkey聚合,相异的rowkey分散,有利于查询

简短性:rowkey作为key的一部分存储在HFile中,如果为了可读性将rowKey设计得过长,那么将会增加存储压力

唯一性:rowKey必须具备明显的区别性

业务性:举些例子

假如我的查询条件比较多,而且不是针对列的条件,那么rowKey的设计就应该支持多条件查询

如果我的查询要求是最近插入的数据优先,那么rowKey则可以采用叫上Long.Max-时间戳的方式,这样rowKey就是递减排列

15 HBase列族设计规则

列族越少越好

Region 由一个或者多个 Store 组成,每个 Store 保存一个列族。当同一个 Region 内,如果存在大小列族的场景,即一个列族一百万行数据,另一个列族一百行数据,此时总数据量达到了 Region 分裂的阈值,那么不光那一百万行数据会被分布到不同的 Region 上,小列族的一百行数据也会分布到不同 region,问题就来了,扫描小列族都需要去不同的 Region 上读取数据,明显会影响性能。一个Region中有多个Store,如果每个CF的数据量分布不均匀时,比如CF1为100万,CF2为1万,则Region分裂时导致CF2在每个Region中的数据量太少,查询CF2时会横跨多个Region导致效率降低

MemStore 刷盘的过程大家也不陌生了,每个 Store 都会有自己对应的那一块 MemStore,但是MemStore 的触发是 Region 级别的,意思就是大列族对应的 MemStore 刷盘的时候会导致小列族对应的 MemStore 也刷盘,尽管小列族的 MemStore 还没有达到刷盘的阈值。这样又会导致小文件问题。

多列族优势:

HBase中数据时按列进行存储的,那么查询某一列族的某一列时就不需要全盘扫描,只需要扫描某一列族,减少了读I/O;

其实多列族设计对减少的作用不是很明显,适用于读多写少的场景。

劣势:

降低了写的I/O性能。原因如下:数据写到store以后是先缓存在memstore中,同一个region中存在多个列族则存在多个store,每个store都一个memstore,当其实memstore进行flush时,属于同一个region的store中的memstore都会进行flush,增加I/O开销。

多个CF代表有多个Store,也就是说有多个MemStore(2MB),也就导致内存的消耗量增大,使用效率下降。

Region 中的 缓存刷新 和 压缩 是基本操作,即一个CF出现缓存刷新或压缩操作,其它CF也会同时做一样的操作,当列族太多时就会导致IO频繁的问题。

16 HBase中的memStrore

1、什么是MemStore

HBase中,Region是集群节点上最小的数据服务单元,用户数据表由一个或多个Region组成。在Region中每个ColumnFamily的数据组成一个Store。每个Store由一个Memstore和多个HFile组成

2、MemStore的作用

HBase是基于LSM-Tree模型的,所有的数据更新插入操作都首先写入Memstore中(同时会顺序写到日志HLog中),达到指定大小之后再将这些修改操作批量写入磁盘,生成一个新的HFile文件,这种设计可以极大地提升HBase的写入性能;

HBase为了方便按照RowKey进行检索,要求HFile中数据都按照RowKey进行排序,Memstore数据在flush为HFile之前会进行一次排序,将数据有序化

为了减少 flush 过程对读写的影响,HBase 采用了类似于两阶段提交的方式,将整个 flush 过程分为三个阶段:

prepare 阶段:遍历当前 Region 中的所有 MemStore,将 MemStore 中当前数据集 kvset 做一个快照 snapshot,然后再新建一个新的 kvset,后期的所有写入操作都会写入新的 kvset 中。整个 flush 阶段读操作读 MemStore 的部分,会分别遍历新的 kvset 和 snapshot。prepare 阶段需要加一把 updateLock 对写请求阻塞,结束之后会释放该锁。因为此阶段没有任何费时操作,因此持锁时间很短。

flush 阶段:遍历所有 MemStore,将 prepare 阶段生成的 snapshot 持久化为临时文件,临时文件会统一放到目录.tmp下。这个过程因为涉及到磁盘IO操作,因此相对比较耗时。

commit 阶段:遍历所有的 MemStore,将 flush 阶段生成的临时文件移到指定的 Column family 目录下,生成对应的 Storefile(HFile) 和 Reader,把 Storefile 添加到 HStore 的 Storefiles 列表中,最后再清空 prepare 阶段生成的 snapshot。

频繁的 MemStore Flush 会创建大量的 HFile。在检索的时候,就不得不读取大量的 HFile,读性能会受很大影响。为预防打开过多 HFile 及避免读性能恶化(读放大),HBase 有专门的 HFile 合并处理(HFile Compaction Process),根据一定的策略,合并小文件和删除过期数据。

17 HBase的HFile(StoreFile)

数据存储在 HFile 中,以 Key/Value 形式。当 MemStore 累积了足够多的数据后,整个有序数据集就会被写入一个新的 HFile 文件到 HDFS 上。整个过程是一个顺序写的操作,速度非常快,因为它不需要移动磁盘头。(注意 HDFS 不支持随机修改文件操作,但支持 append 操作。)

nosql 数据库总结_数组_20

Data:数据块。每个 HFile 有多个 Data 块,我们存储在 HBase 表中的数据就在这里,Data 块其实是可选的,但是几乎很难看到不包含 Data 块的 HFile。

Meta:元数据块。Meta 块是可选的,Meta 块只有在文件关闭的时候才会写入。Meta 块存储了该 HFile 文件的元数据信息,在 v2 之前布隆过滤器(Bloom Filter)的信息直接放在 Meta 里面存储,v2 之后分离出来单独存储。

FileInfo:文件信息,其实也是一种数据存储块。FileInfo 是 HFile 的必要组成部分,是必选的,它只有在文件关闭的时候写入,存储的是这个文件的信息,比如最后一个 Key(LastKey),平均的 Key 长度(AvgKeyLen)等;

DataIndex:存储 Data 块索引信息的块文件。索引的信息其实也就是 Data 块的偏移值(offset),DataIndex 也是可选的,有 Data 块才有 DataIndex

MetaIndex:存储 Meta 块索引信息的块文件。MetaIndex 块也是可选的,有 Meta 块才有 MetaIndex。

Trailer:必选的,它存储了 FileInfo、DataIndex、MetaIndex 块的偏移值。

nosql 数据库总结_数组_21

一个 KeyValue 类里面最后一个部分是存储数据的 Value,而前面的部分都是存储跟该单元格相关的元数据信息。如果你存储的 value 很小,那么这个单元格的绝大部分空间就都是 rowkey、column family、column 等的元数据,所以大家的列族和列的名字如果很长,大部分的空间就都被拿来存储这些数据了。


18 HBase如何处理写入失败

在像HBase这样的大型分布式系统中,故障很常见。但是,我们可以使用HBase预写日志(WAL)防止数据失败。属于HBase群集的每个服务器都维护一个WAL,以记录HBase数据中发生的更改。除非针对每个写操作在WAL中写入一个新条目,否则它将不会被视为成功。此外,Hadoop分布式文件系统(HDFS)支持HBase。因此,如果HBase发生故障,则将使用WAL从MemStore中清除未刷新的数据。

19 HBase的HA过程

宕机分为 HMaster 宕机和 HRegisonserver 宕机。

如果是 HRegisonserver 宕机, HMaster 会将其所管理的 region 重新分布到其他活动的 RegionServer 上,由于数据和日志都持久在 HDFS中,该操作不会导致数据丢失。所以数据的一致性和安全性是有保障的。并且会对 HRegionServer 上存在 memstore 中还未持久化到磁盘中的数据进行恢复;

如果是 HMaster 宕机,会通过Zookeeper 的 Master Election 机制重新选出一个正在运行的Master 进程作为活跃节点,继续提供服务。

20 HBase为什么写比读快

Hbase底层的存储引擎为LSM-Tree(Log-Structured Merge-Tree)。LSM核心思想的核心就是放弃部分读能力,换取写入的最大化能力。LSM Tree它的核心思路其实非常简单,就是假定内存足够大,因此不需要每次有数据更新就必须将数据写入到磁盘中,而可以先将最新的数据驻留在内存中,等到积累到最后多之后,再使用归并排序的方式将内存内的数据合并追加到磁盘队尾(因为所有待排序的树都是有序的,可以通过合并排序的方式快速合并到一起)。另外,写入时候将随机写入转换成顺序写,数据写入速度也很稳定。

不过读取的时候稍微麻烦,需要合并磁盘中历史数据和内存中最近修改操作,所以写入性能大大提升,读取时可能需要先看是否命中内存,否则需要访问较多的磁盘文件。极端的说,基于LSM树实现的HBase的写性能比MySQL高了一个数量级,读性能低了一个数量级。

LSM树原理把一棵大树拆分成N棵小树,它首先写入内存中,随着小树越来越大,内存中的小树会flush到磁盘中,磁盘中的树定期可以做merge操作,合并成一棵大树,以优化读性能。

21 HBase Region大小

Region过小会发生多次compaction,将数据读一遍并重写一遍到hdfs上,占用io,region过大会造成多次split,region 会下线,影响访问服务。

22 HBase常用命令

get:查询操作

put:插入操作

delete:删除操作

scan:扫描所有记录

23 HBase HRegionServer作用

1.维护分配到的region,处理对这些region的IO请求

2.负责切分达到阀值的region

3.每个RegionServer各自保管自己的Hlog

24 HBase 数据热点问题

1、数据热点问题

产生数据热点问题的原因:

(1)Hbase的数据是按照字典排序的,当大量连续的rowkey集中写到个别的region,各个region之间实际分布不均衡;

(2)创建表时没有提前预分区,创建的表默认只有一个region,大量的数据写入当前region;

(3)创建表已经提前预分区,但是设计的rowkey没有规律可循。

热点问题的解决方案:

(1)随机数+业务主键,如果更好的让最近的数据get到,可以加上时间戳;

(2)Rowkey设计越短越好,不要超过10-100个字节;

(3)映射regionNo,这样既可以让数据均匀分布到各个region中,同时可以根据startkey和endkey可以个get到同一批数据。

25 HBase的缓存与批处理

setCache用于设置缓存,即设置一次RPC请求可以获取多行数据。对于缓存操作,如果行的数据量非常大,多行数据有可能超过客户端进程的内存容量,由此引入批量处理这一解决方案。

setBatch 用于设置批量处理,批量可以让用户选择每一次ResultScanner实例的next操作要取回多少列,例如,在扫描中设置setBatch(5),则一次next()返回的Result实例会包括5列。如果一行包括的列数超过了批量中设置的值,则可以将这一行分片,每次next操作返回一片,当一行的列数不能被批量中设置的值整除时,最后一次返回的Result实例会包含比较少的列,如,一行17列,batch设置为5,则一共返回4个Result实例,这4个实例中包括的列数分别为5、5、5、2。

26 HBase rowkey设计原则

1.唯一性

  rowkey必须是唯一的,确保每个行都可以被准确定位。通常,唯一性是通过在rowkey中包含唯一标识符或时间戳来实现的。

2.散列分布

  HBase中的数据是通过rowkey的散列值进行分布存储的,这意味着好的rowkey设计应该在散列分布上均匀,避免热点问题。热点问题可能导致数据不平衡,从而影响性能。

3.顺序性

  HBase在存储数据时,相邻rowkey的数据通常会被存储在相邻的地方,因此在设计rowkey时,考虑到查询需求,优化顺序性可以提高扫描效率。例如,使用时间戳作为rowkey,可以方便地按时间范围进行查询。

4.简洁性

  rowkey的设计应该尽量简洁,因为它直接影响数据存储的大小和读写性能。避免过长的rowkey可以减少存储空间的占用,并提高读写性能。

5.避免频繁更新

  由于HBase是按列族存储的,频繁更新相同的rowkey可能会导致数据存储的碎片化,影响性能。在设计rowkey时,避免频繁更新同一行的数据是一个考虑因素。

6.前缀设计

  如果rowkey包含多个部分信息,可以考虑将常用的查询字段放在rowkey的前缀部分,这样可以更快地定位到相关数据。

7.考虑数据倾斜

  在设计rowkey时,需要考虑数据倾斜的情况。如果某些rowkey的查询频率远高于其他,可能会导致一些Region服务器负载过重。可以通过哈希前缀、随机数或其他技术来解决数据倾斜的问题。

++++++++++++++++++++++++++++++++++++

1.Cassandra 特点

弹性可扩展性 - Cassandra是高度可扩展的; 它允许添加更多的硬件以适应更多的客户和更多的数据根据要求。

始终基于架构 - Cassandra没有单点故障,它可以连续用于不能承担故障的关键业务应用程序。

快速线性性能 - Cassandra是线性可扩展性的,即它为你增加集群中的节点数量增加你的吞吐量。因此,保持一个快速的响应时间。

灵活的数据存储 - Cassandra适应所有可能的数据格式,包括:结构化,半结构化和非结构化。它可以根据您的需要动态地适应变化的数据结构。

便捷的数据分发 - Cassandra通过在多个数据中心之间复制数据,可以灵活地在需要时分发数据。

事务支持 - Cassandra支持属性,如原子性,一致性,隔离和持久性(ACID)。

快速写入 - Cassandra被设计为在廉价的商品硬件上运行。 它执行快速写入,并可以存储数百TB的数据,而不牺牲读取效率。

2.Cassandra使用场景

  • 数据写入操作密集
  • 数据修改操作很少
  • 通过主键查询
  • 需要对数据进行分区存储
  • 存储日志型数据
  • 类似物联网的海量数据
  • 对数据进行跟踪

3.Cassandra基本概念

nosql 数据库总结_数组_22

nosql 数据库总结_数组_23

ColumnFamily 的2种类型

静态column family(static column family)

静态的column family,字段名是固定的,比较适合对于这些column都有预定义的元数据

动态column family(dynamic column family)

动态的column family,字段名是应用程序计算出来并且提供的,所以column family只能定义这些字段的类型,无法不可以指定这些字段的名字和值,这些名字和值是由应用程序插入某字段才得出的

2)Row key

ColumnFamily 中的每一行都用Row Key(行键)来标识,这个相当于关系数据库表中的主键,并且总是被索引的

3)主键

Cassandra可以使用PRIMARY KEY 关键字创建主键,主键分为2种

Single column Primary Key

如果 Primary Key 由一列组成,那么称为 Single column Primary Key

Composite Primary Key

如果 Primary Key 由多列组成,那么这种情况称为 Compound Primary Key 或 Composite Primary Key

nosql 数据库总结_高负载_24

nosql 数据库总结_数据存储_25

4.Cassandra数据类型

nosql 数据库总结_数组_26

nosql 数据库总结_高负载_27

nosql 数据库总结_数据存储_28

nosql 数据库总结_数据存储_29

++++++++++++++++++++++++++++++++++++++++

1.RocksDB概念

RocksDB 是一种可持久化的、内嵌型 kv 存储。它是为了存储大量的 key 及其对应 value 设计出来的数据库。可以基于这种简单 kv 数据模型来构建倒排索引、文档数据库、SQL 数据库、缓存系统和消息代理等复杂系统。

RocksDB 是 2012 年从 Google 的 LevelDB fork 出来的分支,并针对跑在 SSD 上的服务器进行了优化。目前,RocksDB 由 Meta 开发和维护。

基本接口

RocksDB 以 kv 对集合的形式存储数据, key 和 value 是任意长度的字节数组(byte array),因此都是没有类型的。RocksDB 提供了很少的几个用于修改 kv 集合的函数底层接口:

  • put(key, value):插入新的键值对或更新已有键值对
  • merge(key, value):将新值与给定键的原值进行合并
  • delete(key):从集合中删除键值对

通过点查来获取 key 所关联的 value:

  • get(key)

通过迭代器可以进行范围扫描——找到特定的 key,并按顺序访问该 key 后续的键值对:

  • iterator.seek(key_prefix); iterator.value(); iterator.next()

单独的 Get/Put/Delete 是原子操作,要么成功要么失败,不存在中间状态。

如果需要进行批量的 Get/Put/Delete 操作且需要操作保持原子属性,则可以使用 WriteBatch。

WriteBatch 还有一个好处是保持加快吞吐率。

2.RocksDB数据结构

Log-structured merge-tree

nosql 数据库总结_数组_30

RocksDB 的核心数据结构被称为日志结构合并树 (LSM-Tree)。它是一种树形的数据结构,由多个层级组成,每层的数据按 key 有序。LSM-Tree 主要设计用来应对写入密集型工作负载,并于 1996 年在同名论文 The Log-Structured Merge-Tree (LSM-Tree) 被大家所知。

LSM 树而且通过批量存储技术规避磁盘随机写入问题。 LSM 树的设计思想非常朴素, 它的原理是把一颗大树拆分成N棵小树, 它首先写入到内存中(内存没有寻道速度的问题,随机写的性能得到大幅提升),在内存中构建一颗有序小树,随着小树越来越大,内存的小树会flush到磁盘上。磁盘中的树定期可以做 merge 操作,合并成一棵大树,以优化读性能。

RocksDB 的 LSM 体现在多 level 文件格式上,最热最新的数据尽在 L0 层,数据在内存中,最冷最老的数据尽在 LN 层,数据在磁盘或者固态盘上。

LSM-Tree 的最高层保存在内存中,包含最近写入的数据。其他较低层级的数据存储在磁盘上,层数编号从 0 到 N 。第 0 层 L0 存储从内存移动到磁盘上的数据,第 1 层及以下层级则存储更老的数据。通常某层的下个层级在数据量上会比该层大一个数量级,当某层数据量变得过大时,会合并到下一层。

nosql 数据库总结_数组_31

 LSM-Tree 的概念适用于大多数底层采用此技术实现的数据库(例如 Bigtable、HBase、Cassandra、ScyllaDB、LevelDB 和 MongoDB WiredTiger)。

3.RocksDB 写入数据

MemTable

LSM-Tree 的顶层被称为 MemTable。MemTable 是一个内存缓冲区,在键值对写入磁盘之前,Memtable 会缓存住这些键值对。所有插入和更新操作都会过 MemTable。当然也包括删除操作:不过,在 RocksDB 中,并不会直接原地修改键值对,而是通过插入墓碑记录(tombstone )来进行标记删除。

MemTable 具有可配置的字节数限制。当一个 MemTable 变满时,就会切到一个新的 MemTable,同时原 MemTable 变为不可修改状态。

预写日志

无论是在进程意外崩溃退出还是计划内重启时,其内存中的数据都会丢失。为了防止数据丢失,保证数据的持久化,除了 MemTable 之外,RocksDB 会将所有更新写入到磁盘上的预写日志(WAL,Write-ahead log)中。这样,在重启后,数据库可以回放日志,进而恢复 MemTable 的原始状态。

WAL 是一个只允许追加的文件,包含一组更改记录序列。每个记录包含键值对、记录类型(Put / Merge / Delete)和校验和(checksum)。与 MemTable 不同,在 WAL 中,记录不按 key 有序,而是按照请求到来的顺序被追加到 WAL 中。

Flush

RocksDB 使用一个专门的后台线程定期地把不可变的 MemTable 从内存持久化到磁盘。一旦刷盘(flush)完成,不可变的 MemTable 和相应的 WAL 就会被丢弃。RocksDB 开始写入新的 WAL、MemTable。每次刷盘都会在 L0 层上产生一个新的 SST 文件。该文件一旦写入磁盘后,就不再会修改。

RocksDB 的 MemTable 的默认基于跳表实现。该数据结构是一个具有额外采样层的链表,从而允许快速、有序地查询和插入数据。有序性使得 MemTable 刷盘时更高效,因为可以直接按顺序迭代键值对顺序写入磁盘。将随机写变为顺序写是 LSM-Tree 的核心设计之一

顺序写

而LSM用了一种很有趣的方法,将所有数据不组织成一个整体索引结构,而组织成有序的文件集。每次LSM面对磁盘写,将数据写入一个或几个新生成的文件,顺序写入且不能修改其他文件,这样就将随机读写转换成了顺序读写。

LSM将一次性集体写入的文件作为一个level,磁盘上划分多level,level与level之间互相隔离。这就形成了,以写入数据时间线形成的逻辑上、而非物理上的层级结构,这也就是为什么LSM被命名为”tree“,但不是“tree”。

将数据按key排序,在合并不同file、level上的数据时类似merge-join:如果一直保持生成新的文件,不仅写入会造成冗余空间,而且也会大量降低读的性能。所以要高效的、周期性合并不同file、level。而如果数据是乱序的,根本做不到高效合并。所以LSM要将数据按key排序,在合并不同file、level上的数据时类似 merge-join。

SST

SST 文件包括从 MemTable 刷盘而来的键值对,并且使用一种对查询友好的数据格式来存储。SST 是 Static Sorted Table 的缩写(其他数据库中也称为 Sorted String Table)。它是一种基于块( block) 的文件格式,会将数据切成固定大小的块(默认为 4KB)进行存储。RocksDB 支持各种压缩 SST 文件的压缩算法,例如 Zlib、BZ2、Snappy、LZ4 或 ZSTD 算法。与 WAL 的记录类似,每个数据块中都包含用于检测数据是否损坏的校验和。每次从磁盘读取数据时,RocksDB 都会使用这些校验和进行校验。

memtable 的内存空间被填满之后,会有一部分老数据被转移到 sstfile 里面,这些数据对应的 logfile 里的 log 就会被安全删除。

Compaction

空间放大是存储数据所用实际空间与逻辑上数据大小的比值。假设一个数据库需要 2 MB 磁盘空间来存储逻辑上的 1 MB 大小的键值对是,那么它的空间放大率是 2。类似地,读放大用来衡量用户执行一次逻辑上的读操作,系统内所需进行的实际 IO 次数。

随着我们的不断写入,MemTable 不断被刷到磁盘,L0 上的 SST 文件数量也在增长:

  • 删除或更新 key 所占用的空间永远不会被回收。例如,cat 这个 key 的三次更新记录分别在 SST1,SST2 和 SST3 中,而 chipmunk 在 SST1 中有一次更新记录,在 SST2 中有一次删除记录,这些无用的记录仍然占用额外磁盘空间。
  • 随着 L0 上 SST 文件数量的增加,读取变得越来越慢。每次查找都要逐个检查所有 SST 文件。

RocksDB 引入了压实( Compaction )机制,可以降低空间放大和读放大,但代价是更高的写放大。Compaction 会将某层的 SST 文件同下一层的 SST 文件合并,并在这个过程中丢弃已删除和被覆盖的无效 key。Compaction 会在后台专用的线程池中运行,从而保证了 RocksDB 可以在做 Compaction 时能够正常处理用户的读写请求。

compaction 流程大致为:

1 找到 score 最高的 level;

2 根据一定策略从 level 中选择一个 sst 文件进行 compact,L0 的各个 sst 文件之间 key range 【minkey, maxkey】 有重叠,所以可能一次选取多个;

3 获取 sst 文件的 minkey 和 maxkey;

4 从 level + 1 中选取出于 (minkey, maxkey) 用重叠的 sst 文件,有重叠的文件则把文件与 level 中的文件进行合并(merge - sort)作为目标文件,没有重叠文件则把原始文件作为目标文件;

5 对目标文件进行压缩后放入 level + 1 中。

nosql 数据库总结_数据存储_32


同步写 && 异步写

默认情况下,RocksDB 的写是异步的:仅仅把数据写进了操作系统的缓存区就返回了,而这些数据被写进磁盘是一个异步的过程。如果为了数据安全,可以用如下代码把写过程改为同步写。

<span style="color:#000000"><span style="background-color:#f5f2f0"><span style="color:black"><code class="language-C">    rocksdb::WriteOptions write_options;
    write_options.sync = true;
    db->Put(write_options, …);</code></span></span></span>

异步写的吞吐率是同步写的一千多倍。异步写的缺点是机器或者操作系统崩溃时可能丢掉最近一批写请求发出的由操作系统缓存的数据,但是 RocksDB 自身崩溃并不会导致数据丢失。而机器或者操作系统崩溃的概率比较低,所以大部分情况下可以认为异步写是安全的。

RocksDB 由于有 WAL 机制保证,所以即使崩溃,其重启后会进行写重放保证数据一致性。如果不在乎数据安全性,可以把 write_option.disableWAL 设置为 true,加快写吞吐率。

4.RocksDB读取数据

BlockCache(逻辑读)

Block Cache 是 RocksDB 的数据的缓存,这个缓存可以在多个 RocksDB 的实例下缓存。一般默认的Block Cache 中存储的值是未压缩的,而用户可以再指定一个 Block Cache,里面的数据可以是压缩的。用户访问数据先访问默认的 Block Cache,待无法获取后再访问用户 Cache,用户 Cache 的数据可以直接存入 page cache 中。

Cache 有两种:LRUCache 和 BlockCache。Block 分为很多 Shard,以减小竞争,所以 shard 大小均匀一致相等,默认 Cache 最多有 64 个 shards,每个 shard 的 最小 size 为 512k,总大小是 8M,类别是 LRU。

RocksDB 的读流程分为逻辑读(logical read)和物理读(physical read)。逻辑读通常是对 cache【Block Cache & Table Cache】进行读取,物理读就是直接读磁盘。


物理读

nosql 数据库总结_数组_33

在MemTable中查找,无法命中转到下一流程;

在immutable_memtable中查找,查找不中转到下一流程;

在第0层SSTable中查找,无法命中转到下一流程;

对于L0 的文件,RocksDB 采用遍历的方法查找,所以为了查找效率 RocksDB 会控制 L0 的文件个数。

在剩余SSTable中查找。

对于 L1 层以及 L1 层以上层级的文件,每个 SSTable 没有交叠,可以使用二分查找快速找到 key 所在的 Level 以及 SSTfile。              

搜索 SST 文件涉及:

  1. (可选)探测布隆过滤器。
  2. 查找 index 来找到可能包含这个 key 的 block 所在位置。
  3. 读取 block 文件并尝试在其中找到 key。

nosql 数据库总结_数据存储_34



5.RocksDB的Merge

RocksDB 还提供了一个同时涉及读路径和写路径的功能:合并(merge)操作。假设,你在数据库中存了一个整数列表,偶尔需要扩展该列表。为了修改列表,你需要从数据库中读取其现有值,在内存中更新该列表,最后把更新后的值写回数据库。

 在 flush 和 compaction 时,RocksDB 调用 merge_operator() ,在条件允许时,将若干更新合并成单个更新或者单个值。在用户调用 Get 或扫描读取数据时,如果发现任何待 merge 的更新,也会调用 merge_operator 向用户返回一个经过 merge 而得到的值。

Merge 非常适合于需要持续对已有值进行少量更新的写入密集型场景。那么,代价是什么?读将变得更加昂贵——读时的合并值没有写回。对该 key 的查询需要一遍又一遍地执行相同的合并过程,直到触发 flush 和 compaction 为止。与 RocksDB 其他部分一样,我们可以通过限制 MemTable 中 merge 对象的数量、降低 L0 中 SST 文件数量来优化读行为。

nosql 数据库总结_高负载_35


6.RocksDB 数据GC

LSM 的另外一个关键特性就在于其是一种自带数据 Garbage Collect 的有序数据集合,对外只提供了 Add/Get 接口,其内部的 Compaction 就是其 GC 的关键,通过 Compaction 实现了对数据的删除、附带了 TTL 的过期数据地淘汰、同一个 Key 的多个版本 Value 地合并。RocksDB 基于 LSM 对外提供了 Add/Delete/Get 三个接口,用户则基于 RocksDB 提供的 transaction 还可以实现 Update 语义。

7.RocksDB数据快照

RocksDB 能够保存某个版本的所有数据(可称之为一个 Snapshot)以方便读取操作,创建并读取 Snapshot 方法如下:

<span style="color:#000000"><span style="background-color:#f5f2f0"><span style="color:black"><code class="language-C">    rocksdb::ReadOptions options;
    options.snapshot = db->GetSnapshot();
    … apply some updates to db ….
    rocksdb::Iterator* iter = db->NewIterator(options);
    … read using iter to view the state when the snapshot was created ….
    delete iter;
    db->ReleaseSnapshot(options.snapshot);</code></span></span></span>

如果 ReadOptions::snapshot 为 null,则读取的 snapshot 为 RocksDB 当前版本数据的 snapshot。

8.RocksDB事务

当使用 TransactionDB 或者 OptimisticTransactionDB 的时候,可以使用 RocksDB 的 BEGIN/COMMIT/ROLLBACK 等事务 API。RocksDB 支持活锁或者死等两种事务。

WriteBatch 默认使用了事务,确保批量写成功。

当打开一个 TransactionDB 的时候,如果 RocksDB 检测到某个 key 已经被别的事务锁住,则 RocksDB 会返回一个 error。如果打开成功,则所有相关 key 都会被 lock 住,直到事务结束。TransactionDB 的并发特性表现要比 OptimisticTransactionDB 好,但是 TransactionDB 的一个小问题就是不管写发生在事务里或者事务外,他都会进行写冲突检测。

OptimisticTransactionDB 提供了一个更轻量的事务实现,它在进行写之前不会进行写冲突检测,当对写操作进行 commit 的时候如果发生了 lock 冲突导致写操作失败,则 RocksDB 会返回一个 error。这种事务使用了活锁策略,适用于读多写少这种写冲突概率比较低的场景下

9.LSM Tree vs B+ Tree

读写性能上

  • LSM-Tree:顺序写,追加更新,LSM-Tree 写性能更好。由于采用 append 的方式,会造成数据冗余,需要后台线程做 compaction 操作。在读取数据时,可能需要同时遍历 memtable 和 SST,读性能较差。
  • B+ Tree:随机写,就地更新,B+ Tree 的读性能更好。由于磁盘随机 IO 的速度小于磁盘顺序 IO 的速度,且 DML 操作时需要对整个 B+ 树加锁,锁粒度大,因此写性能较差。

在存储空间上

  • LSM-Tree:紧凑存储,占用空间少, 高压缩比, SST 每一层级的空间浪费控制在 10%
  • B+ Tree:B+ 树分裂时,造成 50% 空间浪费,存在页内碎片。

+++++++++++++++++++++++++++++++++++++++++

10 LSM树

nosql 数据库总结_高负载_36

nosql 数据库总结_数组_37

日志化结构合并树(Log-Structured Merge-Tree)是一种分层、有序、面向磁盘的数据结构,其核心思想是充分利用磁盘批量的顺序写远比随机写高效的特性,放弃部分读效率换取最大化的写操作效率

我们知道最大化发挥磁盘特性的使用方式是一次性地读取或写入固定大小的一块数据,并尽可能地减少随机寻道操作。LSM 树的设计思想就是依据磁盘这个特性。

假定内存足够,不需要每次有数据更新就将其写入磁盘,而是先将最新的数据驻留在内存中,等数据量积累到足够多之后,再使用归并排序的方式将内存中的数据与磁盘中的数据合并,批量追加到磁盘。合并过程因为所有待排序的数据都是有序的,所以可以通过归并排序快速合并

与B-tree相比,LSM 树能在较长的时间提供对文件的高速增删,但是在需要高效查询时性能相对较低,所以 LSM 树通常适用于索引插入比检索更频繁的系统。不过 LSM-Tree 结构除了利用磁盘顺序写实现高性能写,也通过划分内存+磁盘的多层合并结构及各种优化实现尽量保证读性能

LSM 树结构的文件结构策略目前已经应用在多种 NoSQL 数据库,如 Hbase,Cassandra,Leveldb,RocksDB,MongoDB,TiDB,SQLite等

nosql 数据库总结_数组_38

1) MemTable

MemTable是在内存中的数据结构,用于保存最近更新的数据,会按照Key有序地组织这些数据,LSM树对于具体如何组织有序地组织数据并没有明确的数据结构定义,例如Hbase使跳跃表来保证内存中key的有序。

因为数据暂时保存在内存中,内存并不是可靠存储,如果断电会丢失数据,因此通常会通过WAL(Write-ahead logging,预写式日志)的方式来保证数据的可靠性。

2) Immutable MemTable

当 MemTable达到一定大小后,会转化成Immutable MemTable。Immutable MemTable是将转MemTable变为SSTable的一种中间状态。写操作由新的MemTable处理,在转存过程中不阻塞数据更新操作。

3) SSTable(Sorted String Table)

有序键值对集合,是LSM树组在磁盘中的数据结构。为了加快SSTable的读取,可以通过建立key的索引以及布隆过滤器来加快key的查找。

nosql 数据库总结_数组_39

因此当MemTable达到一定大小flush到持久化存储变成SSTable后,在不同的SSTable中,可能存在相同Key的记录,当然最新的那条记录才是准确的。这样设计的虽然大大提高了写性能,但同时也会带来一些问题:

1)冗余存储,对于某个key,实际上除了最新的那条记录外,其他的记录都是冗余无用的,但是仍然占用了存储空间。因此需要进行Compact操作(合并多个SSTable)来清除冗余的记录。
2)读取时需要从最新的倒着查询,直到找到某个key的记录。最坏情况需要查询完所有的SSTable,这里可以通过前面提到的索引/布隆过滤器来优化查找速度。

Compact压缩策略

1)读放大:读取数据时实际读取的数据量大于真正的数据量。例如在LSM树中需要先在MemTable查看当前key是否存在,不存在继续从SSTable中寻找。

2)写放大:写入数据时实际写入的数据量大于真正的数据量。例如在LSM树中写入时可能触发Compact操作,导致实际写入的数据量远大于该key的数据量。

3)空间放大:数据实际占用的磁盘空间比数据的真正大小更多。上面提到的冗余存储,对于一个key来说,只有最新的那条记录是有效的,而之前的记录都是可以被清理回收的。

1) size-tiered 策略

size-tiered策略保证每层SSTable的大小相近,同时限制每一层SSTable的数量。如上图,每层限制SSTable为N,当每层SSTable达到N后,则触发Compact操作合并这些SSTable,并将合并后的结果写入到下一层成为一个更大的sstable。

由此可以看出,当层数达到一定数量时,最底层的单个SSTable的大小会变得非常大。并且size-tiered策略会导致空间放大比较严重。即使对于同一层的SSTable,每个key的记录是可能存在多份的,只有当该层的SSTable执行compact操作才会消除这些key的冗余记录。

2) leveled策略

nosql 数据库总结_数据存储_40

但是跟size-tiered策略不同的是,leveled会将每一层切分成多个大小相近的SSTable。这些SSTable是这一层是全局有序的,意味着一个key在每一层至多只有1条记录,不存在冗余记录。之所以可以保证全局有序,是因为合并策略和size-tiered不同,接下来会详细提到。

nosql 数据库总结_高负载_41

nosql 数据库总结_高负载_42

nosql 数据库总结_数组_43

nosql 数据库总结_数组_44

+++++++++++++++++++++++++++++++++++++++++++++

TIDB学习

引言

 Mysql 数据库到达一定量级以后,性能就会逐步下降,而解决此类问题,常用的手段就是引入数据库中间件进行分库分表处理,比如使用 Mycat、ShadingShpere、tddl,但是这种都是过去式了,现在使用分布式数据库可以避免分库分表

分库分表以后,会面临以下问题:

分页问题,例如:使用传统写法,随着页数过大性能会急剧下降

分布式事务问题

数据迁移问题,例如:需要把现有数据通过分配算法导入到所有的分库中

数据扩容问题,分库分表的数据总有一天也会到达极限,需要增大分片

开发模式变化,比如在请求数据时,需要带分片键,否则就会导致所有节点执行

跨库跨表查询问题

业务需要进行一定取舍,由于分库分表的局限性,有些场景下需要业务进行取舍

TiDB介绍

TiDB 是 PingCAP 公司研发的一款开源分布式关系型数据库,从 2015年 9 月开源,至今已经有9 年时间,可以说已经非常成熟,它是一款同时支持OLTP(在线事务处理)和OLAP(在线分析处理)的融合型分布式数据库产品,具备水平扩缩容,金融级高可用、实时 HTAP(Hybrid Transactional and Analytical Processing)、云原生的分布式数据库,兼容 MySQL 5.7 协议和 MySQL 生态等重要特性,它适合高可用、强一致要求较高、数据规模较大等各种应用场景。

核心特性:

金融级高可用

在线水平扩容或者缩容,并且存算分离

云原生的分布式数据库,支持部署在公有云,私有云,混合云中

实时HTAP,提供TIKV行存储引擎和TiFlash列存储引擎

兼容MySQL协议和MySQL生态

分布式事务强一致性

从 MySQL 无缝切换到 TiDB,几乎无需修改代码,迁移成本极低

PD在分布式理论CAP方面满足CP,是强一致性的

应用场景:

对数据一致性及高可靠、系统高可用、可扩展性、容灾要求较高的金融行业属性的场景

对存储容量、可扩展性、并发要求较高的海量数据及高并发的OLTP场景

数据汇聚、二次加工处理的场景

数据库架构

nosql 数据库总结_数组_45

TiDB Server

SQL 层,对外暴露 MySQL 协议的连接 endpoint,负责接收SQL请求,处理SQL相关的逻辑,并通过PD找到存储计算所需数据的TiKV地址,与TiKV交互获取数据,最终返回结果。TiDB Server 是无状态的,其本身并不存储数据,只负责计算,可以无限水平扩展,可以通过负载均衡组件(LVS、HAProxy或F5)对外提供统一的接入地址,客户端的连接可以均匀地分摊在多个 TiDB 实例上以达到负载均衡的效果。

PD Server

  • 存储集群的元信息(某个Key存储在哪个TiKV节点);
  • TiKV集群进行调度和负载均衡、Leader选举;
  • 分配全局唯一且递增的事务ID。

PD 是一个集群,需要部署奇数个节点,一般线上推荐至少部署3个节点。PD在选举的过程中无法对外提供服务,这个时间大约是3秒

TIKV Server

nosql 数据库总结_数组_46

TiDB 现在同时支持OLTP 和 OLAP,而TiKV负责存储OLTP数据,从外部看TiKV是一个分布式的提供事务的Key-Value存储引擎。存储数据的基本单位是Region,每个Region负责存储一个Key Range(从StartKey到EndKey的左闭右开区间)的数据,每个TiKV节点会负责多个Region。

高可用

nosql 数据库总结_高负载_47

就是把数据复制到多台机器上,这样一个节点down 机,其他节点上的副本还能继续提供服务;复杂理解,需要这个数据可靠并且高效复制到其他节点,并且能处理副本失效的情况,那怎么做呢,就是使用 Raft一致性算法

Region 与副本之间通过 Raft 协议来维持数据一致性,任何写请求都只能在 Leader 上写入,并且需要写入多数副本后(默认配置为 3 副本,即所有请求必须至少写入两个副本成功)才会返回客户端写入成功。

分布式事务

TiKV 支持分布式事务,我们可以一次性写入多个 key-value 而不必关心这些 key-value 是否处于同一个数据切片 (Region) 上,TiKV 的分布式事务参考了Google 在 BigTable 中使用的事务模型Percolator

对比Mysql

nosql 数据库总结_数据存储_48

nosql 数据库总结_数据存储_49




宽表研究

对于写密集型应用,每天写入量巨大,数据增长量无法预估,且对性能和可靠性要求非常高,普通关系型数据库无法满足其需求。对于全文搜索和数据分析这类对查询性能要求极高的场景也是如此。

Bigtable 会把数据存储在若干个 Table(表)中,Table 中的每个 Cell(数据单元)的形式如下:Cell 内的数据由字节串(string)构成,使用行、列和时间戳三个维度进行定位。

nosql 数据库总结_数组_50

Bigtable 在存储数据时会按照 Cell 的 Row Key 对 Table 进行字典排序,并且将一个 Table 按 Row 切分成若干个相邻的 Tablet,并将 Tablet 分配到不同的 Tablet Server 上存储。如此一来,客户端查询较为接近的 Row Key 时 Cell 落在同一个 Tablet 上的概率也会更大,查询的效率也会更高。

Table 中的不同 Cell 可以保存同一份数据的多个版本,以时间戳进行区分。时间戳本质上为 64 位整数,可由 Bigtable 自动设定为数据写入的当前时间(微秒),也可由应用自行设定,但应用需要自行确保 Cell 间不会出现冲突。对于拥有相同 Row Key 和 Column Key 的 Cell,Bigtable 会按照时间戳降序进行排序,如此一来最新的数据便会被首先读取。

由于Google Bigtable解决了海量数据场景下并发检索与查询、高速日志写入等核心业务场景需求,工业界受其启发开发了一些项目,如HBase、Cassandra等,并统称其为Wide Column Store(宽表存储,又称表格存储)。宽表存储可以无schema限制,表的字段可以自由扩展。一个数据表可以有无穷多的column,并且每行可以有不同的column,也允许每行有很多的空值,类似一个稀疏矩阵。一个列族(column family)存储经常被一起查询的相关数据。

HBase是一个面向列的分布式NoSQL数据库,是Google Bigtable 框架的开源实现,能够响应随机、实时的数据检索需求。HBase主要的存储和处理对象是大宽表,存储模式可以兼容本地存储、HDFS和Amazon S3等Hadoop支持的文件系统,相比于RDBMS有很强的线性扩展能力。HBase通过采用基于LSM树的存储系统以保证稳定的数据写速率,同时利用其日志管理机制以及HDFS多副本机制确保数据库容错能力。通常的适用场景为:面向多版本、稀疏的、半结构化和结构化的数据高并发写入/查询的OLTP业务。

Lindorm学习

Lindorm是面向物联网、互联网、车联网等设计和优化的云原生多模超融合数据库,支持宽表、时序、文本、对象、流、空间等多种数据的统一访问和融合处理,并兼容SQL、HBase/Cassandra/S3、TSDB、HDFS、Solr、Kafka等多种标准接口和无缝集成三方生态工具,适用于日志、监控、账单、广告、社交、出行、风控等场景,Lindorm也是为阿里巴巴核心业务提供支撑的数据库之一。

nosql 数据库总结_数据存储_51

多模介绍

Lindorm支持宽表、时序、对象、文件、队列、空间等多种数据模型,提供标准SQL和开源接口两种方式,模型之间数据互融互通,帮助应用开发更加敏捷、灵活、高效。多模型的核心能力主要由以下几大数据引擎提供

nosql 数据库总结_数据存储_52

多模能力

nosql 数据库总结_高负载_53

nosql 数据库总结_数组_54

nosql 数据库总结_数组_55

应用场景

海量数据存储与分析

nosql 数据库总结_高负载_56

  • 低成本:高压缩比,数据冷热分离,支持HDD/OSS存储。
  • 数据通道:通过LTS(原BDS)构建Lindorm与异构计算系统的高效、易用的数据链路。
  • 快速导入:通过BulkLoad将海量数据快速导入Lindorm,效率比传统方式提升一个数量级。
  • 高并发:水平扩展至千万级QPS。
  • 弹性:存储计算分离架构,支持独立伸缩,自动化扩容

海量广告营销数据的实时存储

使用Lindorm存储广告营销中的画像特征、用户事件、点击流、广告物料等重要数据,提供高并发、低延迟、灵活可靠的能力,帮助您快速构建实时竞价、广告定位投放等系统服务。

nosql 数据库总结_高负载_57

优势

  • 低延迟:单个毫秒响应,支持双集群请求并发加速。
  • 高并发:水平扩展至千万级QPS。
  • 使用灵活:动态列,自由增减特征/标签属性;TTL,数据自动过期。
  • 低成本:高压缩比,数据冷热分离,支持HDD/OSS存储。
  • 数据通道:通过LTS(原BDS)构建Lindorm与异构计算系统的高效、易用的数据链路。
  • 高可用:主备双活容灾,请求自动容错,满足99.95% SLA。

海量订单记录与风控数据的实时存储

使用Lindorm存储金融交易中的海量订单记录,金融风控中的用户事件、画像特征、规则模型、设备指纹等重要数据,提供低成本、高并发、灵活可靠的能力,帮助您构建有竞争力的金融交易与风控服务。

nosql 数据库总结_高负载_58

  • 低成本:高压缩比,数据冷热分离,支持HDD/OSS存储。
  • 高并发:水平扩展至千万级QPS。
  • 使用灵活:动态列,自由增减特征/标签属性;TTL,数据自动过期;多版本。
  • 低延迟:单个毫秒响应,支持双集群请求并发加速。
  • 数据通道:通过LTS(原BDS)构建Lindorm与异构计算系统的高效、易用的数据链路。
  • 高可用:主备双活容灾,请求自动容错,满足99.95% SLA。

车辆轨迹与状况数据的高效存储处理

使用Lindorm存储车联网中的行驶轨迹、车辆状况、精准定位等重要数据,提供低成本、弹性、灵活可靠的能力,帮助您构建网约车、物流运输、新能源车检测等场景服务

nosql 数据库总结_数据存储_59

高效、稳定的社交Feed流信息存储

使用Lindorm存储社交场景中的聊天、评论、帖子、点赞等重要数据,提供易开发、高可用、低延迟的能力,帮助您快速构建稳定可靠的现代社交Feed流系统。

nosql 数据库总结_数据存储_60

产品架构

传统业务数据架构

nosql 数据库总结_高负载_61

典型的技术碎片化的处理方案。针对不同的数据,使用不同的数据库来处理。有如下几个弊端:

  • 涉及的技术组件多且杂
  • 技术选型复杂
  • 数据存取、数据同步的链路长

使用Lindorm统一整合

nosql 数据库总结_数据存储_62

总体架构

Lindorm创新性地使用存储计算分离、多模共享融合的云原生架构,以适应云计算时代资源解耦和弹性伸缩的诉求。其中云原生分布式文件系统LindormDFS为统一的存储底座,向上构建各个垂直专用的多模数据引擎,包括宽表引擎、时序引擎、搜索引擎、流引擎等。在多模引擎之上,Lindorm既提供统一的SQL访问,支持跨模型的联合查询,又提供多个开源标准接口(HBase/Cassandra、OpenTSDB/InfluxDB、Kafka、HDFS),满足存量业务无缝迁移的需求。最后,数据通道服务(LTS)负责引擎之间的数据流转和数据变更的实时捕获,以实现数据迁移、实时订阅、数湖转存、数仓回流、单元化多活、备份恢复等能力。

nosql 数据库总结_数组_63

分布式文件系统LDFS

LDFS(Lindorm DFS,也称为Lindorm文件引擎)是面向云基础存储设施设计、兼容HDFS协议的分布式存储系统,并同时支持运行在本地盘环境,以满足部分大客户的需求,向多模引擎和外部计算系统提供统一的、与环境无关的标准接口,整体架构如下

nosql 数据库总结_数组_64

LDFS提供性能型、标准型、容量型等多种规格,并且面对真实场景的数据冷热特点,支持性能型/标准型、容量型多种存储混合使用的形态,以适应真实场景的数据冷热特点,可结合多模引擎的冷热分离能力,实现冷热存储空间的自由配比,让用户的海量数据进一步享受云计算的低成本红利。

在计算分析、备份归档、数据导入等场景,Lindorm支持外部系统通过LDFS直接访问多模数据引擎的底层文件,从而大幅提升数据的读写效率。例如用户可以在离线计算系统直接生成底层数据格式的物理文件,导入至Lindorm中,以减少对在线服务的影响。

宽表引擎

LindormTable是面向海量半结构化、结构化数据设计的分布式NoSQL系统,适用于元数据、订单、账单、画像、社交、feed流、日志等场景,兼容HBase、Cassandra等开源标准接口。其基于数据自动分区+分区多副本+LSM的架构思想,具备全局二级索引、多维检索、动态列、TTL等查询处理能力,支持单表百万亿行规模、高并发、毫秒级响应、跨机房强一致容灾,高效满足业务大规模数据的在线存储与查询需求。面向海量半结构化、结构化数据设计的分布式NoSQL系统,能够兼容HBase、Cassandra等开源标准接口。

整体架构如下

nosql 数据库总结_数组_65

LindormTable的数据持久化存储在LDFS中,表的数据通过自动Sharding分散到集群中的多台服务器上,并且每一个分区可以拥有1至N个副本,这N个副本拥有主、从两种角色,主从副本可以加载在不同的Zone,从而保障集群的高可用和强一致。针对不同的一致性模式,主从副本之间的数据同步和读写模式如下:

  • 强一致模式。只有主副本提供读写,数据会异步回放到从副本,主副本所在节点故障,从副本晋升为主副本。晋升之前会保障数据同步完成,从副本拥有所有最新数据,整体过程由Master协调负责。
  • 最终一致模式。主从副本都提供读写,数据会相互同步,保证副本之间的数据最终一致。

LindormTable的多副本架构基于PACELC理念设计,每一个数据表都支持单独支持设置一致性模式,从而拥有不同的可用性和性能。在最终一致模式下,服务端会对每一个读写请求在一定条件下触发多副本并发访问,从而大幅提升请求的成功率和减少响应毛刺。该并发机制建立在内部的异步访问框架上,相比于启动多线程,额外资源消耗可以忽略不计。对于并发访问的触发条件,主要包括两个类型:

  • 限时触发,对于每一个请求,都可以单独设置一个GlitchTimeout,当请求运行时间超过该值未得到响应后,则并发一个请求到其他N-1个副本,最终取最快的那个响应。
  • 黑名单规避,服务端内部会基于超时、抛错、检测等机制,主动拉黑存在慢、停止响应等问题的副本,使得请求能够主动绕开受软硬件缺陷的节点,让服务最大可能保持平滑。比如在掉电断开的场景下,在节点不可服务至失去网络心跳往往会存在一两分钟的延迟,利用LTable的这种多副本协同设计,可以大幅提升服务的可用性。

LindormTable的LSM结构面向冷热分离设计,支持用户的数据表在引擎内自动进行冷热分层,并保持透明查询,其底层结合LStore的冷热存储混合管理能力,大幅降低海量数据的总体存储成本。

LindormTable提供的数据模型是一种支持数据类型的松散表结构。相比于传统关系模型,LindormTable除了支持预定义字段类型外,还可以随时动态添加列,而无需提前发起DDL变更,以适应大数据灵活多变的特点。同时,LindormTable支持全局二级索引、倒排索引,系统会自动根据查询条件选择最合适的索引,加速条件组合查询,特别适合如画像、账单场景海量数据的查询需求。

搜索引擎

LindormSearch是面向海量数据设计的分布式搜索引擎,兼容开源Solr标准接口,同时可无缝作为宽表、时序引擎的索引存储,加速检索查询。其整体架构与宽表引擎一致,基于数据自动分区+分区多副本+Lucene的结构设计,具备全文检索、聚合计算、复杂多维查询等能力,支持水平扩展、一写多读、跨机房容灾、TTL等,满足海量数据下的高效检索需求,具体如下

nosql 数据库总结_数据存储_66

LindormSearch的数据持久化存储在LDFS中,通过自动Sharding的方式分散到多台SearchServer中,每一个分片拥有多个副本,支持一写多读,提升查询聚合的效率,同时这些副本之间共享存储,有效消除副本之间的存储冗余。

在Lindorm系统中,LindormSearch既可以作为一种独立的模型,提供半结构化、非结构化数据的松散文档视图,适用于日志数据分析、内容全文检索;也可以作为宽表引擎、时序引擎的索引存储,对用户保持透明,即宽表/时序中的部分字段通过内部的数据链路自动同步搜索引擎,而数据的模型及读写访问对用户保持统一,用户无需关心搜索引擎的存在,跨引擎之间的数据关联、一致性、查询聚合、生命周期等工作全部由系统内部协同处理,用简单透明的方式发挥多模融合的价值。

引擎类型

nosql 数据库总结_数据存储_67

nosql 数据库总结_高负载_68