这是一个用于学习存储技术练习项目。Append-Only语义的接口,在存储系统中很常见。 但是很多上层应用不能直接使用Append-Only语义的存储接口,一般要求update-in-place 接口。则就需要把append-only接口转换成update-in-place接口,或者说,基于append-only 接口实现update-in-place接口。
Append-only接口指的是,只能在文件或者对象尾部添加数据,已经写入了文件或者对象的 数据,不能修改,只能把文件或者对象整个删除掉才能释放已经占用空间。append-only的 文件或者对象,支持随机读,也就是可以读取任何位置的数据。update-in-place接口,则 可以修改文件和对象中任何位置的数据。
Flash芯片的特性就跟Append-only语义很接近。Flash芯片只能以block为单位进行擦除, 通常一个block是64KB、128KB、256KB,甚至1MB。每次擦除之后,Flash芯片的这个block 可以被改写一次,改写的单位是page,通常是4KB或者8KB。一旦被改写,则不能再次被 改写,再次改写之前,必须先对整个block执行擦除操作。擦除操作性能开销很大。 基于flash的这些特性,可以很容易实现append-only接口。但是要实现update-in-place 接口,则没那么容易。SSD盘内部的FTL软件,一个主要功能就是实现append-only和 update-in-place之间的转换。
最近存储行业提出了ZNS接口的SSD盘,这个ZNS接口就是append-only语义的接口。 由于上层应用更多使用update-in-place接口,例如:POSIX文件接口、SCSI/NVMe等块设备 接口,或者KV数据库接口,等等。因此就需要,在ZNS接口上要实现update-in-place接口 语义的接口,块设备接口、POSIX文件接口、或者KV接口。
另外,很多分布式存储系统的存储层接口,也是Append-Only语义的。例如,阿里云的Pangu, 微软的WAS,谷歌的GFS等等,也需要基于Append-Only语义接口要实现块设备接口,文件 接口等。例如,阿里云的块存储服务EBS是基于Pangu的Append-only接口实现的。
对于存储行业研发人员来说,基于Append-only语义存储接口,如何实现update-in-place 接口,已经是必备知识了。
如下图所示,BDEV是一个基于append-only接口实现的nbd server。通过Linux内核的nbd client可以链接到这个nbd server上,就可以看到Linux主机上增加了一个新的块设备 /dev/nbd0,然后可以在这个块设备上创建文件系统,挂载这个块设备。
BDEV中nbd-server协议层代码,直接用go-nbd的 实现。块设备本身Write-in-place的语义,bdev/bd这个目录中的源码实现的。几个子目录 中源码功能如下:
用golang描述的底层append-only接口如下
//append-only对象的定义
type AOF struct {
f *os.File
}
//对已经存在的AOF文件,用open打开
func Open(name string) (a *AOF, err error)
//对不存在的AOF文件,用Create创建
func Create(name string) (a *AOF, err error)
//返回AOF的属性
func Stat(name string)(fi os.FileInfo, err error)
//AOF文件属性包括下面一些信息
type FileInfo interface {
Name() string // base name of the file
Size() int64 // length in bytes for regular files;
Mode() FileMode // file mode bits
ModTime() time.Time // modification time
IsDir() bool // abbreviation for Mode().IsDir()
Sys() any // underlying data source (can return nil)
}
//删掉已经存在的AOF文件
func Remove(name string)
//从AOF文件的某个位置读出len(b)这么多数据
func (f *AOF) ReadAt(b []byte, off int64) (n int, err error)
//AOF文件的追加写的接口,返回写入的位置
func (f *AOF) Append(b []byte) int64
//关闭AOF文件
func (f *AOF) Close() error
从gitee上下载,git clone git@gitee.com:mphyatyh/bdev.git
。
从github上下载,git clone git@github.com:pojntfx/go-nbd.git
。
进入go-nbd-client所在目录,执行go build
,然后用ls
命令,可以看到生成了一个
go-nbd-example-client
可执行程序。
root@hy:~/go-nbd/cmd/go-nbd-example-client# go build
root@hy:~/go-nbd/cmd/go-nbd-example-client# ls -lah
total 3.3M
drwxr-xr-x 2 root root 4.0K Apr 26 21:48 .
drwxr-xr-x 5 root root 4.0K Apr 23 21:46 ..
-rwxr-xr-x 1 root root 3.3M Apr 29 09:06 go-nbd-example-client
-rw-r--r-- 1 root root 1.4K Apr 23 21:46 main.go
进入bdev目录,执行make
编译bdev。然后装入nbd模块,并运行./bdev
。
root@hy:~/bdev# make
go build
root@hy:~/bdev#
root@hy:~/bdev# modprobe nbd
root@hy:~/bdev# ./bdev
2024/04/29 09:11:31 Listening on [::]:10809
进入go-nbd/cmd/go-nbd-example-client目录,执行go-nbd-example-client命令。
root@hy:~/go-nbd/cmd/go-nbd-example-client# ./go-nbd-example-client
2024/04/29 09:34:26 Connected to 127.0.0.1:10809
此时应该可以看到bdev进程有如下输出,即表示nbd-client已经链接上了。
root@hy:~/bdev# ./bdev
2024/04/29 09:33:11 Listening on [::]:10809
2024/04/29 09:34:26 1 clients connected
执行lsblk,可以看到一个nbd0的块设备,表明bdev已经正常工作。
root@hy:~# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 80G 0 disk
├─sda1 8:1 0 1M 0 part
├─sda2 8:2 0 1G 0 part /boot
└─sda3 8:3 0 79G 0 part
└─ubuntu--vg-ubuntu--lv 253:0 0 79G 0 lvm /
sdb 8:16 0 40G 0 disk /usr1
sr0 11:0 1 1024M 0 rom
nbd0 43:0 0 100M 0 disk
执行mkfs.ext4 /dev/nbd0
创建文件系统。
root@hy:~/bdev# mkfs.ext4 /dev/nbd0
mke2fs 1.45.6 (20-Mar-2020)
Creating filesystem with 25600 4k blocks and 25600 inodes
Allocating group tables: done
Writing inode tables: done
Creating journal (1024 blocks): done
Writing superblocks and filesystem accounting information: done
执行下面命令,安装这个把/dev/nbd0这个块设备mount到/mnt/bdev这个目录。
root@hy:~/bdev# mount /dev/nbd0 /mnt/bdev/
执行下面命令检查是否mount成功。
root@hy:~/bdev# mount -l -t ext4
/dev/mapper/ubuntu--vg-ubuntu--lv on / type ext4 (rw,relatime)
/dev/sdb on /usr1 type ext4 (rw,relatime)
/dev/sda2 on /boot type ext4 (rw,relatime)
/dev/nbd0 on /mnt/bdev type ext4 (rw,relatime)
执行下面命令,创建一个文件,创建一个目录。
root@hy:~/bdev# echo hello > /mnt/bdev/a.txt
root@hy:~/bdev# cat /mnt/bdev/a.txt
hello
root@hy:~/bdev# mkdir /mnt/bdev/dir0
root@hy:~/bdev# ls -lai /mnt/bdev/
total 32
2 drwxr-xr-x 4 root root 4096 Apr 29 17:07 .
2883585 drwxr-xr-x 4 root root 4096 Apr 29 09:37 ..
12 -rw-r--r-- 1 root root 6 Apr 29 17:06 a.txt
13 drwxr-xr-x 2 root root 4096 Apr 29 17:07 dir0
11 drwx------ 2 root root 16384 Apr 29 16:55 lost+found
至此,已经实现了一个简单EBS系统的核心功能,用了不到1000行Golang代码。这个简单 EBS系统的核心是一个简单的LSM Tree,它依赖的底层存储是Append-only语义的接口。
root@hy:~/bdev# cloc .
18 text files.
18 unique files.
4 files ignored.
github.com/AlDanial/cloc v 1.82 T=0.05 s (323.3 files/s, 31686.5 lines/s)
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Go 9 190 82 955
Markdown 1 48 0 169
make 4 2 0 13
Bourne Shell 1 2 2 7
-------------------------------------------------------------------------------
SUM: 15 242 84 1144
-------------------------------------------------------------------------------
你可以考虑以BDEV为基础,做一些更加有意义的工作。例如,可以为BDEV增加快照特性, 这样就可以在不停止服务的情况下,基于BDEV的快照来做备份。如果块设备不支持快照, 在不停止服务的情况下,无法保证备份出来的数据是一致的。基于快照备份块设备数据, 可以保证数据的一致性性。
可以把aof目录中的append-only接口修改为真正的分布式append-only接口,这就实现了 阿里云的EBS+Pangu的主要功能。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。