分布式解决方案 Percolator--详解

news2025/1/21 0:53:48

Percolator简介

  Google在2012年将Percolator的架构设计作为论文发表,其目的是构建于BigTalbe的基础上,主要用于网页搜索索引等服务。由于BigTable只支持单行级别的事务,不支持多行事务等更复杂的事务,因此Percolator的诞生就是为了解决这个问题。Percolator支持ACID语义,通过多版本时间戳排序实现了Snapshot Isolation隔离级别,所以可以将其看作是一种通用的分布式事务解决方案。基于google的Bigtable来实现,其本质上是一个二阶段提交协议,利用了Bigtable的行事务。

架构

1 Percolator 包含三个组件:
Client:Client 是整个协议的控制中心,是两阶段提交的协调者(Coordinator);
TSO:一个全局的授时服务,提供全局唯一且递增的时间戳 (timetamp);
Bigtable:实际持久化数据的分布式存储;

2.1. Client
二阶段提交算法中有两种角色,协调者和参入者。在Percolator中,Client充当协调者的角色,负责发起和提交事务。

2.2. Timestamp Oracle (TSO)
Percolator依赖于TSO提供一个全局唯一且递增的时间戳,来实现Snapshot Isolation。在事务的开始和提交的时候,Client都需要从TSO拿到一个时间戳。

2.3 Bigtable
Bigtable从数据模型上可以理解为一个multi-demensional有序Map,键值对形式如下

(row:string, column:string,timestamp:int64)->string

key由三元组 (row, column, timestamp) 组成,value可以是认为byte数组。

在Bigtable中,一行 (row) 可以包含多个 (column),Bigtable提供了单行的跨多列的事务能力,Percolator利用这个特性来保证对同一个row的多个column的操作是原子性的。Percolator的元数据存储在特殊的column中,如下:

列名作用
lock锁信息
write事务提交时间戳
data数据

Percolator事务处理(文尾附图)

第一步: 为每个事务分配一个开始时间戳。与MVCC类似,开始时间戳决定了该事务所能看到的数据集。

Transaction() : start ts (oracle.GetTimestamp()) {}

第二步:将事务中所有的写操作(insert/update/delete)缓冲起来,直到提交时再一并写入。

void Set(Write w) { writes .push back(w); }

第三步:prewrite (预写阶段),该阶段为两阶段提交的第一阶段。此阶段从所有的写操作中选出一个作为主(Primary)锁,其它的操作作为次(Secondary)锁。预写阶段需要用主锁锁住事务中写操作涉及的所有数据,具体流程如下:
1)启动一个BigTable单行事务;
2)读取写操作涉及行的write元数据信息,检查是否有其他事务在该事务开始后【 即时间范围为[start_ts, +++], +++为正无穷】提交并修改了改行的数据,如果有则终止本事务;否则,执行下一步;
3)读取写操作涉及行的lock元数据信息,检查是否有其他事务持有该行的锁,如果有,则代表存在写冲突,Percolator不等待锁释放,而是直接终止事务(无等待的死锁预防策略)。
4)顺利通过冲突检查后,事务开始更新数据,以事务开始时间戳作为BigTable的时间戳,将数据写入data列中。由于采用多版本并发控制因此不会覆盖原来的数据,而是新建一行写入相应的数据
5) 更新完数据后,获取对应行事务锁,同样以事务开始时间戳BigTable的时间戳,但以主锁的{primary.row, primary.col}作为值,写入lock列。
6)进入事务提交阶段。
伪代码

Prewrite tries to lock cell w, returning false in case of conflict.
bool Prewrite(Write w, Write primary) {
 Column c = w.col;
 bigtable::Txn T = bigtable::StartRowTransaction(w.row);
 Abort on writes after our start timestamp . . .
 if (T.Read(w.row, c+“write”, [start ts , +++]))
  return false;
// . . . or locks at any timestamp.
 if (T.Read(w.row, c+“lock”, [0, ∞])) return false;
 T.Write(w.row, c+“data”, start ts , w.value);
 T.Write(w.row, c+“lock”, start ts ,
   {primary.row, primary.col}); // The primary’s location.
  return T.Commit();
}

第四步:提交事务
1)获取提交时间戳commit_ts;
2)对主锁涉及的行启动一个单行事务,接着检查事务是否还持有lock列的锁,如果检查失败则终止事务;
3)如果事务持有锁,则以提交时间戳commit_ts作为BigTble的时间戳,以开始时间戳作为write列值更新数据,使数据对其他事务可见;
4)释放事务持有的主锁;
5)主锁的写操作提交后Percolator认为整个事务已完成,进入原子提交的第二阶段。第二阶段更新所有的次锁写操作,写完后释放次锁,这一步可以异步执行。
伪代码

