日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 人文社科 > 生活经验 >内容正文

生活经验

Rocksdb 事务(一): 隔离性的实现

發(fā)布時(shí)間:2023/11/27 生活经验 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Rocksdb 事务(一): 隔离性的实现 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

      • 前言
      • 1. 隔離性
      • 2. Rocksdb實(shí)現(xiàn)的隔離級(jí)別
        • 2.1 常見的四種隔離級(jí)別
        • 2.2 Rocksdb 支持的隔離級(jí)別及基本實(shí)現(xiàn)
          • 2.2.1 ReadComitted 隔離級(jí)別的測(cè)試
          • 2.2.2 ReadCommitted的實(shí)現(xiàn)
          • 2.2.3 RepeatableRead的實(shí)現(xiàn)
          • 2.2.4 事務(wù)并發(fā)處理
      • 3. 一些總結(jié)

前言

Rocksdb 作為單機(jī)存儲(chǔ)引擎,已經(jīng)非常成熟得應(yīng)用在了許多分布式存儲(chǔ)(CEPH, TiKV),以及十分通用的數(shù)據(jù)庫(kù)之上(mysql, mongodb, Drango等),所以Rocksdb本身需要能夠?qū)崿F(xiàn)ACID屬性,尤其是其中的不同的隔離級(jí)別才能夠作為一個(gè)公共的存儲(chǔ)組件。本節(jié),結(jié)合rocksdb6.4.6代碼以及官網(wǎng)wiki來梳理一下rocksdb的事務(wù)管理以及隔離性的實(shí)現(xiàn)。


1. 隔離性

ACID中的隔離性意味著 同時(shí)執(zhí)行的事務(wù)之間是互不影響的。這個(gè)時(shí)候,在一些同時(shí)執(zhí)行事務(wù)的場(chǎng)景下,就需要有針對(duì)事務(wù)的隔離級(jí)別,來滿足客戶端針對(duì)存儲(chǔ)系統(tǒng)的要求。

圖1.1 兩個(gè)客戶之間的競(jìng)爭(zhēng)狀態(tài)同時(shí)遞增計(jì)數(shù)器

如上圖1.1,user1和user2對(duì)數(shù)據(jù)庫(kù)的訪問

  • user1先從數(shù)據(jù)庫(kù)中g(shù)et,得到了42。完成get事務(wù)之后拿著get的結(jié)果+1,將43set到數(shù)據(jù)庫(kù)中
  • user1下發(fā)set的同時(shí)user2從數(shù)據(jù)庫(kù)中g(shù)et,同樣得到了42,也進(jìn)行42+1 的操作
  • 兩者的事務(wù)都是各自隔離的,且是串行執(zhí)行互不影響(user2的get并無法同時(shí)訪問user1 set的結(jié)果),保證了結(jié)果對(duì)用戶的正確性


圖1.2 違反了隔離性:一個(gè)事務(wù)讀取了另一個(gè)事務(wù)執(zhí)行的結(jié)果

如上圖中,user2將user1的insert過程中的 hello 作為了自己的輸入,即一個(gè)事務(wù)能夠讀取另一個(gè)事務(wù)未被執(zhí)行狀態(tài)。這個(gè)過程被稱作臟讀

2. Rocksdb實(shí)現(xiàn)的隔離級(jí)別

2.1 常見的四種隔離級(jí)別

  • ReadUncommited 讀取未提交內(nèi)容,所有事務(wù)都可以看到其他未提交事務(wù)的執(zhí)行結(jié)果,存在臟讀
  • ReadCommited 讀取已提交內(nèi)容,事務(wù)只能看到其他已提交事務(wù)的更新內(nèi)容,多次讀的時(shí)候可能讀到其他事務(wù)更新的內(nèi)容
  • RepeatableRead 可重復(fù)讀,確保事務(wù)讀取數(shù)據(jù)時(shí),多次操作會(huì)看到同樣的數(shù)據(jù)行(innodb引擎使用快照隔離來實(shí)現(xiàn))。
  • Serializability 可串行化,強(qiáng)制事務(wù)之間的執(zhí)行是有序的,不會(huì)互相沖突。

2.2 Rocksdb 支持的隔離級(jí)別及基本實(shí)現(xiàn)

2.2.1 ReadComitted 隔離級(jí)別的測(cè)試

