最近遇到这样一个情况,ceph运行环境里时间被修改到以前的时间之后,ceph -sceph df都看不到在这之后创建的资源池了。

在大佬的带领下,得知了这部分数据来源于ceph-mgrpgmap例行同步。时间出现变化之后,debug日志里也的确不再出现pgmap的同步日志了。从这个方向找到了入手点。

ceph-mgr pgmap日志代码溯源(基于Luminous版本)

根据打开调试信息后, 看到的日志中大量的pgmap v137057: 565 pgs: 576 active+clean

搜索日志pgmap v,是在DaemonServer.cc中调用的send_report

自底向上

MgrStandby和Mgr是组合关系,MgrStandby里实例化了一个active的Mgr

在触发一次tick之后,还会记录一个事件,好像让下一次触发timer.add_event_adter(g_conf->get_val<int64_t>("mgr_tick_period") new FunctionContext([this](int r))){ tick();}的逻辑

对,在MgrStandby启动时调用init,触发第一次tick。之后应该就是这个计时器在工作。

这个mgr_tick_period的功能,看看咋工作的,好像只是std::chrono::seconds的封装。

这个mgr_tick_period应该只是个配置文件,记录多久同步的吧,果然,ceph daemon里可以看到是2s一次。

timer.add_event_after应该才是重头戏,这里居然是设置的根据时间戳来调用add_event_at添加回调的任务……当timer_thread执行到ceph_clock_now,用gettimeofday拿到系统时间,如果当前时间大于设置的时间,就是永远不会触发了?

所以这里存在一个潜在问题,是不是所有使用这个计时器的地方都存在会直接受到这个时钟往回调影响的问题。目前来看定时器应该只有这一个。

SafeTimer理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

