ceph的 cache tier实现分析

栏目: 服务器 · 发布时间: 5年前

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

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存储技术的总体架构如下图:

ceph的 cache 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<int,OSDSession*> 。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消息:

objecter_timeout

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操作的响应对处理,具体流程如下:

_op_submit
_op_submit

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等处理方式,均可借鉴,但这些扩展都受目前的实现方式的制约。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

腾讯传

腾讯传

吴晓波 / 浙江大学出版社 / 2017-1-1 / 58.00元

腾讯官方唯一授权的权威传记 著名财经作家吴晓波倾力之作 当市值最高的中国互联网公司,遇上中国财经界最冷静的一双眼睛 读懂腾讯,读懂中国互联网 . 内容简介 本书全景式地记录了腾讯崛起的经历,并以互联网的视角重新诠释了中国在融入全球化进程中的曲折与独特性。 从1998年开始创业到成为世界级互联网巨头,腾讯以即时通信工具起步,逐渐进入社交网络、互动娱乐、网络媒......一起来看看 《腾讯传》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码