ceph的 cache tier实现分析

1 基本介绍

1.1 设计思想

数据的存储可划分为active和inactive两大类,active数据是小部分,会频繁访问,使用更高性能的底层存储介质进行存储;inactive的数据是全集,使用廉价的存储介质存储。这种分冷热的思想与CPU的多级缓存、操作系统的cache、各类软件系统的软件缓存是相通的,但ceph通过软件层面的配合,充分利用不同的硬件介质和软件存储模式,为上层用户提供透明统一的访问接口。

目的:在兼顾存储成本前提下,通过配合使用不同的存储介质,并使用不同的软件存储模式,达到更好的数据存取性能。
说明:cache tier是ceph在其抽象的ObjectStore层面实现的冷热分层,上层的RGW/CEPHFS/RBD均可受益,其中RGW本身还可据此实现生命周期功能。

1.2 实现要点

  1. CRUSH Map:通过指定不同的root,并创建多个相应的crush_rule,用来从软件上定义如何使用底层存储介质
  2. pool的crush_rule:创建不同存储池,为各自设置对应的crush_rule
  3. pool的存储策略:通过crush_rule的类型来指定软件层面的存储模式,包括多副本(副本数可配)、EC(K、M可配)
  4. tier关联:动态配置两个存储池的关联关系和迁移策略,包括none、writeback、forward、readonly、readforward、proxy、readproxy
  5. Objecter:屏蔽底层实现,为客户端的读写请求提供统一接口

1.3 总体架构

ceph官网提供的tier存储技术的总体架构如下图:

对于设置了cache tier的存储池,客户的读写请求是无感的,通过单独的objecter模块负责实现cache tier和storage tier的联动,并根据管理员配置的各项cache tier的策略,自动实现数据的更新和同步。

1.4 特殊性

  1. 适用于热点访问场景
    添加tier之后,能够提升总体的性能与具体的数据存取场景高度依赖,因为需要将热存储池的数据迁移到冷存储池,因此对访问小部分热点数据的场景比较适合,绝大多数请求只会访问一小部分数据。
  2. 通用场景性能较差且benchmark困难
    通用的非热点访问的场景下,都不是cache友好的,都会有性能损失。另外通用的benckmark方法无法发挥tier的优势,不会只访问一小部分的数据,因此性能数据也表现不佳。
  3. 对象遍历操作不稳定
    由于tier层的小部分热数据的存在,如果用户直接调用librados库的对象遍历API,可能会得到不一致的结果,但是直接使用RGW/RBD/CephFS没有影响。
  4. 复杂性提升
    使用tier后,会为Rados层带来额外的复杂性,可能会增加出现bug的风险。

2 功能实现分析

cache tier是在ceph抽象的ObjectStore层面,处于底层的统一的KV存储层,其具体实现依附于ceph的存储池,cache tier作为存储池的属性,依据用户配置,存储池在完成具体的读写操作时提供相应的cache tier功能。ceph将存储池进行分片(PG),在具体实现功能时又是基于PG为最基本的单位来实现。

2.1 参数设计

cache tier的参数是存储池的属性,底层数据结构定义为pg_pool_t:

struct pg_pool_t {
  ...
  typedef enum {
     CACHEMODE_NONE = 0,                  /// no caching
     CACHEMODE_WRITEBACK = 1,             /// write to cache, flush later
     CACHEMODE_FORWARD = 2,               /// forward if not in cache
     CACHEMODE_READONLY = 3,              /// handle reads, forward writes [not strongly consi
     CACHEMODE_READFORWARD = 4,           /// forward reads, write to cache flush later
     CACHEMODE_READPROXY = 5,             /// proxy reads, write to cache flush later
     CACHEMODE_PROXY = 6,                 /// proxy if not in cache
   } cache_mode_t;
   ...
   set uint64_t tiers;     /// pools that are tiers of us
   int64_t tier_of;         /// pool for which we are a tier(-1为没有tier)
   // Note that write wins for read+write ops
   int64_t read_tier;       /// pool/tier for objecter to direct reads(-1为没有tier)
   int64_t write_tier;      /// pool/tier for objecter to direct write(-1为没有tier)
   cache_mode_t cache_mode; /// cache pool mode
   
   uint64_t target_max_bytes;   /// tiering: target max pool size
   uint64_t target_max_objects; /// tiering: target max pool size

