0%

环境

1
2
3
4
5
6
7
8
# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 22.04.4 LTS
Release: 22.04
Codename: jammy
# uname -a
Linux ubu22 5.15.0-100-generic #110-Ubuntu SMP Wed Feb 7 13:28:04 UTC 2024 aarch64 aarch64 aarch64 GNU/Linux

包安装

安装软件包

1
apt install -y bison flex build-essential git cmake make libelf-dev strace tar libfl-dev libssl-dev libedit-dev zlib1g-dev  python  python3-distutils libcap-dev llvm clang

安装 bcc

bcc 是一组用于 eBPF 开发的工具,它包括了一组用于编写和调试 eBPF 程序的库和命令行工具。使用 bcc,可以更加方便地开发和调试 eBPF 程序,提高开发效率和代码质量。

1
apt install bpfcc-tools

安装 bpftool

bpftool 是一个用于管理和调试 eBPF 代码的命令行工具。它允许你查看和分析系统中运行的 eBPF 程序和映射,以及与内核中的 eBPF 子系统进行交互。更多内容可以查看 bpftool Github

使用 bpftool,你可以执行以下操作:

  • 列出当前系统中所有加载的 eBPF 程序和映射
  • 查看指定 eBPF 程序或映射的详细信息,例如指令集、内存布局等
  • 修改 eBPF 程序或映射的属性,例如禁用一个程序或清空一个映射
  • 将一个 eBPF 程序或映射导出到文件中,以便在其他系统上重新导入
  • 调试 eBPF 程序,例如跟踪程序的控制流、访问内存等
1
apt install -y linux-tools-$(uname -r)

默认情况下 bpftool 命令会安装到/usr/local/sbin/ 下

1
2
3
4
5
6
7
8
# bpftool version -p
{
"version": "5.15.143",
"features": {
"libbfd": false,
"skeletons": false
}
}

安装linux源码

1
apt install linux-source-5.15.0

编译bpf sample

解压缩

1
2
cd /usr/src/linux-source-5.15.0/debian
tar -jxvf linux-source-5.15.0.tar.bz2

Copy config文件

1
2
3
cd linux-source-5.15.0
cp -v /boot/config-$(uname -r) .config
make oldconfig && make prepare

编译Samples

1
make -C samples/bpf

若遇到如下错误

1
2
3
4
5
6
7
8
......
CC /usr/src/linux-source-5.15.0/samples/bpf/bpftool/prog.o
LINK /usr/src/linux-source-5.15.0/samples/bpf/bpftool/bpftool
/usr/src/linux-source-5.15.0/samples/bpf/Makefile:369: *** Cannot find a vmlinux for VMLINUX_BTF at any of " /usr/src/linux-source-5.15.0/vmlinux", build the kernel or set VMLINUX_BTF or VMLINUX_H variable. Stop.
make[1]: *** [Makefile:1911: /usr/src/linux-source-5.15.0/samples/bpf] Error 2
make[1]: Leaving directory '/usr/src/linux-source-5.15.0'
make: *** [Makefile:275: all] Error 2
make: Leaving directory '/usr/src/linux-source-5.15.0/samples/bpf'

说明需要 vmlinux,可以指定vmlinux再进行编译

1
make VMLINUX_BTF=/sys/kernel/btf/vmlinux -C samples/bpf

or

1
make VMLINUX_BTF=/sys/kernel/btf/vmlinux M=samples/bpf

示例

示例需要在 usr/src/linux-source-5.15.0/samples/bpf目录下进行编译。samples/bpf 下的程序一般组成方式是 xxx_user.c 和 xxx_kern.c

  • xxx_user.c:为用户空间的程序用于设置 BPF 程序的相关配置、加载 BPF 程序至内核、设置 BPF 程序中的 map 值和读取 BPF 程序运行过程中发送至用户空间的消息等。
  • xxx_kern.c:为 BPF 程序代码,通过 clang 编译成字节码加载至内核中,在对应事件触发的时候运行,可以接受用户空间程序发送的各种数据,并将运行时产生的数据发送至用户空间程序。

新建两个文件:bob_user.cbob_kern.c

bob_kern.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <uapi/linux/bpf.h>
#include <linux/version.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

SEC("tracepoint/syscalls/sys_enter_execve")
int bpf_prog(struct pt_regs *ctx)
{
char fmt[] = "bob %s !\n";
char comm[16];
bpf_get_current_comm(&comm, sizeof(comm));
bpf_trace_printk(fmt, sizeof(fmt), comm);

return 0;
}

char _license[] SEC("license") = "GPL";
u32 _version SEC("version") = LINUX_VERSION_CODE;

bob_user.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <unistd.h>
#include <bpf/libbpf.h>
#include "trace_helpers.h"

int main(int ac, char **argv)
{
struct bpf_link *link = NULL;
struct bpf_program *prog;
struct bpf_object *obj;
char filename[256];

snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
obj = bpf_object__open_file(filename, NULL);
if (libbpf_get_error(obj)) {
fprintf(stderr, "ERROR: opening BPF object file failed\n");
return 0;
}

prog = bpf_object__find_program_by_name(obj, "bpf_prog");
if (!prog) {
fprintf(stderr, "ERROR: finding a prog in obj file failed\n");
goto cleanup;
}

/* load BPF program */
if (bpf_object__load(obj)) {
fprintf(stderr, "ERROR: loading BPF object file failed\n");
goto cleanup;
}

link = bpf_program__attach(prog);
if (libbpf_get_error(link)) {
fprintf(stderr, "ERROR: bpf_program__attach failed\n");
link = NULL;
goto cleanup;
}

read_trace_pipe();

cleanup:
bpf_link__destroy(link);
bpf_object__close(obj);
return 0;
}

修改 samples/bpf/Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
tprogs-y += xdp_sample_pkts
tprogs-y += ibumad
tprogs-y += hbm
# 增加 user programs
tprogs-y += bob
...
ibumad-objs := ibumad_user.o
hbm-objs := hbm.o $(CGROUP_HELPERS)
# 增加 objs
bob-objs := bob_user.o $(TRACE_HELPERS)
...
always-y += hbm_edt_kern.o
always-y += xdpsock_kern.o
# 增加 kernel programs
always-y += bob_kern.o

编译

1
make VMLINUX_BTF=/sys/kernel/btf/vmlinux M=samples/bpf

运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# cd samples/bpf
# ./bob
libbpf: elf: skipping unrecognized data section(4) .rodata.str1.1
<...>-3622529 [001] d...1 1751426.283602: bpf_trace_printk: bob sshd !


sshd-3622530 [001] d...1 1751426.288591: bpf_trace_printk: bob sshd !


sshd-3622531 [001] d...1 1751430.924951: bpf_trace_printk: bob sshd !


<...>-3622532 [000] d...1 1751430.929273: bpf_trace_printk: bob sshd !


bash-3622532 [000] d...1 1751430.930114: bpf_trace_printk: bob bash !


<...>-3622533 [001] d...1 1751430.934470: bpf_trace_printk: bob sshd !

参考&鸣谢

简介

DRBD(Distributed Replicated Block Device,分布式复制块设备)是一个用软件实现的、无共享的、服务器之间镜像块设备内容的存储复制解决方案。DRBD是镜像块设备,是按数据位镜像成一样的数据块。

安装

ubuntu环境

准备两个ubuntu系统,一个作为源端ubu1,一个作为目的端ubu2。

1
2
3
4
5
6
7
8
bob@ubu1:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04.5 LTS
Release: 20.04
Codename: focal
bob@ubu1:~$ uname -a
Linux ubu1 5.4.0-139-generic #156-Ubuntu SMP Sat Jan 21 13:46:46 UTC 2023 aarch64 aarch64 aarch64 GNU/Linux
1
2
3
4
5
6
7
8
bob@ubu2:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04.5 LTS
Release: 20.04
Codename: focal
bob@ubu2:~$ uname -a
Linux ubu2 5.4.0-139-generic #156-Ubuntu SMP Sat Jan 21 13:46:46 UTC 2023 aarch64 aarch64 aarch64 GNU/Linux

brbd.ko为linux内核自带,不需要另行安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
bob@ubu1:~$ lsmod | grep drbd
drbd 434176 3
lru_cache 20480 1 drbd
libcrc32c 16384 4 btrfs,xfs,drbd,raid456
bob@ubu1:~$ modinfo drbd
filename: /lib/modules/5.4.0-139-generic/kernel/drivers/block/drbd/drbd.ko
alias: block-major-147-*
license: GPL
version: 8.4.11
description: drbd - Distributed Replicated Block Device v8.4.11
author: Philipp Reisner <phil@linbit.com>, Lars Ellenberg <lars@linbit.com>
srcversion: B438804C5AE8C84C95D0411
depends: lru_cache,libcrc32c
intree: Y
name: drbd
vermagic: 5.4.0-139-generic SMP mod_unload modversions aarch64
sig_id: PKCS#7
signer: Build time autogenerated kernel key
sig_key: 43:64:51:B8:71:C1:53:C3:AE:29:64:EC:5F:B2:A3:30:54:25:F9:B5
sig_hashalgo: sha512
signature: 0F:16:6D:12:54:0F:95:51:A5:18:4A:0A:A9:7C:72:F6:2D:31:21:39:
44:D9:71:C8:1B:7C:3F:1B:4B:66:53:08:1A:63:92:C3:CE:19:19:F0:
13:9F:9D:D6:7B:0B:27:F8:54:19:D3:44:F9:CC:8F:6E:E1:82:07:7A:
30:B1:37:28:5E:60:92:5B:C9:26:B0:25:DD:50:9D:A5:2E:76:96:A8:
E2:58:A1:36:DC:AA:84:D2:40:EF:66:F1:82:E5:2B:51:D7:F8:49:62:
9A:62:22:DC:42:A9:01:EC:6B:DC:9F:C6:8E:DD:9D:4A:CD:93:9B:F9:
3B:E4:4A:B4:34:BD:12:1A:89:C0:2C:2B:96:4D:86:27:D5:93:3A:51:
D4:DD:C7:34:9D:2D:A0:6C:BC:57:D1:9F:54:C2:55:56:B2:60:4D:FB:
86:55:18:09:9C:A5:C1:80:54:F5:F5:35:97:7E:32:36:A9:9B:F9:B2:
69:5B:BD:83:A3:41:89:9B:4F:7D:BA:A9:F1:E2:28:6A:FD:D4:7A:94:
67:34:D1:3C:8D:39:18:F7:43:A7:5F:67:71:B6:3B:63:3C:10:16:B7:
44:EA:D2:D4:52:19:28:F3:60:31:35:69:6B:EA:25:3B:35:FC:32:05:
B9:5A:99:7E:61:27:59:52:A8:55:8C:B6:15:8E:77:26:0F:DC:61:93:
F9:10:EA:8F:FD:E1:E0:DF:60:2D:83:92:9E:33:D7:99:10:93:AF:03:
44:8B:A0:6A:E8:E7:08:71:77:1C:CC:35:96:8C:94:DB:62:CA:ED:64:
B8:17:43:24:FD:BF:FC:3B:D7:D7:03:1C:83:06:0E:B5:D3:82:2A:4B:
E4:A7:F9:C4:07:9C:0F:2B:3E:D4:EA:48:FF:6E:BD:79:2E:85:FB:EE:
2D:A2:70:96:38:77:9C:29:58:5C:45:B1:8A:29:D2:7A:87:55:A6:A3:
32:A2:FC:F5:A2:4F:9C:20:85:F0:55:E6:A6:01:D2:76:EE:27:2F:A0:
BC:67:F2:B9:9B:A4:5D:2D:3D:6F:E9:46:C0:EF:4A:0C:01:15:19:E7:
9C:61:A9:00:E8:1A:D3:7D:2E:86:81:FD:BA:53:44:85:17:19:D9:B3:
B8:E2:7B:39:01:28:3E:F3:DF:87:3C:C5:E4:37:89:C1:67:16:B0:2C:
C7:4D:C8:6F:71:97:55:19:24:63:7B:D6:08:A2:EC:EB:C3:AA:71:91:
35:B4:C6:47:B5:EA:2B:DF:E4:AC:9C:06:77:73:1B:33:F5:AF:EE:58:
AC:F2:C4:A0:C9:00:58:AB:3F:62:1B:E5:E5:C3:E1:39:98:5B:2A:C9:
BD:10:6B:AF:FC:44:B0:7D:57:07:B0:5B
parm: allow_oos:DONT USE! (bool)
parm: disable_sendpage:bool
parm: proc_details:int
parm: minor_count:Approximate number of drbd devices (1-255) (uint)
parm: usermode_helper:string

至于使用drbd的工具还是要安装的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
bob@ubu1:~$ sudo apt-cache show drbd-utils
Package: drbd-utils
Architecture: arm64
Version: 9.11.0-1build1
Priority: extra
Section: admin
Origin: Ubuntu
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Original-Maintainer: Debian DRBD Maintainers <debian-ha-maintainers@lists.alioth.debian.org>
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Installed-Size: 2062
Depends: lsb-base (>= 3.0-6), libc6 (>= 2.28), libgcc-s1 (>= 3.0), libstdc++6 (>= 5.2), init-system-helpers (>= 1.51)
Recommends: heirloom-mailx | mailx
Suggests: heartbeat
Breaks: drbd8-utils (<< 2:8.9.0)
Replaces: drbd8-utils (<< 2:8.9.0)
Filename: pool/main/d/drbd-utils/drbd-utils_9.11.0-1build1_arm64.deb
Size: 654128
MD5sum: 45a8962e941c9a87c7de423de34f616b
SHA1: 3734ee6c0611348c5b8c080feb713c05f9d84c4c
SHA256: ae63aa90d145faab00551f151c10588bafebe2673e64b137c980cce7c299bccf
Homepage: https://www.drbd.org/
Description-en: RAID 1 over TCP/IP for Linux (user utilities)
Drbd is a block device which is designed to build high availability
clusters by providing a virtual shared device which keeps disks in
nodes synchronised using TCP/IP. This simulates RAID 1 but avoiding
the use of uncommon hardware (shared SCSI buses or Fibre Channel).
It is currently limited to fail-over HA clusters.
.
This package contains the programs that will control the drbd kernel
module provided in the Linux kernel.
Description-md5: 7da3dade742b03d1a9c08b339123f93b

