华为云TaurusDB性能挑战赛note

  • 不可重复读和幻读的区别
    如果使用锁机制来实现这两种隔离级别,在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复 读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会 发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。

C语言文件操作

  • fwrite()
    函数原型:size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
    ptr – 这是指向要被写入的元素数组的指针。
    size – 这是要被写入的每个元素的大小,以字节为单位。
    nmemb – 这是元素的个数,每个元素的大小为 size 字节。
    stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。

  • fseek()
    int fseek(FILE *stream, long offset, int fromwhere);
    函数描述:函数设置文件指针stream的位置。如果执行成功,stream将指向以fromwhere(偏移起始位置:文件头0(SEEK_SET),当前位置1(SEEK_CUR),文件尾2(SEEK_END))为基准,偏移offset(指针偏移量)个字节的位置。如果执行失败(比如offset超过文件自身大小),则不改变stream指向的位置。
    成功返回0,失败返回非0。

  • ftell()
    函数原型:long ftell(FILE *stream);
    用于得到文件位置指针当前位置相对于文件首的偏移字节数

  • stat
    函数原型:int stat(const char *path, struct stat *buf)
    获取文件的状态

  • DIR

struct __dirstream   
   {   
    void *__fd;    
    char *__data;    
    int __entry_data;    
    char *__ptr;    
    int __entry_ptr;    
    size_t __allocation;    
    size_t __size;    
    __libc_lock_define (, __lock)    
   };   
typedef struct __dirstream DIR;
  • opendir()
    DIR* opendir (const char * path ); (获取path子目录下的所由文件和目录的列表,如果path是个文件则返回值为NULL)
  • dirent
struct dirent   
{   
  long d_ino; /* inode number 索引节点号 */  
     
    off_t d_off; /* offset to this dirent 在目录文件中的偏移 */  
     
    unsigned short d_reclen; /* length of this d_name 文件名长 */  
     
    unsigned char d_type; /* the type of d_name 文件类型 */  
     
    char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最长255字符 */  
}
  • readdir()
    头文件:#include<dirent.h>
    函数原型:struct dirent* readdir(DIR* dir_handle);
#include <iostream>
#include <stdio.h>
#include <dirent.h>

using namespace std;
unlink()函数功能即为删除文件。执行unlink()函数会删除所给参数指定的文件。
int main() {
    DIR *dir;
    struct dirent *ptr;
    int i;
    dir = opendir("test");

    while ((ptr = readdir(dir)) != NULL) {
        cout << ptr->d_name << endl;
    }
    closedir(dir);miaoshu
    return 0;
}

目录结构
在这里插入图片描述
输出结果:

.
..
dir1
test2
test1
  • unlink()
    unlink()函数功能即为删除文件。执行unlink()函数会删除所给参数指定的文件。
    头文件:#include<unistd.h>
    函数原型:int unlink(const char *pathname);
  • closedir(DIR *dir) 关闭目录流
  • strncpy()
    函数原型:char *strncpy(char *dest, const char *src, size_t n)
    函数描述:C 库函数 char *strncpy(char *dest, const char *src, size_t n) 把 src 所指向的字符串复制到 dest,最多复制 n 个字符。当 src 的长度小于 n 时,dest 的剩余部分将用空字节填充。
    dest – 指向用于存储复制内容的目标数组。
    src – 要复制的字符串。
    n – 要从源中复制的字符数。
    返回值为最终复制的字符串
  • snpritnf()
    函数原型:int snprintf(char *str, size_t size, const char *format, …);
    函数描述:最多从源字符串format中拷贝size字节的内容(含字符串结尾标志’\0’)到目标字符串
    example:
int main() {
    char str[256];
    snprintf(str, sizeof(str), "%010d%s", 10, ".data");
    cout << str << endl;
    return 0;
}

在DataMgr::Append()中

  1. pos1 = cur_file_no_ << 32
  2. pos2 = pos1 + cur_file_size_

在KVStore::Set将key和DataMgr::Append()中返回的pos2 Append到meta_中,最后返回pos2 >> 32(此时pos == cur_file_no_)

MetaMgr::Get()返回pos2
DataMgr::Get()通过pos来获取key和val的位置,,其中通过no = (pos2 >> 32) & 0xffffffff来获取key和val所在文件的编号,通过offset = (pos & 0xffffffff)来获取key和val在该文件中的偏移量

硬盘知识
  • SSD写放大
    当SSD硬盘写满时,需要修改某一个部分只能通过修改一个无效的块来存储被更改的内容,在修改的过程中需要将块读取到缓存中,修改缓存中的内容,擦除这个块原本的数据,将缓存中的内容写入这个块中。

  • 扇区 块
    扇区是硬盘最小的存储单位,每个扇区存储512 Byte。但操作系统不会每次只读取一个扇区,这样读取的效率太低。在操作系统中最小存取单位为块,块由连续的多个扇区组成,一般为4KB = 8个扇区存储的数据。

  • SSD和机械硬盘的物理区别

