#gStore-weekly | gStore源码解析(十)事务机制

1.1 简介

本章主要介绍gStore事务的MVCC实现和事务管理两部分,前者介绍了在事务模式下数据的存储方法,后者介绍了事务的处理流程。请注意,本章的代码省略了磁盘IO的相关操作。

1.2 MVCC实现

gStore的事务实现选择了MVCC(多版本并发控制),保证了读写间的有限并发性,实现了READ_COMMITTED
(读已提交)、SNAPSHOT
(快照隔离)、SERIALIZABLE
(可串行化)三种隔离机制,三种隔离级别下分别简写为RC、SI、SR。

1.2.1 事务实现总述

gStore的底层存储是Key-Value模式的,Key的类型是由上层编号的unsigned,而Value的类型是IVEntry
类。完成Key-Value映射的Hash表是IVArray
类。下文提及的版本链就是IVEntry
层实现的。

根据MVCC的思想,gStore中的数据(IVEntry
)在事务模式下具有版本链vList
,每个数据的每个版本有一组时间戳对(begin_ts, end_ts)
,表示该版本适用的时间戳范围。

事务由Transaction
类实现,最重要的属性就是时间戳。Transaction
类中有TID
CID
两类时间戳,TID
是事务开始时的时间戳,CID
是事务提交时的时间戳。版本时间戳对(begin_ts, end_ts)
中,begin_ts
是创建该版本的事务CID
,而end_ts
是创建下一个版本的CID-1
。而判断某版本是否对某事务可见的条件是begin_ts < TID < end_ts

1.2.2 事务写操作

执行写操作的时候,同一个数据只能有一个写操作执行,因此需要先申请加上写锁。SR级别下是读写互斥的,因此SR级别额外拥有一个读计数+写标记的读写锁,保证读读不互斥的前提下读写互斥。

由于事务尚未提交,不确定该修改是否会落盘,因此在该数据版本链上新增一个私人版本。该版本时间戳对为(-1, TID)
,其中-1
表示该版本为私人版本,TID
指示所属事务编号。这种特殊的时间戳对可以看作是实现写写阻塞的一种锁。

注意到只有SR级别需要加写锁或将读锁升级为写锁(由于读写互斥,因此读锁升级要求只有当前事务执行读操作),这是因为只有SR级别是读写互相阻塞的,需要额外记录下来。获取写锁的代码如下:

    auto TID = txn->GetTID();
 shared_ptr<Version> new_version = make_shared<Version>(INVALID_TS, TID); //[-1, TID]
 rwLatch.lockExclusive();
 if(txn->GetIsolationLevelType() == IsolationLevelType::READ_COMMITTED)
 {
  //check the timestamp(lock)
  if(!checkVersion(TID, true)){
   rwLatch.unlock();
   return 0;
  }
 }
 else if(txn->GetIsolationLevelType() == IsolationLevelType::SNAPSHOT)
 {
  //check the timestamp(lock)
  if(!checkVersion(TID, false)){
   rwLatch.unlock();
   return 0;
  }
 }
 else if(txn->GetIsolationLevelType() == IsolationLevelType::SERIALIZABLE)
 {
  if(!checkVersion(TID, false)){
   rwLatch.unlock();
   return 0;
  }
  if(has_read){
   if(glatch.tryupgradelatch(TID) == false){
    rwLatch.unlock();
    return 0;
   }
  }
  else{
   if(glatch.tryexclusivelatch(TID, true) == false){
    rwLatch.unlock();
    return 0;
   }
  }
 }
 ······
 vList.push_back(new_version);
 setVersionFlag();
 rwLatch.unlock();
 return 1;


检查版本信息(即上方代码中的checkVersion函数)的代码如下,请注意写锁不合法的情况有两种:1. 最新版本是其他事务的私有版本。2. 非RC隔离级别下,最新版本虽然是公共版本但起始时间戳starttime
已经大于当前版本。

 int n = vList.size();
 auto latest = vList[n-1];
 if(latest->get_begin_ts() == INVALID_TS ){
  if(latest->get_end_ts() == TID) return true; //owned lock
  else return false; // locked
 } 
 if(!IS_RC && TID < latest->get_begin_ts()) return false; //old write
 return true;

成功获取写锁后就可以根据insert
remove
操作修改私人版本了,其中AddSet
DelSet
分别表示该版本的增量和减量。代码如下:

    rwLatch.lockShared();
 for(auto it: AddSet)
  vList.back()->add(it);
 for(auto it: DelSet)
  vList.back()->remove(it);
 rwLatch.unlock();
 return 1;

1.2.3 事务读操作

事务读时需要根据设定的隔离等级判定可以获取的数据。

RC级别下可以读取脏数据,因此允许读取除了私人版本的所有数据(由getLatestVersion
函数实现);

