Ceph Filesystem 能保证强一致性吗?

CephFs 简介

 Ceph Filesystem 是基于librados给应用提供的一种兼容posix语义的文件接口。它提供了Multi MetaData Server架构,这样能够大幅的提高元数据存取Performence,而且有效规避了单点瓶颈的问题。现在支持FUSE,Kernel 和 Libcephfs 三种使用方式。关于它的详细介绍社区都有成熟的文档,我就不过多介绍了,进入今天的主题。

CephFS 一致性分析

 个人觉得CephFs的优势一是不俗的元数据存取能力,二就是它的强一致特性。你可能想问,既然强一致性是它的一个特性,那标题的的答案不就是肯定的么,但其实答案是片面的,也就是在某些场景是强一致的,有时候不是!!!

强一致场景(元数据)

 我们先来说说强一致的场景,假如有客户端Jack和客户端Rose。我们有时候需要Jack客户端创建一个文件,然后在Rose客户端立即要使用该文件。这种需求CephFs可以保证绝对强一致性,因为它不缓存创建,删除等元数据操作,任何的创建和删除(文件或者目录)都会直接发到MDS端。MDS端收到创建请求之后会顺势revoke掉Rose客户端对该文件父目录的共享权限,换句话说就是标志了Rose客户端和MDS的元数据信息不一致。这样当Rose客户端去读取该文件或者readdir的时候,就会直接去MDS获取,而不是读本地的缓存。

"非强一致场景(数据)"

 说完了元数据的强一致,那么我们说说CephFilesystem对于数据的强一致特性。这个时候可能就又人要怀疑了,谁说ceph不能保证数据的强一致?不应该是这样的吗?


Ceph Filesystem 能保证强一致性吗?_第1张图片
fuse-client

 有两个fuse客户端Jack 和 Rose。当Rose要读数据,会通知Jack把你的dirty数据刷下来,我要读取最新的数据。对应的,当Jack要写数据数据,那么要通知Rose你把你的缓存失效吧,我要写新数据了。基于上面的逻辑好像确实能够保证的数据的一致性!
 但是他们两个进程是独立的,可能在两个完全不知道对方服务器上,为了和对方通信那就只能通过第三方MetaData Server。看起来很美好的事情,往往都有坑,因为这两个不可能每次读写数据都跟MDS通信,这样数据和元数据耦合性太高了,效率太低了,再说了当客户端多了之后MDS压力会非常大。所以就会有问题了,如果Rose之前缓存过数据,那么它的下一次读取就非常可能读到旧的数据。
 Rose发现Jack总是背着自己偷偷做一些事情(写数据),不高兴了,就去找Jack理论了,两人达成统一意见。以后Jack每次写之前都给MDS说一下,Rose每次读之前也都去问问MDS,这样就不会误会了。但即便是这样Rose还是会发现偶尔会读到旧数据,又在怀疑Jack在搞鬼了。这时候MDS站出来为Jack说了句公道话,它说Jack确实给我说了,而且我也给你说了,读到旧数据这事真不能懒Jack,赖你。后来Rose不服气又找来OSD来理论,最后发现确实是自己的错,就没有给OSD发送正确的读取offset和length。Jack确实把数据写在OSD上,但是Rose她自己没有去OSD去读最新的数据,二是直接从fuse page cache里面命中直接返回了。这回Rose决定从自己身上找问题,反省了好几天终于发现原来是自己的配置有问题,自己的“fuse_use_invalidate_cb”选项没有打开,导致MDS通知自己清理缓存的时候没有执行下面的函数从而没能清除掉fuse page cache。

Option("fuse_use_invalidate_cb", Option::TYPE_BOOL, Option::LEVEL_ADVANCED)
.set_default(true)
.set_description("use fuse 2.8+ invalidate callback to keep page cache consistent")

static void ino_invalidate_cb(void *handle, vinodeno_t vino, int64_t off,
                  int64_t len)
{
#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 8)
  CephFuse::Handle *cfuse = (CephFuse::Handle *)handle;
  fuse_ino_t fino = cfuse->make_fake_ino(vino.ino, vino.snapid);
  fuse_lowlevel_notify_inval_inode(cfuse->ch, fino, off, len);
#endif
}

 自从Rose打开了这个选项之后,跟Jack相处的很和谐,再也没有误会产生了。但是好景不长,随着大版本升级到Luminous, Rose发现老问题又来了,经历了上次的闹剧之后,这次她先从自身找问题,确定打开了"fuse_use_invalidate_cb" 选项之后又找MDS理论去了。
 MDS 看着阵势,不把这事解决了今天没发给Rose一个交代,深深吸了一口烟之后带上眼睛开始找问题到底出在哪里了。仔细地翻阅着所有的Pull request,终于发现了一个可疑patch:https://github.com/ukernel/ceph/commit/7db1563416b5559310dbbc834795b83a4ccdaab4
 经过分析发现这个commit是为了所有客户端在getattr的时候拍长队,所以做了优化,这样就导致Jack和Rose通信不是每次都有效了,这下可把Rose急坏了。不过姜还是老的辣,MDS说了,别慌,我给你出个主意,你开启一下"fuse_disable_pagecache"选项就可以了。

