From 514983ffd297ece8ad478c72b93c2021bab10cce Mon Sep 17 00:00:00 2001 From: gueFDF <3237455241@qq.com> Date: Tue, 12 Sep 2023 20:24:24 +0800 Subject: [PATCH] =?UTF-8?q?summer2023HMDFS=E5=88=86=E5=B8=83=E5=BC=8F?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=B3=BB=E7=BB=9F=E8=83=BD=E5=8A=9B=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=EF=BC=8C=E5=AE=9E=E7=8E=B0=E5=A4=9A=E5=89=AF=E6=9C=AC?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../doc/hmdfs_interimDoc.md" | 243 ++++++++++ ...23\345\255\230\345\256\236\347\216\260.md" | 458 ++++++++++++++++++ .../src/add-dcache-parameter-support.patch" | 65 +++ .../test/access.sh" | 35 ++ .../test/client.sh" | 12 + .../test/listendir.sh" | 13 + .../test/server.sh" | 10 + .../test/test_hmdfs.cpp" | 290 +++++++++++ ...13\350\257\225\346\226\207\346\241\243.md" | 209 ++++++++ 9 files changed, 1335 insertions(+) create mode 100644 "summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/doc/hmdfs_interimDoc.md" create mode 100644 "summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/doc/hmdfs\347\274\223\345\255\230\345\256\236\347\216\260.md" create mode 100644 "summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/src/add-dcache-parameter-support.patch" create mode 100755 "summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/access.sh" create mode 100755 "summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/client.sh" create mode 100755 "summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/listendir.sh" create mode 100755 "summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/server.sh" create mode 100644 "summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/test_hmdfs.cpp" create mode 100644 "summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/\346\265\213\350\257\225\346\226\207\346\241\243.md" diff --git "a/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/doc/hmdfs_interimDoc.md" "b/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/doc/hmdfs_interimDoc.md" new file mode 100644 index 0000000..bc53f05 --- /dev/null +++ "b/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/doc/hmdfs_interimDoc.md" @@ -0,0 +1,243 @@ +# HMDFS开发文档 +hmdfs是一个堆叠式的网络文件系统,提供跨设备文件访问能力。此次的开发目的是分析hmdfs自带的缓存机制,为原本的缓存机制提供优化,并提供离线访问能力。 + + +## hmdfs远程读取模型 +* 1. 当上层调用open打开一个远端文件时,底层会调用```hmdfs_file_open_remote```函数,该函数会先判断inode的引用计数,若计数为零,则调用```hmdfs_do_open_remote```函数获取远端文件的inode(仅进只是获取inode,并没有获取文件内容)。 +* 2. 当上层调用read时,底层调用```hmdfs_file_read_iter_remote```函数,此函数会先通过```hmdfs_tcpi_rtt```函数预测网络时延,从而确定预读页数(```ra_page```),再通过内核函数```generic_file_read_iter```调用```hmdfs_readpage_remote```从远端获取文件按内容。 + + +hmdfs中 remote file 注册的部分方法 +1. hmdfs_dev_file_fops_remote + + remote_file_fops + +![hmdfs](https://gitee.com/bite-sky/hmdfs/raw/master/picture/hmdfs_dev_file_fops_remot.png) + + + +## hmdfs缓存机制的支持 + a. 页高速缓存的支持(mmap) + b. 本地持久化 + +### a.页高速缓存的支持 + 在测试过程中,应导师要求对hmdfs的远端文件读取时间进行了测试,对1KB,1MB,20MB,100MB,500MB文件进行读取,每种大小的文件测五次,记录时间。 + + 使用工具命令 + ```shell + time cat file_name > /dev/null + ``` + 测试结果如下 + ![hmdfs_test_readtime](https://gitee.com/bite-sky/hmdfs/raw/master/picture/read_time.png) + + 由上面结果可以看出除500MB的文件外,其他大小文件后面四次读取速率远高于第一次读取。 + +原因分析:Linux内核自带页高速缓存机制,在第一次读取后会进行页缓存,后序读取就只需要从页缓存中获取文件内容。 + +大致流程 + +``` + hmdfs_file_read_iter_remote() + | + |___ generic_file_read_iter() + | + |___ mmap(命中) --> 从mmap中获取文件内容(快) + | + |___ mmap(未命中) --> hmdfs_readpage_remote远端获取(慢) --> 进行页缓存 + + +``` + +如何确保一致性? + +hmdfs在读取文件会定期从远端重新获取inode,并与本地的inode比对从而判断缓存是否是失效,若失效就会调用```truncate_inode_pages(inode->i_mapping, 0)```函数清空该inode的高速页缓存中的数据。 + +为什么在读取500MB的文件是前几次速度都很慢? + +因为文件过大,导致机器内存占满,此时Linux的高速页缓存机制会优先清空,访问次数少的缓存页。 + + +### b.本地持久化 +该机制需要在挂载时配置缓存目录: cache_dir=DirName + +挂在好后目录结构如下: +``` +. +├── cache +│   └── stash +│   └── v1 +├── dst +└── src +``` +#### 目录项本地持久化 +开启cache机制后,当一个目录项数量到达一定的阈值后,会进行目录项缓存,后续访问目录项会优先从目录项缓存中获取 + +流程 +* 1. 查看缓存有效性(是否超时) ,缓存有效直接返回。 +* 2. 获取目录项的```relative_path```。 +* 3. 远端获取目录项。 +* 4. 若dentry_count > sbi->dcache_threshold(阈值),add cache。 + + +```decache_timecount``` 和 ```dcache_threshold```,在挂载时被初始化。硬编码,采用默认配置如下 +```c +#define DEFAULT_DCACHE_TIMEOUT 300 +#define DEFAULT_DCACHE_PRECISION 10 +#define DEFAULT_DCACHE_THRESHOLD 1000 +``` +该效果触发后效果如,会在cache目录下对目录项进行本地缓存,效果如下 +``` +├── cache +│   ├── dentry_cache +│   │   └── client +│   │   ├── fake_cid123456_000000000000002f +│   │   ├── ........... +│   │   └── fake_cid123456_000000000016d5d3 +│   └── stash +│   └── v1 +├── dst +│   ├── device_view +│   │   ├── fake_cid123456 +│   │   │   ├── a +│   │   │   ├── ..... +│   │   │   ├── +│   │   │   ├── f +│   │   │   └── g +│   │   └── local +│   └── merge_view +│   ├── a +│   ├── ..... +│   ├── +│   ├── f +│   └── g +└── src +``` + +#### 脏页本地持久化 +hmdfs在断开远端连接时,对于未同步到对端的文件数据脏页,会进行本地持久化。 + +```c +// hmdfs/stash.c +//关键函数 +hmdfs_stash_offline_prepare() +hmdfs_stash_offline_do_stash() +hmdfs_stash_online_prepare() +hmdfs_stash_online_do_restore() +hmdfs_stash_del_do_cleanup() +``` +在向内核的hmdfs发出cmd断开一个节点时,会回调```hmdfs_stash_offline_prepare()``` 和 +```hmdfs_stash_offline_do_stash()```两个函数,会检查已被打开并且有写权限的文件,查看是否存在未刷新到对端的脏页,若有就会将脏页数据持久化到 ```cache/stash/v1/```目录下。 +当再次向内核的hmdfs发出建立链接的cmd,回调```hmdfs_stash_online_prepare()```和 +```hmdfs_stash_online_do_restore()```函数,将脏页再次加载到内存当中。 + + +触发该机之后,会在```cache/stash/v1/```持久化,效果如下 +``` +. +├── cache +│   ├── dentry_cache +│   │   └── client +│   │   +│   └── stash +│   └── v1 +│   └── fake_cid123456 +│   └── 0x3c2d5a9a000c2227 +├── dst +│   ├── device_view +│   │   └── local +│   └── merge_view +└── src + +``` + +## 初步优化改进 + +### 增添dcache相关配置 +将dache相关信息的硬编码改为可配置项,在用户挂载时可以设定,若没有设置,则采用默认配置 + + +具体修改 + +此处主要是提供下面三个配置项 +``` +dcache_threshold +dcache_precision +dcache_timeout +``` +1. 添加配置项 + +```c +//super.c +enum +{ + OPT_RA_PAGES, + ......... + OPT_USER_ID, + OPT_DCACHE_THRESHOLD, + OPT_DCACHE_PRECISION, + OPT_DCACHE_TIMEOUT, + OPT_ERR +}; + +static match_table_t hmdfs_tokens = { + {OPT_RA_PAGES, "ra_pages=%s"}, + ......... + {OPT_DCACHE_THRESHOLD, "dcache_threshold=%d"}, + {OPT_DCACHE_PRECISION, "dcache_precision=%d"}, + {OPT_DCACHE_TIMEOUT, "dcache_timeout=%d"}, + {OPT_ERR, NULL}, +}; +``` +2. ```hmdfs_parse_options```函数中对配置项进行解析 + +```c +int hmdfs_parse_options(struct hmdfs_sb_info *sbi, const char *data) { + ...........//略 + unsigned int decache = 0; + + ...........//略 + while ((p = strsep(&options_src, ",")) != NULL) { + int token; + if (!*p) + continue; + args[0].to = args[0].from = NULL; + token = match_token(p, hmdfs_tokens, args); + switch (token) + { + ...........//略 + case OPT_DCACHE_THRESHOLD: + err = match_int(&args[0], &decache); + if (err) + goto out; + sbi->dcache_threshold = decache; + break; + case OPT_DCACHE_PRECISION: + err = match_int(&args[0], &decache); + if (err) + goto out; + sbi->dcache_precision = decache; + break; + case OPT_DCACHE_TIMEOUT: + err = match_int(&args[0], &decache); + if (err) + goto out; + sbi->dcache_timeout = decache; + break; + default: + err = -EINVAL; + goto out; + } + } +out: + ...........//略 + return err; +} + +``` + +当需要在挂载时,配置这三个项的话,如下 + +```shell +sudo mount -t hmdfs -o merge,local_dst=${server_node_dir}"/dst",cache_dir=${server_node_dir}"/cache",dcache_threshold=1000,dcache_timeout=30,dcache_precision=10 ${server_node_dir}"/src" ${server_node_dir}"/dst" +``` + diff --git "a/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/doc/hmdfs\347\274\223\345\255\230\345\256\236\347\216\260.md" "b/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/doc/hmdfs\347\274\223\345\255\230\345\256\236\347\216\260.md" new file mode 100644 index 0000000..bad2895 --- /dev/null +++ "b/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/doc/hmdfs\347\274\223\345\255\230\345\256\236\347\216\260.md" @@ -0,0 +1,458 @@ +# 文件缓存 +## 目的预期 + + +为了提高效率,实现文件缓存具有以下目的和预期: + +目的:作为分布式文件系统的一部分,hmdfs存储文件内容在远程服务器上。在打开一个文件时,采用懒加载策略,仅获取远程文件的inode信息而不获取文件内容。当文件需要读取时,才会从远程服务器获取文件内容。然而,对于热点文件,如果每次都从远程服务器获取内容,效率会很低。因此,引入文件缓存来提高性能。 + +预期: + +首次读取:当read_iter函数首次读取一个文件时,从远程服务器获取数据,并生成缓存。 +后续读取:如果有其他读取请求,read_iter函数首先检查缓存。如果请求的数据已经在缓存中,直接从缓存中读取数据。 + + +## 如何实现 +HMDFS主要借助Linux的高速页缓存机制实现缓存的。 + +Linux的高速页缓存是内核提供的一种机制,用于在内存中缓存文件系统的数据块。当应用程序读取文件时,数据块会首先被加载到高速页缓存中。如果后续的读取请求命中了缓存,就可以直接从高速页缓存中获取数据,避免了频繁的磁盘访问,从而提高了读取性能。 + +在Hmdfs中,文件的数据块也会被加载到Linux的高速页缓存中。当客户端请求读取文件时,Hmdfs首先会检查高速页缓存中是否存在所需的数据块。如果数据块已经在缓存中,就可以直接从缓存中读取,而无需访问远程存储节点,从而提高了读取性能。 + +当文件数据发生变化时,Hmdfs会相应地更新高速页缓存中的数据块。在写入数据时,Hmdfs会将数据块写入远程存储节点,并更新高速页缓存中的数据块内容。这样,下次读取相同数据块时就可以直接从缓存中获取最新的数据。 + +通过借助Linux的高速页缓存,Hmdfs能够有效地利用内存作为缓存,提高文件读取的性能和响应速度,并减少对远程存储节点的访问频率。这对于提升分布式文件系统的整体性能和可扩展性非常重要。 + +实现页高速缓存的最重要的结构体要算是 address_space ,在 ``` ``` 中 +```c +struct address_space { + struct inode *host; /* 拥有此 address_space 的inode对象 */ + struct radix_tree_root page_tree; /* 包含全部页面的 radix 树 */ + spinlock_t tree_lock; /* 保护 radix 树的自旋锁 */ + unsigned int i_mmap_writable;/* VM_SHARED 计数 */ + struct prio_tree_root i_mmap; /* 私有映射链表的树 */ + struct list_head i_mmap_nonlinear;/* VM_NONLINEAR 链表 */ + spinlock_t i_mmap_lock; /* 保护 i_map 的自旋锁 */ + unsigned int truncate_count; /* 截断计数 */ + unsigned long nrpages; /* 总页数 */ + pgoff_t writeback_index;/* 回写的起始偏移 */ + const struct address_space_operations *a_ops; /* address_space 的操作表 */ + unsigned long flags; /* gfp_mask 掩码与错误标识 */ + struct backing_dev_info *backing_dev_info; /* 预读信息 */ + spinlock_t private_lock; /* 私有 address_space 自旋锁 */ + struct list_head private_list; /* 私有 address_space 链表 */ + struct address_space *assoc_mapping; /* 缓冲 */ + struct mutex unmap_mutex; /* 保护未映射页的 mutux 锁 */ +} __attribute__((aligned(sizeof(long)))); + +``` + +HMDFS需要实现```address_space```的操作表```address_space_operations```,如下 +```c +const struct address_space_operations hmdfs_dev_file_aops_remote = { + .readpage = hmdfs_readpage_remote, + .write_begin = hmdfs_write_begin_remote, + .write_end = hmdfs_write_end_remote, + .writepage = hmdfs_writepage_remote, + .set_page_dirty = __set_page_dirty_nobuffers, +}; +``` + +当上层用户调用read读取远端某一个文件时,调用```hmdfs_file_read_iter_remote()```如下 +```c +static ssize_t hmdfs_file_read_iter_remote(struct kiocb *iocb, + struct iov_iter *iter) +{ + ....................//略 +retry: + err = hmdfs_remote_check_and_reopen(info, filp); + if (err) + return err; + + ra = &filp->f_ra; + /* rtt is measured in 10 msecs */ + rtt = hmdfs_tcpi_rtt(info->conn) / 10000; + switch (rtt) { + case 0: + break; + case 1: + ra->ra_pages = 256; + break; + case 2: + ra->ra_pages = 512; + break; + default: + ra->ra_pages = 1024; + break; + } + + err = generic_file_read_iter(iocb, iter); + if (err < 0 && !tried && hmdfs_remote_need_reopen(info)) { + /* Read from a stale fid, try read again once. */ + tried = true; + goto retry; + } + + return err; +} +``` + +该函数,主要调用```hmdfs_tcpi_rtt```来预估网速,来确定预读页数,然后调用```generic_file_read_iter```,进一步操作,该步骤就 + +```generic_file_read_iter```是Linux内核中的一个函数, + +以下是```generic_file_read_iter```的主要操作: + +1. 参数检查:首先,函数会检查传入的参数,如kiocb(表示I/O控制块)和iov_iter(表示I/O向量迭代器,用于描述用户空间的缓冲区)。 + +2. 文件位置检查:函数会检查请求的读取位置是否超出了文件的大小。如果超出,函数会直接返回,不进行任何读取。 + +3. 高速缓存查找:函数会查找高速页缓存(Page Cache)以确定请求的数据是否已经在缓存中。如果数据在缓存中,它会直接从缓存中读取,而不是调用read_page读取。 + +4. read_page读取:如果数据不在高速缓存中,函数会read_page读取数据。读取的数据会被放入高速页缓存中,以便后续的读取操作可以直接从缓存中获取。 + +5. 数据复制到用户空间:一旦数据可用(无论是从高速缓存还是read_page读取),它会被复制到用户空间的缓冲区中。 + +6. 更新文件位置:函数会更新kiocb中的文件位置,以反映读取的字节数。 + +7. 返回结果:函数返回实际读取的字节数。如果发生错误,它会返回一个负的错误代码。 + +HMDFS借助这一个函数,若无缓存,调用hmdfs具体实现read_page,从远端读取数据 +```c +static int hmdfs_readpage_remote(struct file *file, struct page *page) +{ + struct inode *inode = file_inode(file); + struct hmdfs_inode_info *info = hmdfs_i(inode); + loff_t isize = i_size_read(inode); + pgoff_t end_index = (isize - 1) >> PAGE_SHIFT; + struct hmdfs_fid fid; + + if (!isize || page->index > end_index) { + hmdfs_fill_page_zero(page); + return 0; + } + if (!isize || page->index > end_index) { + hmdfs_fill_page_zero(page); + return 0; + } + hmdfs_remote_fetch_fid(info, &fid); + return hmdfs_client_readpage(info->conn, &fid, page); +} +``` + +大致流程 +``` + hmdfs_file_read_iter_remote() + | + |___ generic_file_read_iter() + | + |___ mmap(命中) --> 从mmap中获取文件内容(快) + | + |___ mmap(未命中) --> hmdfs_readpage_remote远端获取(慢) --> 进行页缓存 + +``` + + +## 如何确保一致性 +要保证缓存的有效性,也就是要在某个文件或数据块已经过时时,它可以丢弃该过期缓存。 +在hmdfs打开一个远端文件时调用```hmdfs_file_open_remote```,在读取文件内容时,会在特定的地方调用```hmdfs_remote_file_reopen```(来检测是否需要重新打开),他们底层都会调用```hmdfs_do_open_remote```如下 +```c +int hmdfs_do_open_remote(struct file *file, bool keep_cache) +{ + struct hmdfs_inode_info *info = hmdfs_i(file_inode(file)); + struct hmdfs_peer *conn = info->conn; + struct hmdfs_open_ret open_ret; + __u8 file_type = hmdfs_d(file->f_path.dentry)->file_type; + char *send_buf; + int err = 0; + + send_buf = hmdfs_get_dentry_relative_path(file->f_path.dentry); + if (!send_buf) { + err = -ENOMEM; + goto out_free; + } + err = hmdfs_send_open(conn, send_buf, file_type, &open_ret); + if (err) { + hmdfs_err("hmdfs_send_open return failed with %d", err); + goto out_free; + } + + err = hmdfs_open_final_remote(info, &open_ret, file, keep_cache); + +out_free: + kfree(send_buf); + return err; +} + +``` +该函数流程如下: +1. 获取Inode信息和连接信息:函数首先获取与文件相关联的inode信息(hmdfs_inode_info)和远程连接信息(hmdfs_peer)。 + +2. 获取文件类型和路径:函数获取文件类型(file_type)和相对路径(send_buf)。 + +3. 发送打开文件请求:通过```hmdfs_send_open```函数,发送一个打开文件的请求到远程节点。 + +4. 处理打开文件的返回结果:```hmdfs_open_final_remote```函数用于处理打开文件的返回结果,判断缓存是否失效 + +5. 释放资源:释放动态分配的内存。 + +核心处理缓存是否失效的逻辑在```hmdfs_open_final_remote```函数,该函数主要根据一下: +1. 检查文件大小和ctime:如果远程文件的大小或ctime与本地的不匹配,函数会标记需要截断本地的缓存。 + +2. 检查写缓存过期:如果设置了```writecache_expire```,函数会检查写缓存是否过期。如果过期,函数会标记需要截断本地的缓存。 + +3. 检查```stable_ctime```:这部分的逻辑比较复杂,但主要是基于```stable_ctime```来决定是否需要截断本地的缓存。```stable_ctime```可能是一个表示文件稳定状态的时间戳,函数会根据这个时间戳和其他条件来决定缓存的一致性。 + +主要通过```truncate_inode_pages(inode->i_mapping, 0);```阶段缓存,也就是清空高速页缓存,下次通过```read_page```读取远端文件数据重新生成缓存 + +## 效果展示 +生成1KB,1MB,20MB,100MB,500MB五种大小的文件,进行文件读取,每种大小的文件测五次,记录时间。 + +使用工具命令 +```shell +time cat file_name > /dev/null +``` + 测试结果如下 +![hmdfs_test_readtime](https://gitee.com/bite-sky/hmdfs/raw/master/picture/read_time.png) + +首次读取读取远快于后续读取。 + +# 目录项缓存 +## 目的预期 +目录项缓存(Directory Entry Cache)是一种缓存机制,用于存储文件系统中目录的信息。它的目的是提高文件系统的性能和效率。 + +在文件系统中,目录是用于组织和管理文件和子目录的结构。每个目录包含了一组目录项,每个目录项对应一个文件或子目录,并记录了它们的名称和相关的元数据(如inode号、权限等)。 + +目录项缓存的目的是为了加速文件系统的操作,包括文件查找、路径解析、文件创建和删除等。 + + +* 消息合并优化,合并readdir和lookup +* 同步目录树,基于目录树访问管理订阅关系 +* 基于事件触发和超时的一致性检查机制,保证最终一致性 + + + +## 如何实现 +开启cache机制后,当一个目录项数量到达一定的阈值后,会进行目录项缓存,后续访问目录项会优先从目录项缓存中获取 + +流程 + +* 查看缓存有效性(是否超时) ,缓存有效直接返回。 +* 获取目录项的relative_path。 +* 远端获取目录项。 +* 若dentry_count > sbi->dcache_threshold(阈值),add cache。 + +打开远端文件夹时调用```hmdfs_dir_open_remote```如下 +```c +int hmdfs_dir_open_remote(struct inode *inode, struct file *file) +{ + struct hmdfs_inode_info *info = hmdfs_i(inode); + struct clearcache_item *cache_item = NULL; + + if (info->conn && info->conn->version <= USERSPACE_MAX_VER) + { + return 0; + } + else if (info->conn) + { + if (!hmdfs_cache_revalidate(READ_ONCE(info->conn->conn_time), + info->conn->device_id, + file->f_path.dentry)) + get_remote_dentry_file_sync(file->f_path.dentry, + info->conn); + cache_item = hmdfs_find_cache_item(info->conn->device_id, + file->f_path.dentry); + if (cache_item) + { + file->private_data = cache_item->filp; + get_file(file->private_data); + kref_put(&cache_item->ref, release_cache_item); + return 0; + } + return -ENOENT; + } + return -ENOENT; +} +``` +主要流程: +* 1. 检查连接版本:如果info->conn存在并且其版本小于或等于USERSPACE_MAX_VER,则函数直接返回0,表示成功打开目录。 + +* 2. 缓存验证:如果info->conn存在并且版本大于USERSPACE_MAX_VER,函数会调用hmdfs_cache_revalidate来验证缓存的有效性。这个函数可能会根据连接时间、设备ID和目录项来决定缓存是否仍然有效。 + +* 3. 获取远程目录项:如果缓存验证失败,函数会调用get_remote_dentry_file_sync来同步获取远程的目录项。 + +* 4. 查找缓存项:函数会调用hmdfs_find_cache_item来查找与给定设备ID和目录项相关联的缓存项。 + +* 5. 设置文件的私有数据:如果找到了缓存项,函数会设置文件的private_data为缓存项中的文件指针,并增加文件的引用计数。然后,函数会减少缓存项的引用计数,并释放缓存项(如果引用计数为0)。 + +* 6. 返回错误:如果没有找到缓存项或info->conn不存在,函数会返回-ENOENT错误,表示没有找到目录。 + +总的来说,hmdfs_dir_open_remote函数在打开一个远程目录时,会与缓存机制交互,首先尝试验证和使用缓存,如果缓存不可用或失效,它会尝试从远程获取目录的信息。这种机制可以提高文件系统的性能,因为在许多情况下,本地的缓存可以满足读取请求,而无需与远程节点通信。 + + +无缓存时,从远端获取后会调用```hmdfs_add_file_to_cache```进行持久化 +```c +int hmdfs_add_file_to_cache(struct dentry *dentry, struct hmdfs_peer *con, + struct file *file, const char *relative_path) +{ + struct hmdfs_sb_info *sbi = con->sbi; + struct file *newf = file; + + if (cache_get_dentry_count(sbi, file) >= sbi->dcache_threshold) + newf = cache_file_persistent(con, file, relative_path, false); + else + cache_file_find_and_delete(con, relative_path); + + return hmdfs_add_cache_list(con->device_id, dentry, newf); +} +``` +1. 首先,函数接受一些参数包括dentry(目录项)、con(hmdfs_peer结构)、file(待添加的文件)、relative_path(相对路径)。 +2. 接下来,函数从con中获取hmdfs_sb_info结构的指针sbi,该结构包含了与超级块相关的信息。 +3. 函数使用file指针初始化一个新的文件指针newf。 +4. 然后,函数检查缓存中与file关联的目录项数量是否超过了sbi->dcache_threshold(缓存阈值)。如果超过了阈值,就调用cache_file_persistent函数,将file持久化到缓存中,并返回一个新的文件指针newf。否则,调用cache_file_find_and_delete函数,根据relative_path在缓存中查找并删除相应的文件。 +5. 最后,函数调用hmdfs_add_cache_list函数,将newf添加到缓存列表中,并返回结果。 + + + +## 如何确保一致性 +除了缓存超时,会淘汰目录项缓存外,当对端增添目录项,删除目录项,重命名时,也应该通知缓存端,淘汰缓存,这样才能保证一致性。 +以rname为例,当修改本地文件name时调用```hmdfs_rename_local```,会调用```hmdfs_drop_remote_cache_dents```,通知对端清空当前目录的目录项缓存 + + +```c +int hmdfs_rename_local_dentry(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) +{ + struct path lower_old_path; + struct path lower_new_path; + struct dentry *lower_old_dentry = NULL; + struct dentry *lower_new_dentry = NULL; + struct dentry *lower_old_dir_dentry = NULL; + struct dentry *lower_new_dir_dentry = NULL; + struct dentry *trap = NULL; + int rc = 0; + kuid_t old_dir_uid, new_dir_uid; + + if (flags) + return -EINVAL; + + hmdfs_get_lower_path(old_dentry, &lower_old_path); + lower_old_dentry = lower_old_path.dentry; + if (!lower_old_dentry) { + hmdfs_err("lower_old_dentry as NULL"); + rc = -EACCES; + goto out_put_old_path; + } + + hmdfs_get_lower_path(new_dentry, &lower_new_path); + lower_new_dentry = lower_new_path.dentry; + if (!lower_new_dentry) { + hmdfs_err("lower_new_dentry as NULL"); + rc = -EACCES; + goto out_put_new_path; + } + + lower_old_dir_dentry = dget_parent(lower_old_dentry); + lower_new_dir_dentry = dget_parent(lower_new_dentry); + trap = lock_rename(lower_old_dir_dentry, lower_new_dir_dentry); + old_dir_uid = hmdfs_override_inode_uid(d_inode(lower_old_dir_dentry)); + new_dir_uid = hmdfs_override_inode_uid(d_inode(lower_new_dir_dentry)); + + /* source should not be ancestor of target */ + if (trap == lower_old_dentry) { + rc = -EINVAL; + goto out_lock; + } + /* target should not be ancestor of source */ + if (trap == lower_new_dentry) { + rc = -ENOTEMPTY; + goto out_lock; + } + + rc = vfs_rename(d_inode(lower_old_dir_dentry), lower_old_dentry, + d_inode(lower_new_dir_dentry), lower_new_dentry, NULL, + flags); +out_lock: + dget(old_dentry); + + hmdfs_revert_inode_uid(d_inode(lower_old_dir_dentry), old_dir_uid); + hmdfs_revert_inode_uid(d_inode(lower_new_dir_dentry), new_dir_uid); + + unlock_rename(lower_old_dir_dentry, lower_new_dir_dentry); + if (rc == 0) { + hmdfs_drop_remote_cache_dents(old_dentry->d_parent); + if (old_dentry->d_parent != new_dentry->d_parent) + hmdfs_drop_remote_cache_dents(new_dentry->d_parent); + } else { + hmdfs_clear_drop_flag(old_dentry->d_parent); + if (old_dentry->d_parent != new_dentry->d_parent) + hmdfs_clear_drop_flag(old_dentry->d_parent); + d_drop(new_dentry); + } + + dput(old_dentry); + dput(lower_old_dir_dentry); + dput(lower_new_dir_dentry); + +out_put_new_path: + hmdfs_put_lower_path(&lower_new_path); +out_put_old_path: + hmdfs_put_lower_path(&lower_old_path); + return rc; +} + +``` + +1. 参数检查:如果flags参数不为0,函数直接返回-EINVAL错误。 + +2. 获取旧目录项的下层路径:使用hmdfs_get_lower_path函数获取old_dentry的下层路径,并检查其有效性。 + +3. 获取新目录项的下层路径:使用hmdfs_get_lower_path函数获取new_dentry的下层路径,并检查其有效性。 + +4. 获取下层目录的父目录项:使用dget_parent函数获取lower_old_dentry和lower_new_dentry的父目录项。 + +5. 锁定重命名操作:使用lock_rename函数锁定两个目录项,以确保重命名操作的原子性。 + +6. 覆盖Inode的UID:使用hmdfs_override_inode_uid函数覆盖两个目录的Inode的UID。 + +7. 检查重命名的有效性:确保源目录项不是目标目录项的祖先,反之亦然。 + +8. 执行重命名操作:使用vfs_rename函数执行实际的重命名操作。 + +9. 解锁重命名操作:使用unlock_rename函数解锁两个目录项。 + +10. 处理缓存:如果重命名操作成功,函数会调用hmdfs_drop_remote_cache_dents函数来删除远程缓存的目录项。如果重命名操作失败,函数会调用hmdfs_clear_drop_flag函数来清除目录项的删除标志,并调用d_drop函数来删除新目录项。 + +11. 释放目录项和路径:使用dput函数释放目录项,并使用hmdfs_put_lower_path函数释放路径。 +## 效果展示 +当缓存生效后就会生成目录的缓存(根目录缓存),视图如下 +``` +. +├── cache +│ ├── dentry_cache +│ │ └── client +│ │ └── fake_cid123456_000000000000002f +│ └── stash +│ └── v1 +├── dst +│ ├── device_view +│ │ ├── fake_cid123456 +│ │ │ ├── a +│ │ │ ├── b +│ │ │ ├── c +│ │ │ ├── d +│ │ │ ├── e +│ │ │ └── f +│ │ └── local +│ └── merge_view +│ ├── a +│ ├── b +│ ├── c +│ ├── d +│ ├── e +│ └── f +└── src + +``` +具体测试方案参考[HMDFS参数优化测试文档](https://gitee.com/bite-sky/hmdfs/blob/master/test/%E6%B5%8B%E8%AF%95%E6%96%87%E6%A1%A3.md) \ No newline at end of file diff --git "a/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/src/add-dcache-parameter-support.patch" "b/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/src/add-dcache-parameter-support.patch" new file mode 100644 index 0000000..67843b6 --- /dev/null +++ "b/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/src/add-dcache-parameter-support.patch" @@ -0,0 +1,65 @@ +diff --git a/hmdfs/super.c b/hmdfs/super.c +index 6dfb816..18f222c 100644 +--- a/super.c ++++ b/super.c +@@ -21,25 +21,19 @@ enum { + OPT_NO_OFFLINE_STASH, + OPT_NO_DENTRY_CACHE, + OPT_USER_ID, +- OPT_DCACHE_THRESHOLD, +- OPT_DCACHE_PRECISION, +- OPT_DCACHE_TIMEOUT, +- OPT_ERR ++ OPT_ERR, + }; + + static match_table_t hmdfs_tokens = { +- {OPT_RA_PAGES, "ra_pages=%s"}, +- {OPT_LOCAL_DST, "local_dst=%s"}, +- {OPT_CACHE_DIR, "cache_dir=%s"}, +- {OPT_S_CASE, "sensitive"}, +- {OPT_VIEW_TYPE, "merge"}, +- {OPT_NO_OFFLINE_STASH, "no_offline_stash"}, +- {OPT_NO_DENTRY_CACHE, "no_dentry_cache"}, +- {OPT_USER_ID, "user_id=%s"}, +- {OPT_DCACHE_THRESHOLD, "dcache_threshold=%d"}, +- {OPT_DCACHE_PRECISION, "dcache_precision=%d"}, +- {OPT_DCACHE_TIMEOUT, "dcache_timeout=%d"}, +- {OPT_ERR, NULL}, ++ { OPT_RA_PAGES, "ra_pages=%s" }, ++ { OPT_LOCAL_DST, "local_dst=%s" }, ++ { OPT_CACHE_DIR, "cache_dir=%s" }, ++ { OPT_S_CASE, "sensitive" }, ++ { OPT_VIEW_TYPE, "merge" }, ++ { OPT_NO_OFFLINE_STASH, "no_offline_stash" }, ++ { OPT_NO_DENTRY_CACHE, "no_dentry_cache" }, ++ { OPT_USER_ID, "user_id=%s"}, ++ { OPT_ERR, NULL }, + }; + + #define DEAULT_RA_PAGES 128 +@@ -156,24 +150,6 @@ int hmdfs_parse_options(struct hmdfs_sb_info *sbi, const char *data) + sbi->user_id = user_id; + } + break; +- case OPT_DCACHE_THRESHOLD: +- err = match_int(&args[0], &decache); +- if (err) +- goto out; +- sbi->dcache_threshold = decache; +- break; +- case OPT_DCACHE_PRECISION: +- err = match_int(&args[0], &decache); +- if (err) +- goto out; +- sbi->dcache_precision = decache; +- break; +- case OPT_DCACHE_TIMEOUT: +- err = match_int(&args[0], &decache); +- if (err) +- goto out; +- sbi->dcache_timeout = decache; +- break; + default: + err = -EINVAL; + goto out; diff --git "a/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/access.sh" "b/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/access.sh" new file mode 100755 index 0000000..c1fe76b --- /dev/null +++ "b/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/access.sh" @@ -0,0 +1,35 @@ +#!/bin/bash +# 目标目录 +directory="/home/mnt/hmdfs_dev/server_node/dst/merge_view" + +dir_exists() { + local file="$1" + + if [ -d "$file" ]; then + echo "true" + else + echo "false" + fi +} + + +# 定义递归遍历目录的函数 +recursive_traverse_directory() { + local directory="$1" + + # 递归遍历目录 + find "$directory" -type f -print0 | while IFS= read -r -d '' file + do + + done +} + + +while true +do + result=$(dir_exists "$directory") + if [ "$result" = "true" ]; then + recursive_traverse_directory "$directory" + fi + +done diff --git "a/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/client.sh" "b/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/client.sh" new file mode 100755 index 0000000..c2447e9 --- /dev/null +++ "b/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/client.sh" @@ -0,0 +1,12 @@ +#! /bin/bash +# 客户端启动脚本 + +client_node_dir="/home/mnt/hmdfs_dev/client_node" +sudo mkdir -p ${client_node_dir}"/src" +sudo mkdir -p ${client_node_dir}"/dst" +sudo mkdir -p ${client_node_dir}"/cache" + +# 挂载hmdfs目录 +sudo mount -t hmdfs -o merge,local_dst=${client_node_dir}"/dst"cache_dir=${server_node_dir}"/cache",dcache_threshold=5,dcache_timeout=6 ${client_node_dir}"/src" ${client_node_dir}"/dst" + +sudo ./test_hmdfs ${client_node_dir}"/dst" -c 192.168.31.246 diff --git "a/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/listendir.sh" "b/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/listendir.sh" new file mode 100755 index 0000000..a09e5cb --- /dev/null +++ "b/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/listendir.sh" @@ -0,0 +1,13 @@ +#!/bin/bash +inotifywait -mrq --timefmt '%Y-%m-%d %H:%M:%S' --format '%T %w%f %e' --event delete,create /home/mnt/hmdfs_dev/server_node/cache | while read date time file event + do + case $event in + MODIFY|CREATE|MOVE|MODIFY,ISDIR|CREATE,ISDIR|MODIFY,ISDIR) + echo $event'-'$file'-'$date'-'$time + ;; + + MOVED_FROM|MOVED_FROM,ISDIR|DELETE|DELETE,ISDIR): + echo $event'-'$file'-'$date'-'$time + ;; + esac + done diff --git "a/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/server.sh" "b/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/server.sh" new file mode 100755 index 0000000..ff50e1b --- /dev/null +++ "b/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/server.sh" @@ -0,0 +1,10 @@ +#! /bin/bash +# 服务端启动脚本 +server_node_dir="/home/mnt/hmdfs_dev/server_node" +sudo mkdir -p ${server_node_dir}"/src" +sudo mkdir -p ${server_node_dir}"/dst" +sudo mkdir -p ${server_node_dir}"/cache" +# 挂载hmdfs目录 +sudo mount -t hmdfs -o merge,local_dst=${server_node_dir}"/dst",cache_dir=${server_node_dir}"/cache",dcache_threshold=5,dcache_timeout=6 ${server_node_dir}"/src" ${server_node_dir}"/dst" + +sudo ./test_hmdfs ${server_node_dir}"/dst" -s 192.168.31.246 diff --git "a/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/test_hmdfs.cpp" "b/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/test_hmdfs.cpp" new file mode 100644 index 0000000..3e63d21 --- /dev/null +++ "b/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/test_hmdfs.cpp" @@ -0,0 +1,290 @@ +// 测试hmdfs跨网络通信,网络使用socket通信 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +int port = 6666; +const int GROOTFS_KEY_SIZE = 32; +const int GROOTFS_ACCOUNT_HASH_MAX_LEN = 21; +char *ipaddr; +const int GROOTFS_CID_SIZE = 64; +static const std::string CTRL_PATH_BASE = "/sys/fs/hmdfs"; +static std::string g_ctrlPath {}; +bool g_connectQuit = false; +#define MASTER_KEY "01234567890123456789012345678901" +#define CID "fake_cid123456" +int gCount = 2; + +enum { + CMD_UPDATE_SOCKET = 0, + CMD_UPDATE_DEVSL, + CMD_OFFLINE, + CMD_OFF_LINE_ALL, + CMD_CNT +}; + +enum { + SOCKET_STAT_ACCEPT = 0, + SOCKET_STAT_OPEN +}; + +struct UpdateSocketParam { + int32_t cmd; + int32_t newfd; + uint32_t devsl; + uint8_t status; + uint8_t masterkey[GROOTFS_KEY_SIZE]; + char cid[GROOTFS_CID_SIZE]; +} __attribute__((packed)); + +struct UpdateDslParam { + int32_t cmd; + uint32_t devsl; + char cid[GROOTFS_CID_SIZE]; +} __attribute__((packed)); + +struct OfflineParam { + int32_t cmd; +} __attribute__((packed)); + +template +void setCmd(T &cmd) { + int file = open(g_ctrlPath.c_str(), O_RDWR); + if (file < 0) { + cout << "setCmd Open node file error." << endl; + return; + } + int err = write(file, &cmd, sizeof(T)); + if (err < 0) { + cout << "setCmd write to file error." << endl; + } + close(file); + return; +} + +int socketConnected(int sock) { + if (sock < 0) { + return 0; + } + struct tcp_info info; + socklen_t len = sizeof(info); + getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, &len); + return info.tcpi_state; +} + +// 服务端启监听 +void serverListen() { + list clientList; + const int LISTEN_NUM = 10; + const int ACCEPT_TIMEOUT = 6; + struct sockaddr_in serverAddr; + struct sockaddr_in clientAddr; + socklen_t addrLen = sizeof(clientAddr); + int reuse = 1; + + int serverSocket = socket(AF_INET, SOCK_STREAM, 0); + if (serverSocket < 0) { + perror("socket"); + return; + } + + // bind前设置重用这个socket地址,即多个socket可以公用这个地址 + if (setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) { + perror("setsockopt SO_REUSEADDR"); + close(serverSocket); + return; + } + + // bind前设置重用这个socket的端口,即多个socket可以公用这个port + if (setsockopt(serverSocket, SOL_SOCKET, SO_REUSEPORT, (const void*)&reuse, sizeof(reuse)) < 0) { + perror("setsockopt SO_REUSEPORT"); + close(serverSocket); + return; + } + + bzero(&serverAddr, sizeof(serverAddr)); + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(port); + serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); + if (::bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) { + perror("connect"); + close(serverSocket); + return; + } + + // 开始监听 + if (listen(serverSocket, LISTEN_NUM) < 0) { + perror("listen"); + close(serverSocket); + return; + } + + struct timeval timeout = {ACCEPT_TIMEOUT, 0}; + + while (!g_connectQuit) { + // 设置recv超时时间,因为默认是阻塞模式,防止长时间阻塞 + if (setsockopt(serverSocket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) { + perror("set timeout"); + close(serverSocket); + return; + } + + int acclient = accept(serverSocket, (struct sockaddr *)&clientAddr, &addrLen); + if (acclient >= 0) { + cout << "IP is: " << inet_ntoa(clientAddr.sin_addr) << endl; + cout << "Port is: " << htons(clientAddr.sin_port) << endl; + clientList.push_back(acclient); + + UpdateSocketParam cmd = { + .cmd = CMD_UPDATE_SOCKET, + .newfd = acclient, + .devsl = 0, + .status = SOCKET_STAT_ACCEPT, + }; + memcpy(cmd.masterkey, MASTER_KEY, GROOTFS_KEY_SIZE); + memcpy(cmd.cid,CID, GROOTFS_CID_SIZE); + setCmd(cmd); + + UpdateDslParam cmd1 = { + .cmd = CMD_UPDATE_DEVSL, + .devsl = 2, + }; + memcpy(cmd1.cid, CID, GROOTFS_CID_SIZE); + setCmd(cmd1); + } + + for (auto it = clientList.begin(); it != clientList.end(); ++it) { + if (socketConnected(*it) != 1) { + cout << "disconnect is: " << *it << endl; + OfflineParam cmd = { + .cmd = CMD_OFF_LINE_ALL, + }; + setCmd(cmd); + + close(*it); + clientList.erase(it); // list删除方便 + break; // ? + } + } + } + for (auto it = clientList.begin(); it != clientList.end(); ++it) { + cout << "disconnect is: " << *it << endl; + OfflineParam cmd = { + .cmd = CMD_OFF_LINE_ALL, + }; + setCmd(cmd); + close(*it); + clientList.erase(it); + } + + close(serverSocket); +} + +void clientConnect() { + struct sockaddr_in serverAddr; + const int SEND_BUF_LEN = 200; + char sendBuf[SEND_BUF_LEN]; + int clientSocket; + + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(port); + serverAddr.sin_addr.s_addr = inet_addr(ipaddr); + + while (true) { + clientSocket = socket(AF_INET, SOCK_STREAM, 0); + if (clientSocket < 0) { + perror("create client socket"); + return; + } + + cout << "begin to connect..." << endl; + + if (connect(clientSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) { + cout << "client connect error." << endl; + close(clientSocket); + sleep(10); + continue; + } + + cout << "connected to " << inet_ntoa(serverAddr.sin_addr); + cout << ":" << htons(serverAddr.sin_port) << endl; + + UpdateSocketParam cmd = { + .cmd = CMD_UPDATE_SOCKET, + .newfd = clientSocket, + .devsl = 0, + .status = SOCKET_STAT_OPEN, + }; + memcpy(cmd.masterkey, MASTER_KEY, GROOTFS_KEY_SIZE); + memcpy(cmd.cid, CID, GROOTFS_CID_SIZE); + setCmd(cmd); + + UpdateDslParam cmd1 = { + .cmd = CMD_UPDATE_DEVSL, + .devsl = 4, + }; + memcpy(cmd1.cid, CID, GROOTFS_CID_SIZE); + setCmd(cmd1); + + while (true) { + sleep(5); + if (socketConnected(clientSocket) != 1) { + OfflineParam cmd = { + .cmd = CMD_OFF_LINE_ALL, + }; + setCmd(cmd); + close(clientSocket); + break; + } + } + } +} + +static uint64_t pathHash(const char *path, int len) { + uint64_t res = 0; + const char *kp = path; + while (*kp) { + res = (res << 5) - res + (uint64_t) * kp++; + } + return res; +} + +int main(int argc, char* argv[]) { + const int ARG_NUM = 3; + if (argc < 3) { + cout << "Please input source ./xxxx path -s/-c ip" << endl; + return -1; + } + + uint64_t hash = pathHash(argv[1], strlen(argv[1])); + std::stringstream path; + path << CTRL_PATH_BASE << "/" << hash << "/cmd"; + g_ctrlPath = path.str(); + cout << "ctrl path" << g_ctrlPath << endl; + if (argc != ARG_NUM + 1) { + cout << "Client input param num is invalid." << endl; + return -1; + } + ipaddr = argv[3]; + if (strcmp(argv[2], "-s") == 0) { + serverListen(); + } else if (strcmp(argv[2], "-c") == 0) { + clientConnect(); + } else { + cout << "invalid input param." << endl; + } + + return 0; +} \ No newline at end of file diff --git "a/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/\346\265\213\350\257\225\346\226\207\346\241\243.md" "b/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/\346\265\213\350\257\225\346\226\207\346\241\243.md" new file mode 100644 index 0000000..bfd7c49 --- /dev/null +++ "b/summer2023HMDFS\345\210\206\345\270\203\345\274\217\346\226\207\344\273\266\347\263\273\347\273\237\350\203\275\345\212\233\345\242\236\345\274\272\357\274\214\345\256\236\347\216\260\345\244\232\345\211\257\346\234\254\346\250\241\345\274\217/test/\346\265\213\350\257\225\346\226\207\346\241\243.md" @@ -0,0 +1,209 @@ +# HMDFS参数优化测试文档 + +## 加入修改部分,重新编译hmdfs + +1. 下载src-openEuler下的代码仓 + +``` +git clone -b openEuler-22.03-LTS-SP2 https://gitee.com/src-openeuler/hmdfs.git +``` + +2. 安装rpmbuild相关依赖和新建编译⽬录(如果有可忽略) +``` +dnf install gcc rpm-build rpm-devel rpmlint make python bash coreutils diffutils patch rpmdevtools +``` + +3. 进⼊hmdfs的⽬录,将hmdfs-1.0.0.tar.gz和patch⽂件移动到rpmbuild对应⽬录 +``` +cd hmdfs +mv *.tar.gz *.patch ~/rpmbuild/SOURCES +``` + +4. 进入hmdfs_Cache/src目录将优化代码加入 + +``` +mv add-dcache-parameter-support.patch ~/rpmbuild/SOURCES +``` + +5. 进⼊~/rpmbuild/SOURCES的⽬录,修改hmdfs.spec +``` +vim hmdfs.spec +``` +在```Patch1: 0002-bugfix-null-pointer-in-memcpy.patch```后加入 +``` +Patch2: add-dcache-parameter-support.patch +``` +在```%patch1 -p1```后加入 +``` +%patch2 -p1 +``` +保存并退出 + +6. 安装hmdfs需要的编译依赖 + +``` +dnf builddep hmdfs.spec +``` + +7. rpmbuild⽣成适⽤当前运⾏环境的hmdfs rpm包 +``` +rpmbuild -bb hmdfs.spec +``` + +8. 运⾏成功之后,会在~/rpmbuild/RPMS ⽬录下有hmdfs的rpm包,安装 +``` +rpm -ivh hmdfs-1.0.0-1.oe2203.aarch64.rpm +``` + +9. 插入内核,安装成功之后,会在```/usr/lib/modules/%(rpm-aqkernel-devel|cut-d"-" -f 3,4)/hmdfs``` ⽬录下有⼀个hmdfs.ko⽂件 +``` +sudo insmod hmdfs.ko +``` + +## 优化以及使用介绍 +将dache相关信息的硬编码改为可配置项,在用户挂载时可以设定,若没有设置,则采用默认配置 + +| 优化参数| 效果 |默认大小| +|--|--|--| +| dcache_threshold | 目录项缓存生效的阈值(到达阈值产生目录项缓存) |1000| +| dcache_timeout | 目录项缓存超时时长|30s| + +使用方法 +当需要在挂载时,配置这三个项的话,如下 + +```shell +sudo mount -t hmdfs -o merge,local_dst=${server_node_dir}"/dst",cache_dir=${server_node_dir}"/cache",dcache_threshold=1000,dcache_timeout=30,dcache_precision=10 ${server_node_dir}"/src" ${server_node_dir}"/dst" +``` + +## 测试分析(server和client) +| 步骤 | 操作 | 执行指令 | 预期结果 | +|------|--------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------| +| 1 | 准备两台openEuler设备,确保网络通畅且在同一网络段中 | - | - | +| 2 | 修改`client.sh`和`server.sh`中的IP地址和参数配置 | - | - | +| 3 | 编译`test_hmdfs.cpp` | `g++ -std=c++11 test_hmdfs.cpp -o test_hmdfs -lpthread` | - | +| 4 | 启动服务器 | `./server.sh` | - | +| 5 | 启动客户端 | `./client.sh` | - | +| 6 | 查看目录视图 | `tree /home/mnt/hmdfs_dev/server_node` | 目录结构显示挂载成功 | +| 7a | 逐渐创建文件`a`、`b`、`c`、`d`、`e`、`f`,查看目录视图 | 依次执行以下命令:
`cd /home/mnt/hmdfs_dev/client_node/dst/device_view/local`
`touch a b c e f` | - 第一次访问生成缓存条目
- 第二次访问目录视图显示缓存存在于`cache/dentry_cache/client`目录下 | +| 7b | server执行监听和访问脚本,观察缓存条目的创建和删除 | server打开两个终端:
终端1:`./listendir.sh`
终端2:`./access.sh` | - 缓存条目每隔6秒被删除并重新创建,验证超时时长是否为6秒 | + + + + + + + +## 具体测试流程 +准备两台openEuler设备,确保两个设备之间⽹络是通畅的,并且在同⼀⽹段。以下操作需要在两台设备都进⾏操作。 + +1. 进入hmdfs_Cache/test +修改```client.sh```和```server.sh```中的ip地址(原本是127.0.0.1),改为这两个设备中做服务节点的设备,并且设定```dcache_threshold```参数和```dcache_timeout```,此处设置为5和6(为了后面测试验证) + +2. 编译```test_hmdfs.cpp``` +``` +g++-std=c++11 test_hmdfs.cpp -o test_hmdfs -lpthread +``` + +3. 启动srever(只在server设备执⾏) +``` +./srever.sh +``` + +4. 启动client(只在client设备执⾏) + +``` +./client.sh +``` + +5. 查看目录视图 + +``` +tree /home/mnt/hmdfs_dev/srever_node +``` +如下 +``` +. +├── cache +│   └── stash +│   └── v1 +├── dst +└── src +``` +表示挂载成功 + +## 测试 + +### dcache_threshold +此处我们设置的配置阈值为5(```server.sh```和```client.sh```中设置) + +预期 +当一个文件夹中的文件数量超过5时,在·```cache/dentry_cache/```下生成缓存条目 + + +测试 +在```client_node/src```中逐渐创建文件,观察对端```cache/dentry_cache/client```下是产生缓存项 +分别创建 a,b,c,d,e,f 文件,每次创建文件都使用```sudo tree .```,查看目录视图,至少两次(因为第一次访问生成缓存,第二次访问目录视图既可以看见缓存) + +当生成clinet_node/src中创建超过五个文件时,就会生成目录的缓存(根目录缓存),视图如下 + + +``` +. +├── cache +│   ├── dentry_cache +│   │   └── client +│   │   └── fake_cid123456_000000000000002f +│   └── stash +│   └── v1 +├── dst +│   ├── device_view +│   │   ├── fake_cid123456 +│   │   │   ├── a +│   │   │   ├── b +│   │   │   ├── c +│   │   │   ├── d +│   │   │   ├── e +│   │   │   └── f +│   │   └── local +│   └── merge_view +│   ├── a +│   ├── b +│   ├── c +│   ├── d +│   ├── e +│   └── f +└── src + +``` + +### dcache_timeout +此处我们设置目录超时时间为6,当到达阈值后会生成目录缓存,为超时时每次访问目录项就会从缓存中获取,若超时则会先删除原来的缓存项,再创建新的缓存项文件,我们可以借此性质,来测试超时时长是否被设置为6 + +开启两个终端 + +一个执行 +```shell +./listendir.sh +``` +这个用来监听cache文件夹下文件的变化 + +另一个执行 +```shell +./access.sh +``` +这个程序用来递归访问cache文件夹中的文件 + + 注意执行前先清空```cache/dentry_cache/client```文件夹下的文件 + + +一段时间后,执行```./listendir.sh```的终端输出 +``` +CREATE-/home/mnt/hmdfs_dev/server_node/cache/dentry_cache/client/fake_cid123456_000000000000002f-2023-08-16-22:07:12 +DELETE-/home/mnt/hmdfs_dev/server_node/cache/dentry_cache/client/fake_cid123456_000000000000002f-2023-08-16-22:07:18 +CREATE-/home/mnt/hmdfs_dev/server_node/cache/dentry_cache/client/fake_cid123456_000000000000002f-2023-08-16-22:07:18 +DELETE-/home/mnt/hmdfs_dev/server_node/cache/dentry_cache/client/fake_cid123456_000000000000002f-2023-08-16-22:07:24 +CREATE-/home/mnt/hmdfs_dev/server_node/cache/dentry_cache/client/fake_cid123456_000000000000002f-2023-08-16-22:07:24 + +``` + +由上述输出可以看出,第一次访问创建缓存条目,后续每隔6秒删除原来的条目又创建新的条目。 \ No newline at end of file -- Gitee