【山大智云】SeafileServer源码分析之Seafile对象存储

2021SC@SDUSC

Seafile对象存储

一些Seafile对象需要持久化至硬盘。它们的存储方式有两种,一种是非关系型存储,另一种是关系型存储。前者要求对象必须含有id。

请添加图片描述

非关系型存储

非关系型存储主要被Seafile文件管理系统所使用。因为Seafile文件管理系统是分布式的,而非关系型存储能为分布式存储提供良好的基础。

相关代码的结构如下:

与块系统类似,并且有多种实现方式。

使用这种方式存储的的Seafile对象包括:commit、seafile、seafdir、seafdirent等。

  • 对象的逻辑位置

    只考虑Seafile文件管理系统中的Seafile对象,我们可以通过以下信息定位一个Seafile对象:

    1. repo_id

      仓库存储id。与块的store_id相同。

    2. version

      仓库的seafile版本。

    3. obj_id

      对象id。是一个UUID。

  • 对象序列化

    要将对象以非关系的形式存储到硬盘,则需要先进行序列化,将其转化为字节流。每个使用非关系存储的对象必须实现序列化与反序列化。实现方式有以下两种:

    • 结构体字节对齐

      这是seafile版本0中的方法。使用如下标识,告诉编译器将结构体字节对齐:

      __attribute__ ((packed)) update_pack_t;
      

      然后用虚指针和sizeof运算符可以直接获取对象字节流。

    • json字符串

      这是seafile版本1中的方法。使用jansson库生成对象对应的json对象,然后转化为json字符串。

  • 对象存储操作

    obj-backend:规定了对象进行非关系型存储所需要的抽象操作。

    操作说明
    根据仓库id、版本、对象id,获取对象的字节流。
    根据仓库id、版本、对象id,写入对象的字节流。支持备份与同步。
    存在根据仓库id、版本、对象id,判断是否存在。
    删除根据仓库id、版本、对象id,删除对象字节流。
    遍历仓库根据仓库id、版本,遍历仓库中的所有对象字节流。
    复制给定对象id,将其从某个版本的某个仓库复制到另一个仓库。
    清空移除某个仓库内的所有对象字节流。

对象存储器

obj-store:对象存储器是对对象后台的进一步封装。

目前基于的是文件系统实现的后台。可由SeafileSession和对象类型生成对象存储器。

基于文件系统实现

项目中实现了一个基于文件系统的非关系型存储系统。

  • 对象的物理位置

    对象的物理存储位置如下:

    [obj_dir] / [repo_id] / [obj_id[:2]] / [obj_id[2:]]
    

    其中obj_dir由对象存储器给定,格式如下:

    [seaf_dir] / storage / [obj_type]
    
    1. seaf_dir:即配置文件中的seafile_dir
    2. obj_type:Seafile对象类型。(例如fs表示文件系统相关对象)
  • 对象存储操作的实现

    主要利用的是文件系统调用,略。

基于riak实现

Riak是一个分布式NoSQL数据存储系统,使用键值存储。

  • 对象的物理位置

    [host]:[port] / [bucket] / [obj_id]
    

    通过主机、端口、桶名和对象id定位对象。

  • 对象存储操作的实现

    使用Riak的API实现,略。

关系型存储

部分Seafile对象采用的是关系型存储方式。相关代码的结构如下:

请添加图片描述

支持的数据库有:sqlite、mysql。编译时只能选择一个,默认为sqlite。(源码中还定义了pgsql,但未实现)

使用这种方式存储的的Seafile对象包括:branch、group等。

关系化对象

  • 对象的数据库和表

    1. 数据库

      依照对象从属的系统,规定对象被存储的数据库。整个项目有两个数据库:seafile(用于seafile系统)、ccnet(用于ccnet系统)。

    2. 依照对象类型,创建表。如SeafBranch对象对应了Branch表。

  • 对象关系映射

    读写对象本质上就是读写数据库。相关对象中借助数据库操作的抽象封装,对自身的各个属性实现了双向数据流动。

    项目中整个部分是面向过程的,没有统一的ORM。结构体中通过一个私有指针指向数据库上下文(SeafDB),每次读写都对应了一个SQL和一个数据库操作。

