最近跟国内几家热门公司做分布式存储的大佬们聊了聊,过程十分愉快,但同时也有点小虐。说到底,自己在这个领域并没有很久的经验,很多东西仍停留在知其然而不知其所以然的地步。魔鬼藏在细节之处。

不过这也正好是个巩固自己知识的机会。具体来说,这篇我想思考的是bigtable如何处理tablet server在chubby锁失效时的一些细节。

由于不想通过阅读源代码的方法学习(主要是因为懒,而且要是真读了我也就不能写了...),因此我准备尝试一种新的方式:通过论文[1]中列出的事实,做一些思想实验,推导出一套方案。

背景知识

bigtable中的一个table由一系列tablets组成。一个tablet包含一段连续的rows,并按照row key排序。

从系统架构来看,bigtable由master和tablet server构成。master主要负责监控tablet server以及将unassigned tablets分配到tablet server(s)上。tablet server负责数据的读写。实际使用中,用户基本只需要跟table server进行交互。

bigtable还依赖于google的分布式文件系统gfs(已淘汰)和分布式锁服务chubby。gfs用于存储tablets的数据,chubby则用来进行状态监控、选主和一系列metadata存取。

如何找到一个tablet?

[1] 5.1中对这一过程有比较详细的描述。大概来说,bigtable中有一个特殊的METADATA table(简称MD),用来存储构成所有user tables的tablets的位置信息。MD中的每一行都对应bigtable中的某个tablet,并存了它当前所在的tablet server。

MD本身分为了两块,其中第一个MD tablet有些特殊,被称为root tablet。

root tablet不允许split。它用来存其他所有MD tablets的位置。其位置保存在了一个chubby file中。

图1. 查询tablet位置的多层结构

tablet 是如何实现的?

tablet的实现原理为lsm tree。lsm tree包括一个存在ram中的memtable(一个有序的kv map),一个write ahead log (wal) 和一系列sstables。wal和sstables需要持久化存储。

tablet需要持久化存储的wal和sstables存在了哪?

gfs

一个tablet是怎么管理它的wal和sstables的?

[1] 5.3对此略有提及: "This metadata contains the list of SSTables that comprise a tablet and a set of a redo points..." 因此一种合理的假设是,构成一个tablet的wal和sstables的gfs文件名等元信息也都存在了MD相应的行中。

lsm的写过程是什么样的?

以写入一个(k, v) record为例:

  1. 将(k, v) append到wal末尾
  2. 更新内存中的memtable

一个bigtable的写请求在成功写入wal后就算commit了。

memtable的容量是否会无限制增大?

不会。

lsm通过compaction机制定期把memtable dump为一个新的sstable,并清空memtable和wal。因此memtable并不会无限制的增大。

再稍微补充一点,minor compaction是将memtable写入为新的一个sstable,而merging/major compaction是合并已有的sstables,并将合并后的结果写入新的sstables。已有的sstable是不可被修改的。

wal的作用?

wal主要用来灾后恢复工作。当原有的tablet server挂了后,新的tablet server可以通过重放wal重新构建出memtable的状态。

服务一个tablet

[1] 5.2大致描述了tablet server和master是如何协作来提供某一个tablet的服务的。

  1. 当一个tablet server启动后,它会在某个特定的chubby directory下创建一个chubby file,并获得该file的独占锁。
  2. 当tablet server发现自己不再持有该独占锁时,它会立刻停止在其上的tablets的任何读写服务。一旦锁丢失后,tablet server会尝试再去获取该锁,直到重新上锁,或者chubby file消失。
  3. 一旦chubby file消失后,tablet server就必须直接terminate。不过在terminate之前,tablet server会首先尝试释放chubby锁。这样能加速master重新分配tablets的过程。