   uint32_t cache_target_dirty_ratio_micro; /// cache: fraction of target to leave dirty
   uint32_t cache_target_dirty_high_ratio_micro; /// cache: fraction of  target to flush with high speed
   uint32_t cache_target_full_ratio_micro;  /// cache: fraction of target to fill before we evict in earnest

   uint32_t cache_min_flush_age;  /// minimum age (seconds) before we can flush
   uint32_t cache_min_evict_age;  /// minimum age (seconds) before we can evict

   HitSet::Params hit_set_params; /// The HitSet params to use on this pool
   uint32_t hit_set_period;      /// periodicity of HitSet segments (seconds)
   uint32_t hit_set_count;       /// number of periods to retain
   bool use_gmt_hitset;          /// use gmt to name the hitset archive object
   uint32_t min_read_recency_for_promote;   /// minimum number of HitSet to check before promote on read
   uint32_t min_write_recency_for_promote;  /// minimum number of HitSet to check before promote on write
   uint32_t hit_set_grade_decay_rate;   /// current hit_set has highest priority on objects
                                        ///temperature count,the follow hit_set's priority deca
                                        ///by this params than pre hit_set
   uint32_t hit_set_search_last_n;   /// accumulate atmost N hit_sets for temperature


   bool is_tier() const { return tier_of >= 0; }
   bool has_tiers() const { return !tiers.empty(); }
   void clear_tier() {
     tier_of = -1;
     clear_read_tier();
     clear_write_tier();
     clear_tier_tunables();
   }
   bool has_read_tier() const { return read_tier >= 0; }
   void clear_read_tier() { read_tier = -1; }
   bool has_write_tier() const { return write_tier >= 0; }
   void clear_write_tier() { write_tier = -1; }
   void clear_tier_tunables() {
     if (cache_mode != CACHEMODE_NONE)
       flags |= FLAG_INCOMPLETE_CLONES;
     cache_mode = CACHEMODE_NONE;
     target_max_bytes = 0;
     target_max_objects = 0;
     cache_target_dirty_ratio_micro = 0;
     cache_target_dirty_high_ratio_micro = 0;
     cache_target_full_ratio_micro = 0;
     hit_set_params = HitSet::Params();
     hit_set_period = 0;
     hit_set_count = 0;
     hit_set_grade_decay_rate = 0;
     hit_set_search_last_n = 0;
     grade_table.resize(0);
   }
   ...
};

这些参数详细记录了cache tier的具体实现细节,总结如下:

  • tier的存储池:自身的tier pool ID(tier_of字段),以及以自身作为tier的其他pool的ID集合(tiers字段)
  • cache mode:定义了六种cache模式,默认为CACHEMODE_NONE,按照用户配置执行相应的操作,为cache tier实现的核心部分
  • cache tunable参数:在执行flush和evict操作时,用户可为cache这两种行为设置的多种触发参数,以便和具体的应用场景匹配

2.2 实现主体

基于为cache tier定义的各项参数,在具体实现的时候,依附于PG为主体实现具体的功能。pg_pool_t作为底层存储的定义,属于PGPoolinfo字段,用来记录一个存储池的详细属性。PG类定义了在存储池上完成最基本数据读写的实体,包含了一个它所属的存储池的pool字段。

 struct PGPool {
   CephContext* cct;
   epoch_t cached_epoch;
   int64_t id;
   string name;
   uint64_t auid;
   pg_pool_t info;
   ...
 };
 class PG : public DoutPrefixProvider {
  ...
  protected:
    PGPool pool;
  ...
 };

PG类定义了针对全部数据请求的所有功能,主要包括shard分片、recovery和backfill功能、状态收集和统计、blocked请求的等待处理、scrub处理等功能,对于具体的数据读写请求的处理,定义了必须进一步实现的全部抽象接口,其中所有请求的入口接口如下:

virtual void do_request(OpRequestRef& op, ThreadPool::TPHandle &handle) = 0;

目前的PG针对数据读写请求的全部实现都由PrimaryLogPG类负责,这里主要关注cache tier相关的具体实现。

2.3 Objecter

Objecter本身实现上是一个Dispatcher,对外可作为OSD的一个client,负责向tier的storage层的存储池读写数据;对内作为本存储池的OSD的一个成员,负责在
各个cache mode的具体实现中,生成具体的Operation OP并提交。

2.3.1 注册与启动