bob@ubu1:~$ apt install drbd-utils

两个节点ubu1和ubu2都需要安装这个drbd-utils

常规用法

配置

配置/etc/drbd.d/global_common.conf

1
2
3
4
5
6
7
8
9
global {
usage-count yes;
}

common {
net {
protocol C;
}
}

DRBD系统向虚拟块的镜像中写入数据时,支持三种协议

  • protocol A 数据一旦写入磁盘并发送到网络中就认为完成了写入操作
  • protocol B 收到接收确认就认为完成了写入操作
  • protocol C 收到写入确认就认为完成了写入操作

基于安全考虑我们一般选择protocol C

配置DRBD资源/etc/drbd.d/r0.res

1
2
3
4
5
6
7
8
9
10
11
12
13
14
resource r0 {
on ubu1 {
device /dev/drbd1;
disk /dev/sda;
address 192.168.64.2:7789;
meta-disk internal;
}
on ubu2 {
device /dev/drbd1;
disk /dev/sda;
address 192.168.64.4:7789;
meta-disk internal;
}
}

device指drbd映射出来的快设备名称,disk指用于存放数据的硬盘。

启动

分别在ubu1ubu2上创建DRBD资源元数据

1
bob@ubu1:~$ sudo drbdadm create-md r0
1
bob@ubu2:~$ sudo drbdadm create-md r0

然后再启动ubu1ubu2上的DRBD服务

1
bob@ubu1:~$ sudo systemctl start drbd
1
bob@ubu2:~$ sudo systemctl start drbd

启动r0资源,并设置ubu1为主设备

1
2
bob@ubu1:~$ sudo drbdadm up r0
bob@ubu1:~$ sudo drbdadm primary --force r0

可以使用drbdadm status r0查看状态,也可以用cat /proc/drbd查看同步进度。
peer-disk状态为UpToDate表示数据已经同步到最新状态。

eg:

1
2
3
4
5
bob@ubu1:~$ sudo drbdadm status r0
r0 role:Primary
disk:UpToDate
peer role:Secondary
replication:Established peer-disk:UpToDate

如需查询服务状态详细信息可以通过命令drbdsetup status r0 --verbose --statistics查询

文件系统常规使用

首先通过lsblk查看drbd1快设备是否存在,然后格式化成xfs文件系统,并挂载到/mnt目录上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bob@ubu1:~$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
loop1 7:1 0 49.1M 1 loop /snap/core18/2681
loop2 7:2 0 59.1M 1 loop /snap/core20/1826
loop3 7:3 0 60.7M 1 loop /snap/lxd/21843
loop4 7:4 0 43.2M 1 loop /snap/snapd/17954
loop5 7:5 0 91.9M 1 loop /snap/lxd/24065
loop6 7:6 0 57.5M 1 loop /snap/core20/1332
loop7 7:7 0 43.2M 1 loop /snap/snapd/18363
loop8 7:8 0 49.1M 1 loop /snap/core18/2701
sda 8:0 0 20G 0 disk
└─drbd1 147:1 0 20G 1 disk
sr0 11:0 1 1.1G 0 rom
vda 252:0 0 100G 0 disk
├─vda1 252:1 0 512M 0 part /boot/efi
├─vda2 252:2 0 1G 0 part /boot
└─vda3 252:3 0 98.5G 0 part
└─ubuntu--vg-ubuntu--lv 253:0 0 49.3G 0 lvm /
bob@ubu1:~$ mkfs.xfs /dev/drbd1
...
bob@ubu1:~$ mount /dev/drbd1 /mnt
bob@ubu1:~$ touch /mnt/hello

切换

切换操作,可以认为是切换primary节点操作。需要先将ubu1(primary节点)设置成secondary,再将ubu2(secondary节点)设置成primary,然后ubu2节点就可以挂载drbd1快设备了。

1
bob@ubu1:~$ sudo drbdadm secondary r0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bob@ubu2:~$ sudo drbdadm primary r0
bob@ubu2:~$ sudo mount /dev/drbd1 /mnt/
bob@ubu2:~$ df -h
Filesystem Size Used Avail Use% Mounted on
udev 1.9G 0 1.9G 0% /dev
tmpfs 392M 1.4M 391M 1% /run
/dev/mapper/ubuntu--vg-ubuntu--lv 49G 7.3G 39G 16% /
tmpfs 2.0G 0 2.0G 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 2.0G 0 2.0G 0% /sys/fs/cgroup
/dev/vda2 974M 206M 701M 23% /boot
/dev/vda1 511M 5.3M 506M 2% /boot/efi
/dev/loop3 60M 60M 0 100% /snap/core20/1826
/dev/loop1 50M 50M 0 100% /snap/core18/2681
/dev/loop2 58M 58M 0 100% /snap/core20/1332
/dev/loop5 92M 92M 0 100% /snap/lxd/24065
/dev/loop4 61M 61M 0 100% /snap/lxd/21843
/dev/loop6 44M 44M 0 100% /snap/snapd/17954
tmpfs 392M 0 392M 0% /run/user/1000
/dev/drbd1 20G 175M 20G 1% /mnt

在将ubu1节点从primary状态设置成secondary状态时可能会遇到如下错误

1
2
3
bob@ubu1:~$ sudo drbdadm secondary r0
1: State change failed: (-12) Device is held open by someone
env: python: No such file or directory

这说明当前块设备brbd1正在被挂载或者被其他应用使用,可以使用lsof命令查看改设备的使用情况,并终止使用后再设置节点状态为secondary

硬盘容量大小不一致情况

当源和备硬盘容量大小不一致时,取最小容量做为drbd块设备对外提供的存储空间大小。

ubu1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
bob@ubu1:~$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
loop0 7:0 0 49.1M 1 loop /snap/core18/2681
loop1 7:1 0 49.1M 1 loop /snap/core18/2701
loop2 7:2 0 43.2M 1 loop /snap/snapd/17954
loop3 7:3 0 57.5M 1 loop /snap/core20/1332
loop4 7:4 0 43.2M 1 loop /snap/snapd/18363
loop5 7:5 0 60.7M 1 loop /snap/lxd/21843
loop6 7:6 0 91.9M 1 loop /snap/lxd/24065
loop7 7:7 0 59.1M 1 loop /snap/core20/1826
sda 8:0 0 20G 0 disk
└─drbd1 147:1 0 20G 1 disk
sdb 8:16 0 21G 0 disk
└─drbd2 147:2 0 21G 0 disk
sdc 8:32 0 31G 0 disk
└─drbd3 147:3 0 22G 0 disk
sr0 11:0 1 1.1G 0 rom
vda 252:0 0 100G 0 disk
├─vda1 252:1 0 512M 0 part /boot/efi
├─vda2 252:2 0 1G 0 part /boot
└─vda3 252:3 0 98.5G 0 part
└─ubuntu--vg-ubuntu--lv 253:0 0 49.3G 0 lvm /
bob@ubu1:~$ cat /etc/drbd.d/r1.res
resource r1 {
on ubu1 {
device /dev/drbd2;
disk /dev/sdb;
address 192.168.64.2:7792;
meta-disk internal;
}
on ubu2 {
device /dev/drbd2;
disk /dev/sdb;
address 192.168.64.4:7792;
meta-disk internal;
}
}
bob@ubu1:~$ cat /etc/drbd.d/r2.res
resource r2 {
on ubu1 {
device /dev/drbd3;
disk /dev/sdc;
address 192.168.64.2:7793;
meta-disk internal;
}
on ubu2 {
device /dev/drbd3;
disk /dev/sdc;
address 192.168.64.4:7793;
meta-disk internal;
}
}
bob@ubu1:~$ cat /proc/drbd
version: 8.4.11 (api:1/proto:86-101)
srcversion: B438804C5AE8C84C95D0411

1: cs:Connected ro:Secondary/Secondary ds:UpToDate/UpToDate C r-----
ns:0 nr:0 dw:0 dr:0 al:0 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:0
2: cs:Connected ro:Primary/Secondary ds:UpToDate/UpToDate C r-----
ns:22019388 nr:0 dw:0 dr:22021500 al:0 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:0
3: cs:Connected ro:Primary/Secondary ds:UpToDate/UpToDate C r-----
ns:23067932 nr:0 dw:0 dr:23070036 al:8 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:0

ubu2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bob@ubu2:~$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
loop0 7:0 0 49.1M 1 loop /snap/core18/2681
loop1 7:1 0 49.1M 1 loop /snap/core18/2701
loop2 7:2 0 57.5M 1 loop /snap/core20/1332
loop3 7:3 0 59.1M 1 loop /snap/core20/1826
loop4 7:4 0 60.7M 1 loop /snap/lxd/21843
loop5 7:5 0 43.2M 1 loop /snap/snapd/18363
loop6 7:6 0 43.2M 1 loop /snap/snapd/17954
loop7 7:7 0 91.9M 1 loop /snap/lxd/24065
sda 8:0 0 20G 0 disk
└─drbd1 147:1 0 20G 1 disk
sdb 8:16 0 32G 0 disk
└─drbd2 147:2 0 21G 1 disk
sdc 8:32 0 22G 0 disk
└─drbd3 147:3 0 22G 1 disk
sr0 11:0 1 1.1G 0 rom
vda 252:0 0 100G 0 disk
├─vda1 252:1 0 512M 0 part /boot/efi
├─vda2 252:2 0 1G 0 part /boot
└─vda3 252:3 0 98.5G 0 part
└─ubuntu--vg-ubuntu--lv 253:0 0 49.3G 0 lvm /

硬盘扩容情况

在ubu1上使用lvm构建底层硬盘存储数据,方便后续进行扩容操作。先创建一个20G的lv,然后创建drbd块设备,待同步完数据后对lv进行扩容10G操作,最后再对drbd进行扩容操作。(此间省略lvm相关操作)

ubu2上使用一个30G的scsi设备,不配置lvm,与ubu1上的lvm块设备构成一个异构环境。

ubu1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
bob@ubu1:~/drbd_res$ cat /etc/drbd.d/r1.res
resource r1 {
on ubu1 {
device /dev/drbd2;
disk /dev/drbd-vg/drbdlv1;
address 192.168.64.2:7792;
meta-disk internal;
}
on ubu2 {
device /dev/drbd2;
disk /dev/sdb;
address 192.168.64.4:7792;
meta-disk internal;
}
}
bob@ubu1:~/drbd_res$ sudo drbdadm create-md r1
initializing activity log
initializing bitmap (640 KB) to all zero
Writing meta data...
New drbd meta data block successfully created.
success
bob@ubu1:~/drbd_res$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
loop0 7:0 0 49.1M 1 loop /snap/core18/2681
loop1 7:1 0 49.1M 1 loop /snap/core18/2701
loop2 7:2 0 43.2M 1 loop /snap/snapd/17954
loop3 7:3 0 57.5M 1 loop /snap/core20/1332
loop4 7:4 0 43.2M 1 loop /snap/snapd/18363
loop5 7:5 0 60.7M 1 loop /snap/lxd/21843
loop6 7:6 0 91.9M 1 loop /snap/lxd/24065
loop7 7:7 0 59.1M 1 loop /snap/core20/1826
sda 8:0 0 20G 0 disk
└─drbd1 147:1 0 20G 1 disk
sdb 8:16 0 21G 0 disk
└─drbd--vg-drbdlv1 253:1 0 20G 0 lvm
sdc 8:32 0 31G 0 disk
sr0 11:0 1 1.1G 0 rom
vda 252:0 0 100G 0 disk
├─vda1 252:1 0 512M 0 part /boot/efi
├─vda2 252:2 0 1G 0 part /boot
└─vda3 252:3 0 98.5G 0 part
└─ubuntu--vg-ubuntu--lv 253:0 0 49.3G 0 lvm /
bob@ubu1:~/drbd_res$ sudo drbdadm up r1
bob@ubu1:~/drbd_res$ sudo drbdadm primary --force r1
bob@ubu1:~/drbd_res$ cat /proc/drbd
version: 8.4.11 (api:1/proto:86-101)
srcversion: B438804C5AE8C84C95D0411

1: cs:Connected ro:Secondary/Secondary ds:UpToDate/UpToDate C r-----
ns:0 nr:0 dw:0 dr:0 al:0 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:0
2: cs:SyncSource ro:Primary/Secondary ds:UpToDate/Inconsistent C r-----
ns:14496 nr:0 dw:0 dr:16616 al:8 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:20956348
[>....................] sync'ed: 0.1% (20464/20476)M
finish: 2:21:35 speed: 2,416 (2,416) K/sec

ubu2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bob@ubu2:~$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
loop0 7:0 0 49.1M 1 loop /snap/core18/2681
loop1 7:1 0 49.1M 1 loop /snap/core18/2701
loop2 7:2 0 60.7M 1 loop /snap/lxd/21843
loop3 7:3 0 91.9M 1 loop /snap/lxd/24065
loop4 7:4 0 57.5M 1 loop /snap/core20/1332
loop5 7:5 0 59.1M 1 loop /snap/core20/1826
loop6 7:6 0 43.2M 1 loop /snap/snapd/17954
loop7 7:7 0 43.2M 1 loop /snap/snapd/18363
sda 8:0 0 20G 0 disk
└─drbd1 147:1 0 20G 1 disk
sdb 8:16 0 32G 0 disk
└─drbd2 147:2 0 20G 1 disk
sdc 8:32 0 22G 0 disk
sr0 11:0 1 1.1G 0 rom
vda 252:0 0 100G 0 disk
├─vda1 252:1 0 512M 0 part /boot/efi
├─vda2 252:2 0 1G 0 part /boot
└─vda3 252:3 0 98.5G 0 part
└─ubuntu--vg-ubuntu--lv 253:0 0 49.3G 0 lvm /