再来看master一侧:

  1. master会定期扫描这个特定的chubby directory,并询问每个tablet server关于其chubby锁的状态,来确定server是否运行正常。如果tablet server回复说锁已丢失,或者根本不做回复,master会尝试获取这个chubby锁。一旦获取成功,那就证明tablet server和chubby已经失联。为了防止该server再继续服务,master会直接删除掉这个chubby file。
  2. 一旦某个tablet server对应的chubby file被删除后,master就可以回收其之前负责的tablets,将它们重新分配到新的tablet servers上。

提出问题

看着还比较简单,然而这会有哪些问题呢?

一个最棘手的问题就是分布式锁。跟os的mutex不同,在考虑分布式锁时,必须默认其在任意时刻都会丢失。举一个简单的例子:

chubby.Lock();
// 我们拿到了分布式锁// 假设IamSisyphus()需要运行很长时间,那么其执行过程中,
// |chubby|随时有可能失效。
IamSisyphus();

具体对于Bigtable来说,因为chubby锁失效带来的问题,我能想到两个:1. 写入期间chubby锁失效;2. compaction期间chubby锁失效。

先考虑在tablet server a获得一个写入(k, v)请求后,准备写wal时的一个场景:

  1. 验证自己仍持有chubby锁。若锁在这一步丢失,直接拒绝该请求就好。
  2. 向gfs发送一个append (k, v) to wal的请求。这一步因为rpc,所以是耗时的。
  3. chubby锁丢失,此时第2步还未结束。
  4. master删除了ts a对应的chubby file,并重新将该tablet分配给了ts b。
  5. ts b开始服务该tablet。在服务之前,它通过重放wal来构建出memtable。

可以看出,第2步和第5步会构成一个wal上的读写冲突。

只要第2步在第5步之前完成,那么即使在写入wal过程中chubby锁丢了也没关系。因为ts a不会再服务这之后的任何一个请求了。而直到ts b载入完该tablet之前,不会再有任何新的写请求会被执行。

那假如第2步真的就慢到发生在了第5步之后呢?(见图2)

图2. 一次很慢的gfs写

由于bigtable在移动tablet期间的各种操作都相对比较耗时,我觉得这在实际应用中很可能是个不存在的场景。不过真的发生了话,应该是由gfs,也即写的server端,来充当resolver。比如gfs提供一个特殊的read api——这个读会丢弃所有尚未被执行完的写(翻了翻gfs原文,并没有看到这方面的讨论。这其实是在问文件系统中读一个正在被写的文件会发生什么,答案是由文件系统决定...)

此外,根据[2],文件系统作为server端还可以再次对chubby lock进行验证。虽然锁随时都可能失效,但是由于并发的读写操作在最终的server端是linearized的,只要server端进行锁的验证即可。比如在gfs primary中,2发生先于5,那么2的写在gfs上执行时,即使chubby锁失效了也没事。而假如5发生在了2之前,那么根据应用层逻辑,这是因为chubby锁已经失效了,2的验证一定会失败,写就不会发生。

由于gfs在成文期间还没有chubby,且这种验证本身属于应用层的逻辑,因此gfs应该没有这种处理。

为了进一步避免这个情况,我们可以引入些工程上的处理。因为chubby session都是有一个过期时间的(记为texp),可以人为的引入一个缓冲时间(e.g. 1秒)。写请求只有在当前时间now + 1s < texp时才会被处理。事实上,chubby本身已经有了类似的处理(chubby可以保证一个锁在意外丢失后n秒内不可以被再次被获取,详见lock-delay。)

--------------------------------------------------------------------------------------

再考虑下compaction发生时chubby锁丢失会如何。这里我讨论的是minor compaction,因为它涉及到同时更新sstables和wal的元数据,相对更复杂一些。

之前说了,MD中的某一行会保存该tablet数据的元信息,即wal和sstables的文件名。假设MD的schema中包含了两个column,分别为wal和sstables。wal是一个字符串,而sstables是一个字符串集合(实际实现可能更像leveldb中的MANIFEST文件,由第一个snapshot和一系列增量构成)。

