企业创建网站的途径都有啥,班级微信公众号怎么创建,教育类集群网站建设,杭州市城乡建设 网站Large omap objects现象
以下是真实的问题场景#xff0c;以此文进行记录并分享。 Q1#xff1a;集群出现了Large omap objects告警#xff0c;这是什么问题#xff1f;有什么影响#xff1f;
Q2#xff1a;Large omap objects告警的触发条件是什么#xff1f;
Q3以此文进行记录并分享。 Q1集群出现了Large omap objects告警这是什么问题有什么影响
Q2Large omap objects告警的触发条件是什么
Q3这个告警怎么处理或者怎么优化解决
随着Ceph对象存储的产品不断成熟用户数量的不断增加对集群的性能考验也愈发严峻。特别是某些大型用户在特定场景下需要对单个bucket进行上传大量的对象同时如果用户直接或者间接应用程序调用list object接口对单个bucket进行多次list object操作会有一些IO响应慢日志中可能会出现slow request则可能会发现ceph出现了large omap objects告警。本文基于ceph luminous版本展开讨论该版本引入了large omap objects告警功能。
集群出现如下large omap objects告警下面对此展开分析 出现上述告警后运维人员可以通过ceph health detail查看具体的告警信息进行定位一般large omap objects出现在存储池default.rgw.buckets.index。每个bucket在存储池default.rgw.buckets.index中都对应一个rados索引对象。存储池default.rgw.buckets.index中对象的格式为.dir.marker。 Bucket属性
先简单介绍bucket属性并举例列出bucket的rados索引对象和bucket内对象名称的关系。 图1
上面给出了bucket的idmarkerownerquota等属性。这里我们关注marker通过marker信息可以到对应的pool default.rgw.buckets.index找到该bucket如下 向bucket test1 分别上传obj1obj2obj3三个对象 我们可以通过rados listomapkeys命令查看bucket的索引对象下的对象。 也就是说存储池default.rgw.buckets.index中bucket 对应的索引对象记录了bucket中对象等信息。这里联想单个bucket下大量对象的问题大家应该会想到large omap objects的生成跟这个索引下的对象存在一定关系。 告警产生
接下来通过large omap objects中的告警信息追溯该告警产生的原因并找出对应的指标值。 1. Large omap objects的告警来源如下 void PGMap::get_health_checks(…) const
{
utime_t now ceph_clock_now(); const auto max cct-_conf-get_valuint64_t(mon_health_max_detail); const auto pools osdmap.get_pools();
… if (!detail.empty()) { ostringstream ss; ss pg_sum.stats.sum.num_large_omap_objects large omap objects;//ceph -s中告警信息显示 auto d checks-add(LARGE_OMAP_OBJECTS, HEALTH_WARN, ss.str()); stringstream tip; tip Search the cluster log for Large omap object found for more details.; detail.push_back(tip.str()); d.detail.swap(detail);
}
…
}
从上述代码中可以发现large omap objects的数量统计来源于变量pg_sum.stats.sum.num_large_omap_objects那么通过对变量num_large_omap_objects的跟踪得知large omap objects数量的来源是从ceph的deep scrub中检测出来的。 2. Ceph deep scrub检测large omap objects void PG::scrub_finish()
{
… if (deep_scrub) { if ((scrubber.shallow_errors 0) (scrubber.deep_errors 0)) info.history.last_clean_scrub_stamp now; info.stats.stats.sum.num_shallow_scrub_errors scrubber.shallow_errors; info.stats.stats.sum.num_deep_scrub_errors scrubber.deep_errors;
info.stats.stats.sum.num_large_omap_objects scrubber.omap_stats.large_omap_objects; info.stats.stats.sum.num_omap_bytes scrubber.omap_stats.omap_bytes; info.stats.stats.sum.num_omap_keys scrubber.omap_stats.omap_keys; dout(25) __func__ shard pg_whoami num_omap_bytes info.stats.stats.sum.num_omap_bytes num_omap_keys info.stats.stats.sum.num_omap_keys dendl; publish_stats_to_osd(); } else {
…
}
上述的关键变量并给他们做出解释。
num_large_omap_objects指存储池中omap key或omap bytes超过阈值的shard数。
num_omap_bytes对象的omap大小这里的对象可以是bucket所有shard。
num_omap_keys对象的omap key数量这里的对象可以是bucket所有shard。
跟踪变量num_large_omap_objects这里的large_omap_objects是从下面的函数获取。 3.获取变量large_omap_objects void PGBackend::be_omap_checks(…) const
{
… ScrubMap::object obj it-second; omap_stats.omap_bytes obj.object_omap_bytes; omap_stats.omap_keys obj.object_omap_keys; if (obj.large_omap_object_found) { pg_t pg; auto osdmap get_osdmap(); osdmap-map_to_pg(k.pool, k.oid.name, k.get_key(), k.nspace, pg); pg_t mpg osdmap-raw_pg_to_pg(pg); omap_stats.large_omap_objects; warnstream Large omap object found. Object: k PG: pg ( mpg ) Key count: obj.large_omap_object_key_count Size (bytes): obj.large_omap_object_value_size \n; break; }
…
}
从上述判断if (obj.large_omap_object_found)得知需要根据变量large_omap_object_found来确定large omap对象的判断依据。 4.Ceph deep scrub获取omap相关属性并将单个shard的omap_keys和omap_bytes跟阈值进行比较判断当前shard是否为large omap objects。 int ReplicatedBackend::be_deep_scrub(…)
{
… int max g_conf-osd_deep_scrub_keys; while (iter-status() 0 iter-valid()) { pos.omap_bytes iter-value().length(); pos.omap_keys;//获取omap_keys数量 --max; fixme: we can do this more efficiently. bufferlist bl; ::encode(iter-key(), bl); ::encode(iter-value(), bl); pos.omap_hash bl; iter-next();
… } if (pos.omap_keys cct-_conf- osd_deep_scrub_large_omap_object_key_threshold || pos.omap_bytes cct-_conf- osd_deep_scrub_large_omap_object_value_sum_threshold) { dout(25) __func__ poid large omap object detected. Object has pos.omap_keys keys and size pos.omap_bytes bytes dendl;
o.large_omap_object_found true;//比较每个分片的阈值 o.large_omap_object_key_count pos.omap_keys; o.large_omap_object_value_size pos.omap_bytes; map.has_large_omap_object_errors true; }
…
}
当变量large_omap_object_found为true时需要满足以下两个条件之一即可
1.pos.omap_keysosd_deep_scrub_large_omap_object_key_threshold
2.pos.omap_bytesosd_deep_scrub_large_omap_object_value_sum_threshold 默认阈值 osd_deep_scrub_large_omap_object_key_threshold为20000020万
osd_deep_scrub_large_omap_object_value_sum_threshold为1G
通过上述变量的跟踪可以得知large omap objects是通过ceph deep scrub进行发现并上报的遍历每个索引对象bucket中的分片分片配置下文会讲当分片的omap_keys的数量或omap_bytes的大小超过上述阈值时会影响到large omap objects并产生告警。 分片配置
当用户将大量对象数百万个都存在一个bucket中时存储桶的索引操作会让性能受到严重影响。RGW中存在配置可以对索引进行分片减少性能瓶颈。在对象存储中存在配置分片shard存在两个方法
1. rgw_override_bucket_index_max_shards默认值为0
2. bucket_index_max_shards这个在multisite中应用较多这里不讨论 在图1中看到的bucket info中rgw_override_bucket_index_max_shards值为0 表示桶的索引分片处于关闭状态。下面我们可以看下这个配置的生效阶段。 1. 获取bucket分片值 int RGWRados::init_complete()
{
… bucket_index_max_shards (cct-_conf-rgw_override_bucket_index_max_shards ? cct-_conf-rgw_override_bucket_index_max_shards : get_zone().bucket_index_max_shards); if (bucket_index_max_shards get_max_bucket_shards()) { bucket_index_max_shards get_max_bucket_shards();//最大值不能超过65521 ldout(cct, 1) __func__ bucket index max shards is too large, reset to value: get_max_bucket_shards() dendl; } ldout(cct, 20) __func__ bucket index max shards: bucket_index_max_shards dendl;
…
}
上述bucket分片值在创建桶的时候进行了初始化如下 int RGWRados::create_bucket(…)
{
… if (pmaster_num_shards) { info.num_shards *pmaster_num_shards; } else { info.num_shards bucket_index_max_shards;
}
… int r init_bucket_index(info, info.num_shards);
…
}
bucket_index_max_shards最终写入到bucket info的num_shards中。 对象的shard分配
下面我们通过上传一个对象来跟踪该对象最终是怎么分配到具体的shard中的。为了测试方便我们将rgw_override_bucket_index_max_shards设置为5.
RGW中上传对象的函数入口 int RGWRados::get_bucket_index_object(const string bucket_oid_base, const string obj_key,//上传的对象名称 uint32_t num_shards, RGWBucketInfo::BIShardsHashType hash_type, string *bucket_obj, int *shard_id)
{ int r 0; switch (hash_type) { case RGWBucketInfo::MOD: if (!num_shards) { By default with no sharding, we use the bucket oid as itself (*bucket_obj) bucket_oid_base; if (shard_id) { *shard_id -1; } } else { uint32_t sid rgw_bucket_shard_index(obj_key, num_shards);//用hash算法获取shard_id
… } } break; default: r -ENOTSUP; } return r;
}
inline函数如下rgw_bucket_shard_index将obj_key和设置的shard数通过哈希和求模算法得到具体的分片序号。根据哈希的伪随机可知同名对象的shard分配是固定的也就是如果对象删除后再次上传在相同的shard上仍然能找到同名的对象。 static inline uint32_t rgw_bucket_shard_index(const std::string key, int num_shards) { uint32_t sid ceph_str_hash_linux(key.c_str(), key.size()); uint32_t sid2 sid ^ ((sid 0xFF) 24); return rgw_shards_mod(sid2, num_shards);
}
这里做如下测试向同一个bucket中上传两次同名文件但是大小不一样验证其shard分片
1.查看创建桶的shard数rgw_override_bucket_index_max_shards值为5.对应的bucket的分片数也为5,格式为.dir.markder.shard. 2. 向测试桶testbk中上传对象test_obj对象存储在分片2中即.dir.marker.2。 3. 删除原test_obj并上传不同大小的test_obj对象仍然存储在分片2中 从上述测试结果来看验证了同名对象分配的shard是不变的。 Bucket list请求
这次问题发生的前提条件就是通过RGW日志发现client存在大量的bucket list请求导致IO无法及时响应存在slow request问题。我们下面探索一下list请求的底层处理。 RGW请求处理
RGW作为client在发出list object请求时调用关系如下 1. RGWRados::open_bucket_index调用如下函数根据bucket_info.num_shards获取bucket全部的分片便于后面进行遍历查找。 2. issue_bucket_list_op操作分类两个主要步骤
1 定义bucket list操作类型便于OSD进行对应的请求处理。
2 进行异步操作。 static bool issue_bucket_list_op(…) { librados::ObjectReadOperation op; cls_rgw_bucket_list_op(op, start_obj, filter_prefix, num_entries, list_versions, pdata); return manager-aio_operate(io_ctx, oid, op);
}
上述函数首先定义了bucket list的操作类型然后异步执行这个操作。我们进一步查看这个OP的类型如下。
通过cls_rgw_bucket_list_op定义了这个请求的OP类型以便于后续OSD接收到请求后对应的请求处理。 void cls_rgw_bucket_list_op(…)
{
… op.exec(RGW_CLASS, RGW_BUCKET_LIST, in, new ClsBucketIndexOpCtxrgw_cls_list_ret(result, NULL));
}
#define RGW_CLASS rgw
#define RGW_BUCKET_LIST bucket_list
以下是RGW请求发送及OSD请求处理关系图 图2 RGWOSD发送消息处理 Op类型定义
通过宏定义知道这个OP类型是bucket_list操作结合图2后续OSD收到该请求后会调用对应的函数进行处理。
紧接着librados::ObjectOperation::exec à ObjectOperation::call
查看ObjectOperation::call增加了OSD的操作类型是CEPH_OSD_OP_CALL。 void call(const char *cname, const char *method, bufferlist indata, bufferlist *outdata, Context *ctx, int *prval) { add_call(CEPH_OSD_OP_CALL, cname, method, indata, outdata, ctx, prval); } 请求异步执行 发送对应的请求给OSD处理。_send_op中定义的消息类型是MOSDOp
class MOSDOp : public MOSDFastDispatchOp
通过类的定义可知继承了fastdispatch。消息类型是CEPH_MSG_OSD_OP。 MOSDOp() : MOSDFastDispatchOp(CEPH_MSG_OSD_OP, HEAD_VERSION, COMPAT_VERSION), partial_decode_needed(true), final_decode_needed(true) { } OSD消息处理
通过发送的fastdispatch消息类型定义OSD会从ms_fast_dispatch入口进行处理调用关系如下OSD将此消息进行入队操作如图2。 OSD消息队列处理
PrimaryLogPG::do_osd_ops函数中涉及到omap key操作的有三种情况 1.case CEPH_OSD_OP_CALL
这里会处理RGW过来的bucket list请求操作。 ClassHandler::ClassMethod *method cls-get_method(mname.c_str()); if (!method) { dout(10) call method cname . mname does not exist dendl; result -EOPNOTSUPP; break; } int flags method-get_flags(); if (flags CLS_METHOD_WR) ctx-user_modify true; bufferlist outdata; dout(10) call method cname . mname dendl; int prev_rd ctx-num_read; int prev_wr ctx-num_write; result method-exec((cls_method_context_t)ctx, indata, outdata);
上述method是调用RGW的bucket list也就是OP类型定义中确定的rgw_bucket_list函数。然后调用read_bucket_header à cls_cxx_map_read_header
再次调用do_osd_ops 的case CEPH_OSD_OP_OMAPGETHEADER int cls_cxx_map_read_header(cls_method_context_t hctx, bufferlist *outbl)
{ PrimaryLogPG::OpContext **pctx (PrimaryLogPG::OpContext **)hctx; vectorOSDOp ops(1); OSDOp op ops[0]; int ret; op.op.op CEPH_OSD_OP_OMAPGETHEADER; ret (*pctx)-pg-do_osd_ops(*pctx, ops); if (ret 0) return ret; outbl-claim(op.outdata); return 0;
}
2.case CEPH_OSD_OP_OMAPGETHEADER 1. omap get header的处理情况 case CEPH_OSD_OP_OMAPGETHEADER: tracepoint(osd, do_osd_op_pre_omapgetheader, soid.oid.name.c_str(), soid.snap.val); if (!oi.is_omap()) { return empty header break; } ctx-num_read; { osd-store-omap_get_header(ch, ghobject_t(soid), osd_op.outdata); ctx-delta_stats.num_rd_kb SHIFT_ROUND_UP(osd_op.outdata.length(), 10); ctx-delta_stats.num_rd; } break;
2. 如果ceph底层采用filestore那么调用关系如下
FileStore::omap_get_header
— DBObjectMap::get_header int DBObjectMap::get_header(const ghobject_t oid, bufferlist *bl)
{ MapHeaderLock hl(this, oid); Header header lookup_map_header(hl, oid); if (!header) { return 0; } return _get_header(header, bl);
}
从上述函数可知最后是从数据库中获取对应数据,并存入内存变量bl中
3.case CEPH_OSD_OP_OMAPGETKEYS
这里调用DBObjectMap::get_iterator获得数据库的迭代器用来处理listomapkeys请求从下面代码得知最终是根据oid从数据库中获取数据。最后将获得的key序列化到内存变量bl中。如果是对default.rgw.buckets.index中索引对象进行listomapkeys那么这里encode到bl中的就是bucket内的对象名。 ObjectMap::ObjectMapIterator DBObjectMap::get_iterator( const ghobject_t oid)
{ MapHeaderLock hl(this, oid); Header header lookup_map_header(hl, oid); if (!header) return ObjectMapIterator(new EmptyIteratorImpl()); DBObjectMapIterator iter _get_iterator(header); iter-hlock.swap(hl); return iter;
} Object收到OSD消息的处理 C_ObjectOperation_decodekeys::finish如下 void finish(int r) override { if (r 0) { bufferlist::iterator p bl.begin(); try { if (pattrs) ::decode(*pattrs, p); if (ptruncated) { std::setstd::string ignore; if (!pattrs) { ::decode(ignore, p); pattrs ignore; } if (!p.end()) { ::decode(*ptruncated, p); } else { // the OSD did not provide this. since old OSDs do not // enfoce omap result limits either, we can infer it from // the size of the result *ptruncated (pattrs-size() max_entries); } } } catch (buffer::error e) { if (prval) *prval -EIO; } } }
将传入的bl进行反序列化得到该bucket下的对象名称。 rados listomapkeys
用户采用RGW进行list objects操作时其实是对bucket instance的每个分片进行遍历。底层提供了rados listomapkeys命令可以对单个分片进行list操作下面简单介绍其调用关系。
我们从命令说起如下 rados -p default.rgw.buckets.index listomapkeys .dir.marker.shard_id
命令执行的入口
rados_tool_common
-librados::IoCtx::omap_get_keys
在librados::IoCtx::omap_get_keys分为两个重要的操作
1. ibrados::ObjectReadOperation::omap_get_keys2 -librados::ObjectReadOperation::omap_get_keys定义请求OP类型为CEPH_OSD_OP_OMAPGETKEYS 2. librados::IoCtx::operate发送操作的消息至OSD。后续的消息发送路径与RGW的请求一致。这里给出调用关系librados::IoCtxImpl::operate_read— … Objecter::_send_op OSD根据请求类型CEPH_OSD_OP_OMAPGETKEYS在PrimaryLogPG::do_osd_ops对该场景进行处理获取bucket instance分片下的omap keys即对象名称。 总结
1.large omap objects的告警信息来源于ceph deep scrub。
2.假设存在如下值根据以上的分析逻辑当pool default.rgw.buckets.index中单个bucket分片超过20万时会出现告警那么在不产生large omap objects告警的情况下单个bucket最多存放64*20万1280万个对象。
osd_deep_scrub_large_omap_object_key_threshold20000020万默认值
rgw_override_bucket_index_max_shards64
3.如果想提高bucket list性能业界常用的做法是用SSD为索引pool加速同时修改以上两个参数值到一个合理值。