目前drbd2对外提供的是一个20G的硬盘,接下来在不停drbd复制链接的情况下对其扩容10G(同样,省略lvm相关操作)

ubu1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
bob@ubu1:~/drbd_res$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
loop0 7:0 0 49.1M 1 loop /snap/core18/2681
loop1 7:1 0 49.1M 1 loop /snap/core18/2701
loop2 7:2 0 43.2M 1 loop /snap/snapd/17954
loop3 7:3 0 57.5M 1 loop /snap/core20/1332
loop4 7:4 0 43.2M 1 loop /snap/snapd/18363
loop5 7:5 0 60.7M 1 loop /snap/lxd/21843
loop6 7:6 0 91.9M 1 loop /snap/lxd/24065
loop7 7:7 0 59.1M 1 loop /snap/core20/1826
sda 8:0 0 20G 0 disk
└─drbd1 147:1 0 20G 1 disk
sdb 8:16 0 21G 0 disk
└─drbd--vg-drbdlv1 253:1 0 30G 0 lvm
└─drbd2 147:2 0 20G 0 disk
sdc 8:32 0 31G 0 disk
└─drbd--vg-drbdlv1 253:1 0 30G 0 lvm
└─drbd2 147:2 0 20G 0 disk
sr0 11:0 1 1.1G 0 rom
vda 252:0 0 100G 0 disk
├─vda1 252:1 0 512M 0 part /boot/efi
├─vda2 252:2 0 1G 0 part /boot
└─vda3 252:3 0 98.5G 0 part
└─ubuntu--vg-ubuntu--lv 253:0 0 49.3G 0 lvm /
bob@ubu1:~/drbd_res$ sudo drbdadm status r1
r1 role:Primary
disk:UpToDate
peer role:Secondary
replication:SyncSource peer-disk:Inconsistent done:88.51
bob@ubu1:~/drbd_res$ sudo drbdadm resize r1
bob@ubu1:~/drbd_res$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
loop0 7:0 0 49.1M 1 loop /snap/core18/2681
loop1 7:1 0 49.1M 1 loop /snap/core18/2701
loop2 7:2 0 43.2M 1 loop /snap/snapd/17954
loop3 7:3 0 57.5M 1 loop /snap/core20/1332
loop4 7:4 0 43.2M 1 loop /snap/snapd/18363
loop5 7:5 0 60.7M 1 loop /snap/lxd/21843
loop6 7:6 0 91.9M 1 loop /snap/lxd/24065
loop7 7:7 0 59.1M 1 loop /snap/core20/1826
sda 8:0 0 20G 0 disk
└─drbd1 147:1 0 20G 1 disk
sdb 8:16 0 21G 0 disk
└─drbd--vg-drbdlv1 253:1 0 30G 0 lvm
└─drbd2 147:2 0 30G 0 disk
sdc 8:32 0 31G 0 disk
└─drbd--vg-drbdlv1 253:1 0 30G 0 lvm
└─drbd2 147:2 0 30G 0 disk
sr0 11:0 1 1.1G 0 rom
vda 252:0 0 100G 0 disk
├─vda1 252:1 0 512M 0 part /boot/efi
├─vda2 252:2 0 1G 0 part /boot
└─vda3 252:3 0 98.5G 0 part
└─ubuntu--vg-ubuntu--lv 253:0 0 49.3G 0 lvm /

ubu2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bob@ubu2:~$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
loop0 7:0 0 49.1M 1 loop /snap/core18/2681
loop1 7:1 0 49.1M 1 loop /snap/core18/2701
loop2 7:2 0 60.7M 1 loop /snap/lxd/21843
loop3 7:3 0 91.9M 1 loop /snap/lxd/24065
loop4 7:4 0 57.5M 1 loop /snap/core20/1332
loop5 7:5 0 59.1M 1 loop /snap/core20/1826
loop6 7:6 0 43.2M 1 loop /snap/snapd/17954
loop7 7:7 0 43.2M 1 loop /snap/snapd/18363
sda 8:0 0 20G 0 disk
└─drbd1 147:1 0 20G 1 disk
sdb 8:16 0 32G 0 disk
└─drbd2 147:2 0 30G 1 disk
sdc 8:32 0 22G 0 disk
sr0 11:0 1 1.1G 0 rom
vda 252:0 0 100G 0 disk
├─vda1 252:1 0 512M 0 part /boot/efi
├─vda2 252:2 0 1G 0 part /boot
└─vda3 252:3 0 98.5G 0 part
└─ubuntu--vg-ubuntu--lv 253:0 0 49.3G 0 lvm /

使用drbdadm resize r1完成对r1资源的扩容操作。

硬盘存在有效数据情况

ubu1和ubu2上分别创建了两个硬盘,一个20G存drbd的meta-data,一个30G用于存drbd的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bob@ubu1:~/drbd_res$ cat r2.res
resource r2 {
on ubu1 {
device /dev/drbd3;
disk /dev/sdc;
address 192.168.64.2:7793;
meta-disk /dev/sdb;
}
on ubu2 {
device /dev/drbd3;
disk /dev/sdb;
address 192.168.64.4:7793;
meta-disk /dev/sdc;
}
}

先对ubu1上的数据盘sdc创建文件系统,并写入数据。然后再做drbd pair。

ubu1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
bob@ubu1:~/drbd_res$ sudo mkfs.ext4 /dev/sdc
mke2fs 1.45.5 (07-Jan-2020)
Discarding device blocks: done
Creating filesystem with 8126464 4k blocks and 2031616 inodes
Filesystem UUID: 656f1b08-77a2-4488-acb1-f25ff616b257
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
4096000, 7962624

Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done

bob@ubu1:~/drbd_res$ sudo mount /dev/sdc /mnt/
bob@ubu1:~/drbd_res$ echo "Hi, bob" > /mnt/bob.txt
bob@ubu1:~/drbd_res$ sudo umount /mnt
bob@ubu1:~/drbd_res$ sudo drbdadm create-md r2
md_offset 0
al_offset 4096
bm_offset 36864

Found some data

==> This might destroy existing data! <==

Do you want to proceed?
[need to type 'yes' to confirm] yes

initializing activity log
initializing bitmap (672 KB) to all zero
Writing meta data...
New drbd meta data block successfully created.
success
bob@ubu1:~/drbd_res$ sudo drbdadm up r2
bob@ubu1:~/drbd_res$ sudo drbdadm primary --force r2
bob@ubu1:~/drbd_res$ cat /proc/drbd
version: 8.4.11 (api:1/proto:86-101)
srcversion: B438804C5AE8C84C95D0411

1: cs:Connected ro:Secondary/Secondary ds:UpToDate/UpToDate C r-----
ns:0 nr:0 dw:0 dr:0 al:0 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:0

3: cs:SyncSource ro:Primary/Secondary ds:UpToDate/Inconsistent C r-----
ns:130656 nr:0 dw:0 dr:131328 al:8 bm:0 lo:0 pe:4 ua:0 ap:0 ep:1 wo:f oos:32376640
[>....................] sync'ed: 0.5% (31616/31744)M
finish: 1:14:56 speed: 7,176 (7,176) K/sec

ubu2:

1
2
3
4
5
6
bob@ubu2:~$ sudo drbdadm create-md r2
initializing activity log
initializing bitmap (704 KB) to all zero
Writing meta data...
New drbd meta data block successfully created.
success

此时drbd pair已经创建好了,drbd3也已经创建完成,直接挂载drbd3查看数据是否依然还在

ubu1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
bob@ubu1:~/drbd_res$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
loop0 7:0 0 49.1M 1 loop /snap/core18/2681
loop1 7:1 0 49.1M 1 loop /snap/core18/2701
loop2 7:2 0 43.2M 1 loop /snap/snapd/17954
loop3 7:3 0 57.5M 1 loop /snap/core20/1332
loop4 7:4 0 43.2M 1 loop /snap/snapd/18363
loop5 7:5 0 60.7M 1 loop /snap/lxd/21843
loop6 7:6 0 91.9M 1 loop /snap/lxd/24065
loop7 7:7 0 59.1M 1 loop /snap/core20/1826
sda 8:0 0 20G 0 disk
└─drbd1 147:1 0 20G 1 disk
sdb 8:16 0 21G 0 disk
└─drbd3 147:3 0 31G 0 disk
sdc 8:32 0 31G 0 disk
└─drbd3 147:3 0 31G 0 disk
sr0 11:0 1 1.1G 0 rom
vda 252:0 0 100G 0 disk
├─vda1 252:1 0 512M 0 part /boot/efi
├─vda2 252:2 0 1G 0 part /boot
└─vda3 252:3 0 98.5G 0 part
└─ubuntu--vg-ubuntu--lv 253:0 0 49.3G 0 lvm /
bob@ubu1:~/drbd_res$ sudo mount /dev/drbd3 /mnt/
bob@ubu1:~/drbd_res$ ls /mnt/
bob.txt lost+found
bob@ubu1:~/drbd_res$ cat /mnt/bob.txt
Hi, Bob

原理

DRBD(Distributed Replicated Block Device)是一个基于内核模块的高可用性(HA)软件,它在两个或多个节点之间复制块设备数据,使得在其中一个节点发生故障时,其他节点可以接管工作并保持服务可用性。

drbd-in-kernel.png

DRBD的工作原理:

  1. DRBD将每个块设备划分为数据和元数据两个区域。元数据包含块设备的状态信息和同步状态,而数据包含实际的用户数据。
  2. DRBD使用一个专用的网络通信通道(通常是TCP / IP)来在节点之间传输数据。当发生写操作时,数据被写入本地节点的数据区域,并通过网络传输到远程节点,然后在远程节点上写入远程节点的数据区域。
  3. DRBD还使用一种名为“生成标识符”(Generation Identifiers)的机制来检测节点间的数据同步状态。每个生成标识符对应一个生成版本,节点在进行数据同步时,使用生成标识符来判断版本是否相同。
  4. DRBD提供了多种数据同步策略,例如全同步,增量同步和快速同步,以便在不同的应用场景下获得最佳的性能和数据完整性。
  5. DRBD支持不同的工作模式,如协议A和协议C,用于适应不同的应用需求。协议A用于异步复制,适用于数据传输速度较慢的场景,而协议C则用于同步复制,适用于对数据完整性有较高要求的场景。

DRBD Metadata

DRBD元数据包括:

  • DRBD容量
  • GI(Generation Identifiers)
  • Activity Log
  • Quick-sync Bitmap

Internal meta data

资源配置成internal方式存储元数据,这意味着元数据将与底层实际存储的数据放在一起。它通过在设备末尾设置一个区域来专门存储元数据。

  • 优点
    由于元数据与实际数据密不可分,因此在硬盘发生故障时,管理员不需要采取任何特殊措施。元数据将随着实际数据丢失,并与实际数据一起恢复。

  • 缺点
    如果底层设备是单个物理硬盘(而不是 RAID 集),则内部元数据可能会对写入吞吐量产生负面影响。应用程序发起的写请求的性能可能会触发 DRBD 中元数据的更新。如果元数据存储在同一磁盘上,则写操作可能会导致硬盘的写入/读取头产生两次额外的移动。

如果您计划将内部元数据与已经有需要保留的数据的现有底层设备一起使用,您必须考虑 DRBD 元数据所需的空间。另外,在创建 DRBD 资源时,新创建的元数据可能会覆盖底层设备末尾的数据,从而可能破坏现有的文件。
为了避免这种情况发生,有三种解决方法:

  1. 扩容底层硬盘,保证其有足够的容量存储元数据。
  2. 缩小已存在于底层硬盘的数据的大小,这需要文件系统的支持。
  3. 使用external meta data 替代。

External meta data

External meta data 只是简单地存储在一个与包含生产数据的设备不同的、专用的块设备中。

  • 优势
    对于某些写操作,使用外部元数据可以产生略微改善的延迟行为。

  • 缺点
    元数据与实际生产数据并不密不可分。这意味着,在硬件故障仅破坏生产数据(但不破坏 DRBD 元数据)的情况下,需要手动干预,以实现从存活的节点对新替换磁盘的全面数据同步。

如果满足以下所有条件,使用外部元数据也是唯一可行的选择,您正在使用 DRBD 复制一个已经包含您希望保留的数据的现有设备,且该现有设备不支持扩大容量,且该设备上的现有文件系统不支持缩小容量。

Generation Identifiers

DRBD 使用Generation Identifiers(GIs)来标识“复制数据代”。数据同步是基于GI实现的。GI是一个递增的整数,每个DRBD设备都有自己的GI计数器,用于记录本地和远程节点上最新的GI。当DRBD设备从远程节点读取数据时,它会比较本地和远程节点上的GI值。如果本地GI值低于远程节点上的GI值,那么DRBD设备会自动启动数据同步,将远程节点上的数据复制到本地节点上。

该GI在DRBD的内部机制如下:

  • 确定这两个节点是否实际上是同一集群的成员(而不是意外连接的两个节点)
  • 确定后台重新同步的方向(如果有必要)
  • 确定是否需要进行完全重新同步或是否仅需要进行部分重新同步
  • 标识脑裂

分裂大脑是指当DRBD设备与另一个节点失去联系时发生的问题。在这种情况下,每个节点都可能认为自己是唯一的“活动”节点,并开始独立地写入数据。当两个节点再次连接时,它们可能会发现它们的数据不同步。GI用于解决这个问题。当两个节点重新连接时,它们比较彼此的GI值。如果GI值相同,则两个节点处于相同的“代”,可以继续同步数据。如果GI值不同,则两个节点处于不同的“代”,需要手动解决分裂大脑问题,通常需要将其中一个节点上的数据进行回滚。

数据代(Data generations)

以下情况下标记新的数据生成开始:

  1. 初始设备全量同步时
  2. 断开的资源切换到主要角色时
  3. 资源在主要角色下线时