bool Commit() {
 Write primary = writes [0];
 vector secondaries(writes .begin()+1, writes .end());
 if (!Prewrite(primary, primary)) return false;
 for (Write w : secondaries)
  if (!Prewrite(w, primary)) return false;

  int commit ts = oracle .GetTimestamp();

// Commit primary first.
  Write p = primary;
 bigtable::Txn T = bigtable::StartRowTransaction(p.row);
 if (!T.Read(p.row, p.col+“lock”, [start ts , start ts ]))
  return false; // aborted while working
 T.Write(p.row, p.col+“write”, commit ts,
  start ts ); // Pointer to data written at start ts .
 T.Erase(p.row, p.col+“lock”, commit ts);
 if (!T.Commit()) return false; // commit point

 // Second phase: write out write records for secondary cells.
  for (Write w : secondaries) {
  bigtable::Write(w.row, w.col+“write”, commit ts, start ts );
   bigtable::Erase(w.row, w.col+“lock”, commit ts);
  }
return true;
}

  对于读操作,第一步先检查时间戳在[0, start_ts]内是否有其他事务持有该行锁。注意,根据快照隔离性质,允许 start_ts之后的事务持有其他事务持有该行的锁,因为发生在start_ts的事务读操作只能读取到 start_ts之前的数据版本,此时间之后的数据并不关心。如果检查发现[0, start_ts]内存在冲突的锁,则读事务必须等待,直到锁被释放。
  如果发现没有冲突的锁,则读取[0, start_ts]内的所有数据版本,进一步判断最新的版本,如果不存在则表明找不到可读的数据,反之最新的数据即为所读数据。
伪代码

bool Get(Row row, Column c, string* value) {
 while (true) {
  bigtable::Txn T = bigtable::StartRowTransaction(row);
   // Check for locks that signal concurrent writes.
   if (T.Read(row, c+“lock”, [0, start ts ])) {
   // There is a pending lock; try to clean it and wait
   BackoffAndMaybeCleanupLock(row, c);
   continue;
 }

 // Find the latest write below our start timestamp.
 latest write = T.Read(row, c+“write”, [0, start ts ]);

  if (!latest write.found()) return false; // no data
  int data ts = latest write.start timestamp();
 *value = T.Read(row, c+“data”, [data ts, data ts]);
 return true;
}

Client Crash场景

Percolator的事务协调者在Client端,而Client是可能出现crash的情况的。如果Client在提交过程中出现异常,那么事务之前写入的锁会被留下来。如果这些锁没有被及时清理,会导致后续的事务无限制阻塞在锁上

Percolator采用 lazy 的方式来清理锁,当事务 A 遇到一个事务 B 留下来的锁时,事务 A 如果确定事务 B 已经失败了,则会将事务 B 留下来的锁给清理掉。但是事务 A 很难百分百确定判断事务 B 真的失败了,那就可能导致事务 A 正在清理事务 B 留下来的锁,而事务 B 其实还没有失败,且正在进行事务提交。

  为了避免出现此异常,Percolator事务模型在每个事务写入的锁中选取一个作为Primary lock,作为清理操作和事务提交的同步点。在清理操作和事务提交时都会修改primary lock的状态,因为修改锁的操作是在Bigtable的行事务下进行的,所有清理操作和事务提交中只有一个会成功,这就避免了前面提到的并发场景下可能出现的异常。

根据primary lock的状态就可以确定事务是否已经成功commit:

1)如果Primary Lock不存在,且 write 列中已经写入了 commit_ts,那么表示事务已经成功commit;
2)如果Primary Lock还存在,那说明事务还没有进入到commit阶段,也就是事务还未成功commit。

事务 A 在提交过程中遇到事务 B 留下的锁记录时需要根据事务 B 的Primary Lock的状态来进行操作。

1 如果事务 B 的Primary Lock不存在,且 write 列中有 commit_ts 了,那么事务

A 需要将事务 B 的锁记录 roll-forward。roll-forward操作是rollback操作的反向操作,也就是将锁记录清除,并在 write 列中写入 commit_ts。
如果事务 B 的Primary Lock存在,那么事务 A 可以确定事务 B 还没有成功commit,此时事务 A 可以选择将事务 B 留下锁记录清除掉,在清除掉之前,需要将事务 B 的Primary Lock先清理掉。
如果事务 B 的Primary Lock不存在,且 write 列中也没有 commit_ts 信息,那么说明事务 B 已经被 rollback 了,此时也只需要将事务 B 留下的锁清理掉即可。

虽然上面的操作逻辑不会出现不一致的情况,但是由于事务 A 可能将存活着的事务 B 的Primary Lock清理掉,导致事务 B 被rollback,这会影响到系统的整体性能。