我们先梳理下一个可能的minor compaction的过程:

  1. 创建一个新的wal文件,记为w'。
  2. 把memtable dump到一个新的sstable,记为s'。
  3. 验证自己仍持有chubby锁。若锁在这一步丢失,直接放弃minor compaction。
  4. 向维护本tablet的MD tablet所在的server发一个写请求。该请求会尝试更新wal和sstables。前者被指向w',后者新添了s'。这个写是原子性的,其原子性由Bigtable本身提供。

而当MD tablet server收到第4步的请求时,这就是一次常规的Bigtable写请求,其正确性已经由第一部分对wal的讨论保证了。

这个minor compaction在哪一刻被commit了?

可以看出,在第4步之前,我们生成的所有数据s'和w'都并没有被该tablet的元数据记载,因此minor compaction在前三步是否成功都对系统的正确性没有影响。一个minor compaction在第4步成功执行后才算被commit了。

第4步执行期间chubby锁失效了怎么办?

如果此时chubby锁失效,master会分配该tablet到新的server。新server会读取其对应的MD tablet,并做回放wal等操作。这里可以看到一个读写冲突。

解决思路其实跟上一段讲的差不多,同样是利用server端做resolver,因为server端可以linearize并发的读写请求的顺序。注意这里client端是user tablet server,而server端是MD tablet server。

本文想要讨论的问题就到此结束了。

然而这个minor compaction方案的效率很差。minor compaction在执行期间,tablet server必须拒绝一切写请求。否则假如新的写请求被记录到了新的wal w'中,而第4步失败了,那么新的wal所记录的数据就丢失了。

[1] 6 - Speeding up tablet recovery一段透露了一点实际的minor compaction过程:在第一次dump memtable时,tablet server仍然可以处理写请求。然而这些写仍被记录在了老的wal中。在dump结束后,再对在此期间处理的写做一次minor compaction,且这次的速度往往会快很多。根据同样的原因,第二次minor compaction需要暂停处理写请求。

能否做到minor compaction时完全不暂停写请求呢?

以下是我想到的一种设计:

在MD的元数据有关wal部分,存储两个column:wal和wal_buffer,两者都是字符串,存的都是文件名。一般来说,wal_buffer为空。

一次minor compaction的执行过程为:

  1. 创建一个新的wal文件,记为w',并将w'文件名写入到wal_buffer中。
  2. 把memtable dump到一个新的sstable,记为s'。这个期间发生的所有写都存到新的w'中。
  3. 验证自己仍持有chubby锁。
  4. 向维护本tablet的MD tablet所在的server发一个写请求。该写请求除了包含新的s',会将wal设置为w'并清空wal_buffer。

而恢复重建时,需要按照wal -> wal_buffer的顺序回放两个log中的所有内容。

声明

本文仅代表作者个人观点。再次强调,这是我个人对[1]缺失的信息的一些补充思考,并不是bigtable真正的实现。

一番胡言,欢迎拍砖。

参考文献

  1. Bigtable: A distributed storage system for structured data.ACM Transactions on Computer Systems (TOCS),26(2), p.4.
  2. How to do distributed locking, Martin Kleppmann, Feb 2016