文件系统
虚拟文件系统
  • 主要模块
  1. 超级块
    保存文件系统中的所有元数据

  2. 目录项模块
    目录项的块中存储这目录中所有文件的文件名和inode等信息
    目录文件就是一系列目录项(dirent)的列表,每个目录项由两个部分组成:文件名和inode号码

  3. inode模块
    文件的唯一标识,通过inode可以方便地找出文件在磁盘中的位置
    inode实际上为一个结构体,其中存储的信息有:
    (1) Size 文件的字节数
    (2) Uid 文件拥有者的User ID
    (3) Gid 文件的Group ID
    (4) Access 文件的读、写、执行权限
    (5) 文件的时间戳,共有三个:
    (6) Change 指inode上一次变动的时间
    (7) Modify 指文件内容上一次变动的时间
    (8) Access 指文件上一次打开的时间
    (9) Links 链接数,即有多少文件名指向这个inode
    (10) Inode 文件数据block的位置
    (11) Blocks 块数
    (12) IO Blocks 块大小
    (13) Device 设备号码

  4. 打开文件列表模块
    包含内核中所有打开的文件,打开文件列表模块包含了一个列表,每个列表表项是一个结构struct file,结构体中的信息是文件的各种状态参数。

  5. file_operations模块
    维护一个结构体,是一系列函数指针的集合,包括open,read,write,mmap等系统调用函数。

  6. address_space模块
    表示是一个文件在页缓存中已经缓存的物理页

  • 关于多线程访问static局部变量,不同线程访问的是同一个变量
#include <iostream>
#include <stdio.h>
#include <dirent.h>
#include <unistd.h>
#include <thread>

using namespace std;

int static_test() {
    static int times = -1;
    times++;
    cout << times << endl;
}

int main() {
    thread t1(static_test);
    thread t2(static_test);
    thread t3(static_test);
    t1.join();
    t2.join();
    t3.join();
    return 0;
}

运行结果:

0
1
2

关于硬盘性能评价标准:
磁盘的 IOPS,也就是在一秒内,磁盘进行多少次 I/O 读写。
磁盘的吞吐量,也就是每秒磁盘 I/O 的流量,即磁盘写入加上读出的数据的大小。

获得内存信息

void get_meminfo()
{
    FILE *fd;
    char buff[256];
    double mem_used_rate;
    char name01[32];
    unsigned long mem_total;
    char name02[32];

    char name11[32];
    unsigned long mem_free;
    char name12[32];

    fd = fopen ("/proc/meminfo", "r");

    fgets (buff, sizeof(buff), fd);

    sscanf (buff, "%s %lu %s\n", name01, &mem_total, name02);

    fgets (buff, sizeof(buff), fd);

    sscanf (buff, "%s %lu %s\n", name11, &mem_free, name12);

    printf("mem_free : %lu, mem_total : %lu\n", mem_free, mem_total);

    fclose(fd);
}

获得硬盘信息

#include <sys/statvfs.h>
void get_diskinfo()
{
    const int GB = 1024 * 1024 *1024;
    int state;
    struct statvfs vfs;
    fsblkcnt_t block_size = 0;
    fsblkcnt_t block_count = 0;
    fsblkcnt_t total_size;
    fsblkcnt_t free_size;
    fsblkcnt_t used_size;
    fsblkcnt_t avail_size;
    /*读取根目录,就是整个系统的大小*/
    state = statvfs("../",&vfs);   /*设置路径,查看不同文件目录的大小*/
    if(state < 0){
        printf("read statvfs error!!!\n");
    }

    block_size = vfs.f_bsize; /*获取一个block的大小*/
    /*获取总容量*/
    total_size = vfs.f_blocks * block_size;
    /*获取可用容量*/
    free_size = vfs.f_bfree * block_size;
    /*获取使用容量*/
    used_size = (vfs.f_blocks - vfs.f_bavail) * block_size;
    /*获取有效容量*/
    avail_size = vfs.f_bavail * block_size;

    printf(" total_size   = %0.2lf  GB\n",(double)total_size / (GB) );
    printf(" free_size   = %0.2lf  GB\n",(double)free_size / (GB) );
    printf(" used_size  = %0.2lf  GB\n",(double)used_size / (GB) );
    printf(" avail_size  = %0.2lf  GB\n",(double)avail_size / (GB) );
}
关于data的目录

当将data写在父目录时会出现硬盘空间不够的情况

性能测试阶段崩溃原因

获取评测机中的内存信息,初始时的free memory为119890372,最后一次初始化时可用的free memory为115706536

>>> memory_used = 119890372-115706536
>>> memory_used / 1024 / 1024 
3.9900169372558594

评测机上限制的内存为4G,所以程序进行到性能测试时,全部init完戛然而止的原因应该时超过内存限制被cgroup kill掉了

map与unordered_map

区别
STL中,map 对应的数据结构是 红黑树。红黑树是一种近似于平衡的二叉查找树,里面的数据是有序的。在红黑树上做查找操作的时间复杂度为 O(logN)。而 unordered_map 对应 哈希表,哈希表的特点就是查找效率高,时间复杂度为常数级别 O(1), 而额外空间复杂度则要高出许多。所以对于需要高效率查询的情况,使用 unordered_map 容器。而如果对内存大小比较敏感或者数据存储要求有序的话,则可以用 map 容器。.

  • unordered_map的使用方式
    key必须为key元素提供Hash函数和重载==符号
    example