为了解决这个问题,Percolator使用了Chubby lockservice来存储每个正在进行事务提交的Client的存活状态,这样就可以确定Client是否真的已经挂掉了。只有在Client真的挂掉了之后,冲突事务才会真的清除掉Primary Lock以及冲突锁记录。但是还可能出现Client存活,但是其实其已经Stuck住了,没有进行事务提交的动作。这时如果不清理掉其留下的锁记录,会导致其他冲突事务无法成功提交。

为了处理这种场景,每个存活状态中还存储了一个wall time,如果判断wall time太旧之后,则进行冲突锁记录的处理。长事务则需要每隔一定的时间去更新这个wall time,保证其事务不会因此被rollback掉。

最终的事务冲突逻辑如下:

如果事务 B 的Primary Lock不存在,且 write 列中有 commit_ts 了,那么事务 A 需要将事务 B 的锁记录 roll-forward。roll-forward操作是rollback操作的反向操作,也就是将锁记录清除,并在 write 列中写入 commit_ts。
如果事务 B 的Primary Lock不存在,且 write 列中也没有 commit_ts 信息,那么说明事务 B 已经被 rollback 了,此时也只需要将事务 B 留下的锁清理掉即可。
如果事务 B 的Primary Lock存在,且TTL已经过期,那么此时事务 A 可以选择将事务 B 留下锁记录清除掉,在清除掉之前,需要将事务 B 的Primary Lock先清理掉。
如果事务 B 的Primary Lock存在,且TTL还未过期,那么此时事务 A 需要等待事务 B 的commit或者rollback后继续处理。

Percolator的优缺点

优点:
1 松耦合,无需对底层存储做任何的改动,这对于大多数的OLTP场景业务是非常有用的,例如Google日历,Gmail等;
2 采用lazy方式处理事务遗留下来的锁,简单易行。

缺点:
1 单点瓶颈,全局时间戳的分配由TSO提供,且一次事务至少需要与TSO进行两次通信 ,在高并发场景下TSO会出现性能瓶颈;同时在高可用方面存在不足(TSO集群代替);
2 没有死锁检测手段,在一定程度上会增加冲突事务的延迟。

在这里插入图片描述在这里插入图片描述
参考:Peng D, Dabek F. “Large-scale Incremental Processing Using Distributed Transactions and Notifications”.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/40813.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据增强:Simple Questions Generate Named Entity Recognition Datasets

数据增强的方式一般是无标注数据集的情形的一种解决方式,今天的讲座报告中对这问题做了梳理。11.27学术报告文章,应该是韩旭的报告。 文章目录问题背景一、论文核心二、文章内容三、experiments总结问题背景 还是在于方法的创新,虽然是数据增…

Centos7下安装Oracle11g

1. 下载安装包 由于Oracle官网上无法下载Oracle数据库之前的版本,可以在 Oracle Software Delivery Cloud 里搜索oracle database 11g 或者百度网盘下载链接:https://pan.baidu.com/s/1r57xI5fSVba_Q3biCj06yg 提取码:xk30 2. 创建运行ora…

【BSC】使用Python玩转PancakeSwap(入门篇)

需求 最近我们需要在BSC上实现代币的自动化兑换,比如自动把BNB兑换成USDT,自动把USDT兑换成CAKE等其它代币,同时也要监视价格,在价格合适的时候再兑换代币。而PancakeSwap正是BSC上最大的去中心化平台,我们已经学会了…

潜匿的怪物,你的供应链真的安全吗?

网络钓鱼、DNS欺骗      勒索软件、MITM攻击      在这个网络环境      风声鹤唳的时代      这些网络攻击类型      你一定不会感到陌生      无孔不入,这个词用来形容网络攻击毫不为过。世上没有绝对锋利的矛,同样也没有坚不可摧的盾,即使您养成了安…

如何编写列名中带有空格的SQL查询

在这篇文章中,我们将学习如何写出列名中带有空格的SQL查询。空格在数据库对象的名称和表的列名的命名规则中受到限制。如果你想在对象名或列名中加入空格,查询和应用代码必须以不同的方式编写。在编写动态SQL查询时,你必须小心和精确。本文解…

【JavaWeb】Servlet系列 --- Tomcat安装及配置和常见的问题(2022最新详解、图文教程)

Tomcat的配置安装1. 关于WEB服务器软件2. 配置Tomcat的服务器第一步:配置Java的运行环境第二步:Tomcat的安装第三步:启动Tomcat3. 问题一:解决Tomcat服务器在DOS命令窗口中的乱码问题(控制台乱码)4. 测试To…

linux 用户不在sudoers文件中,此事将被报告

出现如下提示 gaokaoli 出现不在 sudoers 文件中。此事将被报告 一般是该用户 权限不够 既然知道权限不够可以添加到root用户组,获取权限即可 通过命令行添加到权限,发现还是不行 sudo usermod -g root gaokaoli 那就直接在配置文件中修改 通过执行…