Rocksdb支持ReadCommited的隔離級(jí)別,它能夠提供兩個(gè)保障

  • 從數(shù)據(jù)庫(kù)讀時(shí),只能看到已提交的數(shù)據(jù)(沒有臟讀(dirty reads):不同事務(wù)之間能夠讀到對(duì)方未提交的內(nèi)容
  • 寫入數(shù)據(jù)庫(kù)時(shí),只會(huì)覆蓋已經(jīng)寫入的數(shù)據(jù)(沒有臟寫(dirty writes):不同事務(wù)之間的寫在提交之前能夠相互覆蓋

先看一下簡(jiǎn)單的測(cè)試代碼:

  //支持事務(wù)的方式打開rocksdbStatus s = TransactionDB::Open(options, txn_db_options, kDBPath, &txn_db);// 開啟事務(wù)操作,定義當(dāng)前事務(wù)為t1Transaction* txn = txn_db->BeginTransaction(write_options);assert(txn);// 先下發(fā)一個(gè)t1的讀操作s = txn->Get(read_options, "abc", &value);assert(s.IsNotFound());// 再下發(fā)一個(gè)t1的寫操作(注意此時(shí)是在同一個(gè)事務(wù)t1內(nèi)部,現(xiàn)在只是不同的操作)s = txn->Put("abc", "def");assert(s.ok());// 在當(dāng)前事務(wù)外部下發(fā)一個(gè)t2讀操作,確認(rèn)是否存在臟讀(txn_db->Get是一個(gè)不同于當(dāng)前事務(wù)的獨(dú)立事務(wù),t2)s = txn_db->Get(read_options, "abc", &value);std::cout << "t2 Get result " << s.ToString() << std::endl;// 在當(dāng)前事務(wù)外部下發(fā)一個(gè)t3寫操作,這里更新的是不同的key,如果更新相同的key。則t1事務(wù)commit的時(shí)候會(huì)報(bào)錯(cuò)//s = txn_db->Put(write_options, "xyz", "zzz");s = txn_db->Put(write_options, "abc", "zzz");std::cout << "t3 Put result " << s.ToString() << std::endl;// 提交t1事務(wù)s = txn->Commit();assert(s.ok());//提交之后再get一次s = txn_db->Get(read_options, "abc", &value);std::cout << "t4 Get result after commit: " << value << std::endl;delete txn;

輸出如下:

# 兩個(gè)事務(wù)Get時(shí)不可見對(duì)方未提交內(nèi)容,不存在臟讀
t2 Get result NotFound: 
# 在提交之后能夠發(fā)現(xiàn)Set的結(jié)果也并未生效,不存在臟寫,切Put相同的key發(fā)現(xiàn)加鎖超時(shí)
t3 Put result Operation timed out: Timeout waiting to lock key
# t4在t1提交之后get t1的結(jié)果的時(shí)候能夠看到t1的結(jié)果生效
t4 Get result after commit def

通過這個(gè)簡(jiǎn)單的測(cè)試代碼以及對(duì)應(yīng)的輸出結(jié)果,我們能夠看出當(dāng)前Rocksdb已經(jīng)能夠支持ReadCommited的隔離級(jí)別,不存在臟讀,同時(shí)臟寫實(shí)現(xiàn)看起來像是通過加鎖來避免的。

2.2.2 ReadCommitted的實(shí)現(xiàn)

簡(jiǎn)單描述一下該隔離特性,Rocksdb的一個(gè)事務(wù)操作是通過Rocksdb內(nèi)部WriteBatch實(shí)現(xiàn)的,針對(duì)不同事務(wù)Rocksdb會(huì)為其分配對(duì)應(yīng)的WriteBatch,由WriteBatch來處理具體的寫入。同時(shí)針對(duì)同一個(gè)事務(wù)的讀操作,會(huì)優(yōu)先從當(dāng)前事務(wù)的WriteBatch中讀,來保證能夠讀到當(dāng)前寫操作之前未提交的更新。提交的時(shí)候則依次寫入WAL和memtable之中,保證ACID的原子性和一致性。

大體的流程如下2.1圖

圖2.1 通過WriteBatch實(shí)現(xiàn) ReadCommitted

以上過程結(jié)合我們的測(cè)試代碼,可以有兩種方式來進(jìn)行

  • 顯式得通過事務(wù)的方式寫入,提交
    Transaction* txn = txn_db->BeginTransaction(write_options);
    txn->Get(read_option,"abc",&value);
    txn->Put("abc","value1");
    txn->commit();
    
  • 直接通過TransactionDB生成一個(gè)auto transaction,transactionDB會(huì)將這個(gè)單獨(dú)的操作封裝成事務(wù),并自動(dòng)commit。
    txn_db->Get(read_options, "abc", &value);
    txn_db->Put(write_options, "abc", "zzz");
    

一種transactionDB這里沒有鎖的沖突檢查,而我們使用transaction的方式進(jìn)行Put,實(shí)驗(yàn)代碼中也能看到有鎖的超時(shí)檢查.

2.2.3 RepeatableRead的實(shí)現(xiàn)

可重復(fù)讀是指Rocksdb重復(fù)多次讀取數(shù)據(jù)的時(shí)候,能夠訪問到預(yù)期的數(shù)值,而不會(huì)被其他事務(wù)的更新操作影響。
這里的可重復(fù)讀其實(shí)在SQL指定標(biāo)準(zhǔn)之前是用快照隔離來描述的,通用的關(guān)系型數(shù)據(jù)庫(kù)都使用MVCC機(jī)制來進(jìn)行多版本管理,多版本的訪問也就是通過快照來進(jìn)行的。

Rocksdb這里的實(shí)現(xiàn)是通過為每一個(gè)寫入的key-value請(qǐng)求添加一個(gè)LSN(Log Sequence Number),最初是0,每次寫入+1,達(dá)到全局遞增的目的。同時(shí)當(dāng)實(shí)現(xiàn)快照隔離時(shí),通過Snapshot設(shè)置其與一個(gè)lsn綁定,則該snapshot能夠訪問到小于等于當(dāng)前l(fā)sn的k-v數(shù)據(jù),而大于該lsn的key-value是不可見的。

相關(guān)代碼在snapshot_impl.h之中

class SnapshotImpl : public Snapshot {public://lsn numberSequenceNumber number_;  ......SnapshotImpl* prev_;SnapshotImpl* next_;SnapshotList* list_;                 // 鏈表頭指針int64_t unix_time_; //時(shí)間戳// 用于寫沖突的檢查bool is_write_conflict_boundary_;
};

snapshot可以有多個(gè),它的創(chuàng)建和刪除是通過操作一個(gè)全局的雙向鏈表來進(jìn)行,天然得根據(jù)創(chuàng)建的時(shí)間來進(jìn)行排序SetSnapShot()函數(shù)創(chuàng)建一個(gè)快照。
快照隔離的測(cè)試代碼如下:

  // 通過設(shè)置set_snapshot=true,來在BeginTransaction的時(shí)候就設(shè)置一個(gè)快照value = "def";txn_options.set_snapshot = true;txn = txn_db->BeginTransaction(write_options, txn_options);//讀取一個(gè)快照const Snapshot* snapshot = txn->GetSnapshot();// 重新生成一個(gè)寫入事務(wù)db->Put(write_options, "abc", "xyz");// 通過讀取的snapshot,來訪問指定的keyread_options.snapshot = snapshot;// 通過GetForUpdate來進(jìn)行讀操作,這個(gè)函數(shù)鎖定多個(gè)事務(wù)操作,即也會(huì)讓之前的Put加入到WriteBatch中。s = txn->GetForUpdate(read_options, "abc", &value);assert(value == "def");// 提交事務(wù)s = txn->Commit();// 新生成的事務(wù)可能與讀操作沖突,不過這里用了GetForUpdate就不會(huì)產(chǎn)生沖突了assert(s.IsBusy());delete txn;// 釋放snapshotread_options.snapshot = nullptr;snapshot = nullptr;

其中用到了GetForUpdate函數(shù),區(qū)別于Get接口,GetForUpdate對(duì)讀記錄加獨(dú)占寫鎖,保證后續(xù)對(duì)該記錄的寫操作是排他的。保證了多個(gè)事務(wù)的操作都能夠被GetForUpdate鎖定,而不是一個(gè)GetForUpdate成功,其他的失敗。

2.2.4 事務(wù)并發(fā)處理

通過對(duì)以上事務(wù)的隔離性的分析,能夠總結(jié)出以下幾種事務(wù)并發(fā)時(shí)Rocksdb的處理方式。

  1. 如果事務(wù)都是讀操作,不論操作之間是否有交集,都不會(huì)觸發(fā)鎖定
  2. 如果事務(wù)沖包含讀、寫操作
    • 所有的讀事務(wù)都不會(huì)觸發(fā)鎖定,讀的結(jié)果與snapshot請(qǐng)求相關(guān)
    • 寫事務(wù)之間不存在交集,則不會(huì)鎖定
    • 寫事務(wù)之間存在交集,如果此時(shí)設(shè)置了snapshot,則會(huì)串行提交;如果沒有設(shè)置snapshot,則只執(zhí)行第一個(gè)寫操作,其他的操作都會(huì)失敗。

3. 一些總結(jié)

本文通過探索Rocksdb的事務(wù)機(jī)制 以及描述了事務(wù)的基本實(shí)現(xiàn),讀提交以及可重復(fù)讀的特性基本能夠讓其作為單機(jī)存儲(chǔ)引擎底座,來適配分布式存儲(chǔ)中的ACID特性。
同時(shí)還有一些更加細(xì)粒度的實(shí)現(xiàn)需要探索:

  • 像針對(duì)寫事務(wù)的交集如何進(jìn)行沖突檢測(cè)以及如何通過鎖機(jī)制解決沖突。
  • 默認(rèn)使用的悲觀鎖以及可以顯式調(diào)用的樂觀鎖 在隔離性的幾個(gè)級(jí)別中是如何生效的。
  • 還有2PC(Two-Pharse-Commit)的實(shí)現(xiàn)機(jī)制,以及2PC上層的應(yīng)用場(chǎng)景

不得不說一個(gè)公共的存儲(chǔ)底座實(shí)現(xiàn)是真的不容易,后續(xù)將嘗試手寫一些隔離級(jí)別,來加深對(duì)分布式鎖的理解。

總結(jié)

以上是生活随笔為你收集整理的Rocksdb 事务(一): 隔离性的实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。