理解flashcache(1)

| 分类 linux  | 标签 flashcache linux 

前言

flashcache 是facebook 发布的开源的混合存储方案。它是基于内核的DeviceMapper机制,允许讲SSD设备影射成机械存储设备的缓存,堆叠成一个虚拟设备供用户读写,从OS的角度看,就是一块普通的磁盘。

为什么要讲SSD和普通的机械硬盘绑在一起呢?传统的机械硬盘,对于顺序读写的能力还是不错的,但是,如果存在大量的随机小io,大量的时间花在寻道上,性能就会下降很多。SSD不存在这个寻道的问题,对于随机小io,性能一样很好。但是我们知道SSD的价格目前还是非常贵的,所有的存储都是用SSD,价格上很难承担。

如果存在一种技术,将SSD和HDD绑在一起,对于连续的大IO直接写入HDD,对于离散的小io写入SSD即为成功,则相当于同时享受了SSD对离散小io的高性能和HDD的大容量。这种技术就是facebook的Mohan Srinivasan 大神发布的flashcache。

flashcache layout

flashcache 本质是一种cache,它的原理和CPU L1 Cache非常相似。因为L1 Cache容量有限,不可能容纳内存中的所有内容,同样道理SSD因为容量的原因,也不可能容纳后备的HDD的所有内容。那么哪些内容会在SSD中,OS系统如何从SSD中找到对应的块或扇区呢,OS又如何判断目前查找的扇区是否落在SSD之中呢?

flashcache将SSD的几乎所有内容(superblock和元数据除外),组织成如下结构:

首先,上图中的每一个方格都代表一个block,默认值是4K。 每一行都代表一个cache set,默认情况下,512个block组成一个cache size(即上图中的m=512),因此,cache size的容量为512*4K = 2M。

假如说,我拿一个100G的SSD来做flashcache的,很容易算出一共可以构造出多少个这样的cache set,即

    100G/2M=51200

因此上图中的n为51200 。

那么问题来了,如果手头上有一个flashcache,如何确定flashcache的layout呢?

root@node-186:~# lsblk
NAME                   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda                      8:0    0   1.8T  0 disk 
├─sda1                   8:1    0  30.5M  0 part 
├─sda2                   8:2    0 488.3M  0 part 
├─sda3                   8:3    0  93.1G  0 part /
├─sda4                   8:4    0   128G  0 part [SWAP]
└─sda5                   8:5    0   1.6T  0 part 
sdb                      8:16   0   7.3T  0 disk 
└─sdb1                   8:17   0   7.3T  0 part 
  └─OSD-SATA-01 (dm-0) 252:0    0   7.3T  0 dm   /data/osd.0
sdc                      8:32   0   7.3T  0 disk 
└─sdc1                   8:33   0   7.3T  0 part 
  └─OSD-SATA-02 (dm-1) 252:1    0   7.3T  0 dm   /data/osd.1
sdd                      8:48   0 372.6G  0 disk 
├─sdd1                   8:49   0    32G  0 part 
└─sdd2                   8:50   0 340.6G  0 part 
  └─OSD-SATA-01 (dm-0) 252:0    0   7.3T  0 dm   /data/osd.0
sde                      8:64   0 372.6G  0 disk 
├─sde1                   8:65   0    32G  0 part 
└─sde2                   8:66   0 340.6G  0 part 
  └─OSD-SATA-02 (dm-1) 252:1    0   7.3T  0 dm   /data/osd.1
rbd0                   251:0    0    10T  0 disk 

从上图可以看出,7.3T的sdc1 和 340.6G的sd32, 组成了一个flashcache,通过dmsetup,创建成了一个device mapper,即dm-1。通过dmsetup table命令可以得到flashcache的layout

root@node-186:~# dmsetup table /dev/mapper/OSD-SATA-01 
0 15623780319 flashcache conf:
	ssd dev (/dev/disk/by-partlabel/OSD-SATA-01-ssd), disk dev (/dev/disk/by-partlabel/OSD-SATA-01-data) cache mode(WRITE_BACK)
	capacity(347422M), associativity(512), data block size(4K) metadata block size(4096b)
	disk assoc(256K)
	skip sequential thresh(32K)
	total blocks(88940032), cached blocks(38453498), cache percent(43)
	dirty blocks(25037914), dirty percent(28)
	nr_queued(0)