#include <iostream>
#include <stdio.h>
#include <dirent.h>
#include <unistd.h>
#include <thread>
#include <sys/statfs.h>
#include <cstring>
#include <map>
#include <unordered_map>

using namespace std;

typedef pair<string, string> Name;

namespace std {
    template<>
    class hash<Name> {
    public:
        size_t operator()(const Name &name) const {
            return hash<string>()(name.first) ^ hash<string>()(name.second);
        }
    };
}

class Item {
public:
    Item(int i) : i_(i) {}

    bool operator()(const Item &other) const {
        return i_ == other.i_;
    }

    int GetI() const {
        return i_;
    }

    bool operator==(const Item &other) const {	// 必须是const函数
        return i_ == other.i_;
    }

    bool operator<(const Item &other) const {
        return i_ < other.i_;
    }

private:
    int i_;
};

struct hash_func {	// 提供的Hash function
    int operator()(const Item &i) const {	// 必须是const函数
        return i.GetI() & 0x7fffff;
    }
};

void get_meminfo()
{
    FILE *fd;
    char buff[256];
    double mem_used_rate;
    char name01[32];
    unsigned long mem_total;
    char name02[32];

    char name11[32];
    unsigned long mem_free;
    char name12[32];

    fd = fopen ("/proc/meminfo", "r");

    fgets (buff, sizeof(buff), fd);

    sscanf (buff, "%s %lu %s\n", name01, &mem_total, name02);

    fgets (buff, sizeof(buff), fd);

    sscanf (buff, "%s %lu %s\n", name11, &mem_free, name12);

    printf("mem_free : %lu, mem_total : %lu\n", mem_free, mem_total);

    fclose(fd);
}

int main() {
    get_meminfo();
    unordered_map<Item, int, hash_func> m;
    for (int i = 0; i < 30000000; i++) {
        m[Item(i)] = i;
    }
    get_meminfo();
    m.clear();
    get_meminfo();
    for (int i = 0; i < 30000000; i++) {
        m[Item(i)] = i;
    }
    get_meminfo();
    return 0;
}

经测试,运行上面的程序,map使用1404096KB内存,unordered_map使用1179560KB内存,而且unordered_map版本要比map的运行速度快一档。

  • 关于clear()
    unordered_map和map使用clear()后并不会将立即将使用的内存释放掉
    下面是关于clear()的测试结果
mem_free : 1288340, mem_total : 8077164
存3000w条数据
mem_free : 135512, mem_total : 8077164
clear()
mem_free : 135676, mem_total : 8077164
存3000w条数据
mem_free : 138140, mem_total : 8077164

通过测试结果可以发现,第一次存3000w条数据大概用了1.1G的内存,在使用了clear()后,内存占用无明显变化,第二次存3000w条数据时并不会像第一次一样消耗大量内存,内存占用无明显变化,这是因为第二次存数据时用到了回收到内存池中的内存。

Page Cache

Page Cache是指read(),write()和文件之间的那一层缓存
Linux的IO栈分为三层:

  • 文件系统层:拷贝用户态数据到文件系统的Cache中
  • 块层:管理块设备的IO队列,对IO请求进行合并、排序
  • 设备层:通过DMA与内存直接交互
    read()流程:
    传统的Buffered IO使用read(2)读取文件的过程什么样的?假设要去读一个冷文件(Cache中不存在),open(2)打开文件内核后建立了一系列的数据结构,接下来调用read(2),到达文件系统这一层,发现Page Cache中不存在该位置的磁盘映射,然后创建相应的Page Cache并和相关的扇区关联。然后请求继续到达块设备层,在IO队列里排队,接受一系列的调度后到达设备驱动层,此时一般使用DMA方式读取相应的磁盘扇区到Cache中,然后read(2)拷贝数据到用户提供的用户态buffer中去(read(2)的参数指出的)。
  • read()与mmap的区别:
    整个过程有几次拷贝?从磁盘到Page Cache算第一次的话,从Page Cache到用户态buffer就是第二次了。而mmap(2)做了什么?mmap(2)直接把Page Cache映射到了用户态的地址空间里了,所以mmap(2)的方式读文件是没有第二次拷贝过程的。那Direct IO做了什么?这个机制更狠,直接让用户态和块IO层对接,直接放弃Page Cache,从磁盘直接和用户态拷贝数据。好处是什么?写操作直接映射进程的buffer到磁盘扇区,以DMA的方式传输数据,减少了原本需要到Page Cache层的一次拷贝,提升了写的效率。对于读而言,第一次肯定也是快于传统的方式的,但是之后的读就不如传统方式了(当然也可以在用户态自己做Cache,有些商用数据库就是这么做的)。
本地测试结果

在本机上测试顺序写和顺序读的时间差不多,顺序读比随机读快3倍
下面是线程对100w条数据的

write time: 30.307s
seqread time:  29.101s
randomread time: 100.572s
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值