Dawn's Blogs

分享技术 记录成长

0%

文件系统抽象

在 dubbo-kubernetes 中,利用 golang 提供的 fs 抽象接口,实现了多种不同文件系统。各种文件系统的实现在 app/dubboctl/internal/filesystem 文件夹下,首先定义了一个 Filesystem 接口:

1
2
3
4
5
type Filesystem interface {
fs.ReadDirFS
fs.StatFS
Readlink(link string) (string, error)
}

Filesystem 接口将访问 template 文件(至于什么是模版,可以不知道)的各种实现差异,封装到这个接口中。Filesystem 接口的实现包括以下:

  • zipFS:用于对 zip 压缩文件的读写,将 zip 压缩文件作为一个文件系统,压缩文件中的一个个被压缩的文件或者文件夹就是文件系统中的文件。
  • billyFS:表示 git repo 中的文件系统,用于访问在远程 git 仓库中的 template。
  • osFilesystem:由操作系统支持的文件系统,用于访问 template。
  • subFS:表示 Filesystem 中的子目录,单独作为一个文件系统。
  • maskingFS:基于 Filesystem,用于屏蔽某些文件的文件系统。

Golang 提供了 io.Reader 和 io.Writer 接口,抽象出对文件、字节流、socket 的读写。至于问什么提出这样的接口,将对象看作是文件一样读写,可以在 Linux 中找到启发:Linux 中,一切皆是文件

在 1.6 中提供了 fs.FS 抽象文件系统抽象,既然很多对象可以被看作是对象,那么这些抽象的文件聚集起来,就可以抽象出文件系统。

阅读全文 »

索引

MongoDB 支持建立索引,索引可以建立在一个或者多个字段上。并且由于索引是在内存中的,所以查询效率很高。但是在写操作时,由于还需要维护索引,所以写操作的效率会有所降低。

MongoDB 索引的数据结构是 B 树

索引覆盖查询

索引覆盖查询就是之间在索引上查询并返回结果,不需要在磁盘上扫描结果。索引覆盖查询需要满足以下条件:

  • 索引字段包含了查询条件中的所有字段。
  • 查询条件中只包含索引字段,不包含其他字段。
  • 查询结果只需要索引字段的值,不需要其他字段的值。
1
2
3
4
// users有三个字段name,gender,email
// 在name和gender字段上建立索引
// 仅仅查询gender字段,没有排除_id,查询就不会被覆盖
db.users.find({gender:"M"},{user_name:1,_id:0})

对于索引覆盖查询来说,不会去数据库文件中去查找,而是在索引中直接提取数据

关系

在 MongoDB 中,可以表示一对一、一对多、多对一、多对多关系。

对于一对多关系,可以有两种方法:

  • 嵌入完整的文档:在更新时需要非常的小心,需要同时更新所有嵌入的完整文档。同时冗余数据多,数据量不断变大,会影响读写性能。
  • 关系引用:通过引用文档的 _id 字段来建立关系。也就是说,MongoDB 支持在文档中引用其他文档。

引用

在 MongoDB 中,数据库引用有两种方式:手动引用和DBRefs。

手动引用就是直接去在对应的字段中记录 _id 信息。

DBRefs 的形式为 { $ref : , $id : , $db : }。三个字段表示的意义如下:

  • $ref:集合名称。
  • $id:引用的 _id。
  • $db:引用的数据库名称,可选参数。

因为 DBRefs 可以指定引用的 id 和数据库名称,所以更加相比于手动引用更加规范和灵活。

复制

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

MongoDB 的数据复制至少需要两个节点,一个主节点,其余的节点为从节点(称为副本集)。主节点记录在其上的所有操作 oplog,从节点定期轮询主节点获取这些操作,然后对自己的数据副本执行这些操作,从而保证从节点的数据与主节点一致。

MongoDB复制结构图

在 MongoDB 中,副本集中所有的写操作都在主节点上进行,任何一个节点都可以称为主节点,并且可以自动故障转移自动恢复

分片

在 MongoDB 中也可以进行分片,分片可以提高数据的吞吐量。一个分片集群结构如下,包含三个部分:

  • Shard:用于存储实际的数据块,实际生产环境中一个 shard server 角色可由几台机器组成个一个 replica set 承担,防止主机单点故障。
  • Config Server:MongoDB服务器实例,存储了整个集群的元信息。
  • Query Routers:客户端由此接入,且让整个集群看上去像单一数据库,前端应用可以透明使用。

img

数据库操作

创建数据库

在 MongoDB 中,使用 use 命令并且插入一条数据,就可以完成创建一个数据库了。