OSD的main函数创建了7个messenger用来处理各类网络rpc消息,其中包括为Objecter创建对独立messenger。OSD类为一个Dispatcher,负责实现网络处理的各个接口,其包含一个OSDService对象,负责完成除网络交互之外的具体实现。Objecter为OSDService的一个成员,由OSDService在构造时动态创建Objecter对象,并在构造函数调用Objecter的init函数初始化Objecter的内部数据结构。

在对象创建完成之后,main函数启动这7个messenger,此时还不能无法处理网络请求,随后调用OSD的init函数,其负责将其OSDService成员的Objecter成员注册到相应messenger的dispatcher队列:

objecter_messenger->add_dispatcher_head(service.objecter);

同时,也会将OSD自身添加到其他messenger的Dispatcher队列,并完成一系列的如初始化MON和MGR的client对象、启动OSD的多个工作线程池、启动tick等初始化操作,其中包括连接MON进行鉴权,在鉴权通过之后调用OSDService成员的final_init函数,负责启动Objecter:

void OSDService::final_init() {
    objecter->start(osdmap.get());
}

2.3.2 Tick与OSDSession

Objecter的start函数用来启动objecter服务,其工作内容为:启动tick并拷贝一份osdmap。tick的间隔时间由配置文件制定:objecter_tick_interval,默认为5秒。Objecter会通过拷贝的osdmap信息,主动向其他目标OSD发起连接,并以内嵌类OSDSession表示,本身维护了一个osd ID到OSDSession的映射结构
map。OSDSession的成员包括目标OSD的ID、底层网络连接等基础信息之外,主要包括到目标OSD的各类OP操作的队列:

struct OSDSession {
    ...
    map ops;
    map linger_ops;
    map command_ops;
    ...
    int osd;
    ConnectionRef con;
    ...
};

每一次tick的任务就是遍历Objecter维护的OSDSession映射结构,循环检查它的OP队列,包括普通的OP队列、linger OP队列、command OP队列,依据如下标准执行是否发送依次MPing消息:

  • 普通OP:配置的objecter_timeout,默认10秒,依据OP的开始时间是否超过这个超时时间来决定是否发送
  • linger OP(执行比较缓慢的操作):只要存在于队列中就会发送
  • command OP:只要存在于队列中就会发送

2.3.3 ObjectOperation

Objecter负责为cache tier的具体实现中需要与其他存储池的OSD进行数据交互时提供支持,其本质就是执行一系列ObjectStore抽象的操作,并为cache tier封装一些特定的操作,这部分统一由ObjectOperation类负责完成,实现非常直接:

struct ObjectOperation {
   vector ops;
   int flags;
   int priority;

   vector out_bl;
   vector out_handler;
   vector out_rval;
};

一共就上述6个成员,最重要的就是OSDOp构成的数组,对于一个具体的操作,可能对应多个OSDOp操作,或者可以将多个操作的OP合并后发送,另外包含一个标志位和优先级;同时定义了三个同样与OSDOp对应的输出信息的数组,包括输出内容、输出handler、返回值。

在此基础上,使用一个add_op函数添加一个新的OSDOp成员到内部的ops数组中,并返回引用,再定义了大量的针对Object的具体操作,用来支持cache tier的具体实现,主要分为如下几类:

  • ObjectStore抽象的对象操作:如statreadwriteappendgetxattromap_get_keysomap_get_values
  • PG和Scrub操作:包括pg_lsscrub_ls,用来从其他OSD获取PG、Scrub信息
  • 针对cache tier特殊操作:包括is_dirtyundirtyhit_set_ls/getcopy_getcopy_fromcache_flush/try_flush
    cache_evictcache_pincache_unpin

针对cache tier的操作为实现cache tier的不同模式提供了便利,其中flush和evict直接影响cache tier的行为,其具体处理方式如下:

  • flush操作:若给定的cache tier中的对象是dirty的,就将其写入到backing tier;如果对象是clean,则不做任何操作。当该操作与update并发执行时,
    cache_flush将会阻塞update操作,cache_try_flush则会立即返回一个EAGAIN错误而不会阻塞。
  • evict操作:如果给定的cache tier中的对象是clean则将其从cache tier中删除,否则返回EBUSY。

另外提供的cache_pincache_unpin还可以将某个对象在cache tier中进行锁定和解锁。

3.3.4 具体实现