SI级别下读写可以并发执行,但只允许获取符合时间戳的数据(由getProperVersion
函数实现);

SR级别下有三种情况:

    1. 数据拥有其他事务的私人版本,那么读取失败。

    1. 数据的最新版本begin_ts
      小于等于TID
      ,说明该事务读取的是最新数据。根据读写阻塞需要在读计数上+1(当然这不影响读计数为1时的读锁升级操作)。

    1. 数据的最新版本begin_ts
      大于TID
      ,说明该事务读取的老数据。该情况况下不可能成功写入(因为SR要求可串行化,读老数据写新数据是不允许的)因此不需要加读锁。

    if(txn->GetIsolationLevelType() == IsolationLevelType::READ_COMMITTED)
 {
  getLatestVersion(txn->GetTID(), AddSet, DelSet); //get latest committed version or owned uncommitted version
 }
 else if (txn->GetIsolationLevelType() == IsolationLevelType::SNAPSHOT)
 {
  rwLatch.lockShared();
  getProperVersion(txn->GetTID(), AddSet, DelSet); //get version according to timestamp
  rwLatch.unlock();
 }
 else if (txn->GetIsolationLevelType() == IsolationLevelType::SERIALIZABLE)
 {
  rwLatch.lockShared();
  int ret = checkheadVersion(txn->GetTID());
  if(ret == -1){
   rwLatch.unlock();
   assert(latched == false);
   return false;
  }
  else if(ret == 1 && first_read){
   latched = glatch.trysharedlatch(txn->GetTID());
   if(!latched){
    rwLatch.unlock();
    assert(latched == false);
    return false;
   }
  }
  getProperVersion(txn->GetTID(), AddSet, DelSet);
  rwLatch.unlock();
 }
 else //not defined
 {
  assert(false);
 }
 return true;

1.3 事务管理

事务管理由Txn_manager
类完成。Txn_manager
类分配各事务的编号(TID
),管理各事务的状态,并在需要的时候进行MVCC的垃圾回收。垃圾回收Checkpoint
执行时要求没有事务正在运行,用一个锁checkpoint_lock
实现。

下图是一个事务的完整生命周期。事务初始为WAITING
状态。调用Begin
函数后转为RUNNING
状态,表示可以执行Query
。根据执行的情况,事务可能会被Rollback
进入ABORTED
状态,此时需要重新执行该事务,且该事务执行到一半的影响不会被记录到数据库中,保证数据库的一致性;也可能成功执行后通过Commit
函数最终提交,保证自己修改的数据能够被其他时间戳合法的事务看见。

		   	Begin()            Commit()
	WAITING--------->RUNNING------------>COMMITTED
			 Rollback()             |
						|
						v
					 ABORTED

1.3.1 Begin

Begin
函数将事务编号分配给一个客户端开启事务的请求,并修改该事务的状态为RUNNING

 checkpoint_lock.lockShared();
 txn_id_t TID = this->ArrangeTID();
    ······
 shared_ptr<Transaction> txn = make_shared<Transaction>(this->db_name, Util::get_cur_time(), TID, isolationlevel);
 txn->SetCommitID(TID);
 add_transaction(TID, txn);
 txn->SetState(TransactionState::RUNNING);
 return TID;

1.3.2 Rollback

由于执行过程中的各种情况可能导致回滚,Rollback
将事务的状态修改为ABORTED
。代码中的TransactionRollback
是用来清除该事务残留的各类锁的。

 shared_ptr<Transaction> txn = get_transaction(TID);
 if (txn == nullptr) {
  cerr << "wrong transaction id!" << endl;
  return -1;
 }
 if(db != nullptr)
  db->TransactionRollback(txn);
    ······
 txn->SetState(TransactionState::ABORTED);
 txn->SetEndTime(Util::get_cur_time());
 checkpoint_lock.unlock();
 return 0;

1.3.3 Commit

事务提交时调用Commit
函数。首先获取CID
,然后调用TransactionCommit
函数将该事务执行写操作时创建的私人版本时间戳对由(-1, TID)
修改为(CID, INF)
,表示私人版本成为最新的正式版本。最后给这些脏数据打上标记,以便执行垃圾回收。

另外,Commit结束后会根据Commit的次数执行Checkpoint回收版本,确保版本链不至于过长。

 shared_ptr<Transaction> txn = get_transaction(TID);
 ······
 txn_id_t CID = this->ArrangeCommitID();
 txn->SetCommitID(CID);
 if(db != nullptr)
  db->TransactionCommit(txn);
 ······
 txn->SetState(TransactionState::COMMITTED);
 txn->SetEndTime(Util::get_cur_time());
 add_dirty_keys(txn);
 checkpoint_lock.unlock();
 committed_num++;
 int cycle = 50000;
 if(committed_num.compare_exchange_strong(cycle, 0)){
  Checkpoint();
  cerr << "checkpoint done!" << endl;
 }
 return 0;

