windows 稀疏文件 (sparse file) 的一个实用场景——解决 SetEndOfFile 占据磁盘空间引入的性能问题
前言
之前写过一篇文章说明文件空洞:《[apue] 文件中的空洞》,其中提到了 windows 稀疏文件是制造空洞的一种方式,但似乎没什么用处,如果仅仅处理占用磁盘空间的场景,使用SetEndOfFile 就足够了。
后来在实际工作中,发现稀疏文件在解决一个性能问题方面,有着不可替代的作用,下面且听我一一道来。
问题现象
公司的文件下载产品,为了预防在下载过程中因磁盘空间不足而失败,在 windows 上采取预分配的策略,在下载任务开始前就占据了相当于文件长度的磁盘空间。由于数据源也包括从 P2P 处获取的数据,导致写入时并不是顺序的,存乱序写入的情况,这些信息都存储在数据库中,当应用重启时,会从数据库加载块信息,继续对未落盘的块进行网络请求。
整个逻辑看起来没有问题,然而实测在距离当前写入位置较远的地方写入块时,会发现落盘速度极慢。
下面用一个简单的例子验证这一点,这个 demo 创建一个大文件,通常十几个 GB,具体长度由用户输入决定:
#include <iostream>
#include <Windows.h>
int main(int argc, char* argv[])
{
if (argc < 3)
{
std::cout << "Usage: sparsefile file length (in GB)\n";
return 1;
}
int ret = 0;
HANDLE file_handle = INVALID_HANDLE_VALUE;
LARGE_INTEGER pos = { 0 };
do
{
file_handle = CreateFileA(argv[1], (GENERIC_READ | GENERIC_WRITE),
FILE_SHARE_READ, 0, OPEN_ALWAYS, FILE_FLAG_OVERLAPPED, 0);
if (file_handle == INVALID_HANDLE_VALUE)
{
std::cout << "CreateFile failed, error " << GetLastError() << std::endl;
ret = 2;
break;
}
pos.QuadPart = atoll(argv[2]) * 1024 * 1024 * 1024; // unit in GB
if (::SetFilePointerEx(file_handle, pos, NULL, FILE_BEGIN) == 0)
{
std::cout << "SetFilePointerEx failed, error " << GetLastError() << std::endl;
ret = 3;
break;
}
if (!::SetEndOfFile(file_handle))
{
std::cout << "SetEndOfFile failed, error " << GetLastError() << std::endl;
ret = 4;
break;
}
} while (0);
if (ret == 0)
std::cout << "create file with length " << pos.QuadPart << " success, path " << argv[1] << std::endl;
if (file_handle)
CloseHandle(file_handle);
return ret;
}
执行以下命令创建文件:
PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 10
SetEndOfFile failed, error 112
PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
create file with length 4294967296 success, path movie.mp4
分区 D: 目前仅有 5GB 空间,所以创建 10GB 的文件会失败,错误码 112 即磁盘空间不足;创建 4GB 的可以成功:

文件属性中,文件大小与占用空间是一致的,没有空洞。
在完成预分配后,立即在尾部写入 1 个字符,以模拟真实的使用场景:
...
pos.QuadPart--;
if (::SetFilePointerEx(file_handle, pos, NULL, FILE_BEGIN) == 0)
{
std::cout << "SetFilePointerEx failed, error " << GetLastError() << std::endl;
ret = 30;
break;
}
DWORD bytes = 0;
c_timer t;
if (!::WriteFile(file_handle, " ", 1, &bytes, NULL) || bytes != 1)
{
std::cout << "WriteFile failed, error " << GetLastError() << ", written " << bytes << std::endl;
ret = 31;
break;
}
int elapse = t.get_interval();
std::cout << "write file elapse " << elapse << " ms" << std::endl;
...
注意需要前移文件指针,并在写入前后记录耗时,c_timer 封装了 windows 高精度计时器,可以精确到毫秒。下面是程序输出:
PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
write file elapse 19502 ms
create file with length 4294967295 success, path movie.mp4
写入 4G 文件末尾 1 个字节,消耗了将近 20s,这还是 SSD,如果换作机械硬盘,耗时会更久。这就是 SetEndOfFile 占据磁盘空间产生的性能问题。
问题分析
网上搜索了一番,大概明白这个 20s 耗时是怎么回事了,简单说明一下:当使用 SetEndOfFile 预分配磁盘空间时,文件系统会将原始块分配给文件,这些块可能包含用户之前删除的文件数据,如果不写入新内容就读取,可能会读到用户隐私!黑客完全可以利用这个漏洞盗取用户数据,从而导致严重的安全问题。
为此,windows 在应用写入原始块时,会用零填充文件指针到上次写入位置之间的区域 (没有上次写入位置就从文件头开始),保证这些内容被清空,从而避免原始信息泄漏。上例中的耗时大部分时间用于填充整个 4GB 文件了,速度当然快不起来。
解决方案
目前有两种解决方案,下面分别说明
提权 + SetFileValidData
这种就是官方提出的解决方案,其实是给 service 打的补丁,MSDN 上写的很详细了:
You can use the SetFileValidData function to create large files in very specific circumstances so that the performance of subsequent file I/O can be better than other methods. Specifically, if the extended portion of the file is large and will be written to randomly, such as in a database type of application, the time it takes to extend and write to the file will be faster than using SetEndOfFile and writing randomly. In most other situations, there is usually no performance gain to using SetFileValidData, and sometimes there can be a performance penalty.
需要注意的是,这个函数需要用户拥有 SeManageVolumePrivilege 权限。以管理员身份运行时,默认是没有这个权限的:
D:\test\sparsefile\Release>whoami /priv
特权信息
----------------------
特权名 描述 状态
========================================= ================================== ======
SeIncreaseQuotaPrivilege 为进程调整内存配额 已禁用
SeMachineAccountPrivilege 将工作站添加到域 已禁用
SeSecurityPrivilege 管理审核和安全日志 已禁用
SeTakeOwnershipPrivilege 取得文件或其他对象的所有权 已禁用
SeLoadDriverPrivilege 加载和卸载设备驱动程序 已禁用
SeSystemProfilePrivilege 配置文件系统性能 已禁用
SeSystemtimePrivilege 更改系统时间 已禁用
SeProfileSingleProcessPrivilege 配置文件单一进程 已禁用
SeIncreaseBasePriorityPrivilege 提高计划优先级 已禁用
SeCreatePagefilePrivilege 创建一个页面文件 已禁用
SeBackupPrivilege 备份文件和目录 已禁用
SeRestorePrivilege 还原文件和目录 已禁用
SeShutdownPrivilege 关闭系统 已禁用
SeDebugPrivilege 调试程序 已禁用
SeSystemEnvironmentPrivilege 修改固件环境值 已禁用
SeChangeNotifyPrivilege 绕过遍历检查 已启用
SeRemoteShutdownPrivilege 从远程系统强制关机 已禁用
SeUndockPrivilege 从扩展坞上取下计算机 已禁用
SeManageVolumePrivilege 执行卷维护任务 已禁用
SeImpersonatePrivilege 身份验证后模拟客户端 已启用
SeCreateGlobalPrivilege 创建全局对象 已启用
SeIncreaseWorkingSetPrivilege 增加进程工作集 已禁用
SeTimeZonePrivilege 更改时区 已禁用
SeCreateSymbolicLinkPrivilege 创建符号链接 已禁用
SeDelegateSessionUserImpersonatePrivilege 获取同一会话中另一个用户的模拟令牌 已禁用
为此需要给进程提权:
...
if (!EnablePrivilege(SE_MANAGE_VOLUME_NAME, TRUE))
{
std::cout << "EnablePrivilege failed, error " << GetLastError() << std::endl;
ret = 10;
break;
}
...
EnablePrivilege 封装了 OpenProcessToken、LookupPrivilegeValue、AdjustTokenPrivileges 几个调用,文章末尾有完整的代码实现,提权代码需要放在CreateFile之前。接着设置文件有效数据长度:
...
if (!::SetFileValidData(file_handle, pos.QuadPart))
{
std::cout << "SetFileValidData failed, error " << GetLastError() << std::endl;
ret = 10;
break;
}
...
这段代码需要插入到 SetEndOfFile 与 WriteFile 之间。以管理员身份启动控制台后有如下输出:
D:\test\sparsefile\Release>.\sparsefile movie.mp4 4
write file elapse 0 ms
create file with length 4294967295 success, path movie.mp4
尾部写入数据的耗时大大降低了。如果不以管理员身份启动,会报下面的错误:
PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
EnablePrivilege failed, error 1300
提权失败。如果未提权或 EnablePrivilege位于 CreateFile 之后,则报下面的错误:
D:\test\sparsefile\Release>.\sparsefile movie.mp4 4
SetFileValidData failed, error 1314
权限不足。
稀疏文件
如果无法获取管理员身份进行提权,则需要借助 NTFS 稀疏文件,在 WriteFile 之前加入下面的代码即可:
...
DWORD temp = 0;
if (!::DeviceIoControl(file_handle, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &temp, NULL))
{
std::cout << "DeviceIoControl failed, error " << GetLastError() << std::endl;
ret = 12;
break;
}
...
以普通用户身份运行:
PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
write file elapse 0 ms
create file with length 4294967295 success, path movie.mp4
耗时也大大降低了。稀疏文件依赖于文件系统的支持,可查询某个 Volume 是否支持稀疏文件:
CHAR szVolName[MAX_PATH], szFsName[MAX_PATH];
DWORD dwSN, dwFSFlag, dwMaxLen, nWritten;
BOOL bSuccess;
HANDLE hFile;
bSuccess = GetVolumeInformation(NULL,
szVolName,
MAX_PATH,
&dwSN,
&dwMaxLen,
&dwFSFlag,
szFsName,
MAX_PATH);
if (!bSuccess)
{
printf("errno:%d", GetLastError());
return -1;
}
printf("vol name:%s \t fs name:%s sn: %d.\n", szVolName, szFsName, dwSN);
if (dwFSFlag & FILE_SUPPORTS_SPARSE_FILES)
printf("support sparse file.\n");
else
printf("no support sparse file.\n");
或查询某个文件是否为稀疏文件:
// HANDLE hFile;
BY_HANDLE_FILE_INFORMATION stFileInfo;
GetFileInformationByHandle(hFile, &stFileInfo);
if(stFileInfo.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE)
printf("is sparse file.\n");
else
printf("no sparse file.\n");
也可以通过 fsutil 命令快速确认:
PS D:\test\sparsefile\Release> fsutil.exe sparse
---- 支持 SPARSE 命令 ----
queryflag 查询稀疏
queryrange 查询范围
setflag 设置稀疏
setrange 设置稀疏范围
PS D:\test\sparsefile\Release> fsutil.exe sparse queryflag .\movie.mp4
该文件被设为稀疏
PS D:\test\sparsefile\Release> fsutil.exe sparse queryrange .\movie.mp4
分配的范围[1]: 偏移: 0xffff0000 长度: 0x10000
fsutil 的 sparse 子命令查询文件是否稀疏 (queryflag)、以及有效数据的范围 (queryrange)。
这里 queryrange 返回的长度 0x10000 对应的空间占用是 64KB,查看文件属性:

也是 64KB。看起来即使写入 1 个字节,windows 也会分配一个 64K 的块并将其标记为修改。
带来的新问题
两种方案相比较,稀疏文件方式无需获取管理员权限,看起来似乎更“亲民”一些,不过也有它自己的问题:无法真正占据磁盘空间。考察上例中 4GB 文件的属性,占用空间仅 64KB,此时再生成一个 4GB 的文件,仍能成功。然而在实际写入过程中,注定有一个文件因磁盘空间不足而失败,甚至两个都失败。修改 demo 以演示这个场景:
...
long long file_size = pos.QuadPart;
pos.QuadPart = 0;
if (::SetFilePointerEx(file_handle, pos, NULL, FILE_BEGIN) == 0)
{
std::cout << "SetFilePointerEx failed, error " << GetLastError() << std::endl;
ret = 20;
break;
}
char buf[4096] = { 1 };
c_timer t;
DWORD bytes = 0;
for (int i = 0; i < file_size; i += 4096)
{
if (!::WriteFile(file_handle, buf, 4096, &bytes, NULL) || bytes != 4096)
{
std::cout << "WriteFile failed, error " << GetLastError() << ", written " << bytes << std::endl;
ret = 21;
break;
}
}
int elapse = t.get_interval();
std::cout << "write file elapse " << elapse << " ms" << std::endl;
...
写入整个文件,每次 4KB,执行此程序的同时,通过 dd 开启另外一个 4GB 文件的写入:
$ dd if=/dev/zero of=./movie1.mp4 bs=1M count=1024
dd: error writing './movie1.mp4': No space left on device
335+0 records in
334+0 records out
350224384 bytes (350 MB, 334 MiB) copied, 2.08525 s, 168 MB/s
因磁盘剩余空间不足 8GB,最终 dd & demo 都会报错退出:
PS D:\test\sparsefile\Release>.\sparsefile movie.mp4 4
WriteFile failed, error 112, written 0
write file elapse 16420 ms
这表明稀疏文件即使占据了空间,也会受磁盘实际剩余空间的影响,真是占了个寂寞!
换句话,稀疏文件使 SetEndOfFile 的磁盘空间占用能力"消失"了,不过后者的剩余空间检查能力还在。当剩余空间不足 4GB 时,demo 会直接在 SetEndOfFile 处失败:
PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
SetEndOfFile failed, error 112
所以上例不能采用 dd 预先写入 4GB 的方式进行测试,两个程序必需一先一后启动。
行文至此,正好验证一个说法:windows 稀疏文件会对零进行压缩,从而节省空间占用。如果这种说法为真,当写入的数据也是零,稀疏文件占用的空间也会大大小于文件大小,实际情况会怎样?修改一行代码进行验证:
...
char buf[4096] = { 0 };
...
将缓存区默认值从 1 改为 0,此时写入的数据全为零:
PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
write file elapse 11690 ms
create file with length 0 success, path movie.mp4
PS D:\test\sparsefile\Release> fsutil.exe sparse queryrange .\movie.mp4
分配的范围[1]: 偏移: 0x0 长度: 0x100000000
看起来没有任何空洞,查看文件属性:

确实如此。这个实验说明:稀疏文件并不是对零进行压缩,而是标记了哪些块有写入,从而记录有有效数据区间,和 linux ext4 稀疏文件的实现应该是异曲同工的。
方案对比
总结一下目前的两个方案的缺点
- SetFileValidData:需要提权
- 稀疏文件:无法占据磁盘空间
看起来都挺致命的,难道就没有十全十美的方案了吗?在一次偶然的测试中,发现稀疏文件也能占据空间,只要关闭稀疏文件尾部 1 字节的写入:
PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
create file with length 4294967295 success, path movie.mp4
PS D:\test\sparsefile\Release> fsutil.exe sparse queryrange .\movie.mp4
分配的范围[1]: 偏移: 0x0 长度: 0x100000000
看起来没有文件空洞,查看文件属性:

这样看起来能占据磁盘空间了?使用 dd 灌一些数据:
$ dd if=/dev/zero of=./movie.mp4 bs=1M count=1 seek=4095
1+0 records in
1+0 records out
1048576 bytes (1.0 MB, 1.0 MiB) copied, 0.0284056 s, 36.9 MB/s
在 4095M 位置写入 1M,结果露馅了:

看来还是占不了空间,之前显示空间占用 4G 是虚的靠不住的。
新的探索
看了下开源的种子下载神器 Transmission,当它采用 prealloc 模式时,在 windows 上底层使用的就是稀疏文件方式:
bool tr_sys_file_preallocate(tr_sys_file_t handle, uint64_t size, int flags, tr_error** error)
{
TR_ASSERT(handle != TR_BAD_SYS_FILE);
if ((flags & TR_SYS_FILE_PREALLOC_SPARSE) != 0)
{
DWORD tmp;
if (!DeviceIoControl(handle, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &tmp, nullptr))
{
set_system_error(error, GetLastError());
return false;
}
}
return tr_sys_file_truncate(handle, size, error);
}
不过在占用空间方面,它使用的是 SetFileInformationByHandle:
bool tr_sys_file_truncate(tr_sys_file_t handle, uint64_t size, tr_error** error)
{
TR_ASSERT(handle != TR_BAD_SYS_FILE);
FILE_END_OF_FILE_INFO info;
info.EndOfFile.QuadPart = size;
bool ret = SetFileInformationByHandle(handle, FileEndOfFileInfo, &info, sizeof(info));
if (!ret)
{
set_system_error(error, GetLastError());
}
return ret;
}
这个和 SetEndOfFile 有何区别,修改代码进行验证:
...
FILE_END_OF_FILE_INFO info;
info.EndOfFile.QuadPart = atoll(argv[2]) * 1024 * 1024 * 1024; // unit in GB
if (!SetFileInformationByHandle(file_handle, FileEndOfFileInfo, &info, sizeof(info)))
{
std::cout << "SetFileInformationByHandle failed, error " << GetLastError() << std::endl;
ret = 5;
break;
}
DWORD temp = 0;
if (!::DeviceIoControl(file_handle, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &temp, NULL))
{
std::cout << "DeviceIoControl failed, error " << GetLastError() << std::endl;
ret = 6;
break;
}
...
这段代码替换整个 SetEndOfFile 及之后的代码。再次运行 demo:
PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
create file with length 4294967296 success, path movie.mp4
PS D:\test\sparsefile\Release> fsutil.exe sparse queryflag .\movie.mp4
该文件被设为稀疏
PS D:\test\sparsefile\Release> fsutil.exe sparse queryrange .\movie.mp4
分配的范围[1]: 偏移: 0x0 长度: 0x100000000
看起来没空洞了,但用 dd 测下尾部写入 1MB 后,结果就和之前一样了:

