Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
hnwyllmm committed May 13, 2024
1 parent 1cc7f6b commit 94f07b5
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 92 deletions.
26 changes: 14 additions & 12 deletions docs/docs/design/miniob-bplus-tree-concurrency.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
title: B+ 树并发操作
---

# B+ 树并发操作

> [MiniOB](https://github.com/oceanbase/miniob)[OceanBase](https://github.com/oceanbase/oceanbase) 联合华中科技大学推出的一款用于教学的小型数据库系统,希望能够帮助数据库爱好者系统性的学习数据库原理与实战。
# B+ 树介绍
## B+ 树介绍


B+ 树是传统数据库中常见的索引数据结构,比如MySQL、PostgreSQL都实现了B+树索引。B+ 树是一个平衡多叉树,层级少(通常只有3层)、数据块(内部节点/叶子节点)大小固定,是一个非常优秀的磁盘数据结构。关于B+ 树的原理和实现,网上有非常多的介绍,就不在此聒噪。这里将介绍如何实现支持并发操作的B+树以及MiniOB中的实现。


# B+树的并发操作
## B+树的并发操作
在多线程并发操作时,通常使用的手段是加锁,这里的实现方法也是这样。不过在学习并发B+树实现原理之前,需要对B+树的实现比较熟悉,有兴趣的同学可以网上搜索一下。

## Crabing Protocol
### Crabing Protocol
在操作B+树时加对应的读写锁是一种最简单粗暴但是有效的方法,只是这样实现效率不高。于是就有一些研究创建了更高效的并发协议,并且会在协议设计上防止死锁的发生。
![B+树示例](images/bplus-tree.jpg)

Expand All @@ -33,11 +35,11 @@ B+树的操作除了上述的插入、删除和查询,还有一个扫描操作
问题:哪种场景下,扫描加锁可能会与更新操作的加锁引起死锁?
问题:请参考[2],给出一个遍历时不需要重试的加锁方案。

## MiniOB实现
### MiniOB实现
MiniOB的B+树并发实现方案与上个章节描述的方法是一致的。这里介绍一些实现细节。
> 在这里假设同学们对B+树的实现已经有了一定的了解。
### B+树与Buffer Pool
#### B+树与Buffer Pool
B+树的数据是放在磁盘上的,但是直接读写磁盘是很慢的一个操作,因此这里增加一个内存缓冲层,叫做Buffer Pool。了解数据库实现的同学对这个名词不会陌生。在MiniOB中,Buffer Pool的实现是 `class DiskBufferPool`。对Buffer Pool实现不太了解也没关系,这里接单介绍一下。

`DiskBufferPool` 将一个磁盘文件按照页来划分(假设一页是8K,但是不一定),每次从磁盘中读取文件或者将数据写入到文件,都是以页为单位的。在将文件某个页面加载到内存中时,需要申请一块内存。内存通常会比磁盘要小很多,就需要引入内存管理。在这里引入Frame(页帧)的概念(参考 `class Frame`),每个Frame关联一个页面。`FrameManager`负责分配、释放Frame,并且在没有足够Frame的情况下,淘汰掉一些Frame,然后将这些Frame关联到新的磁盘页面。
Expand All @@ -51,7 +53,7 @@ B+ 树的数据保存在磁盘,其树节点,包括内部节点和叶子节

问题:为什么一定要先执行解锁,再执行unpin(frame引用计数减1)?

### 处理流程
#### 处理流程
B+树相关的操作一共有4个:插入、删除、查找和遍历/扫描。这里对每个操作的流程都做一个汇总说明,希望能帮助大家了解大致的流程。

**插入操作**
Expand Down Expand Up @@ -116,7 +118,7 @@ B+树相关的操作一共有4个:插入、删除、查找和遍历/扫描。
memo.release_last // 释放当前节点之前加到的锁
```

### 根节点处理
#### 根节点处理
前面描述的几个操作,没有特殊考虑根节点。根节点与其它节点相比有一些特殊的地方:
- B+树有一个单独的数据记录根节点的页面ID,如果根节点发生变更,这个数据也要随着变更。这个数据不是被Frame的锁保护的;
- 根节点具有一定的特殊性,它是否“安全”,就是根节点是否需要变更,与普通节点的判断有些不同。
Expand All @@ -126,15 +128,15 @@ B+树相关的操作一共有4个:插入、删除、查找和遍历/扫描。
在MiniOB中,可以参考`LatchMemo`,是直接使用xlatch/slatch对Mutex来记录加过的锁,这里可以直接把根节点数据保护锁,告诉LatchMemo,让它来负责相关处理工作。
判断根节点是否安全,可以参考`IndexNodeHandler::is_safe``is_root_node`相关的判断。

### 如何测试
#### 如何测试
想要保证并发实现没有问题是在太困难了,虽然有一些工具来证明自己的逻辑模型没有问题,但是这些工具使用起来也很困难。这里使用了一个比较简单的方法,基于google benchmark框架,编写了一个多线程请求客户端。如果多个客户端在一段时间内,一直能够比较平稳的发起请求与收到应答,就认为B+树的并发没有问题。测试代码在`bplus_tree_concurrency_test.cpp`文件中,这里包含了多线程插入、删除、查询、扫描以及混合场景测试。

## 其它
### 其它

### 有条件的开启并发
#### 有条件的开启并发
MiniOB是一个用来学习的小型数据库,为了简化上手难度,只有使用-DCONCURRENCY=ON时,并发才能生效,可以参考 mutex.h中`class Mutex``class SharedMutex`的实现。当CONCURRENCY=OFF时,所有的加锁和解锁函数相当于什么都没做。

### 并发中的调试
#### 并发中的调试
死锁是让人非常头疼的事情,我们给Frame增加了调试日志,并且配合pin_count的动作,每次加锁、解锁以及pin/unpin都会打印相关日志,并在出现非预期的情况下,直接ABORT,以尽早的发现问题。这个调试能力需要在编译时使用条件 `-DDEBUG=ON` 才会生效。
以写锁为例:

Expand All @@ -160,7 +162,7 @@ void Frame::write_latch(intptr_t xid)
}
```
# 参考
## 参考
[1] [15445 indexconcurrency](https://15445.courses.cs.cmu.edu/fall2021/notes/08-indexconcurrency.pdf)
[2] Concurrency of Operations on B-Trees
Expand Down
28 changes: 15 additions & 13 deletions docs/docs/design/miniob-mysql-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
title: MySQL 协议
---

# MySQL 通讯协议

> 本篇文档介绍MySQL的通讯流程以及MiniOB对它的支持与实现
# MiniOB 通讯协议简介
## MiniOB 通讯协议简介

MiniOB 支持使用客户端/服务端模式,客户端与服务端需要通过通讯来交互。当前服务端支持普通的文本协议与MySQL协议。
普通的文本协议非常简单,每个请求和应答都使用字符串来传递,字符串以'\0'字符结尾,因此文本协议不能支持二进制数据的传输。
MySQL 是一个非常流行的开源数据库,它有非常丰富的周边生态工具,如果MiniOB可以支持MySQL协议,后续就可以逐步扩展支持这些工具。

# MySQL 通讯协议
## MySQL 通讯协议

MySQL 服务端与客户端交互的过程。

Expand All @@ -28,40 +30,40 @@ MySQL 服务端与客户端交互的过程。
客户端 -> 服务器:发送退出命令包。
5. 四次握手断开 TCP 连接。

## MySQL Packet
### MySQL Packet
MySQL 协议通过packet来交互。每个packet都包含一个packet header和packet payload。
packet header包含payload的长度和当前消息包的sequence。sequence是从1开始,每发出一个消息包,sequence都会加1。
每个消息包都由一些字段构成,字段的类型有很多种,主要有整形和字符串。每种类型又有多种编码方式,比如字符串有固定长度的、以'\0'结尾的和带长度编码的。这些可以参考 mysql_communicator.cpp::store_xxx 函数。

> 注意,MySQL协议中数字都是小端编码。而MiniOB没有对大小端做处理,所以当前只能运行在小端的机器上。
## 认证阶段
### 认证阶段
完成MySQL客户端与MiniOB的建连。
构造handshake包,解析验证包, 返回OK包,确保server与客户端的建连。

**握手包handshake格式**

<img src="images/mysql-handshake.png" width = "50%" alt="mysql-handshake" align=center />
![mysql-handshake](images/mysql-handshake.png)

在accept接收到新的连接时,server端需要先发起handshake握手包给客户端。

**认证报文**

<img src="images/mysql-auth.png" width = "50%" alt="mysql-auth" align=center />
![mysql-auth](images/mysql-auth.png)

这里有两次hash加密,基于随机挑战码和密码加密后返回给server端。

MyqlCommunicator::init是在刚接收到新的客户端连接时的接口,它会构造一个握手包发给客户端。在第一次接收客户端数据时,即在MysqlCommunicator::read_event中,如果判断还没有做过鉴权,就会做一个特殊处理,判断是否可以认证通过。不过当前MiniOB不会真的做密码验证。如果已经做过鉴权,就会认为接收的数据包是一个普通的命令消息。在接收到客户端的认证报文后,需要返回OK包给客户端。

**OK包报文**

<img src="images/mysql-ok-packet.png" width = "50%" alt="mysql-ok-packet" align=center />
![mysql-ok-packet](images/mysql-ok-packet.png)

## 请求交互阶段
### 请求交互阶段

在完成鉴权后,客户端就可以发送普通的请求命令到服务端,比如 "select * from t;" 查询语句。

<img src="images/mysql-command-packet.png" width = "50%" alt="mysql-command-packet" align=center />
![mysql-command-packet](images/mysql-command-packet.png)

MiniOB 仅考虑支持普通的文本查询命令。普通的文本查询命令,包格式也符合MySQL Packet的要求。其payload的第一个字节是command,接着就是请求命令,也就是SQL语句。

Expand All @@ -71,18 +73,18 @@ SQL请求的返回数据类型比较丰富,有OK、Error、EOF和ResultSet。

**OK/EOF包**

<img src="images/mysql-ok-eof-packet.png" width = "50%" alt="mysql-ok-eof-packet" align=center />
![mysql-ok-eof-packet](images/mysql-ok-eof-packet.png)

**Error包**

<img src="images/mysql-error-packet.png" width = "50%" alt="mysql-error-packet" align=center />
![mysql-error-packet](images/mysql-error-packet.png)

**ResultSet**

<img src="images/mysql-result-set-packet.png" width = "50%" alt="mysql-result-set-packet" align=center />
![mysql-result-set-packet](images/mysql-result-set-packet.png)

**抓包**

抓包可以很清晰的看到整个流程。

<img src="images/mysql-packet-flow.png" width = "70%" alt="mysql-packet-flow" align=center />
![mysql-packet-flow](images/mysql-packet-flow.png)
23 changes: 13 additions & 10 deletions docs/docs/design/miniob-thread-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,23 @@
title: 线程模型
---

# 线程模型

> 本篇文档介绍 MiniOB 中的线程池模型。
# 简介
## 简介
多线程是提高系统资源利用率的一种常用手段,也是我们学习软件开发进阶的必经之路。
MiniOB 实现了一个可扩展的线程模型,当前支持两种线程池模型:

- 一个连接一个线程;

- 一个线程池处理所有连接。

> 这种设计是模仿了MySQL/MariaDB的线程模型设计。
# 线程模型设计
## 线程模型设计

## 命令行参数
### 命令行参数
当前MiniOB的线程池模型通过命令行接口指定使用哪种类型:
```bash
# 一个连接一个线程(默认)
Expand All @@ -25,17 +29,17 @@ observer -T=java-thread-pool

ThreadHandler::create 会根据传入的名字创建对应的 ThreadHandler 对象。

## 线程池模型做什么
### 线程池模型做什么
这个模型并不负责所有的任务,只处理客户端发来的请求。包括监听客户端是否有消息到达、处理SQL请求与返回应答、关闭连接等。

线程模型并不负责监听新的客户端连接,这是在主线程中做的事情,参考 `NetServer::serve`。当有新的连接到达时,会调用 `ThreadHandler::new_connection`,线程模型按照自己的模型设计来处理新来的连接。

## 一个连接一个线程
### 一个连接一个线程
OneThreadPerConnectionThreadHandler 会为每个连接创建一个线程,这个线程负责监听这个连接是否有消息到达、处理SQL请求与返回应答、关闭连接等。

<img src="images/thread-model-one-thread-per-connection.png" width = "40%" alt="OneThreadPerConnectionThreadHandler" align=center />
![OneThreadPerConnectionThreadHandler](images/thread-model-one-thread-per-connection.png)

## 线程池模型
### 线程池模型
JavaThreadPoolThreadHandler 会创建一个线程池,线程池中一个线程负责监听所有连接是否有消息到达。如果有消息到达,就将这个连接对象放入线程池任务队列中,等待线程池中的线程来处理。在某个连接的任务处理完成之前,不会监听它的新消息。

这个线程池使用libevent实现消息监听,参考 `JavaThreadPoolThreadHandler::start`
Expand All @@ -44,10 +48,9 @@ JavaThreadPoolThreadHandler 会创建一个线程池,线程池中一个线程

ThreadPoolExecutor 是一个简单的可伸缩线程池。当任务比当前空闲线程多的时候,就会扩容。当某些线程空闲时间比较久,就会自动退出。

<img src="images/thread-model-thread-pool.png" width = "40%" alt="JavaThreadPoolThreadHandler" align=center />

![JavaThreadPoolThreadHandler](images/thread-model-thread-pool.png)

# 参考
## 参考
- [MySQL Percona Thread Pool](https://docs.percona.com/percona-server/5.7/performance/threadpool.html#handling-of-long-network-waits)
- [MariaDB Thread Pool](https://mariadb.com/kb/en/thread-groups-in-the-unix-implementation-of-the-thread-pool/)
- [Java ThreadPoolExecutor](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadPoolExecutor.html)
Loading

0 comments on commit 94f07b5

Please sign in to comment.