ceph log源码分析

ceph log源码分析

ceph中log的处理方法是,主线程生成并提交日志条目给log线程处理,典型用法如下:
ldout (cct, log_level)<< msg << dendl, 其中msg是要写入日志的内容.
以src/librbd/librbd.cc为例:

// 子系统名称,ceph的每个log模块是一个子系统
#define dout_subsys ceph_subsys_rbd
// 根据子系统重新定义dout_prefix,因为它最初定义在src/commom/dout.h中
#undef dout_prefix
#define dout_prefix *_dout << "librbd: "

// aio_read中log的用法, bl是读入数据的buffer
ldout(ictx->cct, 10) << "Image::aio_read() buf=" << (void *)bl.c_str() << "~"
			 << (void *)(bl.c_str() + len - 1) << dendl;

宏ldout和dend定义在文件src/common/dout.h中,如下所示:

#define dout_prefix *_dout

// sub: 子系统名称, v: 日志级别
#define dout_impl(cct, sub, v)						\
  do {									\
    if (cct->_conf->subsys.should_gather(sub, v)) {			\
    	if (0) {								\
        char __array[((v >= -1) && (v <= 200)) ? 0 : -1] __attribute__((unused)); \
  }									\
    static size_t _log_exp_length=80; \
    ceph::log::Entry *_dout_e = cct->_log->create_entry(v, sub, &_log_exp_length);	\
    ostream _dout_os(&_dout_e->m_streambuf);				\
    CephContext *_dout_cct = cct;					\
    std::ostream* _dout = &_dout_os;

#define ldout(cct, v)  dout_impl(cct, dout_subsys, v) dout_prefix

#define dendl std::flush;				\
  _ASSERT_H->_log->submit_entry(_dout_e);		\
    }						\
  } while (0)
// 宏替换之前
ldout(ictx->cct, 10) << "Image::aio_read() buf=" << (void *)bl.c_str() << "~"
			 << (void *)(bl.c_str() + len - 1) << dendl;
// 宏替换之后
do {									\
	if (cct->_conf->subsys.should_gather(ceph_subsys_rbd, v)) {			\
    	if (0) {								\
        	char __array[((v >= -1) && (v <= 200)) ? 0 : -1] __attribute__((unused)); \
        }									\
	    static size_t _log_exp_length=80; \
	    ceph::log::Entry *_dout_e = cct->_log->create_entry(v, ceph_subsys_rbd, &_log_exp_length);	\
	    ostream _dout_os(&_dout_e->m_streambuf);				\
	    CephContext *_dout_cct = cct;					\
	    std::ostream* _dout = &_dout_os;
	    *_dout << "librbd: "<< "Image::aio_read() buf=" << (void *)bl.c_str() << "~"
				 << (void *)(bl.c_str() + len - 1) << std::flush;				\
	  	_dout_cct->_log->submit_entry(_dout_e);		\
	}						\
} while (0)

从以上代码可以看出,由宏ldoutdendl组成一条完整的do { … } while(0)代码块,并在do中进行日志的生成和提交.其中利用**create_entry(来生成一个日志条目,利用submit_entry()**来提交一个日志条目.这两个工作都是由主线程调用log线程进行的处理的.

日志定义

log子系统主要的实现在目录src/log中,里面有一个很重要的类Log,它继承自线程类,自带线程处理日志功能. 因为打印日志,会影响系统的性能,特别是c++的流,对性能影响更明显.ceph这里采取了一些优化:

  1. ceph 对每个子系统的日志都预先定义了日志级别,并且可动态修改.
  2. 每条log都带有日志级别,日志级别越低,优先级越高.低于预先定义的级别才会被打印.
  3. log 信息只需要提交到Log类的线程即可,由log线程接管后台打印日志的任务.对提交log的线程影响较小

Log类的实现比较简单,维护两个队列,m_new用于提交新日志,flush的时候获取m_new的entry用来刷新,m_recent用来存放最近的日志,比如用户通过admin socket发送dump log的命令时,就会将m_recent的日志dump到文件:

class Log : private Thread
{
  Log **m_indirect_this;

  SubsystemMap *m_subs; // 每个子系统的日志级别的map
  
  pthread_mutex_t m_queue_mutex; // 这个锁专门用来提交日志
  pthread_mutex_t m_flush_mutex; // 这个锁用来打印提交的日志
  pthread_cond_t m_cond_loggers;
  pthread_cond_t m_cond_flusher;

  pthread_t m_queue_mutex
Ceph中,stripe是一种将数据分片存储的概念。当进行文件读取操作时,需要通过一系列的计算来确定数据所在的具体位置。本文以CephFS的文件读取流程为例进行分析。 首先,在文件读取过程中,Ceph会将文件划分为若干个条带(stripe),每个条带由多个对象分片(stripe unit)组成。条带可以看作是逻辑上连续的一维地址空间。 接下来,通过file_to_extent函数将一维坐标转化为三维坐标(objectset,stripeno,stripepos),来确定具体的位置。其中,objectset表示所在的对象集,stripeno表示条带号,stripepos表示条带内的偏移位置。 具体的计算过程如下:假设需要读取的数据的偏移量为offset,每个对象分片的大小为su(stripe unit),每个条带中包含的对象分片数为stripe_count。 首先,计算块号blockno = offset / su,表示数据所在的分片号。 然后,计算条带号stripeno = blockno / stripe_count,表示数据所在的条带号。 接着,计算条带内偏移stripepos = blockno % stripe_count,表示数据在条带内的偏移位置。 接下来,计算对象集号objectsetno = stripeno / stripes_per_object,表示数据所在的对象集号。 最后,计算对象号objectno = objectsetno * stripe_count + stripepos,表示数据所在的对象号。 通过以上计算,可以确定数据在Ceph中的具体位置,从而完成文件读取操作。 需要注意的是,以上分析是基于Ceph版本10.2.2(jewel)进行的,尽管版本跨度较大,但是该部分代码在12.2.10(luminous)版本中仍然比较稳定,基本的框架没有发生变化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值