Two-Phase Locking(两阶段锁定,2PL)
2PL与2PC是完全不同(两阶段提交)的概念。
- 事务A读取了一个对象,事务B想写入该对象,必须等A提交或中止才能继续。(确保B不能在A底下意外的改变对象)
- 事务A写入了一个对象,事务B想读取该对象,必须等A提交或中止才能继续。
2PL与快照隔离的关键区别:2PL写入会阻塞其他的写入与读,反之亦然;快照隔离读不阻塞写,写不阻塞读。
读、写的阻塞是通过为数据库中每个对象添加锁实现。锁有共享模式(shared mode)、独占模式(exclusive mode)。使用如下:
- 若事务要读取对象,须先以共享模式获取锁。允许多个事务同时持有共享锁,但如另一事务已在对象持有独占锁,则这些事务必须等待
- 若事务要写入对象,须先获取对象的独占锁,如果对象已有任何锁,该事务必须等待
- 如事务先获取再写入对象,则它可能将共享锁升级为独占锁。
- 事务获得锁后,必须持有锁直到事务结束(提交或中止)。此即为"两阶段"名字的来源:第一阶段(事务执行时)获取锁;第二阶段(事务结束时)释放锁。
两阶段锁定的巨大缺点是性能问题。因为一个事务需要等待另一个事务。
Predicate locks(谓词锁)
会议室例子中的幻读(phantoms)问题,即一个事务改变另一个事务的搜索结果:一个事务在某个时间窗口搜索了一个房间的现有预定,则另一个事务不能同时插入或更新同一时间窗口同一房间的另一个预定。
通过谓词锁实现上述要求:
Serializable Snapshot Isolation(序列化快照隔离,SSI)
似乎序列化的隔离级别和高性能从根本上是矛盾的,SSI算法提供一种解决方案:提供完整的可序列化隔离级别,但与快照隔离相比只有很小的性能损失。SSI于2008年首次提出。
第八章 分布式系统的麻烦
分布式系统中,尽管系统的其他部分工作正常,但系统的某些部分可能会以某种不可预知的方式被破坏。这被称为部分失效(partial failure)。难点在于部分失效是不确定性的。这种不确定性,使得分布式系统难以工作!
互联网和数据中心(通常是以太网)中的大多数内部网络都是异步分组网络(asynchronous packet networks)。在这种网络中,一个节点向另一个节点发送的消息(一个数据包),不能保证到达时间,或者是否到达。如果发送请求并期待响应,则很多事情会出错:
- 请求已丢失(可能有人拔掉了网线)
- 请求在排队,稍后交付(网络或收件人超载)
- 远程节点已失效(崩溃或关机)
- 远程节点暂时停止响应(可能遇到长时间的垃圾回收暂停)
- 远程节点已处理请求,但网络上的响应已丢失(可能是网络交换机配置错误)
- 远程节点已处理请求,但是响应被延迟(可能是网络或自己的机器过载)
如果网络故障的错误处理没有被软件定义与测试,则会发生各种意想不到的错误。
网络拥塞和排队
- 如多个不同的节点同时尝试将数据包发送到同一目的地,则网络交换机必须将它们排队逐个送入目标网络链路。
- 数据包到达目标机器,如果所有CPU繁忙,则请求被操作系统排队
- TCP执行流量控制(flow control),其中节点限制自己的发送速率以避免网络链路或接收节点过载。这意味着在数据甚至进入网络之前,发送者处需要进行额外的排队。
不可靠的时钟
分布式系统中,时间是一件棘手的事情,因为通信不是即时的:消息通过网络从一台机器传送到另一台需要时间。
而且,网络上的每台机器都有自己的时钟,这是一个实际的硬件设备:通常是石英晶体振荡器。可以在一定程度同步时钟:最常用的机制是网络时间协议(NTP)。服务器则从更精准的时间源(如GPS接收机)获取时间。
单调钟与时钟
时钟:根据某个日历返回当前日期和时间,如Java中的System.currentTimeMillis()返回epoch(1970年1月1日 午夜 UTC,格里高利历)以来的秒数(或毫秒)。
单调钟:用于测试持续时间(时间间隔)。如Java中的System.nanoTime()。
你可以在某个时间点检查单调钟的值,做一些事情,且稍后再次检查它。这两个值之间的差异告诉你两次检查之间经过了多长时间。但单调钟的绝对值是毫无意义的:它可能是计算机 启动以来的纳秒数,或类似的任意值。特别是比较来自两台不同计算机的单调钟的值是没有 意义的,因为它们并不是一回事。
有序事件的时间戳
如果依赖时钟,在多节点上对事件进行排序,谁先到达?谁后到达?
尽管如此,节点2接收到这两个事件时,会错误的认为x = 1是最近的值,而丢弃x = 2。效果上,client B的增量操作会消失。
在多领导者复制与无领导者复制数据库中,广泛使用这种解决策略(last write win,LWW,最后写入为准)。
暂停进程
Is it crazy to assume that a thread might be paused for so long?Unfortunately not.There are various reasons why this could happen:
- 许多编程语言(如Java虚拟机)都有垃圾收集器(GC),偶尔需要停止所有运行中的进程(stop-the-world)
- 虚拟化环境中,可以挂起(suspend)虚拟机(暂停执行所有进程并将内存内容保存到磁盘)并恢复(恢复内存内容并继续执行)
- 在最终用户设备(如笔记本电脑)上,执行也可能被暂停并随意恢复,如用户关闭笔记本盖子
- 操作系统切换到另一个线程时,当前运行的线程可在代码的任意处暂停
- 如应用程序执行同步磁盘访问,则线程可能暂停
领导者与锁定
通常情况下,一些东西在一个系统中只能有一个,例如:
- 数据库分区的领导者只能有一个节点(避免脑裂,split brain)
- 特定资源的锁或对象只允许一个事务/客户端持有,以防同时写入和损坏
- 一个特定的用户名只能被一个用户所注册
分布式系统中实现这一点需要注意:即使一个节点认为它是the choosen one(分区负责人、锁持有者),但这不一定意味着有法定人数的节点同意!一个节点可能一年是领导者,但是如果其他节点在此期间宣布它死亡(如网络中断或GC暂停),则它可能已被降级,另一个领导者已被当选。