Objecter要主动向其他OSD发起各类OP操作,封装了具体的方法,包括writereadpg_readgetxattr等,每个方法使用的具体OP,通过上述ObjectOperation类封装的工厂方法来构造,创建好之后调用统一的op_submit函数进行提交,最终从维护的OSDSession映射结构中获取底层网络连接,调用其send_message方法将OP发送出去。对于read方法,其具体实现如下:

   Op *prepare_read_op(
     const object_t& oid, const object_locator_t& oloc,
     ObjectOperation& op,
     snapid_t snapid, bufferlist *pbl, int flags,
     Context *onack, version_t *objver = NULL,
     int *data_offset = NULL,
     uint64_t features = 0,
     ZTracer::Trace *parent_trace = nullptr) {
     Op *o = new Op(oid, oloc, op.ops, flags | global_op_flags |
                    CEPH_OSD_FLAG_READ, onack, objver, data_offset, parent_trace);
     o->priority = op.priority;
     o->snapid = snapid;
     o->outbl = pbl;
     if (!o->outbl && op.size() == 1 && op.out_bl[0]->length())
         o->outbl = op.out_bl[0];
     o->out_bl.swap(op.out_bl);
     o->out_handler.swap(op.out_handler);
     o->out_rval.swap(op.out_rval);
     return o;
   }
   ceph_tid_t read(
     const object_t& oid, const object_locator_t& oloc,
     ObjectOperation& op,
     snapid_t snapid, bufferlist *pbl, int flags,
     Context *onack, version_t *objver = NULL,
     int *data_offset = NULL,
     uint64_t features = 0) {
     Op *o = prepare_read_op(oid, oloc, op, snapid, pbl, flags, onack, objver, data_offset);
     if (features)
       o->features = features;
     ceph_tid_t tid;
     op_submit(o, &tid);
     return tid;
   }
   void Objecter::op_submit(Op *op, ceph_tid_t *ptid, int *ctx_budget) {
     shunique_lock rl(rwlock, ceph::acquire_shared);
     ...
     _op_submit(op, rl, ptid);
   }
   void Objecter::_op_submit(Op *op, shunique_lock& sul, ceph_tid_t *ptid) {
     ...
     MOSDOp * m = _prepare_osd_op(op);
     ...
     _send_op(op, m);
   }
   void Objecter::_send_op(Op *op, MOSDOp *m) {
     ...
     op->session->con->send_message(m);
     ...
   }

Objecter作为可与其他OSD进行数据读写交互的模块,除了发送OP之外,还需接收响应,因此实现了Dispatcher的全部接口,重新定义ms_dispatch函数来处理其他OSD返回给自己的信息。通过接收的消息类型进行分发,只支持如下几种消息:

  • CEPH_MSG_OSD_OPREPLY:普通的OSD读写等OP操作的响应,可进行fast dispatch,调用handle_osd_op_reply处理
  • CEPH_MSG_OSD_BACKOFF:执行BACKOFF的消息,调用handle_osd_backoff处理
  • CEPH_MSG_WATCH_NOTIFY:可进行fast dispatch,调用handle_watch_notify处理
  • MSG_COMMAND_REPLY:仅在发送消息的源也是OSD时才会处理,调用handle_command_reply处理,否则不处理
  • MSG_GETPOOLSTATSREPLY:获取存储池统计信息的返回,调用handle_get_pool_stats_reply处理
  • CEPH_MSG_POOLOP_REPLY:执行存储池操作的返回,调用handle_pool_op_reply处理
  • CEPH_MSG_STATFS_REPLY:获取文件系统信息的返回,调用handle_fs_stats_reply处理
  • CEPH_MSG_OSD_MAP:交互OSDMAP信息的处理,调用handle_osd_map处理

这里我们主要关注第一类消息,就是针对OSD普通的读写等OP操作的响应对处理,具体流程如下:

  1. 从维护的OSDSession映射结构中获取该消息对应的OSDSession
  2. 判断是否需要重试,如果需要就从OSDSession中删除这个OP,并重新调用_op_submit提交该请求
  3. 获取消息的结果,判断是否需要进行redirect,如果需要就从OSDSession删除该OP,并添加新的redirect目的地后调用_op_submit重新提交
  4. 获取消息的输出数据、返回值,并据此调用这个OP创建时注册的处理完成时的回调handler

2.4 IO请求