1.3.4 Checkpoint

Checkpoint
函数选出所有脏数据,将这些数据的所有版本链的信息整合在一起并最终合并到主数据中,以此清理所有的版本链。Checkpoint
函数结束后,内存将不再拥有任何版本。

 checkpoint_lock.lockExclusive();
 vector<unsigned> sub_ids , obj_ids, obj_literal_ids, pre_ids;
 sub_ids.insert(sub_ids.begin(), DirtyKeys[0].begin(), DirtyKeys[0].end());
 pre_ids.insert(pre_ids.begin(), DirtyKeys[1].begin(), DirtyKeys[1].end());
 for(auto key: DirtyKeys[2])
 {
  if(Util::is_entity_ele(key)){
   obj_ids.push_back(key);
  }
  else{
   obj_literal_ids.push_back(key);
  }
 }
  if(db != nullptr)
  db->VersionClean(sub_ids, obj_ids, obj_literal_ids, pre_ids);
 checkpoint_lock.unlock();

1.4 小结

本章介绍了 gStore 中事务实现与管理,建议在阅读的同时结合源码 Database/Txn_manager.cpp,KVstore/IVArray/IVArray.cpp和KVstore/IVArray/IVEntry.cpp一起分析,会更容易理解。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【LeetCode刷题-字符串】--6.N字形变换

6.N字形变换 方法&#xff1a;使用二维矩阵模拟 根据题意&#xff0c;当在矩阵中填写字符时&#xff0c;会向下填写r个字符&#xff0c;然后向右继续填写r - 2个字符&#xff0c;最后回到第一行&#xff0c;因此Z字形变换的周期是t r r - 2 2r - 2&#xff0c;是|/,每个周期…

Django 入门学习总结2 创建一个投票系统

通过学习&#xff0c;我们可以实现一个简单的投票系统。这个投票系统有两部分组成。 公共部分&#xff0c;公众可以查看和进行投票。管理员可以进行增加、删除、修改投票信息。 这里投票系统Python语言版本为3.10.13&#xff0c;Django Web框架版本为4.2.7。 投票系统的实现…

单链表相关面试题--1.删除链表中等于给定值 val 的所有节点