数据库操作

  • 上下文

    • SeafDB:Seafile数据库上下文。
    • SeafRow:Seafile数据库行上下文。
    • SeafTrans:Seafile数据库事务上下文。
    • CcnetDB、CcnetRow、CcnetTrans:对应Ccnet数据库。
  • 抽象操作

    操作说明
    获取连接(根据配置)连接数据库并获取SeafDB。
    释放连接断开连接并释放SeafDB。
    非预编译执行SQL直接执行SQL。
    执行SQL输入SQL、参数列表,然后预编译并执行。
    遍历行输入SQL、参数列表,然后预编译并执行;随后遍历各个行,传递给回调函数
    获取列数获取一行中的列的数量
    获取字符串属性给定行,列的索引,获取字符串属性
    获取整型属性给定行,列的索引,获取整型属性
    获取长整型属性给定行,列的索引,获取长整型属性
  • 操作封装

    seaf-db:对数据库抽象操作进行进一步的封装,以适配本项目的需求。

    seaf-utils:提供了一些实用接口,包括根据会话配置创建数据库连接(SeafDB)。

基于Mysql实现

seaf-db.c中通过Mysql API实现了数据库抽象操作。

关于常规数据库操作的实现略,因为这些操作大都是对API进行的一定程度的封装。Mysql实现中主要关注的是多连接管理。因为当一个连建长时间无操作时,有可能断线,所以需要去反复发出ping信号,防止连接断开。项目中的实现方式是通过线程池进行线程调度。每个连接占有一个线程,这些线程通过一个互斥锁来争夺ping的资源。默认每30s进行一次ping。Mysql实现中不需要考虑数据库内的并发,因为Mysql已经实现了锁机制。

基于Sqlite实现

seaf-db.c中通过sqlite API实现了数据库抽象操作。

关于常规数据库操作的实现略,主要关注数据库并发处理。sqlite提供了一个解锁通知的接口sqlite3_unlock_notify,每次sqlite数据库解锁时,会调用用户提供的回调函数。因此并发机制主要靠用户实现。

项目中主要通过wait_for_unlock_notify函数进行互斥并发,每次执行SQL前将调用该方法等待sqlite解锁:

typedef struct UnlockNotification { // 通知上下文
        int fired; // 是否已经通知
        pthread_cond_t cond; // 条件变量
        pthread_mutex_t mutex; // 条件变量的互斥锁
} UnlockNotification;

static void
unlock_notify_cb(void **ap_arg, int n_arg) // 回调函数
{
    int i;

    for (i = 0; i < n_arg; i++) { // 遍历用户参数
        UnlockNotification *p = (UnlockNotification *)ap_arg[i];
        pthread_mutex_lock (&p->mutex);
        p->fired = 1; // 表示已经通知
        pthread_cond_signal (&p->cond); // 条件变量发送信号
        pthread_mutex_unlock (&p->mutex);
    }
}

static int
wait_for_unlock_notify(sqlite3 *db)
{
    UnlockNotification un;
    un.fired = 0;
    pthread_mutex_init (&un.mutex, NULL);
    pthread_cond_init (&un.cond, NULL);

    int rc = sqlite3_unlock_notify(db, unlock_notify_cb, (void *)&un); // 并发通知

    if (rc == SQLITE_OK) {
        pthread_mutex_lock(&un.mutex);
        if (!un.fired) // 若已经通知,则跳过等待
            pthread_cond_wait (&un.cond, &un.mutex); // 否则等待条件变量发送信号
        pthread_mutex_unlock(&un.mutex);
    }

    pthread_cond_destroy (&un.cond);
    pthread_mutex_destroy (&un.mutex);

    return rc;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值