因此,我们可以总结出,每当资源处于已连接的连接状态,并且两个节点的磁盘状态都为UpToDate时,两个节点上的当前数据生成相同。
反之亦然。请注意,当前实现使用最低位来编码节点的角色(主/辅)。因此,即使被认为具有相同的数据生成,不同节点上的最低位也可能不同。

每个新的数据生成由8字节的通用唯一标识符(UUID)标识。

GI元组(The generation identifier tuple)

BD在本地资源元数据中保存了有关当前和历史数据生成的四个信息:

  1. 当前UUID (Current UUID)
    这是从本地节点的视角看到的当前数据生成的生成标识符。当资源已连接并且同步完成时,当前UUID在节点之间是相同的。
  2. 位图UUID (Bitmap UUID)
    这是用于跟踪在断开连接模式下磁盘同步位图中的更改的生成的UUID。与磁盘同步位图本身一样,该标识符仅在断开连接模式下才相关。如果资源已连接,则此UUID始终为空(零)。
  3. 两个历史UUID (Historical UUIDs)
    它们是当前数据生成之前的两个数据生成的标识符。

GI如何变化

Start of a new data generation

gi-changes-newgen.png

当一个节点(无论是由于网络故障还是手动干预)失去与其对等节点的连接时,DRBD 以下方式修改其本地生成GI:

  1. 为新的数据生成创建一个新的 UUID。这成为主节点的新的 current UUID。
  2. 用之前的current UUID替换现在的Bitmap UUID,因此它成为主节点的新的Bitmap UUID。
  3. 在次要节点上,GI元组保持不变

Start of re-synchronization

gi-changes-syncstart.png

在重新同步初始化时,DRBD在本地GI执行这些修改:

  1. 同步源上的Current UUID保持不变。
  2. 同步源上的Bitmap UUID轮换到第一个历史UUID。
  3. 在同步源上生成一个新的Bitmap UUID。
  4. 此UUID成为同步目标上的新Current UUID。
  5. 同步目标上的Bitmap UUID和历史UUID保持不变。

Completion of re-synchronization

gi-changes-synccomplete.png

当重新同步完成,需要执行如下步骤:

  1. 同步源上的Current UUID保持不变
  2. 同步源上的Bitmap UUID轮转到第一个历史UUID,第一历史UUID替换第二历史UUID(任何已存在的第二历史UUID将被丢弃)
  3. 同步源上的Bitmap UUID将被清空(清零)
  4. 同步目标采用来自同步源的整个GI元组

DRBD如何使用GI

当两个节点之间建立连接时,它们会交换当前可用的生成标识符,并相应地进行操作。可能有许多可能的结果:

  • 两个节点的Current UUID都为空
    本地节点检测到它自己和对等节点的当前UUID都为空。这是刚刚配置好的资源的正常情况,尚未启动初始完全同步。不会进行同步,必须手动启动。
  • 一个节点的Current UUID为空
    本地节点检测到对等节点的Current UUID为空,而自己的不为空。这是刚刚配置好的资源的正常情况,初始全同步刚刚启动,本地节点被选为初始同步源。DRBD现在设置磁盘上同步位图中的所有位(意味着它认为整个设备不同步),并作为同步源开始同步。在相反的情况下(本地Current UUID为空,对等节点不为空),DRBD执行相同的步骤,只是本地节点变成同步目标。
  • Current UUID相等
    本地节点检测到其Current UUID和对等节点的Current UUID都不为空且相等。这是在资源处于从属角色时进入断开连接模式的资源的正常情况,且在断开连接期间没有在任一节点上进行晋升。不会进行同步,因为不需要同步。
  • Bitmap UUID与对等节点的Current UUID匹配
    本地节点检测到它的Bitmap UUID与对等节点的Current UUID匹配,而对等节点的Bitmap UUID为空。这是从属节点故障后的正常预期情况,本地节点处于主角色。这意味着对等节点在此期间从未变为主节点,并一直使用相同的数据生成基础。现在,DRBD会启动正常的后台重新同步,本地节点成为同步源。反之,如果本地节点检测到它的Bitmap UUID为空,并且对等节点的位图与本地节点的Current UUID匹配,那么这是本地节点故障后的正常预期情况。同样,DRBD现在启动正常的后台重新同步,本地节点成为同步目标。
  • Current UUID与对等节点的历史UUID匹配
    本地节点检测到它的Current UUID与对等节点的历史UUID之一匹配。这意味着尽管两个数据集共享一个共同的祖先,并且对等节点具有最新的数据,但在对等节点的位图中保留的信息已经过时且无法使用。因此,正常同步是不足够的。DRBD现在将整个设备标记为不同步,并启动完全后台重新同步,本地节点成为同步目标。在相反的情况(本地节点的历史 UUID 与对等节点的Current UUID匹配),DRBD 执行相同的步骤,只是本地节点成为同步源。
  • Bitmap UUID匹配,但Current UUID不匹配
    本地节点检测到其Current UUID与对等节点的Current UUID不同,但Bitmap UUID匹配。这是脑裂的情况,但数据生成具有相同的父级。这意味着如果已配置,DRBD 将调用脑裂自动恢复策略。否则,DRBD将断开连接并等待手动脑裂解决。
  • Current UUID 和Bitmap UUID 都不匹配
    本地节点检测到其Current UUID与对等节点的Current UUID不同,且Bitmap UUID也不匹配。这是不相关祖先代的脑裂,因此即使配置了自动恢复策略,也是无用的。DRBD断开连接并等待手动脑裂解决。
  • 没有 UUID 匹配
    如果DRBD未能在两个节点的GI元组中检测到任何匹配的元素,它会记录关于不相关数据的警告并断开连接。这是DRBD防止意外连接两个之前从未听说过对方的集群节点的安全保障。

Activity Log

在一个写操作期间,DRBD将写操作转发到本地后备块设备,但也通过网络发送数据块。这两个操作实际上都是同时进行的。随机的时序行为可能会导致这样一种情况:写操作已经完成,但网络传输尚未完成。如果此时活动节点失败并启动故障转移,则这个数据块在节点之间是不同步的——在崩溃之前已在失败的节点上写入,但复制尚未完成。因此,当节点最终恢复时,这个块必须在随后的同步期间从数据集中删除。否则,崩溃的节点将“领先于”存活的节点,这将违反复制存储的“全部或无”的原则。这不仅是DRBD所特有的问题,在实际上所有的复制存储配置中都存在这个问题。许多其他存储解决方案(就像DRBD本身在0.7版本之前)要求在活动节点失败后,该节点必须在恢复后进行完全同步。

DRBD自0.7版本之后。在元数据区域存储活动日志(Activity Log)跟踪了“最近”已经写入的那些块。俗称,这些区域被称为热区段。如果一个临时失败的处于活动模式的节点进行同步,只有在AL中突出显示的那些热区段需要进行同步,而不需要同步整个设备。这大大减少了在活动节点崩溃后进行同步所需的时间。

Activity extents

Activity Log由多个Activity extents组成。可通过资源配置文件中的activity-log-size参数进行配置,该参数表示Active extents数量,必须是2的幂,且范围通常在16到4096之间。每个Activity extents大小为4MiB。

保持大的活动日志可以提高写入吞吐量。每次激活新的扩展时,一个旧的扩展将被重置为非活动状态。这种转换需要向元数据区域写入一个操作。如果活动扩展数很高,旧的活动扩展很少被交换出,从而减少了元数据写操作,从而提高了性能。

保持小的活动日志可以缩短在主节点失败和随后的恢复后的同步时间。

选择一个合适Activity Log Size

al-extents.png

  • R是同步速度(单位:MB/s)
  • tsync是同步时间(单位:s)
  • E是Active extents数量

DRBD是可以控制同步带宽的,使用net配置选项来控制DRBD在网络上使用的带宽。这个选项可以在DRBD配置文件中的全局段中设置,也可以在资源段中设置。

eg:

1
2
3
4
5
6
global {
...
net {
max-rate 1G;
}
}

max-rate选项将同步带宽限制为1Gbit/s。您可以将该选项设置为所需的速率,以控制DRBD在网络上使用的带宽。

Quick-sync Bitmap

Quick-sync Bitmap是 DRBD 在每个资源上使用的内部数据结构,用于跟踪块是否同步(在两个节点上相同)或不同步。它仅在资源处于断开模式时才相关。在Quick-sync Bitmap中,一个比特表示一个 4KiB 的磁盘数据块。如果该位被清除,则意味着相应的块仍与对等节点同步。这意味着该块自断开连接以来尚未被写入。相反,如果该位被设置,则意味着该块已被修改,并且在连接再次可用时需要重新同步。

当 DRBD 检测到断开的设备上的写 I/O 时,因此开始在Quick-sync Bitmap中设置位,它在 RAM 中执行此操作,从而避免了昂贵的同步元数据 I/O 操作。只有当相应的块变得“冷”(从活动日志中过期)时,DRBD 才会在Quick-sync Bitmap的磁盘表示中进行适当的修改。同样,如果在断开连接的同时手动关闭了剩余节点上的资源,则 DRBD 将完整的Quick-sync Bitmap刷新到持久存储中。当对等节点恢复或重新建立连接时,DRBD 将结合两个节点的位图信息,确定必须重新同步的总数据集。同时,DRBD 检查生成标识符以确定同步方向。

充当同步源的节点然后将协商好的块传输到对等节点,在同步目标确认修改时清除同步位。如果重新同步现在被中断(例如,由于另一个网络故障),然后继续恢复,它将在离开时继续进行 - 当然,同时修改的任何其他块都将添加到重新同步数据集中。

参考&鸣谢

问:写一个以太坊智能合约helloworld总共分几步?
答:三步,第一步创建以太坊服务环境;第二步撰写、编译、部署helloworld智能合约;第三步运行智能合约

部署Ethereum环境

本人使用docker搭建Ethereum环境,拉取ubuntu:20.04镜像。

1
docker pull --platform linux/amd64 ubuntu:20.04

然后,创建容器并开始安装Ethereum

1
2
docker run -it --name eth_server --net host ubuntu:20.04 /bin/bash
docker run -it --net bridge -p 8545:8545 -p 8551:8551 --name eth_server ubuntu:20.04 /bin/bash

安装Ethereum

1
2
3
4
5
6
7
8
9
# apt update -y
...
# apt install -y software-properties-common
...
# add-apt-repository -y ppa:ethereum/ethereum
...
# apt update -y
...
# apt install -y ethereum

安装成功后,确认一下eth版本。

1
2
3
4
5
6
7
8
9
# geth version
Geth
Version: 1.10.26-stable
Git Commit: e5eb32acee19cc9fca6a03b10283b7484246b15a
Architecture: amd64
Go Version: go1.18.5
Operating System: linux
GOPATH=
GOROOT=go

初始化

创建配置文件

创建一个genesis.json的文件,填充如下内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"Config": {
"chainId": 110,
"homesteadBlock": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0
},
"difficulty": "0",
"gasLimit": "2100000",
"alloc": {}
}

初始化Ethereum数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# geth --datadir ./eth-data --allow-insecure-unlock --http --http.addr 172.17.0.2 --http.api "admin,debug,web3,eth,txpool,personal,ethash,miner,net" --http.corsdomain "*" --dev init genesis.json
INFO [11-07|16:04:14.139] Maximum peer count ETH=50 LES=0 total=50
INFO [11-07|16:04:14.151] Smartcard socket not found, disabling err="stat /run/pcscd/pcscd.comm: no such file or directory"
INFO [11-07|16:04:14.179] Set global gas cap cap=50,000,000
INFO [11-07|16:04:14.190] Allocated cache and file handles database=/root/eth-data/geth/chaindata cache=16.00MiB handles=16
INFO [11-07|16:04:14.224] Opened ancient database database=/root/eth-data/geth/chaindata/ancient/chain readonly=false
INFO [11-07|16:04:14.226] Writing custom genesis block
INFO [11-07|16:04:14.230] Persisted trie from memory database nodes=0 size=0.00B time="347µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [11-07|16:04:14.238] Successfully wrote genesis state database=chaindata hash=a697c6..9bf39b
INFO [11-07|16:04:14.238] Allocated cache and file handles database=/root/eth-data/geth/lightchaindata cache=16.00MiB handles=16
INFO [11-07|16:04:14.259] Opened ancient database database=/root/eth-data/geth/lightchaindata/ancient/chain readonly=false
INFO [11-07|16:04:14.259] Writing custom genesis block
INFO [11-07|16:04:14.261] Persisted trie from memory database nodes=0 size=0.00B time="21.917µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [11-07|16:04:14.262] Successfully wrote genesis state database=lightchaindata hash=a697c6..9bf39b

启动节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# geth --datadir ./eth-data --networkid 110 --allow-insecure-unlock --http --http.addr 172.17.0.2 --http.api "admin,debug,web3,eth,txpool,personal,ethash,miner,net" --http.corsdomain "*" --dev
INFO [11-07|16:14:23.231] Starting Geth in ephemeral dev mode...
WARN [11-07|16:14:23.234] You are running Geth in --dev mode. Please note the following:

1. This mode is only intended for fast, iterative development without assumptions on
security or persistence.
2. The database is created in memory unless specified otherwise. Therefore, shutting down
your computer or losing power will wipe your entire block data and chain state for
your dev environment.
3. A random, pre-allocated developer account will be available and unlocked as
eth.coinbase, which can be used for testing. The random dev account is temporary,
stored on a ramdisk, and will be lost if your machine is restarted.
4. Mining is enabled by default. However, the client will only seal blocks if transactions
are pending in the mempool. The miner's minimum accepted gas price is 1.
5. Networking is disabled; there is no listen-address, the maximum number of peers is set
to 0, and discovery is disabled.
INFO [11-07|16:14:23.251] Maximum peer count ETH=50 LES=0 total=50
INFO [11-07|16:14:23.261] Smartcard socket not found, disabling err="stat /run/pcscd/pcscd.comm: no such file or directory"
INFO [11-07|16:14:23.291] Set global gas cap cap=50,000,000
INFO [11-07|16:14:23.649] Using developer account address=0xA9CB6DB62D6673ae5CD79D0d29796Dd9DF1d1A5e
INFO [11-07|16:14:23.652] Allocated cache and file handles database=/root/eth-data/geth/chaindata cache=512.00MiB handles=524,288 readonly=true
INFO [11-07|16:14:23.675] Opened ancient database database=/root/eth-data/geth/chaindata/ancient/chain readonly=true
INFO [11-07|16:14:23.691] Allocated trie memory caches clean=154.00MiB dirty=256.00MiB
INFO [11-07|16:14:23.691] Allocated cache and file handles database=/root/eth-data/geth/chaindata cache=512.00MiB handles=524,288
INFO [11-07|16:14:23.756] Opened ancient database database=/root/eth-data/geth/chaindata/ancient/chain readonly=false
INFO [11-07|16:14:23.763]
INFO [11-07|16:14:23.764] ---------------------------------------------------------------------------------------------------------------------------------------------------------
INFO [11-07|16:14:23.764] Chain ID: 110 (unknown)
INFO [11-07|16:14:23.764] Consensus: unknown
INFO [11-07|16:14:23.764]
INFO [11-07|16:14:23.765] Pre-Merge hard forks:
INFO [11-07|16:14:23.765] - Homestead: 0 (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/homestead.md)
INFO [11-07|16:14:23.765] - Tangerine Whistle (EIP 150): 0 (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/tangerine-whistle.md)
INFO [11-07|16:14:23.765] - Spurious Dragon/1 (EIP 155): 0 (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/spurious-dragon.md)
INFO [11-07|16:14:23.765] - Spurious Dragon/2 (EIP 158): 0 (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/spurious-dragon.md)
INFO [11-07|16:14:23.765] - Byzantium: <nil> (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/byzantium.md)
INFO [11-07|16:14:23.765] - Constantinople: <nil> (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/constantinople.md)
INFO [11-07|16:14:23.765] - Petersburg: <nil> (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/petersburg.md)
INFO [11-07|16:14:23.766] - Istanbul: <nil> (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/istanbul.md)
INFO [11-07|16:14:23.766] - Berlin: <nil> (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/berlin.md)
INFO [11-07|16:14:23.766] - London: <nil> (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/london.md)
INFO [11-07|16:14:23.766]
INFO [11-07|16:14:23.766] The Merge is not yet available for this network!
INFO [11-07|16:14:23.766] - Hard-fork specification: https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/paris.md
INFO [11-07|16:14:23.766] ---------------------------------------------------------------------------------------------------------------------------------------------------------
INFO [11-07|16:14:23.766]
INFO [11-07|16:14:23.768] Disk storage enabled for ethash caches dir=/root/eth-data/geth/ethash count=3
INFO [11-07|16:14:23.768] Disk storage enabled for ethash DAGs dir=/root/.ethash count=2
INFO [11-07|16:14:23.769] Initialising Ethereum protocol network=1337 dbversion=8
INFO [11-07|16:14:23.779] Loaded most recent local header number=0 hash=a697c6..9bf39b td=0 age=53y7mo1w
INFO [11-07|16:14:23.780] Loaded most recent local full block number=0 hash=a697c6..9bf39b td=0 age=53y7mo1w
INFO [11-07|16:14:23.780] Loaded most recent local fast block number=0 hash=a697c6..9bf39b td=0 age=53y7mo1w
INFO [11-07|16:14:23.786] Loaded local transaction journal transactions=0 dropped=0
INFO [11-07|16:14:23.786] Regenerated local transaction journal transactions=0 accounts=0
INFO [11-07|16:14:23.791] Gasprice oracle is ignoring threshold set threshold=2
WARN [11-07|16:14:23.798] Engine API enabled protocol=eth
WARN [11-07|16:14:23.798] Engine API started but chain not configured for merge yet
INFO [11-07|16:14:23.802] Starting peer-to-peer node instance=Geth/v1.10.26-stable-e5eb32ac/linux-amd64/go1.18.5
WARN [11-07|16:14:23.802] P2P server will be useless, neither dialing nor listening
INFO [11-07|16:14:23.843] New local node record seq=1,667,808,816,313 id=321fce2047223769 ip=127.0.0.1 udp=0 tcp=0
INFO [11-07|16:14:23.844] Started P2P networking self=enode://2d0246c1dd51623d6d8a8581095c033542366037b1e20ff815ad045af396de50df60f4aa9556148c2f4a89673bad5cab5c2ab22f3075d5bacba1b2fbebaf72e5@127.0.0.1:0
INFO [11-07|16:14:23.848] IPC endpoint opened url=/root/eth-data/geth.ipc
INFO [11-07|16:14:23.852] Loaded JWT secret file path=/root/eth-data/geth/jwtsecret crc32=0xef39c4cb
INFO [11-07|16:14:23.856] HTTP server started endpoint=172.17.0.2:8545 auth=false prefix= cors= vhosts=localhost
INFO [11-07|16:14:23.862] WebSocket enabled url=ws://127.0.0.1:8551
INFO [11-07|16:14:23.862] HTTP server started endpoint=127.0.0.1:8551 auth=true prefix= cors=localhost vhosts=localhost
INFO [11-07|16:14:23.868] Transaction pool price threshold updated price=0
INFO [11-07|16:14:23.868] Updated mining threads threads=0
INFO [11-07|16:14:23.868] Transaction pool price threshold updated price=1
INFO [11-07|16:14:23.868] Etherbase automatically configured address=0xA9CB6DB62D6673ae5CD79D0d29796Dd9DF1d1A5e
INFO [11-07|16:14:23.875] Commit new sealing work number=1 sealhash=6ca53b..19dc17 uncles=0 txs=0 gas=0 fees=0 elapsed=6.285ms
INFO [11-07|16:14:23.877] Commit new sealing work number=1 sealhash=6ca53b..19dc17 uncles=0 txs=0 gas=0 fees=0 elapsed=8.056ms

networkid

--networkid参数需要与genesis.json配置文件中的chainId值一致。

http.api

--http.api设置错误会出现如下错误

1
ERROR[11-07|16:13:36.346] Unavailable modules in HTTP API list     unavailable=[db] available="[admin debug web3 eth txpool personal ethash miner net]"

需要按照available中规定的内容进行配置--http.api参数。

attach交互

接下来需要attach到以太坊节点,在geth节点启动过程中有这样一条日志

1
INFO [11-07|16:14:23.848] IPC endpoint opened                      url=/root/eth-data/geth.ipc

没错,你猜对了,就是要用这个endpoint进行attach

1
2
3
4
5
6
7
8
9
10
11
# geth attach ipc:/root/eth-data/geth.ipc
Welcome to the Geth JavaScript console!

instance: Geth/v1.10.26-stable-e5eb32ac/linux-amd64/go1.18.5
coinbase: 0xa9cb6db62d6673ae5cd79d0d29796dd9df1d1a5e
at block: 0 (Thu Jan 01 1970 08:00:00 GMT+0800 (CST))
datadir: /root/eth-data
modules: admin:1.0 debug:1.0 engine:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

To exit, press ctrl-d or type exit
>

接下来的创建用户挖矿等操作都需要在attach状态下进行。

创建用户

使用personal.newAccout创建用户,然后可以使用eth.accounts查看用户列表。

1
2
3
4
5
6
7
8
> eth.accounts
> ["0xa9cb6db62d6673ae5cd79d0d29796dd9df1d1a5e"]
> personal.newAccount()
> Passphrase:
> Repeat passphrase:
> "0x00e70f2bd5a644cdf4432f886bf25473cbe620ac"
> eth.accounts
> ["0xa9cb6db62d6673ae5cd79d0d29796dd9df1d1a5e", "0x00e70f2bd5a644cdf4432f886bf25473cbe620ac"]

若不解锁用户,部署会提示错误creation of HelloWorld errored: authentication needed: password or unlock。解锁用户

1
2
3
4
> personal.unlockAccount(eth.accounts[1])
> Unlock account 0x00e70f2bd5a644cdf4432f886bf25473cbe620ac
> Passphrase:
> true

挖矿

使用miner.start()开始挖矿;使用miner.stop()停止挖矿。

开始挖矿后,当出现如下日志信息时,说明挖到了。

1
2
3
4
5
6
7
8
9
10
11
12
INFO [11-07|17:59:54.021] Successfully sealed new block            number=1 sealhash=84eaaa..f4d2c5 hash=925c9a..d8cb75 elapsed=1h30m51.894s
INFO [11-07|17:59:54.022] 🔨 mined potential block number=1 hash=925c9a..d8cb75
INFO [11-07|17:59:54.026] Commit new sealing work number=2 sealhash=865003..0e32da uncles=0 txs=0 gas=0 fees=0 elapsed=1.991ms
INFO [11-07|17:59:54.027] Commit new sealing work number=2 sealhash=865003..0e32da uncles=0 txs=0 gas=0 fees=0 elapsed=3.070ms
INFO [11-07|17:59:54.428] Generating DAG in progress epoch=1 percentage=0 elapsed=3.563s
INFO [11-07|17:59:56.581] Successfully sealed new block number=2 sealhash=865003..0e32da hash=f1224d..9dc4b8 elapsed=2.554s
INFO [11-07|17:59:56.582] 🔨 mined potential block number=2 hash=f1224d..9dc4b8
INFO [11-07|17:59:56.584] Commit new sealing work number=3 sealhash=8de6d6..c8886e uncles=0 txs=0 gas=0 fees=0 elapsed="906.417µs"
INFO [11-07|17:59:56.585] Commit new sealing work number=3 sealhash=8de6d6..c8886e uncles=0 txs=0 gas=0 fees=0 elapsed=2.271ms
INFO [11-07|17:59:57.730] Successfully sealed new block number=3 sealhash=8de6d6..c8886e hash=42a018..4e7ef3 elapsed=1.146s
INFO [11-07|17:59:57.731] 🔨 mined potential block number=3 hash=42a018..4e7ef3
INFO [11-07|17:59:57.733] Commit new sealing work number=4 sealhash=26af07..609e57 uncles=0 txs=0 gas=0 fees=0 elapsed=1.165ms

此时使用eth.blockNumber可以查看到当前区块数量,使用eth.getBalance(eth.accounts[0])查看默认用户余额,同理使用eth.getBalance(eth.accounts[1])查看我们创建的用户的余额。

1
2
3
4
> eth.getBalance(eth.accounts[0])
> 25000000000000000000
> eth.getBalance(eth.accounts[1])
> 0

挖到的奖励都进了默认账户了,我们新建的账户里木有哦。来给我转账吧,嘿嘿嘿….

1
2
3
4
5
6
> eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:web3.toWei(10,'ether')})
> "0x244da771baf2bd65e5e040c33ee3047b57a2492b85d271564bc90ccd7cb4fa46"
> eth.getBalance(eth.accounts[1])
> 5000000000000000000
> > eth.getBalance(eth.accounts[0])
> 1.535e+21

哇哦,又挖到矿了。

智能合约

环境搭建好了,现在开始编写智能合约。

1
2
3
4
5
6
7
8
// compiler version must be greater than or equal to 0.8.13 and less than 0.9.0
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
contract HelloWorld {
function sayHelloWorld() public returns (string memory) {
return "Hello World";
}
}

Remix

在这里获Remix

编译

solidity_remix_compile

部署

点击部署,然后选择外部http provider,并配置我们搭建好的ethereum服务。

solidity_remix_deploy_setting_1.jpg

solidity_remix_deploy_setting_2.jpg

接下中,选择我们创建好的账户,然后进行部署。

solidity_remix_deploy.jpg

solidity_remix_deploy_log.jpg

执行智能合约