Size Hist: 512:760473 1024:2 3584:8 4096:2591203166 

data block size : 即上图中每一个方格的大小,即每个方格为4K associativity : 每一行方格的个数,本例为默认值512 capacity : 即所有data 部分的容量。 本例为347422M

通过如下公式,可以算出

 n = capacity / (data block size) / associativity

因此不难算出,对于我们的flashcache实例共有173711个cache set。

上面的讨论简化了flashcache的模型,事实上,flashcache不仅仅有data部分,还有superblock和元数据。 和文件系统的superblock一样,flashcache的superblock解释了flashcache是如何组织的。

struct flash_superblock {
	sector_t size;		/* Cache size */
	u_int32_t block_size;	/* Cache block size */
	u_int32_t assoc;	/* Cache associativity */
	u_int32_t cache_sb_state;	/* Clean shutdown ? */
	char cache_devname[DEV_PATHLEN]; /* Contains dm_vdev name as of v2 modifications */
	sector_t cache_devsize;
	char disk_devname[DEV_PATHLEN]; /* underlying block device name (use UUID paths!) */
	sector_t disk_devsize;
	u_int32_t cache_version;
	u_int32_t md_block_size;
	u_int32_t disk_assoc;
	u_int32_t write_only_cache;
};

虽然flash_superblock消耗的空间很少,但是,flashcache仍然预留了1个data block的size,即默认情况下4K。4K的空间为将来的扩展预留了足够的空间。详情可见 flashcache_writeback_create函数中的如下语句:

#define DEFAULT_BLOCK_SIZE		8
#define DEFAULT_MD_BLOCK_SIZE	8
#define MD_BLOCK_BYTES(DMC)		((DMC)->md_block_size * 512)

	header = (struct flash_superblock *)vmalloc(MD_BLOCK_BYTES(dmc));

除此以外,对于每一个方格,必须要有元数据描述该方格:

很明显,SSD的size是远远小于HDD的size的,因此,对于每一个方格,必须要知道该方格对应的是HDD的那个block。因此,每一个data block,对应的元数据中必须要有dbn,即disk block number。

除此以外,无论方格可能有数据,可能无数据,SSD方格中的数据可能要比后备的HDD对应的block的数据新(writeback模式,先写入flashcache,lazy地刷入HDD),因此,必须要记录方格中block data的状态信息,因此,SSD上每一个block data都要有状态信息。状态有 (INVALID,VALID,DIRTY)。

struct flash_cacheblock {
	sector_t 	dbn;	/* Sector number of the cached block */
	u_int32_t	cache_state; /* INVALID | VALID | DIRTY */
} __attribute__ ((aligned(16)));

注意,对于flashcache上的每一个block data的元数据,占据的空间是16B,因此我们其实并不难计算总共需要多大的空间来存放所有block size的元数据信息。该计算是在flashcache_writeback_create 函数中进行的(都是以writeback模式为例进行的)

输入:

  • dm->size SSD设备扇区个数
  • dm->block_size 每个数据块占用扇区的个数
  • MD_SLOTS_PER_BLOCK 每个block可以存放元数据的个数
dm->size / dm->block_size  = 数据块的个数 

可以初步算出一共有多少个数据块(即有多少个方格)。因为每一个数据块都需要一个1个元数据,也就初步估算出了需要元数据的个数。

MD_SLOTS_PER_BLOCK  = block_size / sizeof(flash_cacheblock)  = 每个block能存放多少个元数据

因此,不难算出所有的元数据需要多少个数据块来存放:

   /*需要多少个block来存放元数据和superblock在内*/
	dmc->md_blocks = INDEX_TO_MD_BLOCK(dmc, dmc->size / dmc->block_size) + 1 + 1;
	
	/*总扇区数,剪掉用于存放元数据和superblock的扇区数,即用语存放数据的区域的扇区个数*/
	dmc->size -= dmc->md_blocks * MD_SECTORS_PER_BLOCK(dmc);
	
	
   /*将扇区个数转化成block 个数,即计算出一共有data block*/
	dmc->size /= dmc->block_size;
	
	/*注意 data block的个数,必须是assoc的倍数,否则最后一个cache set无法凑满足够的assoc*/
	dmc->size = (dmc->size / dmc->assoc) * dmc->assoc	

