接着上一篇05| DLM分布式锁管理器(1)继续分析DLM。
六、 整个加锁、解锁流程可以用如下的简图来描述:
七、加锁流程:ocfs2客户端发起加锁请求 __ocfs2_cluster_lock()
加锁标记:
/* ocfs2_lock_res->l_flags flags. */
#define OCFS2_LOCK_ATTACHED (0x00000001) /* we have initialized the lvb */
#define OCFS2_LOCK_BUSY (0x00000002) /* we are currently in dlm_lock */
#define OCFS2_LOCK_BLOCKED (0x00000004) /* blocked waiting to downconvert*/
前提条件:
1)当前dlm不处于加锁OCFS2_LOCK_BUSY和降级OCFS2_LOCK_BLOCKED处理中,加锁级别大于当前锁级别。
2)客户端ocfs2将锁请求发送到服务端DLM上,根据锁资源的标记res->l_flags是否设置过OCFS2_LOCK_ATTACHED标记来决定此次锁请求时加锁还是锁转换,从而确定下服务端DLM的锁请求标记flags,加锁请求会清除DLM_LKF_CONVERT标记,转换请求会设置DLM_LKF_CONVERT标记,这样服务端DLM根据flag标记来判断时加锁请求还是锁转换请求。
if (!(lockres->l_flags & OCFS2_LOCK_ATTACHED)) {
lockres->l_action = OCFS2_AST_ATTACH;
lkm_flags &= ~DLM_LKF_CONVERT;
} else {
lockres->l_action = OCFS2_AST_CONVERT;
lkm_flags |= DLM_LKF_CONVERT;
}
3)客户端ocfs2将锁请求级别设置到锁资源请求域res->l_requested = level,设置锁资源标记res->flags |= OCFS2_LOCK_BUSY& OCFS2_LOCK_PENDING
进入服务端DLM处理:o2cb_dlm_lock
判断锁请求标记flags:
1、如果是加锁请求:实际上就是upconvert
1.1、本地新生成一把锁dlm_lock,查找锁资源dlm_get_lock_resource()(前文已分析),如果有,就获取该锁资源res,如果没有,就新生成锁资源res,并完成锁主res->owner选举,并在锁dlm_lock中关联该锁资源res,锁dlm_lock上关联AST/BAST处理函数。根据锁资源的锁主res->owner:
a)如果本节点是锁主(local),就直接在本地处理锁请求;
b)如果本节点不是锁主,远端节点是锁主(remote),本地先将锁资源res->state设置为正在处理状态IN_PROGRESS,然后将锁dlm_lock加入锁资源阻塞链表res->blocked,锁设置为res->lock_pending = 1,最后,通过DLM消息将锁请求(携带本节点号为请求节点,请求锁级别)发送create lock消息给远端锁主处理,然后本节点就休眠等待。
1.2、锁主Master:dlmlock_master()
如果远端remote节点是锁主的话,首先,remote节点锁主先新建一把属于请求节点的锁dlm_lock,并在锁dlm_lock中关联锁资源res。然后再调用本地dlmlock_master()处理。
a)锁级别是可兼容的,直接将锁lock加入锁资源res->granted链表上,加锁成功,加锁状态status = NORMAL。随后把锁dlm_lock加入到等待AST处理队列dlm->pending_asts,把锁资源res加入到dlm->dirty_list脏队列上,锁资源状态设置为脏res->state = DIRTY,由dlm thread线程集中处理这些脏的锁资源res和锁dlm_lock的AST消息。
b)锁级别是不兼容的,
b1)如果是非阻塞,比如:对$RECOVERY加锁就是非阻塞,就立即返回加锁状态status = NOTQUEUED,函数返回。
b2)如果是可阻塞,就只能把锁先放到锁资源阻塞链表res->blocked上,这个链表上都是由于锁级别不兼容且还没有处理的锁,加锁状态status = NORMAL。把锁资源res加入到dlm->dirty_list脏队列上,锁资源状态设置为脏res->state = DIRTY,由dlm thread线程集中处理这些脏的锁资源res的BAST消息。
dlmlock_master()处理完成。
2、如果是锁转换请求:已经授予过的锁才需要锁转换,(DLM_LKF_CONVERT标记是在downcovert线程中设置的)
2.1、本节点取出锁dlm_lock和关联的锁资源res,DLM把锁和锁资源交给锁主res->owner节点处理:
a)如果本节点是锁主(local),就直接在本地处理锁请求;
b)如果本节点不是锁主,远端节点是锁主(remote),本地先将锁资源res->state设置为正在处理状态IN_PROGRESS,然后将锁dlm_lock加入锁资源转换链表res->converting上等着,锁设置为res->convert_pending = 1,设置转换类型lock->ml.convert_type = type,然后发convert lock消息给锁主res->owner节点处理。
2.2、锁主Master:dlmconvert_master()
如果远端remote节点是锁主的话,首先,从锁资源授予链表res->grained上找到这把属于请求节点的锁dlm_lock。然后再调用本地dlmconvert_master()处理。
a)如果是锁降级type < lock->ml.type,就走in-place downconvert流程:
锁主立即、直接授予新的锁级别lock->ml.type = type(即:new_level),并将锁移动到锁资源授予链表res->grained末尾,还是在授予链表上。
b)否则就是锁升级type > lock->ml.type,就走upconvert流程:
比较新请求锁级别type跟当前锁资源授予队列res->granted和转换队列res->converting上的锁是否兼容,
b1)如果有不兼容的锁,则该锁需要切换资源队列,将新请求锁级别type赋值到锁转换域lock->ml.convert_type,锁lock转移到锁资源转化队列res->converting的末尾;
b2)如果兼容,锁主立即、直接授予新的锁级别lock->ml.type = type(即:new_level),并将锁移动到锁资源授予链表res->grained末尾。
2.3、随后授予成功的锁就准备调用AST消息处理(没有BAST,只有AST),做AST处理的,就把锁加入到dlm登录AST处理队列dlm->pending_asts。
没有授予成功的锁就等待dlm thread线程做处理,dlm thread线程会找到不兼容该锁级别的那把锁,并向持有这把锁的节点发送BAST消息。
八、DLM线程:
dlm->dlm_thread_task锁主节点处理dlm->dirty_list脏队列中的锁资源和锁的AST/BAST消息的线程。
1、首先,做清理不用的锁资源的操作。
如何清理不用的锁资源?
在dlm thread线程中会判断一个锁资源res是否还在使用,如果本节点上该锁资源res没有锁dlm_lock了,或者锁资源res也不在recovery队列或dlm->dirty_list队列,或者锁资源的引用计数为0了,那这个锁资源就是没用的锁资源,就需要清除掉。将这个锁资源加入到清除队列dlm->purge_list,dlm thread线程会在每个周期中处理dlm->purge_list上不再用的锁资源,a)如果本节点是该锁资源的锁主,则从dlm->purge_list上取下锁资源,并从锁资源哈希表dlm->lockres_hash中删除。b)如果本节点不是该锁资源的锁主,本节点向锁主发送deref lockres消息,通知锁主将本节点从引用计数res->ref_map中删除,然后锁主回复完成,本节点从dlm->purge_list上取下锁资源,并从锁资源哈希表dlm->lockres_hash中删除。
2、处理脏队列dlm->dirty_list。
遍历dlm->dirty_list,如果有锁资源加入,就取下锁资源res,然后分别对这个锁资源的转化队列res->converting中的待转换锁target_lock和阻塞队列res->blocked链表上不兼容阻塞的锁target_lock做如下处理:
遍历锁资源授予队列res->grained上的已授权锁,跟待转换锁target_lock 或 不兼容阻塞的锁target_lock做锁级别兼容性比较:
a)将锁级别兼容的,可授权can_grant的 请求锁target_lock加入到锁资源授予队列res->grained链表上,然后把锁target_lock加入到等待AST队列dlm->pending_asts中,等待AST处理。
b)对锁级别不兼容的,不可授权的请求锁target_lock,找到与其发送冲突的已授权锁lock,将与其发送冲突的已授权锁lock加入到等待BAST队列dlm->pending_basts中,等待BAST处理。
3、锁的AST/BAST消息统一处理,dlm_flush_asts():
3.1、遍历等待AST队列dlm->pending_asts上的请求锁target_lock,向每把请求锁target_lock的请求节点target_lock->ml.node发送AST消息,通知请求加锁节点可以加锁了。(请求加锁节点,如果是远端remote的话,要发送DLM消息承载PROXY_ AST消息;本地local的话就直接调用ast处理函数。)
3.2、遍历等待BAST队列dlm->pending_basts上的已授权锁lock,向每把已授权锁lock的持有高级别锁的节点lock->ml.node发送BAST消息,通知该节点做降级downconvert处理。(持有高级别锁的节点,如果是远端remote的话,要发送DLM消息承载PROXY_ AST消息;本地local的话就直接调用bast处理函数。)
4、清除锁资源状态脏标记res->state ~= DIRTY,继续处理下一个脏锁资源res。
5、异常处理:
如果脏锁资源res正在进行锁资源恢复recoverying(即:锁资源重建),就等待其recovery完成,再将锁资源加入dlm->dirty_list。
锁协议:
static struct ocfs2_locking_protocol lproto = {
.lp_max_version = {
.pv_major = OCFS2_LOCKING_PROTOCOL_MAJOR,
.pv_minor = OCFS2_LOCKING_PROTOCOL_MINOR,
},
.lp_lock_ast = ocfs2_locking_ast,
.lp_blocking_ast = ocfs2_blocking_ast,
.lp_unlock_ast = ocfs2_unlock_ast,
};
九、AST/BAST处理函数: 请求锁节点处理ast;持有高级锁级别的节点处理bast。
AST简介
为了传递锁资源的状态,DLM使用了异步陷阱(AST),它在操作系统处理程序例程中实现为中断。对于Oracle RAC来说,它就是中断。
AST可以是一个"阻塞AST",也可以是一个"获取AST"。
当一个进程请求一个资源上的锁时,DLM向当前对同一资源拥有锁的全部进程发出一个阻塞异步陷阱(BAST)。在必要时,这个锁的拥有者会放弃这个锁,允许请求者获取对该资源的访问。
DLM将向请求者发送一个获取AST(AAST),通知其现在可以拥有这个资源(和这个锁)。通常将AAST看作进程的"唤醒呼叫"。
DLM使用两个队列跟踪所有的lock 请求,并用两个ASTs(asynchronous traps)来完成请求的发送和响应,实际就是异步中断(interrupt)或者陷阱(trap)。
1、锁请求是本节点local的,
a)如果本节点是请求锁:本节点DLM已经完成加锁请求,直接调用do_local_AST处理函数;
b)如果本节点是持有高级别已授权锁lock的节点:本节点直接调用do_local_BAST处理函数。
2、锁请求是其他节点remote的,会收到锁主发送来的PROXY_ AST消息:
a)如果是加锁请求:遍历锁资源阻塞队列res->blocked链表,找到那把锁lock,做do_ast处理。(该节点发送锁请求给锁主时,会在本地把锁加入到res->blocked链表)
b)如果是锁转换请求:遍历锁资源转换队列res->converting链表,找到那把锁lock,做do_ast处理。(该节点发送锁请求给锁主时,会在本地把锁加入到res->converting链表)
c)如果remote节点是持有高级别已授权锁lock的节点:遍历锁资源授予队列res->granted链表,找到同一把锁lock,做do_ast处理。(收到锁主发来的PROXY_ AST消息——消息类型是DLM_BAST的BAST消息,说明本节点对已持有的、已授权锁lock,要做downconvert降级处理)
3、do_ast处理:
a)如果是AST消息,即:锁请求(加锁或锁转换),将锁lock加入锁资源授予队列res->granted链表末尾,本节点加锁成功。如果是锁转换的话,将锁转换域中的锁级别lock->ml.convert_type赋值给锁级别域lock->ml.type,锁转换域中的锁级别lock->ml.convert_type重新设置为无效INVALID_MODE。在锁状态块中记录状态结果lock->lksb->status = NORMAL。调用do_local_ast处理函数。
b)如果是BAST消息,直接调用do_local_bast处理函数。
返回处理状态NORMAL。
4、请求锁的节点调用do_local_AST处理函数:(客户端OCFS2层面的锁资源ocfs2_lock_res)
4.1、获取锁资源状态块中的加锁结果status,如果是失败,就清除加锁前设置的锁资源res->l_flags标记BUSY和BLOCKED(downconvert时设置的)
4.2、如果没有错误,根据客户端OCFS2在加锁时的锁资源动作标记res->l_action分别处理:
a)如果是OCFS2_AST_ATTACH:
客户端OCFS2将请求的锁级别res->l_requested,授予锁资源当前级别res->l_level,即:res->l_level = res->l_requested
给锁资源res->l_flags打上OCFS2_LOCK_ATTACHED标记, 清除BUSY标记。
b)如果是OCFS2_AST_CONVERT:
客户端OCFS2将请求的锁级别res->l_requested,授予锁资源当前级别res->l_level,即:res->l_level = res->l_requested
给锁资源res->l_flags打上OCFS2_LOCK_UPCONVERT_FINISHING标记, 清除BUSY标记。
c)如果是OCFS2_AST_DOWNCONVERT:(客户端OCFS2设置的降级标记,此时DLM已经完成授权降级锁级别)
客户端OCFS2将新的锁级别new_level(降级锁级别)授予锁资源当前级别res->l_level,即:res->l_level = res->l_requested
downconvert处理完成,锁资源阻塞域res->blocking设置为空锁NL。
清除锁资源res->l_flags BLOCKED标记。(锁降级完成,BAST消息处理完成)
5、do_local_BAST处理函数:(持有高级别锁的节点收到锁主发来的BAST消息)
从锁dlm_lock取出关联的锁资源res,判断该锁资源res是否需要做锁降级处理:比较请求锁级别level跟当前锁资源阻塞级别res->l_blocking,
a)如果大于level > res->l_blocking(正常是NL),就需要做downconvert。
将请求锁级别level设置到锁资源阻塞域res->blocking = level,设置阻塞标记res->l_flags |= BLOCKED标记,设置队列标记res->l_flags |= QUEUED,锁资源res被加入到阻塞队列osb->blocked_lock_list,唤醒downconvert线程,随机数+1。
downconvert线程开始处理osb->blocked_lock_list队列中的锁资源。
十、downconvert线程:持有高级别锁的节点处理osb->blocked_lock_list队列中的锁资源,加一把新的、可兼容阻塞请求级别的锁级别,这就是降级处理。
处理osb阻塞队列osb->blocked_lock_list上的锁资源:
1、检查:
a)如果锁资源lockres->l_flags 没有BLOCKED标记了,返回,不做downconvert了。
b)如果锁资源lockres->l_flags 是BUSY标记,说明当前锁资源正在其他线程中做新的锁转换处理中,
b1)如果没有PENDING锁标记,说明已经完成了dlm_lock()函数,此时可以直接设置取消锁转换请求,于是设置ctl->requeue = 1,设置lockres->l_unlock_action = OCFS2_UNLOCK_CANCEL_CONVERT,开启取消这个锁转换的处理(解锁dlm_unlock且Cancel标记),以便开始downconvert处理。
b2)如果有PENDING锁标记,说明还没有完成了dlm_lock()函数,于是设置ctl->requeue = 1,函数先返回再来检查(等待dlm_lock()调用完成)
c)如果本节点刚刚加完锁,此时锁资源状态还是lockres->l_flags & OCFS2_LOCK_UPCONVERT_FINISHING标记,此时收到bast消息,本节点会延迟做downconvert处理,以便本节点有足够的时间做自己的任务。当本节点完成自己的任务后,就尽快做downconvert处理,于是设置ctl->requeue = 1,函数先返回。
if (lockres->l_flags & OCFS2_LOCK_UPCONVERT_FINISHING)
goto leave_requeue;
d)如果本节点已经解锁了,lockres->level = DLM_LOCK_NL,那本节点就不阻塞请求节点了,清除锁资源阻塞域res->blocking = NL,清除阻塞标记res->l_flags ~= BLOCKED标记,函数退出,downconvert结束。
/*
* How can we block and yet be at NL? We were trying to upconvert
* from NL and got canceled. The code comes back here, and now
* we notice and clear BLOCKING.
*/
if (lockres->l_level == DLM_LOCK_NL) {
BUG_ON(lockres->l_ex_holders || lockres->l_ro_holders);
mlog(ML_BASTS, "lockres %s, Aborting dc\n", lockres->l_name);
lockres->l_blocking = DLM_LOCK_NL;
lockres_clear_flags(lockres, OCFS2_LOCK_BLOCKED);
spin_unlock_irqrestore(&lockres->l_lock, flags);
goto leave;
}
e)如果阻塞的是EX锁,lockres->level = DLM_LOCK_EX,且本节点又持有任何ex_holders/ro_holders锁计数,说明本节点正在持有锁,做处理,等待解锁完成,于是设置ctl->requeue = 1,函数先返回。
/* if we're blocking an exclusive and we have *any* holders, then requeue. */
if ((lockres->l_blocking == DLM_LOCK_EX) && (lockres->l_ex_holders || lockres->l_ro_holders)) {
mlog(ML_BASTS, "lockres %s, ReQ: EX/PR Holders %u,%u\n", lockres->l_name, lockres->l_ex_holders, lockres->l_ro_holders);
goto leave_requeue;
}
f)如果阻塞的是PR锁,lockres->level = DLM_LOCK_PR,且本节点又持有任何ex_holders锁计数,说明本节点正在持有锁,做处理,等待解锁完成,于是设置ctl->requeue = 1,函数先返回。
/* If it's a PR we're blocking, then only requeue if we've got any EX holders */
if (lockres->l_blocking == DLM_LOCK_PR && lockres->l_ex_holders) {
mlog(ML_BASTS, "lockres %s, ReQ: EX Holders %u\n", lockres->l_name, lockres->l_ex_holders);
goto leave_requeue;
}
(如果本节点持有锁,要确保本节点先完成自己的操作,然后才会去做downconvert处理)
g)因为锁级别要降级,就要降到兼容当前阻塞级别res->l_blocking(即:请求节点的请求锁级别)的最高级别new_level上。
new_level = ocfs2_highest_compat_lock_level(lockres->l_blocking);
检查“释放一个缓存对象上的元数据锁metadata lock是否安全?”即:检查日志journal中的元数据是否已经被JBD2 checkpointed完成(checkpoint进行真正的写日志操作,把元数据和用户数据写入磁盘实际位置)
g1)如果已经完成checkpointed,返回允许downconvert;
g2)如果没有完成checkpointed,则立即开始checkpoint操作,返回暂不允许downconvert,于是设置ctl->requeue = 1,函数先返回。
g3)如果checkpoint操作返回-EIO,说明写磁盘有问题,设置锁资源lockres->l_flags |= CHECKPOING_FAILED,清除阻塞BLOCKED标记,不允许 downconvert,函数返回。
if (lockres->l_ops->check_downconvert && !lockres->l_ops->check_downconvert(lockres, new_level)) {
mlog(ML_BASTS, "lockres %s, ReQ: Checkpointing\n", lockres->l_name);
goto leave_requeue;
}
2、如果设置了ctl->requeue = 1,表示当前本节点暂时不能做downconvert处理,再等一会儿。
3、开始downconvert处理:锁资源lockres->l_action = OCFS2_AST_DOWNCONVERT设置downconvert动作,赋值新的可兼容的锁级别到请求级别域lockres->l_requested = new_level,锁资源设置BUSY和PENDING标记,然后走加锁流程,加新的可兼容的锁级别new_level。结果就是:加锁是必然成功的,因为这个锁级别是兼容的,这样持有高级别锁的节点通过加一把低级别可兼容的新级别锁实现了降级处理。
downconvert:
ctl->requeue = 0;
gen = ocfs2_prepare_downconvert(lockres, new_level);
|-lockres->l_action = OCFS2_AST_DOWNCONVERT;
|-lockres->l_requested = new_level;
|-lockres_or_flags(lockres, OCFS2_LOCK_BUSY);
|-return lockres_set_pending(lockres);
ret = ocfs2_downconvert_lock(osb, lockres, new_level, set_lvb, gen);
|-ret = ocfs2_dlm_lock(osb->cconn, new_level, &lockres->l_lksb, dlm_flags, lockres->l_name, OCFS2_LOCK_ID_MAX_LEN - 1);
至此,downconvert流程就分析结束了。
十一、解锁流程分析:
1、减少锁级别对应的res->l_ex_holders和res->l_ro_holders计数;
2、唤醒downconvert线程,处理BAST消息时,加入osb阻塞锁队列osb->blocked_lock_list中的锁资源res,这些锁资源res等待做downconvert处理。
ocfs2_cluster_unlock(osb, lockres, level);
|-__ocfs2_cluster_unlock(osb, lockres, level, _RET_IP_);
|-ocfs2_dec_holders(lockres, level);
switch(level) {
case DLM_LOCK_EX://如果是EX锁,减少ex holders计数
BUG_ON(!lockres->l_ex_holders);
lockres->l_ex_holders--;
break;
case DLM_LOCK_PR://如果是PR锁,减少ro holders计数
BUG_ON(!lockres->l_ro_holders);
lockres->l_ro_holders--;
break;
default:
BUG();
}
|-ocfs2_downconvert_on_unlock(osb, lockres);
|-if (lockres->l_flags & OCFS2_LOCK_BLOCKED) {
switch(lockres->l_blocking) {
case DLM_LOCK_EX: //如果是EX锁,不再持有ex和ro holders,因为不兼容
if (!lockres->l_ex_holders && !lockres->l_ro_holders)
kick = 1;
break;
case DLM_LOCK_PR: //如果是PR锁,不再持有ex holders,因为不兼容
if (!lockres->l_ex_holders)
kick = 1;
break;
default:
BUG();
}
|-}
|-if (kick) //本节点已经不再持有,唤醒downconvert线程,看是否有被本节点阻塞的锁请求,让downconvert线程去处理。
ocfs2_wake_downconvert_thread(osb);
|-osb->dc_wake_sequence++;
|-wake_up(&osb->dc_event);
注:一个节点对某个元数据锁资源加锁后,锁资源中的锁级别计数变量res->l_ex_holders和res->l_ro_holders会增加计数;当操作完成之后解锁操作将减少res->l_ex_holders和res->l_ro_holders计数,节点对锁资源解锁但不放锁。此后每次加锁,就只调整res->l_ex_holders和res->l_ro_holders的计数,解锁同理。做downconvert时,是根据res->l_ex_holders和res->l_ro_holders计数来判断本节点是否还持有该元数据锁,但是锁资源的级别res->level并不改变,所以会出现其他点加锁级别不兼容问题,只有本节点不持有锁了,就可以做downconvert降级处理,以便兼容阻塞节点的锁请求。
十二、dlm recovery恢复线程:DLM的锁重建处理流程。
dlm->dlm_reco_thread_task:"dlm_reco-%s",
1、触发条件:
本节点心跳监测,发现有节点leave,于是报告node DOWN事件,DLM响应节点DOWN事件。
dlm_hb_node_down_cb
|-struct dlm_ctxt *dlm = data;
|-dlm_fire_domain_eviction_callbacks(dlm, idx);//回调注册的dlm驱逐节点函数ocfs2_do_node_down()
|-__dlm_hb_node_down(dlm, idx);//dlm预处理
1.1本节点驱逐这个fence节点,实际是调用了回调函数ocfs2_do_node_down(),该函数将启动恢复线程osb->recovery_thread_task,ocfs2rec-%s:ocfs2_recovery_thread():,执行__ocfs2_recovery_thread()处理函数。
__ocfs2_recovery_thread()
|-status = ocfs2_super_lock(osb, 1);
|-status = ocfs2_compute_replay_slots(osb);
|-ocfs2_queue_recovery_completion(osb->journal, osb->slot_num, /* queue recovery for our own slot */
NULL, NULL, NULL, ORPHAN_NO_NEED_TRUNCATE);
|-while (rm->rm_used) {
|-node_num = rm->rm_entries[0];
|-slot_num = ocfs2_node_num_to_slot(osb, node_num);
|-status = ocfs2_recover_node(osb, node_num, slot_num);
|-ocfs2_recovery_map_clear(osb, node_num);
|-}
|-status = ocfs2_check_journals_nolocks(osb); /* Refresh all journal recovery generations from disk */
|-ocfs2_super_unlock(osb, 1);
|-ocfs2_queue_replay_slots(osb, ORPHAN_NEED_TRUNCATE);/* queue recovery for offline slots */
bail:
|-ocfs2_free_replay_slots(osb);
|-osb->recovery_thread_task = NULL;
|-mb(); /* sync with ocfs2_recovery_thread_running */
|-wake_up(&osb->recovery_event);
1.2、进入DLM recovery预处理流程:将fence节点从dlm->live_nodes_map中清除掉;如果在mle中,从中清除掉;从dlm domain中清除掉;唤醒迁移队列,将fence节点设置在dlm->recovery_map中。
static void __dlm_hb_node_down(struct dlm_ctxt *dlm, int idx)
|-clear_bit(idx, dlm->live_nodes_map);//fence节点从dlm记录的存活节点中清除掉。
|-dlm_mle_node_down(dlm, mle, NULL, idx);
|-clear_bit(idx, mle->node_map);//fence节点如果在mle中,从中清除掉。
|-mlog(0, "node %u being removed from domain map!\n", idx);
|-clear_bit(idx, dlm->domain_map); //fence节点从dlm domain中清除掉
|-clear_bit(idx, dlm->exit_domain_map);
|-wake_up(&dlm->migration_wq); //唤醒迁移队列
|-set_bit(idx, dlm->recovery_map);//将fence节点设置在dlm->recovery_map中。
到这一步,除了fence节点外,其他每个正常节点都会检测到fence节点,因此都会在本地做到这一步。然后本节点把fence节点从dlm->domain_map中清除掉,再将fence节点设置到dlm->recovery_map中,一切具备,只等dlm recovery线程开始处理了!
2、dlm recovery恢复线程开始处理static int dlm_recovery_thread(void *data)
2.1、本节点dlm recovery线程检测到dlm->recovery_map中有fence节点了,从中取一个节点设置在dlm->reco.dead_node,开始这个fence节点的recovery处理:
a)将dlm->reco.state设置为active,
b)此时每个节点上dlm->reco.new_master还是UNKNOWN,于是先竞选锁主,大家都去对一个名叫RECOVERY特殊锁资源的锁主未定,会先竞选出锁主,方便加锁时,加锁请求发送给锁主处理),这个加锁是非阻塞的NOQUEUE,失败也立刻返回。
c)等某个节点加锁成功,该节点为了维护dlm->domain_map中节点一致性,它就立刻发送begin reco消息通知其他节点将本节点设置为dlm->reco.new_master,将fence节点设置为dlm->reco.dead_node,fence节点被设置在dlm->recovery_map中,其他节点完成这些设置后,也就立即启动了dlm recovery恢复处理了。
这样该节点就成为了dlm->reco.new_master = dlm->node_num,它将承担起恢复fence节点锁资源res的重任,并将$RECOVERY特殊锁资源解锁。其他节点再去pick recovery master时,就会发现dlm->reco.new_master已经存在了,他就不再去做这个dlm->reco.dead_node节点的recovery恢复处理了。在其他节点中设置的dlm->reco.new_master、dlm->reco.dead_node,会在锁主new master处理完成后,发送消息通知其他节点清除掉。
2.2、本节点作为fence节点上锁资源的新锁主,由它发起锁重建流程:
a)把dlm->domain_map中的所有节点拷贝到dlm->reco.node_map中,对每一个节点:初始化恢复节点数据对象recovery_node_data(用于跟踪该节点锁迁移状态,成员node、state、list_head),随后向这些节点--发起锁请求lock request消息,告知其他节点把锁主是fence节点的锁,迁移到本节点。
b)其他节点收到lock request消息后,就启动一个工作任务work,遍历等待恢复的资源队列dlm->reco.resources,把锁主是fence节点的锁资源和目前还没有锁主UNKNOWN的锁资源res收集起来,保存在一个临时链表&resoures上,因为这些锁资源的锁主不是本节点,所以本节点在这些锁资源上加的锁dlm_lock,都是本节点持有的锁dlm_lock,对每个锁资源,把锁资源上3个链表上的锁dlm_lock信息(每把锁所在的链表也会记录下来),全部收集起来,发送给新锁主(migrate lockres消息,一次最多发送240把锁),当所有的锁资源上的锁全都发送完成后,就给新锁主发送data done消息,表示本节点已经把锁主是fence节点但属于本节点持有的所有锁dlm_lock都发送完毕了。
c)新锁主处理:
c1)收到其他节点发送的migrate lockres消息后,就在本地找这个锁资源,没有,就新建这个锁资源,并把这个锁资源的res->owner设置为自己。然后启动一个工作work进一步处理迁移的锁:新锁主自己的迁移锁migrate lock,就不用再分配锁空间了,都在本地保存着,直接把这些锁dlm_lock移至锁资源上它原来就在的队列(granted/converting/blocked)上即可。其他节点的锁,本节点上没有备份,就需要申请新锁dlm_lock,挂接到锁资源上,并移至锁资源上它原来就在的队列(granted/converting/blocked)上。
c2)经过上一步的处理,每个节点迁移过来的锁都安排妥当了,就给其他节点发送migrate request消息
d)其他节点收到migrate request消息后,在本地查找到锁资源res关联的oldmle对象,并新生成一个类型为MIGRATION的mle对象,在新mle对象中记录下mle->master=fence节点(旧锁主),并将new_maser新锁主设置在mle->maybe_map中,表示这是一个迁移状态的mle对象,new_maser节点想成为新锁主。清理掉oldmle对象。
e)新锁主再向其他节点发送确认消息assert master消息,
f)其他节点收到确认消息assert master消息后,找到类型为MIGRATION的mle对象,从mle->maybe_map中取出设置的new_maser,校验发送过来的assert.node是不是new_maser,校验通过后,记录下mle->master=assert.node(新锁主),同时修改锁资源res->owner=assert.node(新锁主)。
g)新锁主等其他节点都确认完毕,再本地修改锁资源res->owner=自己,锁迁移工作旧完成了。本节点正式接管该锁资源的Master权力,唤醒dlm thread线程,处理dlm->dirty_list上的锁资源,发送AST/BAST消息。
h)新锁主向其他节点发送finalize reco消息stage1
i)其他节点收到finalize reco消息stage1后,找到锁主是fence节点的锁资源,从资源队列dlm->reco.resources中删除,修改锁资源res->owner=new_maser,清除锁资源标记DLM_LOCK_RES_RECOVERING,将锁资源移至dlm->dirty_list队列上,标记锁资源状态res->state |= dirty,再标记锁资源状态res->state |= finalize
j)新锁主向其他节点发送finalize reco消息stage2
k)其他节点收到finalize reco消息stage2后,清除锁资源状态标记res->state |= finalize,把fence节点从dlm->recovery_map中清除掉,清除之前设置dlm->reco.dead_node和dlm->reco.new_master 为无效值INVALID。唤醒dlm recovery线程去处理下一个锁资源。
l)新锁主找到锁主是fence节点的锁资源,锁资源从dlm->reco.resources中删除,修改锁资源res->owner=new_maser,清除锁资源标记DLM_LOCK_RES_RECOVERING,将锁资源移至dlm->dirty_list队列上,标记锁资源状态res->state |= dirty,唤醒dlm recovery线程去处理下一个锁资源。
m)新锁主完成锁重建remaster locks,重置dlm recovery线程,把fence节点从dlm->recovery_map中清除掉,重置dlm->reco.dead_node和dlm->reco.new_master 为无效值INVALID,清除recovery标记active。
dlm recover线程完成一个fence节点的锁资源的锁重建工作,继续检测dlm->recovery_map中下一个fence节点。
======================================================
DLM分布式锁管理主要功能至此分析完毕。