Option("fuse_disable_pagecache", Option::TYPE_BOOL, Option::LEVEL_ADVANCED)
.set_default(false)
.set_description("disable page caching in the kernel for this FUSE mount"),

 从此两人和谐共处...

 上面Jack 和 Rose的场景可以用下面的两个小程序来验证:

write_file_step.c
#include
#include
#include
#include
#include
#include
#include

int fd = 0;
char buf[131073]={0};
char read_buf[131073]={0};
int step_length = 0;

void fill_buf(char *buf, char chars, int len)
{
    int i = 0;
    for (i=0; i < len; i++)
        buf[i] = chars;
}

int write_file(char *a, int len, int fd, unsigned long off)
{
    int r = pwrite(fd, a, len, off);
    printf("write sucess: offset %d %d %c\n", off, r, a[0]);
}

int main(int argc, char **argv)
{
    char command = ' ';
    unsigned long offset = 0;

    if (argc < 2)
    {
        printf("Lack of argument:\n");
        printf("%s file step_length letter\n", argv[0]);
        return -1;
    }
    char *file = argv[1];
    step_length = atoi(argv[2]);

    printf("file is %s step_length is %d \n", file, step_length);

    if ((fd = open(file, O_RDWR | O_CREAT)) == -1)
    {
        printf("can open file %s\n", file);
        return -1;
    }

    printf("please input comand 'n' or 'w' \n \
           'n': offset will add step_length afeter write\n \
           'w/any letter': write the letter and don't change offset\n");

    command = getchar();
    while (command != 'q') {
        getchar();
        char letter = command;
        printf("command is : %c\n", command);
        fill_buf(buf, letter, step_length);
        write_file(buf, step_length, fd, offset);

        if (command == 'r') {
            printf("will write at same offset: %d\n", offset);
        } else if (command == 'n') {
            offset += step_length;
            printf("will write at next offset: %d\n", offset);
        }
        command = getchar();
    }

    printf("command %c not right, exit ...\n", command);
}
read_file_step.c
#include
#include
#include
#include
#include
#include
#include

int fd = 0;
char buf[131073]={0};
char read_buf[131073]={0};
int step_length = 0;

int read_file(char *buf, int len, int fd, unsigned long off)
{
    //lseek(fd, 0, SEEK_SET);
    //int r = read(fd, buf, len);
    //int r = pread(fd, buf, len);
    int r = pread(fd, buf, len, off);
    printf("read sucess: offset %d %d %c\n",off, r, buf[0]);
}


int main(int argc, char **argv)
{
    char command = ' ';
    unsigned long offset = 0;

    if (argc < 2)
    {
        printf("Lack of argument:\n");
        printf("Usage: %s file step_length \n", argv[0]);
        return -1;
    }
    char *file = argv[1];
    step_length = atoi(argv[2]);

    printf("file is %s step_length is %d \n", file, step_length);

    if ((fd = open(file, O_RDWR | O_CREAT)) == -1)
    {
        printf("can open file %s\n", file);
        return -1;
    }

    printf("please input comand 'n' or 'w' \n \
           'n': offset will add step_length afeter read\n \
           'r': read at offset and will not increase it\n");

    command = getchar();
    while (command != 'q') {
        getchar();

        printf("command is : %c\n", command);
        read_file(buf, step_length, fd, offset);

        if (command == 'r') {
            printf("will read at same offset: %d\n", offset);
        } else if (command == 'n') {
            offset += step_length;
            printf("will read at next offset: %d\n", offset);
        }
        command = getchar();
    }

    printf("command %c not right, exit ...\n", command);
}

验证过程如图

Ceph Filesystem 能保证强一致性吗?_第2张图片
Jack.jpg

Ceph Filesystem 能保证强一致性吗?_第3张图片
Rose.jpg
file.jpg

大家从Jack.jpg中可以看到Jack对文件/mnt/seven/congcong 每次写30个字符(/mnt/seven/是FUSE挂载点),依次是30个‘X’, 'T', 'A', 'O'。每次写完之后都让Rose客户端去读取内容(图-Rose.jpg所示),你发现不对的地方了吗?Rose客户端读到的居然每次都是Jack第一次写入的字符‘X’。随后我们查看该文件的时候内容确实已经全部是‘O’了,也就是说Jack客户端是写成功了的,但是Rose客户端没有读成功。

小结

 CephFs 在元数据方面不用配置任何选项可以保证实时一致性。但是对于数据需要开启一些选项才能够保证一致性。在Jewel版开启"fuse_use_invalidate_cb"这个选项之后就可以读到最新的数据了,但是这还要依赖客户端发送"getattr"才能使MDS端对于该Inode的filelock状态机发生变化,从而会revoke掉该客户端的缓存权限,这样才能把自己在ObjectCache层和FUSE Kernel层缓存的数据去掉。但是我们不能期待客户端每次读去或者写之前都会发送"getattr",而且在Luminous版优化了"getattr"。所以应用场景对数据一致性有很严格的要求,那么建议开启 "fuse_disable_pagecache"。但是使能该选项的话,客户端将会以DIRECT IO模式进行读写,又会影响性能,所以这块目前还需要优化。

 如有描述不准确的地方,欢迎各位提出,互相探讨。

你可能感兴趣的:(Ceph Filesystem 能保证强一致性吗?)