锁失效_关于bigtable中chubby锁失效时的一点思考相关推荐

  1. mysql 高并发写入锁表_使用mysql中的锁解决高并发问题

    阿里云产品通用代金券,最高可领1888分享一波阿里云红包. 阿里云的购买入口 为什么要加锁 多核计算机的出现,计算机实现真正并行计算,可以在同一时刻,执行多个任务.在多线程编程中,因为线程执行顺序不可 ...

  2. mysql分组失效_请教MySql中使用表子查询时,试着先排序后分组,出现排序失效的原因?...

    1,今天试着码了一下教程里的题目,是找出每一个班级的身高最高的学生,用的是先order by降序排序所有学生升高,再用 group by分组每一个班级取第一个值,却发现当使用子查询时,得到的仍旧是未排 ...

  3. @Transactional事务中使用锁坑(@Transactional事务中使用锁失效)

    @Transactional事务中使用锁失效 说明: Spring中使用注解@Transactional作事务管理,@Transactional注解在方法上时,是方法完成之后才进行提交事务的 测试代码 ...

  4. 定时跑视图往另外一张表添加数据_聊一聊数据库中的锁

    背景 数据库中有一张叫后宫佳丽的表,每天都有几百万新的小姐姐插到表中,光阴荏苒,夜以继日,日久生情,时间长了,表中就有了几十亿的小姐姐数据,看到几十亿的小姐姐,每到晚上,我可愁死了,这么多小姐姐,我翻 ...

  5. mysql 行锁 超时_技术分享 | MySQL 行锁超时排查方法优化

    作者:xuty 本文来源:原创投稿 * 爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源. 一.大纲 #### 20191219 10:10:10,234 | com.ali ...

  6. 自旋锁和互斥锁实例_多线程编程之自旋锁

    一.什么是自旋锁 一直以为自旋锁也是用于多线程互斥的一种锁,原来不是! 自旋锁是专为防止多处理器并发(实现保护共享资源)而引入的一种锁机制.自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用 ...

  7. mySQL无锁队列_使用 MySQL 实现无锁任务队列(using MySQL as a job queue)

    目录目录 场景 关键问题 有问题的解决方案 方案1 - 锁表 方案2 - SELECT FOR UPDATE 无锁任务队列 关键问题及解决方案 References 场景N 个生产者往 db 里面插入 ...

  8. python解除windows锁屏_用python获取win10锁屏图片

    本文教你如何用python提取win10近期推送的锁屏图片并存储到指定文件夹. 首先保证在个性化页面中的锁屏里选定windows聚焦如下图, 1.png win10近期推送的图片都存储在C:\User ...

  9. android app防止锁屏_设置Android系统永不锁屏永不休眠的方法

    在进行Android系统开发的时候,有些特定的情况需要设置系统永不锁屏,永不休眠.本篇文章给大家介绍Android 永不锁屏,开机不锁屏,删除设置中休眠时间选项,需要的朋友一起学习吧. Android ...

最新文章

  1. 学废了!提高工作效率的五个步骤! | 每日趣闻
  2. linux shell 提示符消失 终端提示符显示-bash-4.1# 解决方法
  3. oracle转64编码,[转]将oracle数据库的编码变成utf-8
  4. python turtle 颜色数字_python的绘图利器--海龟绘图turtle
  5. 真实临床“生态”下实效性研究的挑战和意义
  6. 2 字符串String
  7. Linux学习笔记(二)
  8. OUTLOOK无法解析Exchange通讯录
  9. 【建站知识】如何让我们的网站更快?如何开启全站阿里域名加速?...
  10. mysql查询连续次数_Mysql如何查询连续的时间次数
  11. 【转】Oracle 行列转换
  12. html制作网页案例代码
  13. 大白菜方式制作win10 PE启动U盘
  14. 工具分享-Windows 的绿色软件工具集
  15. (CentOS7)IP地址的配置与主机名和hosts映射
  16. apache-ant-1.7 下载
  17. 幼儿园计算机认识键盘上课教案,认识键盘教案
  18. 如何基于EasyCVR视频技术实现智慧移动执法?
  19. imageView图片放大缩小及旋转
  20. 【圣诞限定】2022的末尾,送TA一颗圣诞树吧

热门文章

  1. structs - 标签库(html)
  2. 统计一个子字符串在另一个字符串中出现的次数
  3. 方差、标准差(均方差),均方误差、均方根误差
  4. websocket php apache,PHP第一篇:PHP WebSocket实现前后端数据交互,亲测可用(windows+ apache2.4 +php5.6 )...
  5. AcWing 853. 有边数限制的最短路(bellman的k边限制最短路)
  6. 真假签到题(签到+打表)
  7. 蓝桥每日真题之负载均衡
  8. 客户端和服务器之间的信息结构,客户端和服务器之间的信息结构
  9. 跨网页的新手引导_做自媒体的新手要注意什么,这些坑不能踩,这些事不能做...
  10. Jupyter插件的使用