删除数据库

使用 db.dropDatabase() 删除数据库。

集合操作

创建集合

在 MongoDB 中,当插入文档时会自动的创建集合。

使用 createCollection 方法显式的创建集合。

1
db.createCollection(name, options)

options 可以是如下参数:

字段 类型 描述
capped 布尔 (可选)如果为 true,则创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。 当该值为 true 时,必须指定 size 参数。
autoIndexId 布尔 3.2 之后不再支持该参数。(可选)如为 true,自动在 _id 字段创建索引。默认为 false。
size 数值 (可选)为固定集合指定一个最大值,即字节数。 如果 capped 为 true,也需要指定该字段。
max 数值 (可选)指定固定集合中包含文档的最大数量。

删除集合

使用 db.COLLECTION_NAME.drop() 删除集合。

文档操作

MongoDB 针对文档,同样支持增删改查操作,这里特别说明一下查询文档。

查询文档

使用 db.COLLECTION_NAME.find 方法查询文档,find 内部可以填写查询条件。如果需要以易读的方式来读取数据,可以使用 pretty() 方法。如:

1
2
3
4
5
6
7
8
db.col.find({
"likes": {$gt:50},
$or: [
{"by": "xxx"},
{"title": "MongoDB学习"}
]
}
).pretty()
阅读全文 »

介绍 MongoDB

MongoDB 是一个由 C++ 编写的,分布式文档型数据库。在高负载的情况下,可以添加更多的节点,可以保证服务器性能。MongoDB 将数据存储为一个文档,数据结构由键值对构成,MongoDB 文档类似于 JSON 对象。字段值可以包含字面量,其他文档,数组及文档数组。

特点

MongoDB 的特点是:

  • 支持索引,利用索引可以实现更快的排序。
  • 支持数据分片和数据复制,也就是具备分布式能力。
  • 支持 Map/Reduce,用于对数据进行批量的处理和聚合操作。
  • 允许在服务器端执行 Javascript 脚本。

概念

在 mongodb 中,最基本的概念就是文档、集合、数据库。

SQL术语/概念 MongoDB术语/概念 解释/说明
database database 数据库
table collection 数据库表/集合
row document 数据记录行/文档
column field 数据字段/域
index index 索引
table joins 表连接,MongoDB不支持
primary key primary key 主键,MongoDB自动将_id字段设置为主键
阅读全文 »

本节首先会介绍数据集切分的方法,并讨论索引和分片的配合;然后将会讨论分片再平衡(rebalancing),集群节点增删会引起数据再平衡;最后,会探讨数据库如何将请求路由到相应的分片并执行。

分片方法

分片方法都是基于键值对的,键值对是数据的一种最通用、泛化的表示,其他种类数据库都可以转化为键值对表示:

  • 关系型数据库:primary key → row
  • 文档型数据库:document id → document
  • 图数据库:vertex id → vertex,edge id → edge

所以数据分片的基本方法就是,首先将数据转化为键值对,然后进行分区分片(Partition) 的本质是对数据集合的划分,可以分为两个步骤:

  • 对数据集进行逻辑划分。
  • 将逻辑分片调度到物理节点上。

本节介绍逻辑划分方法,分别是按照键范围分区和按键散列分区。

按键范围分区

将该连续的定义域进行切分,每个分区内可以按照关键字排序保存(SSLTable)。保存每个切分的上下界,在给出某个 Key 时,就能通过比较,定位其所在分区。

  • 按键范围分区好处在于可以进行快速的范围查询
  • 坏处在于,数据分散不均匀,且容易造成热点

比如关键字是时间戳,可能导致在近期的分区写入负载高,但是其他分区处于空闲状态。

解决方法就是使用除了时间戳以外的其他内容作为关键字的第一项,使用拼接主键的方式。

按键散列分区

为了避免数据倾斜和读写热点,许多数据系统使用散列函数对键进行分区。

一致性哈希也是基于散列分区,它不仅解决了从键到分区的映射,也解决了分区到节点的映射。

阅读全文 »

本节讨论主从复制、多主节点复制和无主节点复制。冗余复制有多个好处:

  • 降低延迟。
  • 提高可用性。
  • 提高读吞吐。

主从复制

主从复制需要注意因为读取不同从数据库、或者从数据库与主数据库的不同步,而造成的暂时不一致性。

主从复制的原理如下:

  • 指定某一个副本为主节点。当客户端写数据库时,必须将写请求发给主节点,主节点首先将新数据写入本地存储。
  • 其他副本为从节点。主节点将日志发送给所有的从节点,从节点收到日志后应用到本地存储。
  • 客户端可以从主节点或者从节点中执行查询操作。