看起来没什么改善。
结语
本文尝试说明 SetEndOfFile 占用空间存在的一个性能问题,并讲解了两种解决方案,分别是SetFileValidData 和稀疏文件,以及它们的局限性;随后尝试破解稀疏文件局限性但失败了;最后验证了 Transmission 开源库基于SetFileInformationByHandle的方案也不可行。
附录中罗列了一些如何高效的拷贝稀疏文件的方法,关键在于遍历稀疏文件中的有效数据区间,感兴趣的读者可以参考附录 8。
代码
本期测试代码上传到了 github:https://github.com/goodpaperman/sparsefile
各种接口的调用尽量做成了选项,方便组合进行测试,参数不足时会展示 Usage:
PS D:\test\sparsefile\Release> .\sparsefile.exe movie.mp4
Usage: sparsefile file length(in GB) [set-file-end-of-file-info] [set-file-valid-data] [sparse-file] [write-file-mode 0|1|2] [fill-char]
文件路径和大小是必选项,5 个可选项分别控制:
set-file-end-of-file-info:使用SetFileInformationByHandle方式,默认为 0 使用SetEndOfFile方式set-file-valid-data:使用SetFileValidData方式,此时需要以管理员身份启动控制台,默认为 0sparse-file:使用稀疏文件,默认为 1write-file-mode:写文件模式,默认为 0
- 0:不写
- 1:写文件末尾 1 字节
- 2:间隔 1M 写 4KB 数据
fill-char:写文件时填充的字符,默认为空 ('')
通过设置参数,可以验证本文的各种方案:
.\sparsefile movie.mp4 4,SetEndOfFile+ 稀疏文件 的方式.\sparsefile movie.mp4 4 0 0 1 1 ' ',在末尾写入数据耗时小,且空间占用失败.\sparsefile movie.mp4 4 0 0 1 1,末尾写入零时对空间占用无影响.\sparsefile movie.mp4 4 0 0 1 2,写入全零块时文件占用空间并未压缩
.\sparsefile movie.mp4 4 0 0 0,仅SetEndOfFile的方式.\sparsefile movie.mp4 4 0 0 0 1 ' ',在末尾写入数据耗时大.\sparsefile movie.mp4 4 0 0 0 1,在末尾写入零耗时小
.\sparsefile movie.mp4 4 1,SetFileInformationByHandle+ 稀疏文件 的方式.\sparsefile movie.mp4 4 1 0 1 1 ' ',在末尾写入数据耗时小,且空间占用失败
.\sparsefile movie.mp4 4 1 0 0,仅SetFileInformationByHandle的方式.\sparsefile movie.mp4 4 1 0 0 1 ' ',在末尾写入数据耗时大
.\sparsefile movie.mp4 4 0 1 0,SetEndOfFile+SetFileValidData+ 提权 的方式.\sparsefile movie.mp4 4 0 1 0 1 ' ',在末尾写入数据耗时小,且空间占用成功
可执行文件已经编译为了静态链接并上传到 git,理论上不需要装 VS 也能运行,配置是 Release x86 & x64 两个平台,方便没有编译环境的同学直接上手。
参考
[1]. 什么是稀疏文件(Sparse File)
[2]. 建希文件
[3]. windows 高精度计时器
[4]. Windows环境下提升进程的权限
[5]. SetFileValidData function
[6]. Windows 下的文件预分配与 SetFileValidData 函数
[8]. 稀疏文件简介
windows 稀疏文件 (sparse file) 的一个实用场景——解决 SetEndOfFile 占据磁盘空间引入的性能问题的更多相关文章
- 第17章 内存映射文件(3)_稀疏文件(Sparse File)
17.8 稀疏调拨的内存映射文件 17.8.1 稀疏文件简介 (1)稀疏文件(Sparse File):指的是文件中出现大量的0数据,这些数据对我们用处不大,但是却一样的占用空间.NTFS文件系统对此 ...
- 用windows自带的fsutil来创建1G稀疏文件(sparse file)
fsutils file createnew a.dat 1073741824 fsutil sparse setflag a.dat fsutil sparse setrange a.dat 0 ...
- mongo 固定集合,大文件存储,简单优化 + 三招解决MongoDB的磁盘IO问题
1.固定集合 > db.createCollection(, max:});//固定集合 必须 显式创建. 设置capped为true, 集合总大小xxx字节, [集合中json个数max] { ...
- 安装Windows SDK7.1时发生的一个错误(附解决办法)
A problem occurred while installing selected Windows SDK components. Installation of the "Micro ...
- linux查找系统中占用磁盘空间最大的文件
Q:下午有一客户磁盘空间占用很大,使用df查看磁盘剩余空间很小了,客户想知道是哪些文件占满了文件. Q1:在Linux下如何查看系统占用磁盘空间最大的文件? Q2:在Linux下如何让文件夹下的文件让 ...
- SQL Server ->> Sparse File(稀疏文件)
Sparse File(稀疏文件)不是SQL Server的特性.它属于Windows的NTFS文件系统的一个特性.如果某个大文件中的数据包含着大量“0数据”(这个应该从二进制上看),这样的文件就可以 ...
- 背水一战 Windows 10 (98) - 关联启动: 使用外部程序打开一个文件, 使用外部程序打开一个 Uri
[源码下载] 背水一战 Windows 10 (98) - 关联启动: 使用外部程序打开一个文件, 使用外部程序打开一个 Uri 作者:webabcd 介绍背水一战 Windows 10 之 关联启动 ...
- 无法打开包括文件:“windows.h”: No such file or directory
VS2012 出现如下错误: 无法打开包括文件:"windows.h": No such file or directory 解决办法,将 C:\Program Files ...
- 【解决】 无法打开包括文件:“windows.h”: No such file or directory
vs编译时错误: 无法打开包括文件:“windows.h”: No such file or directory 出现这种错误什么都不用配置(环境变量),最好办法是将VS安装在C盘,让开发工具自动包含 ...
- Java基础面试操作题: File IO 文件过滤器FileFilter 练习 把一个文件夹下的.java文件复制到另一个文件夹下的.txt文件
package com.swift; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File ...
随机推荐
- 部署kubernetes-dashboard改成http免密登录
原始链接地址 https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml 修改S ...
- vue create与vue init的区别
1.vue ui 图形化界面 2. vue create 是vue-cli3.x的初始化方式,目前模板是固定的,模板选项可自由配置,创建出来的是vue-cli3的项目,与cue-cli2项目结构不同, ...
- SSM或Spring Boot开发中,mapper包中的mapper.xml没有编译到targger中的解决办法
SSM或Spring Boot开发中,mapper包中的mapper.xml没有编译到targger中的解决办法 在pom文件中加入如下配置: <resources> <!-- 编译 ...
- 海量用户IM聊天室的架构设计与实践
本文由网易云信资深服务端开发工程师曹佳俊分享,本文收录时有内容修订和重新排版. 1.引言 聊天室是一类非常重要的 IM 业务形态,不同于单聊和群聊,聊天室是一种大规模的实时消息分发系统.聊天室有多种技 ...
- spark (五) RDD的创建 & 分区
目录 1. RDD的创建方式 1.1 从内存创建RDD 1.2 从外部存储(文件)创建RDD 1.3 从其他的RDD创建 1.4 直接 new RDD 2. 分区(partition) 2.1 mak ...
- ICML 2022 | Graph Machine Learning 论文分享
ICML 2022 | Graph Machine Learning 论文分享 国际机器学习大会(International Conference on Machine Learning,简称ICML ...
- JMeter JDBC 请求实战宝典
<JMeter JDBC 请求实战宝典> 宝子们,今天咱就来唠唠 JMeter 里超厉害的 JDBC 请求,这玩意儿就像是数据库世界的神奇魔杖,能帮咱把数据库里的各种秘密(数据)都挖出来, ...
- Ubuntu 添加多用户和Samba
USERNAME="$1" SMBFILE="/etc/samba/smb.conf" if [ $# != 1 ] then echo "使用方: ...
- Asp.net Core Kestrel 免费实现https
0.概述 先了解下https是个啥: https://www.bilibili.com/video/BV1j7411H7vV so!只要给我们的web服务器配置一个证书就行了,证书可以买,也可以用免费 ...
- Pinely Round 4 (Div. 1 + Div. 2)
题目链接:Pinely Round 4 (Div. 1 + Div. 2) 总结:被B卡了一年. A. Maximize the Last Element tag:模拟 Description:给定一 ...