Ceph MDS 的半衰期方法的 inode 活跃度统计
本文代码以 Ceph 16.2.10 为准。
MDS 的活跃度统计方法
MDS 会统计 inode 的访问活跃度。
对于任何类型的 inode,都有两个计数器,分别表示该 inode 的读、写活跃度。MDS 会在该 inode 发生读或写时,给相应的计数器增加 1。
为了使活跃度的语义更合理,MDS 为活跃度增加了衰变设定:每过一段时间,活跃度值就减少为原来的一半。相比于一直简单累加或只保留最近一段时间的值,这样做更能体现 inode 被访问的情况。
模拟半衰期
设常数 \(H > 0\),考察函数
\[f_H(x) = (\frac{1}{2})^\frac{x}{H}
\]
则有对任意实数 \(x\),有
\[f_H(x+H)=\frac{1}{2}f_H(x)
\]
可见,函数 \(f_H\) 的特点类似于放射性元素的衰变,自变量每增大 \(H\),函数值就是原来的一半。
在计算机中,计算 \(e^x\) 比计算其他数的方幂更快,所以为了提高计算速度,令
\[k = \frac{1}{H} \ln \frac{1}{2}
\]
则
\[(\frac{1}{2})^\frac{x}{H} = e^{kx}
\]
Ceph 相关代码
在 src/common/DecayCounter.h
class DecayRate {
public:
friend class DecayCounter;
DecayRate() {}
// cppcheck-suppress noExplicitConstructor
DecayRate(double hl) { set_halflife(hl); }
DecayRate(const DecayRate &dr) : k(dr.k) {}
void set_halflife(double hl) {
k = log(0.5) / hl;
}
double get_halflife() const {
return log(0.5) / k;
}
private:
double k = 0; // k = ln(0.5)/half_life
};
DecayRate 描述衰变率,它的构造函数和 set_halflife 函数的参数都是半衰期,而自身保存了经过计算的 \(k\)。
// 去掉了一些无关的代码和注释
class DecayCounter {
public:
using time = ceph::coarse_mono_time;
using clock = ceph::coarse_mono_clock;
explicit DecayCounter(const DecayRate &rate) : last_decay(clock::now()), rate(rate) {}
double get() const {
decay();
return val;
}
double get_last() const {
return val;
}
time get_last_decay() const {
return last_decay;
}
double hit(double v = 1.0) {
decay(v);
return val;
}
void adjust(double v = 1.0) {
decay(v);
}
void scale(double f) {
val *= f;
}
void reset() {
last_decay = clock::now();
val = 0;
}
protected:
void decay(double delta) const {
auto now = clock::now();
double el = std::chrono::duration<double>(now - last_decay).count();
// calculate new value
double newval = val * exp(el * rate.k) + delta;
if (newval < 0.01) {
newval = 0.0;
}
val = newval;
last_decay = now;
}
void decay() const {decay(0.0);}
private:
mutable double val = 0.0; // value
mutable time last_decay = clock::zero(); // time of last decay
DecayRate rate;
};
比较核心的函数是 decay,表示把旧值衰变,并累加一个值,达到一边增加一边衰变的效果。
一个 inode 一旦被访问,函数 MDBalancer::hit_inode (参见 src/mds/MDBalancer.cc)就会被调用,
void MDBalancer::hit_inode(CInode *in, int type, int who)
{
// hit inode
in->pop.get(type).hit();
if (in->get_parent_dn())
{
hit_dir(in->get_parent_dn()->get_dir(), type, who);
}
}
这里的 CInode 就表示一个 inode,它有成员 pop:
class CInode : public MDSCacheObject, public InodeStoreBase, public Counter<CInode> {
public:
inode_load_vec_t pop;
// ...
}
而 pop 里面主要就是两个计数器:
class inode_load_vec_t {
public:
static const size_t NUM = 2;
inode_load_vec_t(const DecayRate &rate) : vec{DecayCounter(rate), DecayCounter(rate)} {}
DecayCounter &get(int t) {
return vec[t];
}
private:
std::array<DecayCounter, NUM> vec;
// ...
};
MDBalancer::hit_inode 的参数 type 的取值范围在这里:
#define META_POP_IRD 0
#define META_POP_IWR 1
#define META_POP_READDIR 2
#define META_POP_FETCH 3
#define META_POP_STORE 4
#define META_NPOP 5
对于一般 inode,type 只能是 META_POP_IRD 和 META_POP_IWR,也就是只统计读和写。对于目录类型的 inode,还会统计别的信息,不再赘述,因为思路和代码都是类似的。

浙公网安备 33010602011771号