由于cache tier依附于PG实现,因此cache tier的IO请求路径就是对OSD的任意一次IO请求路径,在这个路径上会判断PG所在的存储池是否是其他存储池的cache tier或是否有其他存储池为自身的cache tier,如果均没有则执行正常的读写请求;否则就依据具体的策略并基于Objecter提供的方法来完成对cache tier的处理。

2.5 各cache mode处理

对于配置了cache tier存储池,按照3.4节点IO请求路径,具体在maybe_handle_cache_detail函数中,依据不同的cache模式进行switch-case分别进行处理, 在maybe_handle_cache_detail函数中具体分为如下六种模式依次处理,详见下述分析。

2.5.1 FORWARD

FORWARD模式表示所有到达cache tier存储池的请求都不会处理,直接将它的后端存储池的ID回复给请求方,并返回-ENOENT的错误号,具体实现比较简单。

该模式的用途是在删除WRITEBACK模式的cache tier时,需将其cache mode先设置为FORWARD,并主动调用cache tier的flush和evict操作,确保cache tier存储池的对象全部evict和flush到后端存储池,保证这个过程中不会有新的数据写入。

2.5.2 READONLY

READONLY模式是指对于所有的写请求,都直接调用do_cache_redirect函数,与FORWARD模式同样处理;对于所有的读请求,会先判断是否存在于cache tier存储池中,如果存在就直接返回,否则会先调用Objecter从后端存储池读取一份数据,并创建一个ObjectContext对象保存,将读取数据返回给客户。

start_copy会创建一个CopyOp对象,该对象保存了请求的参数、返回值、大小、数据缓冲区、omap和xattr缓冲区等,最终调用OSDService的Objecter成员的read方法向目标存储池的OSD发起读取请求。之后调用wait_for_blocked_object将该OP加入到内部维护的一个称为waiting_for_blocked_object的map结构中,key为tid,value为OP。start_copy在发起请求之前会设置好成功时的回调函数,这个回调函数会调用kick_object_context_blocked用来从维护的map结构中查询到之前的OP,调用requeue_ops将这个OP加入到OSD的请求队列中重新执行,并从map结构中删除。注意:requeue_ops会使用enqueue_front插入到OSD的ShardedOpWQ的开头。

2.5.3 PROXY

PROXY模式下,针对读写请求都会执行proxy,也就是作为一个代理向后端存储池发起请求并返回给客户端,除非强制要求先进行promote操作。

对于写请求调用do_proxy_write,则会直接调用会调用OSDService的Objecter成员的mutate方法,将写请求直接写入到后端的存储池中,并记录到内部维护的proxywrite_opsin_progress_proxy_ops两个map结构,另外设置了成功时的回调函数,在写入完成之后从维护的map结构中删除,并返回给客户端CEPH_OSD_FLAG_ACK | CEPH_OSD_FLAG_ONDISK的响应。对于读请求调用do_proxy_read,与写请求处理类似,直接作为代理端发送请求到后端存储池并等待结果完成,同样也会分别记录到两个map结构并在完成时删除。

这种模式下,读写请求的对象的数据都不会在cache tier存储池中保存,自身扮演为一个代理(proxy)的角色,这是与FORWARD模式的区别。

2.5.4 WRITEBACK

WRITEBACK模式是最复杂也是最有实用价值的模式,其具体实现会按照请求类型、cache状态综合判断,并复用前三种模式下的一些处理细节进行综合处理。处于这种模式下的cache tier存储池,其处理流程如下:

  1. 判断cache tier存储池的状态是否已满,如果已满,则对于读请求直接调用do_proxy_read,对于写请求直接将OP加入到waiting_for_cache_not_full队列,并在下一次有新的请求达到时重新放入OP队列处理。
  2. 在cache tier未满的情况下,先判断是否必须进行promote,如果需要就调用promote_object,先阻塞当前请求,从后端存储池读取一份数据到cache tier存储池,并在完成之后再将当前请求加入OP队列
  3. 在cache tier未满且不会强制promote时,这也是最常见的情况下:对于写入请求,会先阻塞,调用promote_object从后端读取一份数据并保存,完成之后将当前请求重新加入OP队列,这样下一次执行这个读请求时,会判断已经存在于cache tier中,就直接写入在cache tier存储池中;对于读请求,则会首先调用 do_proxy_read从后端存储池读取数据但不保存在cache tier存储池中,之后再判断本地读请求是否需要跳过promote,这是在创建该OP时设置的一个flag,通过op->need_skip_promote来判断,在所有的OP请求中有两种场景会设置不需promote,否则都会执行promote从后端存储池读取一份数据保存在cache tier存储池