执行智能合约可以选择在Remix中执行,也可选择attach到eth_server控制台执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
> abi =[{"inputs":[],"name":"sayHelloWorld","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"nonpayable","type":"function"}]
[{
inputs: [],
name: "sayHelloWorld",
outputs: [{
internalType: "string",
name: "",
type: "string"
}],
stateMutability: "nonpayable",
type: "function"
}]
> hello=eth.contract(abi).at('0xCB1B01B40CD752F5d42f5b8dCeE4BE2A637CaAf2')
{
abi: [{
inputs: [],
name: "sayHelloWorld",
outputs: [{...}],
stateMutability: "nonpayable",
type: "function"
}],
address: "0xCB1B01B40CD752F5d42f5b8dCeE4BE2A637CaAf2",
transactionHash: null,
allEvents: function bound(),
sayHelloWorld: function bound()
}
> hello.sayHelloWorld.call()
"Hello World"

参加&鸣谢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct object_t {
std::string name;

......

void encode(ceph::buffer::list &bl) const {
using ceph::encode;
encode(name, bl);
}
void decode(ceph::buffer::list::const_iterator &bl) {
using ceph::decode;
decode(name, bl);
}
};
WRITE_CLASS_ENCODER(object_t)

*** From: src/include/object.h ***

对于Ceph中的每一种需要存储的资源在进行存储前都要进行encode操作,然后再将其写入硬盘。对于读取同样,在从硬盘获取到数据后需要进行decode操作。而每种需要存储资源如何encodedecode当然要由资源自己来决定。所以在资源的classstruct中要实现encodedecode方法。

WRITE_CLASS_ENCODER(object_t)干了些啥呢。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// see denc.h for ENCODE_DUMP_PATH discussion and definition.
#ifdef ENCODE_DUMP_PATH
# define ENCODE_DUMP_PRE() \
unsigned pre_off = bl.length()
# define ENCODE_DUMP_POST(cl) \
do { \
static int i = 0; \
i++; \
int bits = 0; \
for (unsigned t = i; t; bits++) \
t &= t - 1; \
if (bits > 2) \
break; \
char fn[PATH_MAX]; \
snprintf(fn, sizeof(fn), ENCODE_STRINGIFY(ENCODE_DUMP_PATH) "/%s__%d.%x", #cl, getpid(), i++); \
int fd = ::open(fn, O_WRONLY|O_TRUNC|O_CREAT|O_CLOEXEC|O_BINARY, 0644); \
if (fd >= 0) { \
::ceph::bufferlist sub; \
sub.substr_of(bl, pre_off, bl.length() - pre_off); \
sub.write_fd(fd); \
::close(fd); \
} \
} while (0)
#else
# define ENCODE_DUMP_PRE()
# define ENCODE_DUMP_POST(cl)
#endif


#define WRITE_CLASS_ENCODER(cl) \
inline void encode(const cl& c, ::ceph::buffer::list &bl, uint64_t features=0) { \
ENCODE_DUMP_PRE(); c.encode(bl); ENCODE_DUMP_POST(cl); } \
inline void decode(cl &c, ::ceph::bufferlist::const_iterator &p) { c.decode(p); }

*** From: src/include/encoding.h ***

看了上面的代码应该能了解到WRITE_CLASS_ENCODER(object_t)是对encodedecode函数的重载。这是入口,然后再调用其资源自身encodedecode方法。

那么对于一些基础类型(如:int、string等)是如果encodedecode的呢?

int类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// int types

#define WRITE_INTTYPE_ENCODER(type, etype) \
inline void encode(type v, ::ceph::bufferlist& bl, uint64_t features=0) { \
ceph_##etype e; \
e = v; \
::ceph::encode_raw(e, bl); \
} \
inline void decode(type &v, ::ceph::bufferlist::const_iterator& p) { \
ceph_##etype e; \
::ceph::decode_raw(e, p); \
v = e; \
}

WRITE_INTTYPE_ENCODER(uint64_t, le64)
WRITE_INTTYPE_ENCODER(int64_t, le64)
WRITE_INTTYPE_ENCODER(uint32_t, le32)
WRITE_INTTYPE_ENCODER(int32_t, le32)
WRITE_INTTYPE_ENCODER(uint16_t, le16)
WRITE_INTTYPE_ENCODER(int16_t, le16)

*** From: src/include/encoding.h ***

int类型的encodedecode又调用了encode_rawdecode_raw。真是一层套一层啊~(俄罗斯套娃嘛)~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// base types

template<class T>
inline void encode_raw(const T& t, bufferlist& bl)
{
bl.append((char*)&t, sizeof(t));
}
template<class T>
inline void decode_raw(T& t, bufferlist::const_iterator &p)
{
p.copy(sizeof(t), (char*)&t);
}

#define WRITE_RAW_ENCODER(type) \
inline void encode(const type &v, ::ceph::bufferlist& bl, uint64_t features=0) { ::ceph::encode_raw(v, bl); } \
inline void decode(type &v, ::ceph::bufferlist::const_iterator& p) { ::ceph::decode_raw(v, p); }

WRITE_RAW_ENCODER(__u8)
#ifndef _CHAR_IS_SIGNED
WRITE_RAW_ENCODER(__s8)
#endif
WRITE_RAW_ENCODER(char)
WRITE_RAW_ENCODER(ceph_le64)
WRITE_RAW_ENCODER(ceph_le32)
WRITE_RAW_ENCODER(ceph_le16)

*** From: src/include/encoding.h ***

base比较简单,就是无论int几个字节,都是从低到高一个字节一个字节的写下去,再一个字节一个字节的读出来。。。

float类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define WRITE_FLTTYPE_ENCODER(type, itype, etype)                       \
static_assert(sizeof(type) == sizeof(itype)); \
static_assert(std::numeric_limits<type>::is_iec559, \
"floating-point type not using IEEE754 format"); \
inline void encode(type v, ::ceph::bufferlist& bl, uint64_t features=0) { \
ceph_##etype e; \
e = *reinterpret_cast<itype *>(&v); \
::ceph::encode_raw(e, bl); \
} \
inline void decode(type &v, ::ceph::bufferlist::const_iterator& p) { \
ceph_##etype e; \
::ceph::decode_raw(e, p); \
*reinterpret_cast<itype *>(&v) = e; \
}

WRITE_FLTTYPE_ENCODER(float, uint32_t, le32)
WRITE_FLTTYPE_ENCODER(double, uint64_t, le64)

*** From: src/include/encoding.h ***

float类型关键在于reinterpret_cast将一个浮点数转换为整数。更多关于reinterpret_cast的内容

string

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// string
inline void encode(std::string_view s, bufferlist& bl, uint64_t features=0)
{
__u32 len = s.length();
encode(len, bl);
if (len)
bl.append(s.data(), len);
}
inline void encode(const std::string& s, bufferlist& bl, uint64_t features=0)
{
return encode(std::string_view(s), bl, features);
}
inline void decode(std::string& s, bufferlist::const_iterator& p)
{
__u32 len;
decode(len, p);
s.clear();
p.copy(len, s);
}

inline void encode_nohead(std::string_view s, bufferlist& bl)
{
bl.append(s.data(), s.length());
}
inline void encode_nohead(const std::string& s, bufferlist& bl)
{
encode_nohead(std::string_view(s), bl);
}
inline void decode_nohead(int len, std::string& s, bufferlist::const_iterator& p)
{
s.clear();
p.copy(len, s);
}

// const char* (encode only, string compatible)
inline void encode(const char *s, bufferlist& bl)
{
encode(std::string_view(s, strlen(s)), bl);
}

*** From: src/include/encoding.h ***

string的encodedecode分两种,一种是有“害的”(head),一种是无“害的”。有“害的”需要先记录string的长度,再记录string的内容;无“害的”直接记录内容,单再decode过程中需要制定长度。总之这个长度总要有个人来记。好鸡肋!


整个的encodedecode的过程用到了一个bufferlist类型,那么这个bufferlist又是个什么结构呢,详细请见ceph中的buffer

c++中stl涉及到的容器类(或struct)有很多,具体都是什么原理呢?

std::array

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
  template<typename _Tp, std::size_t _Nm>
struct __array_traits
{
typedef _Tp _Type[_Nm];
typedef __is_swappable<_Tp> _Is_swappable;
typedef __is_nothrow_swappable<_Tp> _Is_nothrow_swappable;

static constexpr _Tp&
_S_ref(const _Type& __t, std::size_t __n) noexcept
{ return const_cast<_Tp&>(__t[__n]); }

static constexpr _Tp*
_S_ptr(const _Type& __t) noexcept
{ return const_cast<_Tp*>(__t); }
};
......
template<typename _Tp, std::size_t _Nm>
struct array
{
typedef _Tp value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef value_type* iterator;
typedef const value_type* const_iterator;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;

// Support for zero-sized arrays mandatory.
typedef _GLIBCXX_STD_C::__array_traits<_Tp, _Nm> _AT_Type;
typename _AT_Type::_Type _M_elems;

// No explicit construct/copy/destroy for aggregate type.
......

看这里typename _AT_Type::_Type _M_elems;再看这里typedef _Tp _Type[_Nm];。懂了吗,就是在栈上分配一个大小固定的数组。

std::vector

vector也是一个数组,只是不是分配在栈上的,而是分配在堆上的。

看这里有一个_Alloc默认使用的是std::allocator<_Tp>,这就是在堆上分配内存用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
  /**
* @brief A standard container which offers fixed time access to
* individual elements in any order.
*
* @ingroup sequences
*
* @tparam _Tp Type of element.
* @tparam _Alloc Allocator type, defaults to allocator<_Tp>.
*
* Meets the requirements of a <a href="tables.html#65">container</a>, a
* <a href="tables.html#66">reversible container</a>, and a
* <a href="tables.html#67">sequence</a>, including the
* <a href="tables.html#68">optional sequence requirements</a> with the
* %exception of @c push_front and @c pop_front.
*
* In some terminology a %vector can be described as a dynamic
* C-style array, it offers fast and efficient access to individual
* elements in any order and saves the user from worrying about
* memory and size allocation. Subscripting ( @c [] ) access is
* also provided as with C-style arrays.
*/
template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
class vector : protected _Vector_base<_Tp, _Alloc>
{
#ifdef _GLIBCXX_CONCEPT_CHECKS
// Concept requirements.
typedef typename _Alloc::value_type _Alloc_value_type;
# if __cplusplus < 201103L
__glibcxx_class_requires(_Tp, _SGIAssignableConcept)
# endif
__glibcxx_class_requires2(_Tp, _Alloc_value_type, _SameTypeConcept)
#endif

#if __cplusplus >= 201103L
static_assert(is_same<typename remove_cv<_Tp>::type, _Tp>::value,
"std::vector must have a non-const, non-volatile value_type");
# if __cplusplus > 201703L || defined __STRICT_ANSI__
static_assert(is_same<typename _Alloc::value_type, _Tp>::value,
"std::vector must have the same value_type as its allocator");
# endif
#endif

typedef _Vector_base<_Tp, _Alloc> _Base;
typedef typename _Base::_Tp_alloc_type _Tp_alloc_type;
typedef __gnu_cxx::__alloc_traits<_Tp_alloc_type> _Alloc_traits;

public:
typedef _Tp value_type;
typedef typename _Base::pointer pointer;
typedef typename _Alloc_traits::const_pointer const_pointer;
typedef typename _Alloc_traits::reference reference;
typedef typename _Alloc_traits::const_reference const_reference;
typedef __gnu_cxx::__normal_iterator<pointer, vector> iterator;
typedef __gnu_cxx::__normal_iterator<const_pointer, vector>
const_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef _Alloc allocator_type;
......

再看这里定义了三个指针,用的是typedef typename _Base::pointer pointer;类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
_GLIBCXX_BEGIN_NAMESPACE_CONTAINER

/// See bits/stl_deque.h's _Deque_base for an explanation.
template<typename _Tp, typename _Alloc>
struct _Vector_base
{
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
rebind<_Tp>::other _Tp_alloc_type;
typedef typename __gnu_cxx::__alloc_traits<_Tp_alloc_type>::pointer
pointer;

struct _Vector_impl_data
{
pointer _M_start;
pointer _M_finish;
pointer _M_end_of_storage;

_Vector_impl_data() _GLIBCXX_NOEXCEPT
: _M_start(), _M_finish(), _M_end_of_storage()
{ }
......

指针分别叫:pointer _M_start;pointer _M_finish;pointer _M_end_of_storage; 之前不叫这个的,现在都改了。。。无所谓了,含义没变。。。

vector在堆上申请了一块内存,用于存放数组元素。用_M_start表示数组开始的位置,用_M_finish数据结束的位置,用_M_end_of_storage 表示这块内存容量结束的位置。

  • vector容量不足时,需要进行扩容,扩容会引发内存拷贝。
  • 当向vector插入、删除某些元素时,由于采用连续存储方式,也会引发内存拷贝。

扩容

刚刚提到了扩容,如果一个vector容量不足了需要扩容,应该扩多少呢?这个根据reserve()的值来决定。如果你没有设置的话,按你存入的数据量进行扩容。如果你设置了这个值,那么每次扩reserve()个。

list

To be countinue…

从一个CPP文件编译成ELF可执行文件过程中会把不同的变量和函数映射到不同的内存区域。这些不同的区域具有不同的访问权限,有的是只读的,有的是读写的,有的是可执行的。让我们举个简单的例子来了解一下。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <stdio.h>

template<typename dst_type,typename src_type>
dst_type pointer_cast(src_type src)
{
return *static_cast<dst_type*>(static_cast<void*>(&src));
}

int global_uninit_var;
int global_init_var = 255;
const int const_global_int = 255;

void func() {
printf("Just a function\n");
}
static void static_func() {
}
inline void inline_func() {
}

typedef void (*pfunc_t)();
typedef int (*main_func_t)();
typedef void (*static_func_t)();

class Simple {
public:
static void Show() {
printf("I am Show...\n");
}
void localshow() {
printf("I am localshow...\n");
}
};

int main() {
static int static_var = 255;

int local_uninit_var;
int local_init_var = -1;

const int const_local_int = 127;

int* heap_int = new int();

pfunc_t pf = func;
main_func_t mf = main;
static_func_t sf = static_func;
static_func_t csf = Simple::Show;
void* cpf = pointer_cast<void*>(&Simple::localshow);
pfunc_t ipf = inline_func;

printf("global_uninit_var: 0x%x\n", &global_uninit_var);
printf("global_init_var: 0x%x\n", &global_init_var);
printf("static_var: 0x%x\n", &static_var);
printf("const_global_int: 0x%x\n", &const_global_int);
printf("local_uninit_var: 0x%x\n", &local_uninit_var);
printf("local_init_var: 0x%x\n", &local_init_var);
printf("const_local_int: 0x%x\n", &const_local_int);
printf("heap_int: 0x%x, 0x%x\n", &heap_int, heap_int);
printf("point_func: 0x%x, 0x%x\n", &pf, pf);
printf("point_main_func: 0x%x, 0x%x\n", &mf, mf);
printf("static_func: 0x%x, 0x%x\n", &sf, sf);
printf("class_static_func: 0x%x, 0x%x\n", &csf, csf);
printf("class_local_func: 0x%x, 0x%x\n", &cpf, cpf);
printf("inline_func: 0x%x, 0x%x\n", &ipf, ipf);

return 0;
}

由于ELF格式中有很多信息,我们只取readelf --sections相关信息
ELF结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[Nr] Name              Type             Address           Offset       Size              EntSize          Flags  Link  Info  Align
[ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 00000000004002a8 000002a8 000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.gnu.bu[...] NOTE 00000000004002c4 000002c4 0000000000000024 0000000000000000 A 0 0 4
[ 3] .note.ABI-tag NOTE 00000000004002e8 000002e8 0000000000000020 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400308 00000308 000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 0000000000400328 00000328 0000000000000090 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 00000000004003b8 000003b8 000000000000007d 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000400436 00000436 000000000000000c 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400448 00000448 0000000000000040 0000000000000000 A 6 2 8
[ 9] .rela.dyn RELA 0000000000400488 00000488 0000000000000018 0000000000000018 A 5 0 8
[10] .rela.plt RELA 00000000004004a0 000004a0 0000000000000078 0000000000000018 AI 5 22 8
[11] .init PROGBITS 0000000000401000 00001000 000000000000001a 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 0000000000401020 00001020 0000000000000060 0000000000000010 AX 0 0 16
[13] .text PROGBITS 0000000000401080 00001080 0000000000000392 0000000000000000 AX 0 0 16
[14] .fini PROGBITS 0000000000401414 00001414 0000000000000009 0000000000000000 AX 0 0 4
[15] .rodata PROGBITS 0000000000402000 00002000 000000000000019e 0000000000000000 A 0 0 8
[16] .eh_frame_hdr PROGBITS 00000000004021a0 000021a0 0000000000000064 0000000000000000 A 0 0 4
[17] .eh_frame PROGBITS 0000000000402208 00002208 00000000000001b8 0000000000000000 A 0 0 8
[18] .init_array INIT_ARRAY 0000000000403de8 00002de8 0000000000000008 0000000000000008 WA 0 0 8
[19] .fini_array FINI_ARRAY 0000000000403df0 00002df0 0000000000000008 0000000000000008 WA 0 0 8
[20] .dynamic DYNAMIC 0000000000403df8 00002df8 0000000000000200 0000000000000010 WA 6 0 8
[21] .got PROGBITS 0000000000403ff8 00002ff8 0000000000000008 0000000000000008 WA 0 0 8
[22] .got.plt PROGBITS 0000000000404000 00003000 0000000000000040 0000000000000008 WA 0 0 8
[23] .data PROGBITS 0000000000404040 00003040 000000000000000c 0000000000000000 WA 0 0 4
[24] .bss NOBITS 000000000040404c 0000304c 000000000000000c 0000000000000000 WA 0 0 4
[25] .comment PROGBITS 0000000000000000 0000304c 000000000000005c 0000000000000001 MS 0 0 1
[26] .debug_aranges PROGBITS 0000000000000000 000030a8 0000000000000070 0000000000000000 0 0 1
[27] .debug_info PROGBITS 0000000000000000 00003118 000000000000031a 0000000000000000 0 0 1
[28] .debug_abbrev PROGBITS 0000000000000000 00003432 00000000000001d9 0000000000000000 0 0 1
[29] .debug_line PROGBITS 0000000000000000 0000360b 0000000000000114 0000000000000000 0 0 1
[30] .debug_str PROGBITS 0000000000000000 0000371f 0000000000000235 0000000000000001 MS 0 0 1
[31] .debug_ranges PROGBITS 0000000000000000 00003954 0000000000000060 0000000000000000 0 0 1
[32] .symtab SYMTAB 0000000000000000 000039b8 0000000000000750 0000000000000018 33 52 8
[33] .strtab STRTAB 0000000000000000 00004108 0000000000000298 0000000000000000 0 0 1
[34] .shstrtab STRTAB 0000000000000000 000043a0 0000000000000151 0000000000000000 0 0 1

将上述代码编译运行打印出的结果对应到elf格式中相应的区域如下:

variable

  • 所有代码无聊作何标记全部存储在.text段,换句话说从汇编的角度可以调用任何函数都可以被调用
  • 全局的const常量存放在.rodata
  • 局部的const常量则存储在栈空间内,而常量的右值127则存放在.text
  • 已经初始化的全局变量或static变量存放在.data
  • 未初始化的全局变量存放在bss

智能指针,从其本质上说,就是要控制对象的销毁时机。换句话讲就是何时调用对象的析构函数。从C++11开始引入三个智能指针(unique_ptr,shared_ptr,weak_ptr),准确的说是四种(还有auto_ptr),但从C++17开始auto_ptr被移除了。所以就剩下上述三种了。所有的智能指针都包含在memory头文件中。

首先,很久以前的C++是没有智能指针的,用户创建在堆上的内存,智能自己显示的释放,如果没有释放就会造成内存泄漏。这种特点导致C++的使用成本很高,为了降低成本,引入了智能指针unique_ptrshared_ptr

unique_ptr

unique_ptr采用的是传递所有权的方式来控制对象的销毁时机。如其名字所示,其对象是独享的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Simple {
private:
int m_a;
public:
void Show() {
printf("Hello Simple %d\n", m_a);
}
void showfunc() {
printf("Hello Simple func \n");
}
Simple(int n) {
m_a = n;
printf("Simple Construct\n");
}
Simple(const Simple& p) {
printf("Simple Copy Construct\n");
}
~Simple() {
printf("Simple Destroy\n");
}
};

void unique_test() {
// unique ptr
auto s_ptr = std::make_unique<Simple>(1);
s_ptr->Show();
std::unique_ptr<Simple> s_copy_ptr = std::move(s_ptr);
s_copy_ptr->Show();
//std::unique_ptr<Simple> s_ptr_2(s_ptr); // 无法编译通过
//std::unique_ptr<Simple> s_ptr_2 = s_ptr; // 无法编译通过
s_ptr->showfunc();
//s_ptr->Show(); // 当所有权变更后就不该再用之前的指针访问对象资源。
}
  • unique_ptr禁用拷贝构造
    由于unique_ptr禁用了拷贝构造函数unique_ptr(const unique_ptr&) = delete;,所以一切试图触发拷贝构造函数的操作都会引发编译错误。
  • unique_ptr所有权一旦变更就不能使用原指针访问对象资源
    上述代码中有两处使用了原指针访问对象资源,第一处s_ptr->showfunc();没有报错,可以正常打印;s_ptr->Show();中使用到了成员变量m_a所以会导致报错“this空指针”。这是由于std::move的赋值操作触发了unique_ptr中的Move构造函数(unique_ptr(unique_ptr&&) = default;),从而将s_ptr中的成员清空。所以再次访问原对象指针,就会出错。

shared_ptr

shared_ptr

shared_ptr采用的是引用计数的机制来控制对象的销毁时机。如其名字所示,其对象是共享的。当计数器等于0是,调用对象的析构函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void shared_test_inner(std::shared_ptr<Simple> *steal_ptr) {
// share ptr
auto s_ptr = std::make_shared<Simple>(1);
s_ptr->Show();
std::shared_ptr<Simple> s_copy_ptr(s_ptr);
s_copy_ptr->Show();
std::shared_ptr<Simple> s_ptr_2 = s_ptr;
s_ptr_2->Show();
// try steal
*steal_ptr = s_ptr;
}

void shared_test() {
shared_test_leak();
std::shared_ptr<Simple> s_ptr;
shared_test_inner(&s_ptr);
s_ptr->Show();
}

尽可能使用make_shared创建shared_ptr,如果使用std::shared_ptr<Simple> s_ptr(new Simple(1))创建shared_ptr,需要分配两次内存,一次是new Simple(1);另一次是shared_ptr的引用计数。make_shared只分配一次。

现在创建一个SimpleBack类,然后再让SimpleSimpleBack循环引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class SimpleBack;

class Simple {
private:
int m_a;
public:
void Show() {
printf("Hello Simple %d\n", m_a);
}
void showfunc() {
printf("Hello Simple func \n");
}
Simple(int n) {
m_a = n;
printf("Simple Construct\n");
}
Simple(const Simple& p) {
printf("Simple Copy Construct\n");
}
~Simple() {
printf("Simple Destroy\n");
}
std::shared_ptr<SimpleBack> m_sb;
};

class SimpleBack {
public:
std::shared_ptr<Simple> m_s;
~SimpleBack() {
printf("SimpleBack Destroy\n");
}
};

void shared_test_leak() {
auto s = std::make_shared<Simple>(2);
auto sb = std::make_shared<SimpleBack>();

s->m_sb = sb;
sb->m_s = s;
// 通过打印语句可以看到 s和sb的析构函数并没有调用
}

通过析构函数函数的打印语句可以看出ssb并没有被析构,这说明ssb泄漏了。

为了解决shared_ptr在循环依赖中内存泄漏的问题,推出了weak_ptr

weak_ptr

weak_ptr不会增加引用计数,不能直接操作对象的内存(需要先调用lock接口),需要和shared_ptr配套使用。

将上述代码改成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class SimpleBack;

class Simple {
private:
int m_a;
public:
void Show() {
printf("Hello Simple %d\n", m_a);
}
void showfunc() {
printf("Hello Simple func \n");
}
Simple(int n) {
m_a = n;
printf("Simple Construct\n");
}
Simple(const Simple& p) {
printf("Simple Copy Construct\n");
}
~Simple() {
printf("Simple Destroy\n");
}
std::shared_ptr<SimpleBack> m_sb;
};

class SimpleBack {
public:
// 将循环引用的其中一个改成weak_ptr
std::weak_ptr<Simple> m_s;
~SimpleBack() {
printf("SimpleBack Destroy\n");
}
};

void shared_test_leak() {
auto s = std::make_shared<Simple>(2);
auto sb = std::make_shared<SimpleBack>();

s->m_sb = sb;
sb->m_s = s;
}

通过析构函数的打印语句可以看出,ssb的析构函数在shared_test_lead()调用结束后被调用。

那么,weak_ptr的使用是不是也像shared_ptr一样呢?不是的。weak_ptr需要与shared_ptr配合使用,看一个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void weak_test() {
std::weak_ptr<Simple> w;
{
auto s = std::make_shared<Simple>(3);
w = s;
auto s2 = w.lock();
if(s2 != nullptr) {
s2->Show();
}
}
if(w.expired()) {
printf("object 's' is destroied. \n");
}
}
  • lock
    若对象已被析构,则返回一个空的shared_ptr;否则返回实际的shared_ptr
  • expired
    若对象已被析构,则返回true;否则返回false

参考&鸣谢

书接上文“设计模式——创建型模式”,上回说到前人创造出了很多创建型的模式,这回我们说说结构型模式。先以教科书形式介绍一下。

结构型模式(Structural Pattern)描述如何将类或者对 象结合在一起形成更大的结构,就像搭积木,可以通过 简单积木的组合形成复杂的、功能更为强大的结构。

  • 类结构型模式
    类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。
  • 对象结构型模式
    类与对象的组合,通过关联关系使得在一 个类中定义另一个类的实例对象,然后通过该对象调用其方法。

根据“合成复用原则”,在系统中尽量使用关联关系来替代继 承关系,因此大部分结构型模式都是对象结构型模式。

能看得懂,但是不够生动。接下来我们让它生动、活泼一下。

在元祖王朝赛博坦帝国的一个边远矿业基地,矿区提纯运输车形态的矿工钢锁被工作调动到了这个位于某个卫星上的矿厂。本来与世无争的钢锁,遇到了一起恐怖事件,由此改变了他的火种(心灵)变形形态(肉体)人生轨迹(命运)。他就是我们今天的主角变形金刚。变形金刚分为两派,一为博派(Autobots),一为狂派(Decepticons),二者都有变形的能力。

让我们抽象一下,是否可以将Transforms的变形能力抽化出来,然后再让博派和狂派分别去实现自己的变形方法。

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Transformer {
public:
virtual void transform() = 0;
};

class Autobot {
public:
void AutobotsTransform() {
printf("Autobots Transform, Please...\n");
}
};

class TransformerAdapter : public Transformer {
private:
Autobot *m_a;
public:
TransformerAdapter(Autobot *a) {
m_a = a;
}
void transform();
};

这就是“适配器模式”


适配器模式

再教科书一下。。。

adapter_1

adapter_2


刚刚收到一个需求,要为每一个变形金刚做一个自己独有的变形特效,而且在变形的过程中要加入背景音乐。所以我们需要修改一下之前的“适配器”设计模式,增加一层背景效果层,然后将背景效果传递给变形金刚,当变形金刚变形的时候将效果播放出来就好了。

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Background {
public:
virtual void Display() = 0;
};

class TransformBackground : public Background {
public:
void Display() {
printf("ki ka ka ka......\n");
}
};

class Transformer {
protected:
Background *bg;
public:
virtual void transform() = 0;
};

class Decepticon : public Transformer {
public:
Decepticon(Background* b) {
bg = b;
}
void transform() {
bg->Display();
printf("Decepticon Transform, Please...\n");
}
};

这就是“桥模式”


桥模式

再教科书一下。。。

bridge_1

** 优点: **

  • 分离抽象接口及其实现部分。
  • 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
  • 实现细节对客户透明,可以对用户隐藏实现细节。

bridge_2

** 缺点:**

  • 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程
  • 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。

又来需求了,现在我们需要变形金刚开口说话,以后还需要变形金刚会开炮。总结一下,变形是一个基本的功能,然后先增加一个说话的功能,如果有需要以后还可以增加开炮的功能。每增加一个功能不修改之前的代码,因为修改已经测试过的代码会给程序的稳定性带来隐患。

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Transformer {
protected:
Background *bg;
public:
virtual void transform() = 0;
};

class Bumblebee : public Transformer {
public:
void transform() {
printf("Bumblebee Transform, Please...\n");
}
};

class TransformerDecorator : public Transformer {
protected:
Transformer* tf;
public:
virtual void transform() {
tf->transform();
}
};

class BumblebeeSay : public TransformerDecorator {
public:
BumblebeeSay(Transformer* t) {
this->tf = t;
}
void say() {
printf("wuwuwuwu...\n");
}
void transform() {
TransformerDecorator::transform();
say();
}
};

这就是“装饰模式”


装饰模式

再教科书一下。。。

装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。

decorator_1

  • 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
  • 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
  • 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
  • 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”

decorator_2

  • 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。

战争一触即发,为了取得胜利,我们需要快速的生产变形金刚,需要弄一个map,存储已经创建好的,当有战事发生时,从map里直接取出进行战斗。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Transformer {
protected:
Background *bg;
public:
virtual void transform() = 0;
};

class Bumblebee : public Transformer {
public:
void transform() {
printf("Bumblebee Transform, Please...\n");
}
};

class TransformerFlyweight {
private:
std::map<std::string,Transformer*> members;
public:
Transformer* get(std::string key){
Transformer* tf = nullptr;
auto search = members.find(key);
if(search != members.end()) {
tf = search->second;
} else {
// new
tf = new Bumblebee();
members.insert(std::make_pair(key, tf));
}
return tf;
}
};

这就是“享元模式”


享元模式

再教科书一下。。。

享元模式是一个考虑系统性能的设计模式,通过使用享元模式可以节约内存空间,提高系统的性能。运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。

flyweight_1

** 优点 **

  • 享元模式的优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份。
  • 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。

flyweight_2

** 缺点 **

  • 享元模式使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。

变形金刚出了个Bug,若在每次变形过程中收到攻击,变形金刚将毫无防御能力。所以需要增加一个防护罩,在变形前开启,在变形后关闭,这样增加变形金刚的变形过程中的防御能力。

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Transformer {
protected:
Background *bg;
public:
virtual void transform() = 0;
};

class Bumblebee : public Transformer {
public:
void transform() {
printf("Bumblebee Transform, Please...\n");
}
};

class TransformerProxy : public Transformer {
private:
Transformer *m_tf;
void save_on() {
printf("saving...\n");
}
void save_off() {
printf("save done\n");
}
public:
TransformerProxy(Transformer *tf) {
m_tf = tf;
}
void transform() {
save_on();
m_tf->transform();
save_off();
}
};

这就是“代理模式”


代理模式

proxy_1

** 优点 **

  • 代理模式能够协调调用者和被调用者,在一定程度上降低了系 统的耦合度。
  • 远程代理使得客户端可以访问在远程机器上的对象,远程机器 可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
  • 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系 统资源的消耗,对系统进行优化并提高运行速度。
  • 保护代理可以控制对真实对象的使用权限。

proxy_2

** 缺点 **

  • 由于在客户端和真实主题之间增加了代理对象,因此 有些类型的代理模式可能会造成请求的处理速度变慢。
  • 实现代理模式需要额外的工作,有些代理模式的实现 非常复杂。

参考&鸣谢

设计模式是前人在工作总结出来的一些设计经验。后人使用这些经验进行设计开发,可以减少设计缺陷。常用的设计模式有23个,这23个设计模式分为三类,分别是“创建型模式”、“行为型模式”、“结构型模式”。

创建型模式

顾名思义,用来创建生成对象的设计模式。创建型模式包括:“单例模式”、“工厂模式”和“构建者模式”,其中工厂模式又分为“简单工厂模式”、“工厂方法模式”和“抽象工厂模式”。

故事是这样开始的……

在很久很久以前,有个Product他是这样定义的

1
2
3
4
class ProductA {
public:
void Show();
};

那时候还没有设计模式,人们创建对象一般都是用new在堆上分配或者直接在栈上定义。

Example:

1
2
3
4
5
int main() {
ProductA *p = new ProductA();
p->Show();
return 0;
}

后来,使用这个Product对象的人越来越多,每个人都想new一下,而且Product又不想被频繁的new,于是前人想了个办法将Product的对象定义为一个static的变量,然后每次在new之前判断一下,看是否需要new

Example:

1
2
3
4
5
6
7
8
9
10
11
class SingleInstance {
private:
static ProductA *m_p;
public:
static ProductA* CreateProduct() {
if(m_p == NULL) {
m_p = new ProductA();
}
return m_p;
}
};

这就是“单例模式”。


单例模式

  • 某个类只能有一个实例
  • 它必须自行创建这个实例
  • 它必须自行向整个系统提供这个实例

single_1

  • 单例类的构造函数为私有
  • 提供一个自身的静态私有成员变量
  • 提供一个公有的静态工厂方法

single_2


后来,前人又开发了ProductBProductAProductB同属于Product,于是我们的product变成了这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Product {
public:
virtual void Show() = 0;
};

class ProductA : public Product {
public:
void Show();
};

class ProductB : public Product {
public:
void Show();
};

为了方便创建对象,前人决定用"PA"代表ProductA"PB"代表ProductB,再创建一个工厂,用来创建对象,在创建对象时候指定PA 或者PB,工厂根据指定的内容来决定是创建ProductA还是ProductB

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
enum PD_TYPE {
PA,
PB
};

class SimpleFactory {
public:
Product* CreateProduct(PD_TYPE pt);
};

Product* SimpleFactory::CreateProduct(PD_TYPE pt){
Product *p = NULL;
switch(pt) {
case PA:
{
p = new ProductA();
break;
}
case PB:
{
p = new ProductB();
break;
}
}
}
return p;

这就是“简单工厂模式”。


简单工厂模式

  • 根据参数的不同返回不同类的实例
  • 简单工厂模式专门定义一个类来负责创建其他类的实例
  • 被创建的实例通常都具有共同的父类

simple_factory_1

simple_factory_2


随着Product种类的不断增加,我们需要不断的修改SimpleFactory::CreateProduct方法,这样会增加SimpleFactory::CreateProduct出错的风险,所以前人按照不同的产品创建了不同的工厂,这样以后在增加新的产品只需要增加对应的工厂就可以了。

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MethodFactory {
public:
virtual Product* CreateProduct() = 0;
};

class MethodFactoryPA : public MethodFactory {
public:
Product* CreateProduct();
};

class MethodFactoryPB : public MethodFactory {
public:
Product* CreateProduct();
};

Product* MethodFactoryPA::CreateProduct(){
return new ProductA();
}

Product* MethodFactoryPB::CreateProduct(){
return new ProductB();
}

这就是“工厂方法模式”


工厂方法模式

method_factory_1

工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成。

method_factory_2


从现在开始我们要将产品变多ProductA1ProductA2ProductB1ProductB2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ProductA1 : public Product {
public:
void Show();
};

class ProductA2 : public Product {
public:
void Show();
};

class ProductB1 : public Product {
public:
void Show();
};

class ProductB2 : public Product {
public:
void Show();
};

要求工厂生产ProductA1时,也要生产ProductB1;生产ProductA1时,也要生产ProductB2

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class AbstractFactory {
public:
virtual Product* CreateProductA() = 0;
virtual Product* CreateProductB() = 0;
};

class AbstractFactoryOne : public AbstractFactory {
public:
Product* CreateProductA();
Product* CreateProductB();
};

class AbstractFactoryTwo : public AbstractFactory {
public:
Product* CreateProductA();
Product* CreateProductB();
};

Product* AbstractFactoryOne::CreateProductA() {
return new ProductA1();
}

Product* AbstractFactoryOne::CreateProductB() {
return new ProductB1();
}

Product* AbstractFactoryTwo::CreateProductA() {
return new ProductA2();
}

Product* AbstractFactoryTwo::CreateProductB() {
return new ProductB2();
}

这就是“抽象工厂模式”。


抽象工厂模式

abstract_factory_1

与工厂方法很类似,区别在于工厂方法用于一个产品的构建,抽象工厂适用于多个产品构建

abstract_factory_2


抽象工厂模式可以让我们批量生产产品了,但是抽象工厂只能生产固定种类的产品,如果我们要让ProductA1ProductA2ProductB1ProductB2随意组合,生成不同的套餐,然后再进行生产。前人想了这样一个办法。。。

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Builder {
public:
virtual Product* BuildProduct() = 0;
};

class ProductABuilder : public Builder{
public:
Product* BuildProduct();
};

class Director {
private:
Builder* builder;
public:
void setBuilder(Builder* b);
Product* build();
};

Product* ProductABuilder::BuildProduct() {
return new ProductA();
}

void Director::setBuilder(Builder* b) {
this->builder = b;
}

Product* Director::build() {
return this->builder->BuildProduct();
}

这就是“建造者模式”


建造者模式

builder_1

builder_2

** To be continue …**

SPDK(Storage Performance Development Kit)是Intel发布的存储性能开发工具集

用户使用现在的固态设备,比如Intel® SSD DC P3700 Series Non-Volatile Memory Express(NVMe)驱动,面临一个主要的挑战:因为吞吐量和延迟性能比传统的磁盘好太多,现在总的处理时间中,存储软件占用了更大的比例。换句话说,存储软件栈的性能和效率在整个存储系统中越来越重要。随着存储设备继续发展,它将面临远远超过正在使用的软件体系结构的风险(即存储设备受制于相关软件的不足而不能发挥全部性能)

原理

  • 用户态运行
    避免内核上下文切换和中断将会节省大量的处理开销,允许更多的时钟周期被用来做实际的数据存储。无论存储算法(去冗,加密,压缩,空白块存储)多么复杂,浪费更少的时钟周期总是意味着更好的性能和延迟。这并不是说内核增加了不必要的开销;相反,内核增加了那些可能不适用于专用存储堆栈的通用计算用例的相关开销。SPDK的指导原则是通过消除每一处额外的软件开销来提供最少的延迟和最高的效率。
  • 轮询模式取代中断模式(Polled Mode Drivers, PMDs)
    在传统的I/O模型中,应用程序提交读写请求后睡眠,一旦I/O完成,中断就会将其唤醒。PMDs的工作方式不同,应用程序提交读写请求后继续执行其他工作,以一定的时间间隔回头检查I/O是否已经完成。这种方式避免了中断带来的延迟和开销,并使得应用程序提高了I/O的效率。
    在机械盘时代,中断开销只占整个I/O时间的一个很小的百分比,因此给系统带来了巨大的效率提升。然而,在固态设备的时代,持续引入更低延迟的持久化设备,中断开销成为了整个I/O时间中不能被忽视的部分。这个问题在更低延迟的设备上只会越来越严重。系统已经能够每秒处理数百万个I/O,所以消除数百万个事务的这种开销,能够快速地复制到多个内核中。数据包和数据块被立即分发,等待时间减小到最少,使得延迟更低,一致性延迟更多(抖动更少),吞吐量也得到提高。
  • 无锁机制
    在IO路径上避免采用任何锁机制进行同步,降低时延并提升吞吐量

架构

introduction-to-the-storage-performance-development-kit-spdk-fig2

Hardware Drivers

NVMe Driver

SPDK的基础组件,这个高优化无锁的驱动提供了高扩展性,高效性和高性能。

Inter QuickData Technology

Intel I/O Acceleration Technology(Inter IOAT,英特尔I/O加速技术),这是一种基于Xeon处理器平台上的copy offload引擎。通过提供用户空间访问,减少了DMA数据移动的阈值,允许对小尺寸I/O或NTB的更好利用。

Back-end Block Devices

NVMe-oF Initiator

本地SPDK NVMe驱动和NVMe-oF启动器共享一套共同的API命令。这意味着,比如本地/远程复制非常容易实现。

Ceph RADOS Block Device

Ceph RBD 成为SPDK的后端设备

Blobstore Block Device

由SPDK Blobstore分配的块设备,是虚拟机或数据库可以与之交互的虚拟设备。这些设备得到SPDK基础架构的优势,意味着零拷贝和令人难以置信的可扩展性。

Linux AIO

允许SPDK与内核设备(比如机械硬盘)交互。

Storage Services

Block Device Abstration Layer

这种通用的块设备抽象是连接到各种不同设备驱动和块设备的存储协议的粘合剂。还在块层中提供灵活的API用于额外的用户功能(磁盘阵列,压缩,去冗等等)。

Blobstore

为SPDK实现一个高精简的文件式语义(非POSIX)。这可以为数据库,容器,虚拟机或其他不依赖于大部分POSIX文件系统功能集(比如用户访问控制)的工作负载提供高性能基础。

Storage Protocols

iSCSI Target

建立了通过以太网的块流量规范,大约是内核LIO效率的两倍。现在的版本默认使用内核TCP/IP协议栈。

vhost-scsi target

KVM/QEMU的功能利用了SPDK NVMe驱动,使得访客虚拟机访问存储设备时延迟更低,使得I/O密集型工作负载的整体CPU负载减低。

NVMe-oF Target

实现了NVMe-oF规范。虽然这取决于RDMA硬件,NVMe-oF的目标可以为每个CPU核提供高达40Gbps的流量。

应用方案

spdk_component

网络前端

网络前端子组件包括DPDK网卡驱动和用户态网络服务UNS(这是一个Linux内核TCP/IP协议栈的替代品,能够突破通用TCP/IP协议栈的种种性能限制瓶颈)。DPDK在网卡侧提供了一个高性能的发包收包处理框架,在数据从网卡到操作系统用户态之间提供了一条快速通道。

处理框架

拿到了数据包内容,将iSCSI命令转换为SCSI块级命令。然而,在它将这些命令发到“后端”驱动之前,SPDK提供了一套API框架,让厂商能够插入自己定义的处理逻辑(架构图中绿色的方框)。通过这种机制,存储厂商可在这里实现例如缓存、去重、压缩、加密、RAID计算,或擦除码(Erasure Coding)计算等功能,使这些功能包含在SPDK的处理流程中。

后端

SPDK和物理块设备交互(读和写操作)。如前所述,SPDK提供了用户态的PMD,支持NVMe设备、Linux AIO设备(传统机械硬盘)、RAMDISK设备,以及利用到英特尔I/O加速技术的新设备(CBDMA)。这一系列后端设备驱动涵盖了不同性能的存储分层,保证SPDK几乎与每种存储应用形成关联。

使用及编译

编译安装

SPDK使用了DPDK中的一些功能,编译SPDK需要依赖DPDK,所以需要先编译安装DPDK

编译DPDK

1
2
3
# make config T=x86_64-native-linuxapp-gcc
# make
# make install

默认DPDK会安装到/usr/local目录下

编译SPDK

1
2
# ./configure --with-dpdk=/usr/local
# make

使用

nvme设备插入主机会被系统自动识别。若想使用spdk访问该设备必须现将nvme unbind。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 7.3T 0 disk
└─sda1 8:1 0 7.3T 0 part /
sdb 8:16 0 7.3T 0 disk
sdc 8:32 0 7.3T 0 disk
sdd 8:48 0 3.8G 0 disk
└─sdd1 8:49 0 3.8G 0 part /boot
nvme0n1 259:0 0 1.5T 0 disk

# cd <spdk_dir>/scripts
# sh setup.sh

# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 7.3T 0 disk
└─sda1 8:1 0 7.3T 0 part /
sdb 8:16 0 7.3T 0 disk
sdc 8:32 0 7.3T 0 disk
sdd 8:48 0 3.8G 0 disk
└─sdd1 8:49 0 3.8G 0 part /boot

参考&鸣谢