因此,flashcache的整体layout如下图所示:

值得一提的是,除了落在SSD上的元数据信息外,在内存中也要想好相当可观的内存。flashcache早期的版本每一个数据块要消耗24Byte的内存,因此,300G的SSD,如果4K一个block块,对于WriteBack模式,需要消耗1.8G左右的内存。

后来69fae2ff72c0606e8c00249621365cfbb877531f commit做了改进,每一个数据块在内存中的数据结构消耗18 Byte,节省了不少内存。还是以300G的SSD为例,如果4K一个block块,对于writeback模式,消耗1.35G左右的内存。

commit 69fae2ff72c0606e8c00249621365cfbb877531f
Author: Mohan Srinivasan <mohan@fb.com>
Date:   Tue Aug 6 08:28:33 2013 -0700

    Make each in-memory cacheblock state packed - 18 bytes each.

    Summary: Make 'struct cacheblock' a packed struct. So it fits in
    18 bytes (down from 24 bytes).

    Ported forward from facebook's internal repo.

    Test Plan:

    Reviewers:

    CC:

    Task ID: #

    Blame Rev:

diff --git a/src/flashcache.h b/src/flashcache.h
index 0f37e99..281eab8 100644
--- a/src/flashcache.h
+++ b/src/flashcache.h
@@ -425,7 +425,7 @@ struct cacheblock {
 #ifdef FLASHCACHE_DO_CHECKSUMS
        u_int64_t       checksum;
 #endif
-};
+} __attribute__((packed));

 struct flash_superblock {
        sector_t size;          /* Cache size */
(END)

那么内存中的数据结构到底存放了哪些内容呢? 我们看下定义:

/* Cache block metadata structure */
struct cacheblock {
	u_int16_t	cache_state;
	int16_t 	nr_queued;	/* jobs in pending queue */
	u_int16_t	lru_prev, lru_next;
	u_int8_t        use_cnt;
	u_int8_t        lru_state;
	sector_t 	dbn;	/* Sector number of the cached block */
	u_int16_t	hash_prev, hash_next;
#ifdef FLASHCACHE_DO_CHECKSUMS
	u_int64_t 	checksum;
#endif
} __attribute__((packed));

flashcache mode

对flashcache layout有了初步了解之后,接下来学习flashcache的工作模式。 flashcacheyou三种模式:

/* Cache Modes */
enum {
	FLASHCACHE_WRITE_BACK=1,
	FLASHCACHE_WRITE_THROUGH=2,
	FLASHCACHE_WRITE_AROUND=3,
};
  • WRITEBACK : 对于写入,首先会写入到Cache中,同时将对于block的元数据dirty bit,但是并不会立即写入后备的device
  • WRITETHROUGH : 对于写入,写入到Cache中,同时也会将数据写入backing device,知道写完backing device,才算写完
  • WRITEAROUND : 写入的时候,绕过Cache,直接写入backing device,即SSD只当读缓存。

这三者的区别如下图

在最常用的writeback模式中,还有一个特殊的选项,即writecache:只有write才会使用SSD做的cache,而读直接读取慢速的硬盘。 这种场景适用于读缺失杂乱无章,读命中的概率非常低的情况。

当我们调用flashcache_create 创建flashcache的时候,加上-w option即可变成writecache

-w : write cache mode. Only writes are cached, not reads

在下面提到的flashcache_read中有如下语句:

spin_lock_irqsave(&dmc->ioctl_lock, flags);
	if (res == -1 || dmc->write_only_cache || flashcache_uncacheable(dmc, bio)) {
		spin_unlock_irqrestore(&dmc->ioctl_lock, flags);
		/* No room , non-cacheable or sequential i/o means not wanted in cache */
		if ((res > 0) && 
		    (dmc->cache[index].cache_state == INVALID))
			/* 
			 * If happened to pick up an INVALID block, put it back on the 
			 * per cache-set invalid list
			 */
			flashcache_invalid_insert(dmc, index);
		flashcache_setlocks_multidrop(dmc, bio);
		DPRINTK("Cache read: Block %llu(%lu):%s",
			bio->bi_sector, bio->bi_size, "CACHE MISS & NO ROOM");
		if (res == -1)
			flashcache_clean_set(dmc, hash_block(dmc, bio->bi_sector), 0);
		/* Start uncached IO */
		flashcache_start_uncached_io(dmc, bio);
		return;
	} else 
		spin_unlock_irqrestore(&dmc->ioctl_lock, flags);

注意dmc->write_only_cache, 当我们设置了WRITECACHE模式,我们就会调用flashcache_start_uncached_io,绕过SSD,直接从HDD中读取内容。

SSD中数据块于HDD中数据块的对应关系

本节主要以WriteBack模式来讲解,即读和写都会用到flashcache的情景。

因为flashcache的缓存作用,当页高速缓存(Page Cache)没有命中,就会发起对磁盘的读写,都会调用到submit_io函数。既然SSD在页高速缓存之外,又提供了一个缓存,因此无论是读还是写,第一步都是要在SSD的数据块中寻找对应的块。

对于读,如果在SSD中找到对应的数据块,并且内容是VALID,那么就不需要去HDD寻道读出对应的内容了,直接从SSD中读出即可。

static void
flashcache_read(struct cache_c *dmc, struct bio *bio)
{
	int index;
	int res;
	struct cacheblock *cacheblk;
	int queued;
	unsigned long flags;
	
	DPRINTK("Got a %s for %llu (%u bytes)",
	        (bio_rw(bio) == READ ? "READ":"READA"), 
		bio->bi_sector, bio->bi_size);

	flashcache_setlocks_multiget(dmc, bio);
	res = flashcache_lookup(dmc, bio, &index);
	/* Cache Read Hit case */
	if (res > 0) {
		cacheblk = &dmc->cache[index];
		if ((cacheblk->cache_state & VALID) && 
		    (cacheblk->dbn == bio->bi_sector)) {
			flashcache_read_hit(dmc, bio, index);
			return;
		}
	}

对于WRITEBACK模式下的写入而言,写入到SSD的对应数据块即可,不需要写入到HDD。

static void
flashcache_write(struct cache_c *dmc, struct bio *bio)
{
	int index;
	int res;
	struct cacheblock *cacheblk;
	int queued;

	flashcache_setlocks_multiget(dmc, bio);
	res = flashcache_lookup(dmc, bio, &index);
	if (res != -1) {
		/* Cache Hit */
		cacheblk = &dmc->cache[index];		
		if ((cacheblk->cache_state & VALID) && 
		    (cacheblk->dbn == bio->bi_sector)) {
			/* Cache Hit */
			flashcache_write_hit(dmc, bio, index);
		} else {
			/* Cache Miss, found block to recycle */
			flashcache_write_miss(dmc, bio, index);
		}
		return;
	}

无论是哪一种情况,找到SSD中合适的数据块是第一要务。让我们看着flashcache layout小节中的第一张图,其实很容易想到。

10G的SSD和100G的HDD,通过dmsetup组成一个dm。 每个数据块大小是4K,每一个cache set中有512个数据块,即每行有512个方格,2M的空间。10G的SSD可以有5120行,即一共有5120个set。

如果用户要读取磁盘上偏移54321MB处的2K内容,只需要对54321M这个位置做hash。因为每一个cache set为2M,因此54321M对应的 set number是27160,因为总共有5120个set,因此,21760对应的set是:

21760 % 5120 = 1560 

即我们应该去方格表的1560行去寻找正确的block。但是每行有512个data block,which one才是正确的哪个呢?

比对元数据中的dbn。因为54321M的扇区号为:

54321 * 1024 * 1024 / 512 = 111249408

因此,挨个比较512个data block对应的dbn,如果值等于111249408,则表示 SSD中存在对应的data block。


上一篇     下一篇