其中会设置skip promote标志的两种情况如下:

  1. CEPH_OSD_OP_DELETE请求会设置skip promote
  2. read、sync_read、sparse_read、checksum、writefull请求若设置了CEPH_OSD_OP_FLAG_FADVISE_NOCACHECEPH_OSD_OP_FLAG_FADVISE_DONTNEED
    标志位就会设置skip promote

2.5.5 READFORWARD

READFORWARD是FORWARD与WRITEBACK模式的综合。对于所有读请求执行与FORWARD一样的处理,调用do_cache_redirect,直接返回后端存储池给用户,并返回-ENOENT的错误号;对于写请求则与WRITEBACK模式相同处理,先调用promote_object从后端读取一份数据保存并加入到OP队列重新执行,把数据写入到cache tier存储池中。

2.5.6 READPROXY

READPROXY是PROXY与WRITEBACK模式的综合。对于所有读请求执行与PROXY模式一样的处理,调用do_proxy_read,仅从后端存储池读取数据并返回给用户,不存储读取到的对象;对于写请求则与WRITEBACK模式相同处理,先调用promote_object从后端读取一份数据并保存并加入到OP队列重新执行,把数据写入到cache tier存储池中。

3 总结

cache tier是ceph在底层抽象的统一软件定义存储ObjectStore的基础上,实现的一种缓存存储层,具体依托于ceph的CRUSH MAP和CRUSH RULE,针对不同的存储池配置相应的规则,从而映射到具有存储性能差异的硬件上,从而提升总体性能并降低存储成本。cache tier的实现具体是在底层的存储池层面,因此对于所有基于ObjectStore抽象实现的上层应用都可受惠,包括RGW、CEPHFS、RBD。cache tier本质上可以看做CPU与内存间的高速缓存的一个外延,当数据从内存要写入磁盘时,通过使用性能较高的磁盘作为cache tier层,来弥补内存与普通磁盘间的性能gap,该思想虽然也有如文件系统的page cache、磁盘本身的缓存等实现,但ceph的cache tier相当于是从软件定义存储的角度来对这种思想进行补充。

3.1 实用性

根据上述分析,cache tier虽然提供了六种模式,但FORWARD本身是一种为WRITEBACK切换提供的过渡模式,并无实用价值。PROXY模式则完全充当一个代理来转发请求,同样并无太大的实用价值。另外四种模式,最重要的就是WRITEBACK模式,这是cache tier实现的最具实用性的模式,在这种模式下配置相应的cache tier参数控制其行为。另外三种READONLY、READPROXY、READFORARD三种是针对读请求的比例不同而提供的针对性模式。下面总结了他们的实用场景:

  • WRITEBACK:写请求较多或读写比较均匀的场景,读写的数据都会在cache tier存储池中保存,通过配置flush、evict参数执行向后端存储池的更新和剔除
  • READONLY:适合以读为主的场景,对于极少量读写请求直接让客户端去写入到后端存储池,而读请求则会将对象缓存起来,提升总体读请求的性能
  • READPROXY与READFORWARD:适合以写为主的场景,对于少量的读请求,可以选择使用proxy方式进行处理,也可以选择让客户端直接写入到后端存储池

3.2 局限性

cache tier的实现本身依附于存储池的分片,也就是PG,而PG本身要实现包括副本同步、快照、scrub、recovery等一系列功能,cache tier的实现与他们都混合在一起,大大增加了整体代码实现的复杂性,增加了在极端场景出现bug的可能性,从而对系统稳定性会有一定的影响。因此对于cache tier功能本身需要进行严格的测试,同时对于开启了cache tier的存储池,也需要谨慎的配置各项参数,严密的进行监控。

cache tier提供的多种模式虽然针对不同场景而设置,但并不全面,还有进一步开发的空间。首先可借鉴CPU多级缓存的思想,ceph可支持超过两级的cache tier的配置,但是由于目前的代码实现将其混合在PG中,这种扩展难度很大改动也非常大。其次,对于写请求较少的场景,仅提供了READONLY模式,也可提供类似CPU缓存的write through的模式,同时也还有write allocate等处理方式,均可借鉴,但这些扩展都受目前的实现方式的制约。

你可能感兴趣的:(ceph的 cache tier实现分析)