class SafeTimer
{
CephContext *cct;
Mutex& lock;
Cond cond;
bool safe_callbacks; // 是否是safe_callbacks

friend class SafeTimerThread;
SafeTimerThread *thread; //定时器执行线程

void timer_thread(); //本函数一次检查scheduler中的任务是否到期,其循环检查任务是否到期执行。
void _shutdown();

std::multimap<utime_t, Context*> schedule; //目标时间和定时任务执行函数Context
std::map<Context*, std::multimap<utime_t, Context*>::iterator> events; //定时任务<-->定时任务在shedule中的位置映射
bool stopping; //是否停止

// 下面这个应该才是真实在后台不停刷新检查定时器任务的线程
class SafeTimerThread : public Thread {
SafeTimer *parent;
public:
explicit SafeTimerThread(SafeTimer *s) : parent(s) {}
void *entry() override {
parent->timer_thread();
return NULL;
}
};

以ceph-mgr为例,调用顺序是这样的。

ceph_mgr.cc里实例化MgrStandbyMgrStandby实例化SafeTimer,然后ceph_mgr.cc调用mgr.init,里面调用SafeTimer实例的init,在这里

1
2
thread = new SafeTimerThread(this);
thread->create("safe_timer");

mgr的定时器任务就开始周期执行了。根据safe_callbacks这把锁的状态决定是否要申请到锁阻塞式执行任务,还是非阻塞式。

时间的触发基于cond.WaitUnitl(lock, schedule.begin()->first这个的实现。这个中主要依赖的应该是pthread_cond_timedwait这个超时等待接口。似乎有些库里的sleep就是通过这个实现的。

pthread_cond_timedwait()函数阻塞住调用该函数的线程,等待由cond指定的条件被触发(pthread_cond_broadcast() or pthread_cond_signal())。如果超时了,就可以作为定时器使用。这里传入的是当前的abstime

是否可能使用monotonic运行时间,而不是绝对时间?

但是这块我好像看到,这个函数其实是支持传入monotonic时钟的,这个时钟开机开始计数,不受外部影响。

根据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#man 3 pthread_condattr
DESCRIPTION
Condition attribute objects are used to specify parameters to the
pthread_cond_init(3) function. The pthread_condattr_init() function
initializes a condition attribute object with the default attributes and
the pthread_condattr_destroy() function destroys a condition attribute
object. The pthread_condattr_getclock() function shall obtain the value
of the clock attributes object referenced by attr. The
pthread_condattr_setclock() function sets the system clock to be used for
time comparisons to the one specified in clock. Valid clock values are
**CLOCK_MONOTONIC** and **CLOCK_REALTIME** (the default). The
pthread_condattr_getpshared() function shall obtain the value of the
process-shared attribute from the attributes object referenced by attr.
The pthread_condattr_setpshared() function shall set the process-shared
attribute in an initialized attributes object referenced by attr.

可见应该是支持的,但是这个时钟不知道在分布式环境,能不能保证多节点内及时被chrony或者ntp协调一致。根据这个[^5]来看,应该是能通过ntp服务来保障长时间稳定一致的。

ceph社区是否存在相关思路的方案

根据[^6]的结果来说,mds是应用上了这套,也向下移植到了Luminous版本。

根据[^7],在bluestoreperf上也用上了这个时钟来进行latency延时统计。在Nautilus版本里增加的功能。

Luminous版本里,我也搜到了这个在[^7]里使用的coarse_mono_clock::now()时钟。

根据[^8],在Mimic版本里,好像mon也迁移了部分。

根据[^10],在Mimic版本里,rados bench也开始使用这个时钟。

所以都只是部分组件已支持, 只是mgr没有支持, 目前来看也没有统一迁移到支持的计划.

官方已提供common/Timer: use mono_clock for clock_t by tchaikov · Pull Request #39273 · ceph/ceph

Reference

  1. 《Ceph源码分析》
  2. ceph中的SafeTimer类详解_turou3442的博客-CSDN博客_ceph mds 定时器safe_timer线程cpu占用率高
  3. ceph中的SafeTimer 用法和分析_jason的笔记-CSDN博客_safe_timer
  4. linux多线程编程,你还在用sleep么?用pthread_cond_timedwait吧 - shzwork的个人空间 - OSCHINA
  5. linux - What is the difference between CLOCK_MONOTONIC & CLOCK_MONOTONIC_RAW? - Stack Overflow
  6. Bug #26959: mds: use monotonic clock for beacon message timekeeping - fs - Ceph
  7. os/bluestore: use the monotonic clock for perf counters latencies by mogeb · Pull Request #22121 · ceph/ceph
  8. Ceph v14.2.0 Nautilus released - Ceph
  9. mon: a few conversions to monotonic clock by batrick · Pull Request #18595 · ceph/ceph
  10. tools/rados: use the monotonic clock in rados bench by mogeb · Pull Request #18588 · ceph/ceph

附录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
digraph G {
splines="FALSE";
// rankdir="BT";
node [shape="cds", color="#dddddd", penwidth="1",style="filled", height=0.5, fontname="Futura", fontsize=10]

/* Relationships */
"ceph_mgr.cc" -> "ceph_mgr.cc:MgrStandby mgr"
"ceph_mgr.cc" -> "ceph_mgr.cc:mgr.init()" -> "MgrStandby.cc:MgrStandby::init()"
"ceph_mgr.cc:MgrStandby mgr" -> "MgrStandby.cc:MgrStandby::MgrStandby()"
"MgrStandby.cc:MgrStandby::init()" -> "timer.init();"
"timer.init();" -> "thread = new SafeTimerThread(this);" -> "parent->timer_thread();" -> "cond.WaitUntil(lock, schedule.begin()->first);" -> t_c
t_c [label="pthread_cond_timedwait(&_c, &mutex._m, &ts)", color="#dfc1c1"]
"MgrStandby.cc:MgrStandby::init()" -> "MgrStandby.cc:MgrStandby::handle_mgr_map(MMgrMap* mmap)"
"MgrStandby.cc:MgrStandby::init()" -> "tick();"
"tick();" -> "add_event_adter(g_conf->get_val<int64_t>(\"mgr_tick_period\") new FunctionContext([this](int r))){tick();" -> "SafeTimer::add_event_after" -> "add_event_at(when, callback);"
"SafeTimer::add_event_after" -> "utime_t when = ceph_clock_now(); when += seconds;" -> "gettimeofday(&tv, NULL);"
"MgrStandby.cc:MgrStandby::handle_mgr_map(MMgrMap* mmap)" -> "active_mgr.reset(new Mgr..." [label = "组合,实例化"];
"add_event_adter(g_conf->get_val<int64_t>(\"mgr_tick_period\") new FunctionContext([this](int r))){tick();" ->
"Mgr.cc:Mgr::tick()"->
"DaemonServer.cc:void DaemonServer::send_report()"
}