/* 解题思路&#xff1a;从头节点开始进行元素删除&#xff0c;每删除一个元素&#xff0c;需要重新链接节点 */ struct ListNode* removeElements(struct ListNode* head, int val) {if(head NULL)return NULL;struct ListNode* cur head;struct ListNode* prev NULL;while…

nvm的下载与使用

1.如果已经安装nodejs , 先卸载nodejs; 从控制面板中 卸载程序 卸载nodejs win r打开cmd ,管理员运行 where node 查看是否删除干净nodejs 2.下载nvm 从github 下载nvm , 下载nvm 3.nvm 和node安装路径最好写在同一个路径下 &#xff0c;如D盘 ,D\a\nvm , D\a\nodejs 4.…

什么是美颜SDK?直播美颜SDK技术深度剖析

在实现实时美颜的过程中&#xff0c;美颜SDK扮演着关键的角色&#xff0c;它为开发者提供了一套强大的工具&#xff0c;使得实时美颜效果能够轻松应用于直播平台。 一、美颜SDK的基本概念 美颜SDK是一种软件工具包&#xff0c;通过集成了丰富的图像处理算法和实时计算技术&a…

2024年测试工程师必看系列之fiddler设置手机端抓包全套教程

fiddler设置手机端抓包 安卓手机抓包 第一步&#xff1a;配置电脑和安卓的相关设置 1、手机和fiddler位于同一个局域网内&#xff1b;首先从fiddler处获取到ip地址和端口号&#xff1a; &#xff0c;点击online&#xff0c;最后一行就是ip地址 2、路径&#xff1a;Tools》Op…

国际物流社交销售玩法拆解(三):打造社交电商式分销增长

这一篇&#xff0c;是国际物流行业社交销售玩法最后一篇&#xff0c;也是国际物流企业实现业务经营新增长、打造分销增长体系的新模式。以下&#xff0c;我们一起来拆解这一模式具体内容吧。 #01 国际物流第二曲线&#xff1a;社交电商 经营增长是企业的永恒话题。在客户成本…

【前端学java】Java中的异常处理(15)完结

往期回顾&#xff1a; 【前端学java】JAVA开发的依赖安装与环境配置 &#xff08;0&#xff09;【前端学java】java的基础语法&#xff08;1&#xff09;【前端学java】JAVA中的packge与import&#xff08;2&#xff09;【前端学java】面向对象编程基础-类的使用 &#xff08;…

分享职业技术培训类型

职业技术培训类型包括&#xff1a;Python技术应用、人工智能应机器学习、大数据分析、机器学习。 一、“Python技术应用工程师” “Python技术应用工程师”职业技术认证是由工业和信息化部教育与考试中心推出一套专业化、科学化、系统化的人才考核标准&#xff0c;涉及在互…

单链表相关面试题--2.反转一个单链表

/* 解题思路&#xff1a; 此题一般常用的方法有两种&#xff0c;三指针翻转法和头插法 1. 三指针翻转法记录连续的三个节点&#xff0c;原地修改节点指向 2. 头插法每一个节点都进行头插 */ // 三个指针翻转的思想完成逆置 struct ListNode* reverseList(struct ListNode* head…

数据防泄密软件都有哪些丨十大数据防泄密软件盘点

随着信息化的不断发展&#xff0c;数据防泄密软件的需求越来越大。企业和个人应该根据自身的需求和实际情况选择适合自己的数据防泄密软件来保护数据资产的安全。 数据防泄密&#xff08;Data Loss Prevention&#xff0c;简称DLP&#xff09;&#xff1a;这是一款对敏感数据进…

mysqlbinlog使用记录

首先要确认mysql启用了binlog功能。一般默认启用。 mysql> select log_bin; ----------- | log_bin | ----------- | 1 | ----------- 然后确认binlog目录 mysql> select log_bin_basename; ---------------------------- | log_bin_basename | -----…

关于漏洞:检测到目标SSL证书已过期【原理扫描】

这个漏洞是监听443端口的&#xff0c;如果我们是正式的网站且使用了https那么更新证书即可。 但是我的服务器上面几乎是个空服务器&#xff0c;谁会用443呢&#xff1f; 找不到谁用了那我就部署一个nginx&#xff0c;用nginx代理443&#xff0c;找个证书配一下呗。 然而当我…

进程管理(四)

管程概念及实现要旨 管程引入了条件变量condition。 wait操作,把当前进程挂到条件变量对应的阻塞队列上去,signal把条件队列上的对手进程唤醒。 注意:条件变量的signal操作和信号量的signal是有区别的。条件变量的signal可能啥都不干,如果有阻塞的进程唤醒,没有啥事都不做…

使用Python实现3D曲线拟合

曲线拟合是数据分析和数学建模领域中广泛使用的技术。它涉及到寻找最接近一组数据点的数学函数的过程。在3D曲线拟合中&#xff0c;该过程被扩展到三维空间&#xff0c;其中的目标是找到最好地表示一组3D数据点的函数。 Python是一种用于科学计算的流行编程语言&#xff0c;它…

口袋参谋:如何通过布局“问大家”,快速提高宝贝转化!

问大家对于中小卖家来说&#xff0c;是非常适合的&#xff0c;因为我们完全可以靠对问大家的布局&#xff0c;提高宝贝的转化率。 问大家的作用 问大家主要是方便买家在购买前&#xff0c;了解商品的一些问题&#xff0c;而作为该不该购买的参考。对于卖家来说&#xff0c;是…

Universal adversarial perturbations(2017 CVPR)

Universal adversarial perturbations----《普遍对抗扰动》 通俗UAP算法步骤理解&#xff1a;对于 x i ∈ X {x_i} \in X xi​∈X 的每个采样数据点&#xff0c;比较 k ^ ( x i v ) \hat k({x_i} v) k^(xi​v) 与 k ^ ( x i ) \hat k({x_i}) k^(xi​) &#xff0c;如果 k…

【LeetCode刷题】--9.回文数

9.回文数 class Solution {public boolean isPalindrome(int x) {if(x < 0){return false;}int tmp x, sum 0;boolean flag false;while(x ! 0){sum sum * 10 x % 10;x / 10;}if(sum tmp){flag true;}return flag;} }

二百零六、Flume——Flume1.9.0单机版部署脚本(附截图)

一、目的 在实际项目部署时&#xff0c;要实现易部署易维护&#xff0c;需要把安装步骤变成安装脚本实现快速部署 二、部署脚本在Linux中文件位置 文件夹中只有脚本文件flume-install.sh和tar包apache-flume-1.9.0-bin.tar.gz 三、Flume安装脚本 #!/bin/bash #获取服务器名…

API接口测试工具为什么尤其重要

在现代软件开发中&#xff0c;API接口测试工具扮演着关键的角色&#xff0c;连接不同的软件组件&#xff0c;实现数据传递和功能调用。为确保API的可靠性、安全性和性能&#xff0c;此工具成为不可或缺的一部分。本文将介绍API接口测试工具的重要性! 1. 自动化测试的效率 API接…