word设置页码从非第一页开始

设置过程 参考:https://zhuanlan.zhihu.com/p/84998841 显示出分隔符和分页符 方法一: 在文档中直接按【CtrlShift8】组合键,即可显示出分节符。. 方法二: 点击【开始】-【段落】-【显示/隐藏编辑标记】按钮,也可显…

如何安装Jmeter监控服务器资源插件(JMeterPlugins + ServerAgent 方法一)?

一、下载插件 ServerAgent-2.2.3.zip 下载 JMeterPlugins-Extras-1.4.0.zip下载 JMeterPlugins-Standard-1.4.0.zip下载 (或者可以到网站下载插件:JMeterPlugins-Standard和JMeterPlugins-Extras 下载地址:https://jmeter-plugins.org/down…

计算机图形学(三) -- 3D 变换

文章目录3D 变换缩放(Scale)平移(Translation)旋转(Rotation)3D 旋转(3D Rotation)什么是欧拉角罗德里格斯旋转公式(Rodrigues Rotation Formula)Viewing transformation什么是 View / Camera Transformation相机标准位置(约定俗成)怎样将一个相机从一个任意的摆放,…

clickhouse单节点以及集群的安装

安装 因为clickHouse很消耗cpu资源,所以需要修改:用户可打开的文件数量和最大进程数: vim /etc/security/limits.conf * soft nofile 65536 * hard nofile 65536 * soft nproc 131072 * hard nproc 131072//第一列代表用户用户组&#x…

[LeetCode周赛复盘] 第 92 场双周赛20221015

[LeetCode周赛复盘] 第 92 场双周赛20221015 一、本周周赛总结二、 [Easy] 6249. 分割圆的最少切割次数1. 题目描述2. 思路分析3. 代码实现三、[Medium] 6277. 行和列中一和零的差值1. 题目描述2. 思路分析3. 代码实现四、[Medium] 6250. 商店的最少代价1. 题目描述2. 思路分析…

org.springframework.test.util.ReflectionTestUtils.invokeMethod方法的使用

序言 为什么要用spring框架的ReflectionTestUtils工具类的invokeMethod方法? 当我们想要调用一个实例对象的私有方法时,我们可以利用反射机制去调用该私有方法。 Demo 含有私有方法的类, public final class DemoClass {private static …

Spring - BeanFactoryPostProcessor 扩展接口

文章目录Preorg.springframework.beans.factory.config.BeanFactoryPostProcessor源码探究1 是否实现BeanDefinitionRegistryPostProcessor 接口,分别写入集合2 处理实现了的PriorityOrdered和 BeanDefinitionRegistryPostProcessors 的 bean3. 处理实现了的Ordered…

Linux基础

一、Linux发展历程 1.1、Linux前身-Unix 1968年Multics 项目 MIT|、Bell 实验室、美国通用电气有限公司走到了一起,致力于开发Multics项目。到后期由于开发进度不是很好,MIT 和Bell实验室相继离开这个项目的开发,最终导致项目搁浅。 1970年 …

接口测试用例设计方法方式和流程一文到底

目录 1、通用信息校验 1、URL校验 2、请求方法校验 3、请求头 4、接口鉴权 2、接口参数校验 1、参数的必填项校验 2、参数的选填项校验 3、参数长度校验 4、参数数据类型校验 5、参数的有效性校验 6、参数的唯一性校验 7、参数关联项校验 3、其他补充项 1、幂等…

Kafka必问面试题

一、说说你对kafka的理解 kafka本身是一个流式处理平台,同时也具有消息系统得能力,在我们得系统中更多得是把kafka作为一个消息队列系统来使用 而如果来介绍kafka,大致可以分为这几块: kafka集群元数据得管理,集群得…

【云原生 | Kubernetes 实战】04、k8s 名称空间和资源配额

目录 一、什么是命名空间? 二、namespace 应用场景 三、namespacs 使用案例 四、namespace 资源限额 一、什么是命名空间? Kubernetes 支持多个虚拟集群,它们底层依赖于同一个物理集群。 这些虚拟集群被称为命名空间。 命名空间namespace…

《基础IO》

【一】C文件接口 我们使用C语言向文件写入东西的时候,基本上的套路都是先打开文件,然后调用C的文件接口,向文件中输入相应的数据,然后关闭文件。 a.size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream …

AlibabaP9整理出微服务笔记:Spring微服务不止架构和设计

微服务是一种架构风格,也是一种针对现代业务需求的软件开发方法。微服务并非发明出来的,确切地说是从之前的架构风格演进而来的。 但是深入介绍Spring Boot、Spring Cloud、Docker、 Mesos和Marathon掌握响应式微服务设计原则,轻松构建大规模…