主从复制中,只有主节点是可写的,从节点仅可读。

同步复制 异步复制

复制分为同步复制和异步复制。

  • 同步复制: 主节点发送消息,等待从节点响应。同步复制的优点是,从库保证有与主库一致的最新数据副本。缺点是,如果同步从库没有响应,主库就无法处理写入操作。
  • 异步复制:主节点发送消息,但不等待从节点的响应。异步复制的速度快、效率高,但是可能会出现数据丢失的情形。

基于同步复制和异步复制的优缺点,所以通常情况下采用半同步复制。半同步复制就是有既有同步从节点(一个),又有异步从节点。当所有的同步从节点响应后完成写入操作。

从节点的日志同步

在新的从节点出现时,需要同步主节点中所有的数据。过程如下:

  • 在第一次同步时,主节点会给从节点发送某个时刻的一致性快照
  • 从节点同步快照后,此后的同步,就是增量拉取所有的数据变更。

处理节点宕机

在分布式系统中,任何节点都可能发生宕机。所以必须处理节点的宕机行为,保证集群的高可用。

从库失效:追赶恢复

如果是从节点失效了,则恢复比较容易。从节点可以从日志中知道,在发生故障之前处理的最后一个事务。从库因此可以向主库请求同步之后的所有数据变更,直到追上主节点的数据。

主库失效:故障切换

如果是主节点失效了,则恢复起来非常棘手。其中一个从库需要被提升为新的主库,并且需要重新配置客户端中主库的地址,其他从节点需要拉取新的主节点的数据变更,这个过程就是故障切换

故障切换可以手动进行(通过数据库管理员),或者是自动进行。自动故障切换的过程如下:

  • 确认主库失效。大多数系统只是简单使用 超时:节点频繁地相互来回传递心跳,并且如果一个节点在一段时间内没有响应,就认为它挂了。
  • 选择一个新的主库。通过选举过程(这是共识问题)来完成,或者可以由之前选定的控制器节点来指定新的主库。主库的最佳人选通常是拥有旧主库最新数据副本的从库

故障切换回导致很多问题:

  • 如果采用异步复制,则可能出现写入操作丢失的情况。
  • 新老主节点数据冲突。新主副本在上位前没有同步完所有日志,旧主副本恢复后,可能会发现和新主副本数据冲突。
  • 发生脑裂,集群内部出现多个主节点。
  • 超时阈值选取。如果超时阈值选取的过小,在不稳定的网络环境中可能会造成主副本频繁的切换。
阅读全文 »

当数据 schema 发生变化时,意味着数据库应用程序都会发生变化:

  • 数据库:关系型数据库可以通过 ALTER 语句修改数据表结构。读时模式数据库不会强制模式,因此数据库可以包含在不同时间写入的新老数据格式的混合
  • 应用程序:新旧版本的代码,以及新旧数据格式可能会在系统中同时共处。所以需要保持双向兼容性(后向兼容和前向兼容)。

后向兼容通常不难实现:新代码的作者当然知道由旧代码使用的数据格式,因此可以显示地处理它。

向前兼容性可能会更棘手,因为旧版的程序需要忽略新版数据格式中新增的部分。

编码

采用适当的编码(序列化和反序列化),可以应对新旧数据格式共存的情形。

Json 和 XML

Json 和 XML 是文本格式的编码,具有人类可读性。但是因为其没有用 IDL 定义其格式、传输效率低等问题,而被人诟病。

尽管可以对 Json(MessagePack、BSON 等)和 XML(WBXML、Fast Infoset 等)格式的数据进行二进制编码,但是相比于 Thrift 以及 Protobuf 等编码来说,节省的空间太小。

Thrift 和 ProtoBuf

Thrift

Apache Thrift 需要用 IDL 进行定义:

1
2
3
4
5
struct Person {
1: required string userName,
2: optional i64 favoriteNumber,
3: optional list<string> interests
}

Thrift 有两种不同的二进制编码格式,分别称为BinaryProtocol 和 CompactProtocol,另外还有两种不同的基于 JSON 的编码格式。

在编码时与 Json 不同,不会记录字段名字只记录编号

  • BinaryProtocol:每个字段都有一个类型注释,还可以根据需要指定长度。

img

  • CompactProtocol:在语义上等同于 BinaryProtocol,它通过将字段类型和标签号打包到单个字节中,并使用可变长度整数来实现(每个字节的最高位用来指示是否还有更多的字节来。)。

img

阅读全文 »