目录锁背景

Tera中,Master通过Nexus判断TabletNode是否仍在提供服务,当Master认为某个TabletNode已无法提供服务后,会将其上面所有Tablet迁移到其它TabletNode上进行Load。然而,在发生网络分区,或者网络延时造成的Tera Master处理时序错乱时,可能造成旧的TabletNode上仍Load某个Tablet时,同时给另外一个新的TabletNode发送Load某个Tablet的命令,造成同一个Tablet被多台TabletNode同时Load,从而造成数据写入错乱。因此,必须依赖Tera使用的底层分布式文件系统来彻底解决多重Load问题:Tera中的每个Tablet的数据都存放在一个单独的目录下,在对Tablet进行Load时,持有对应目录的一个排它锁,便可以防止有其它TabletNode同时Load此Tablet

整体设计

  1. FileInfo中添加lock_status以及holder标记,lock_status可能处于lockedunlockcleaning三种状态,分别对应目录已上锁,未上锁,正在清锁三种状态,当状态为locked时,holder中记录的为持锁者的标识(ip:port:timestamp,此标识由客户端(sdk)在加锁时发送给NameServer,在客户端存活期间保持不变
  2. 在对目录进行加锁时,首先检查此目录的FileInfolock_status是否为unlock:
  3. 如果是,则将其状态更改为locked,并连同调用者的ip:port一同持久化到namespace中,加锁成功
  4. 如果不是,则目录锁的状态为lockedcleaning,加锁失败,将锁目前的状态返回给调用者
  5. 在对目录释放锁时,首先检查此目录的FileInfolock_statu是否为locked:
  6. 如果是,将其状态改为cleaning并持久化到namespace中,然后,将此目录下所有正在写的文件及其所处在的ChunkServer地址均扫描出来,构造一个CloseFilesCtx结构,后台依次将其关闭,待全部文件均收到BlockReceived之后,可以认为全部文件已经被关闭,此时将namespace中对应目录的锁状态更改为unlocked,后续允许对此目录进行再次加锁
  7. 如果不是,则此RPC为乱序或者重试的调用,可以不用处理,日志中将其记录即可
  8. 在创建新文件或者删除文件时,先检查其父目录的FileInfo中锁的状态:
  9. 如果为unlock,则说明此目录并没有上锁,允许创建或删除
  10. 如果为locked,则说明此目录上有锁,这时检查调用者ip:port是否与FileInfo中记录的一致,以便确认此调用者是否有权对此目录进行写操作,如果一致,则允许创建或删除,否则,拒绝创建
  11. 如果为cleaning,说明此时刚刚将锁释放,正在关闭所需文件,对调用者返回正在清锁的错误码,调用者可以进行重试

接口

  1. message定义

StatusCode {

   `…...`

   `kLocked`

   `kUnlock`

   `kCleaning`

  `…..`

}

message LockDirRequest {

   `optional int64 sequence_id = 1;`

   `optional string dir_path = 2;`

}

message LockDirResponse {

`optional int64 sequence_id = 1;`

  `optional StatusCode status = 2;`

 `}`

message UnlockDirRequest {

  `optional int64 sequence_id = 1;`

  `optional string dir_path = 2;`

  `optional StatusCode status = 3;`

`}`

message UnlockDirResponse {

  `optional int64 sequence_id = 1;`

  `optional StatusCode status = 2;`

 `}`

2.对指定目录加锁:

void LockDir(::google::protobuf::RpcController* controller,

const LockDirRequest* request,

LockDirResponse* response,

::google::protobuf::Closure* done);

request中存有需要加锁的路径,response中的status表明加锁是否成功

3.释放掉指定目录的锁

void UnlockDir(::google::protobuf::RpcController* controller,

const UnlockDirRequest* request,

UnlockDirResponse* response,

::google::protobuf::Closure* done);

request中存有需要解锁的路径,response中的status表明是否解锁成功

异常处理

  1. 对某个目录加完锁后,NameServer宕机或重启

由于锁状态是持久化在FileInfo中,HA方案已天然的将namespace同步到多台NameServer上,所以无论是宕机重机还是切主,都不影响锁信息的正确性。若重启时发现FileInfo中锁的状态为cleaning,则在RebuildBlockMapping时将此目录下所有正在写的文件记录下来,重新进行清锁流程

  1. 对某个目录解锁时,写完cleaning日志后,即发生切主

可以维护一个正在执行清锁任务的内存结构,当发生切主时,清除此结构

  1. 关闭所有文件时,所有文件已关闭成功的标志是什么?如果有机器不响应怎么办?

所有文件已关闭成功的标志是对应block的incomplete set为空。如果有机器不响应,只能等, 而且关的速度取决于ChunkServer汇报的速度

TODO

  1. 此方案中,清锁动作为Tera的Master主动发出,未来可以考虑TabletNodeNameServer维持心跳,心跳超时后主动清锁的设计

  2. 此方案中,不涉及递归加锁问题,例如对目录home/work进行加锁时,并不会对/home目录进行加锁