原文:USING TRIGGERS ON SCHEMALESS, UBER ENGINEERING’S DATASTORE USING MYSQL
譯者:杰微刊兼職翻譯汪健
本文主要介紹Schemaless triggers的細節(jié)及相關(guān)案例蒂破,從2014年10月開始馏谨,通過這種方式提供給Uber在數(shù)據(jù)存儲方面伸縮的能力。這是本系列文章的第三部分附迷,第一部分是關(guān)于Schemaless總體的設(shè)計惧互,第二部分是關(guān)于其架構(gòu)的討論。
Schemaless triggers是一種具有可伸縮性买喧、容錯性和無損性的技術(shù),它可以監(jiān)聽Schemaless實例的變化低淡。它是隱藏在行程背后的流程管理引擎滥壕,從司機的領(lǐng)航員按下“結(jié)束行程”并支付了行程費用開始胁孙,所有相關(guān)的數(shù)據(jù)都進去我們的數(shù)據(jù)倉庫等待我們分析冈止。在Schemaless系列的最后一篇中,我們將深入探討Schemaless triggers的相關(guān)功能掂器,以及我們?nèi)绾巫屵@個系統(tǒng)具備可伸縮性和容錯性。
總的來說禁漓,在Schemaless中最基本的單元數(shù)據(jù)被稱為單元(cell)肪康,它是不可變的,一旦寫入就不能再重寫(當然在特殊情況下廓潜,我們可能會刪除舊記錄)。一個單元可能被行鍵(row key)、列名稱(column name)及引用鍵(ref key)所引用,單元的內(nèi)容通過寫入一個更高版本的引用鍵來進行更新颖医,其中行鍵和列名稱保持不變。Schemaless不對存儲在其內(nèi)部的數(shù)據(jù)執(zhí)行任何的類數(shù)據(jù)庫的schema操作佛致,所以這也正是為什么叫Schemaless的原因筷弦,從Schemaless的觀點來看,它僅僅存儲JSON對象梗夸。
Schemaless Triggers案例
讓我們看一下實際中Schemaless trigger是如何工作的铅碍,下面的代碼展示了我們是如何異步計費的一個簡化版本,大寫表示Schemaless 列名稱。案例中使用的是python語言:
我們通過添加一個注解符@trigger定義一個trigger,這個注解可以加在一個函數(shù)上,也可以添加在Schemaless 指定的列上伪很。這樣的做法讓Schemaless triggers去調(diào)用指定的函數(shù),在本例中是指bill_rider,當一個單元被寫入一個指定的列時將被觸發(fā)調(diào)用应又。這里的BASE是一個列,當一個新的單元寫入到BASE時表明行程已經(jīng)結(jié)束洞就。然后就會觸發(fā)trigger盆繁,這時行鍵(這里是行程的UUID)就會被傳遞到函數(shù)中,如果需要更多的數(shù)據(jù)旬蟋,程序員必須從Schemaless 實例中獲取其他的實時數(shù)據(jù)油昂,本例是從行程存儲系統(tǒng)Mezzanine中獲取數(shù)據(jù)。
下面的圖片展示了bill_rider的trigger相關(guān)的信息流(乘客結(jié)賬部分)冕碟,箭頭指向表明了調(diào)用方和被調(diào)用方匆浙,旁邊的數(shù)字表示流程的順序:
首先行程進狀態(tài)寫入到Mezzanine首尼,這會使 Schemaless Trigger框架調(diào)用bill_rider埋嵌,在調(diào)用時锭环,函數(shù)要求行程存儲獲取STATUS列的最新版本信息节沦,在本例中is_completed字段不存在,也就是意味著乘客還未結(jié)賬失乾,接著再BASE列的行程信息將被獲取并通過函數(shù)調(diào)用信用卡provider進行結(jié)賬常熙。在本例中,我們成功地使用信用卡進行付款碱茁,所以我們將成功狀態(tài)寫回到Mezzanine中裸卫,然后將STATUS列中的is_completed字段設(shè)置為true。
Trigger框架能保證bill_rider被每個Schemaless 實例的每個單元至少調(diào)用一次纽竣。一般而言trigger函數(shù)只會被觸發(fā)一次墓贿,但在某些出錯的情況下可能會被調(diào)用若干次,這個錯誤可能是trigger函數(shù)本身的錯誤也可能是trigger函數(shù)外部的錯誤蜓氨。這也就意味著trigger函數(shù)需要被設(shè)計成具備冪等性聋袋,在本例中,冪等性可以通過檢查單元是否已經(jīng)被處理完畢來實現(xiàn)穴吹,函數(shù)檢測到如果已經(jīng)處理完畢了則可以直接返回幽勒。
當你在查看Schemaless 如何支持類似本案例的流程時,請記住這個案例港令。我們將會解釋Schemaless 如何被當做變更日志來使用的啥容,并且討論Schemaless 相關(guān)的一些API,最后還會分享我們是通過什么技術(shù)讓流程變得可伸縮和具備容錯能力顷霹。
把Schemaless 當做日志
Schemaless 包含所有單元咪惠,這也就意味著它包含了指定的行鍵、列鍵對的所有版本淋淀。也真是因為它擁有單元所有的歷史版本遥昧,Schemaless 除了可以作為隨機訪問的key-value存儲外,它還可以作為變更日志朵纷。事實上渠鸽,它是一個分區(qū)日志,每個切片都是自己的日志柴罐,如下面圖所示:
每個單元都通過指定的行鍵(這里是指UUID)進行切片映射后寫入特定的切片徽缚,在每個切片中,所有單元都會被賦予一個唯一的標識符革屠,這個標識符叫已添加ID凿试。已添加ID是一個自動遞增的字段排宰,它代表單元插入的順序,越是新的單元就會有一個越新的已添加ID那婉。除了剛剛提到的已添加ID外板甘,每個單元還會有單元寫入時間字段。在所有分片副本中已添加ID具備唯一性详炬,這個特性對于提供failover能力是非常重要的盐类。
Schemaless 的API既支持隨機訪問也支持日志類型訪問,隨機訪問API是相對于單元而言的呛谜,它由row_key在跳、column_key和ref_key三者共同標識。
put_cell (row_key, column_key, ref_key, cell):
// 通過給定的row key隐岛、column key和ref key插入一個單元
get_cell(row_key, column_key, ref_key):
//通過指定的row key猫妙、column key和ref key獲取指定單元
get_cell_latest(row_key, column_key):
// 通過指定的row key和column key獲取具有最高版本號ref key的單元
Schemaless 還包含這些API終端的批處理版本,這里我們省略它聚凹。早前說過的trigger函數(shù)bill_rider就是使用這些函數(shù)去獲取和操作一個單元的割坠。
對于日志類型訪問API,我們主要關(guān)心單元的切片數(shù)字妒牙、時間戳和已添加ID彼哼,這三者合起來稱定位的位置。
get_cells_for_shard(shard_no, location, limit):
// 從“shard_no”切片返回在“l(fā)ocation”后的不多于“l(fā)imit”個單元
與隨機訪問API類似湘今,日志訪問API擁有更多方法讓我們一次性從不同的切片中去批量獲取多個單元沪羔。其中的location可以使時間戳timestamp或已添加ID added_id。調(diào)用get_cells_for_shard除了返回指定的單元外還會返回下一個已添加ID象浑。例如蔫饰,如果你通過指定location為1000去調(diào)用get_cells_for_shards請求了10個單元,那么返回回來的下一個location的位置偏移量就是1010愉豺。
追蹤日志
通過日志類型訪問API你可以追蹤Schemaless 實例篓吁,這個看起來就像在你的操作系統(tǒng)中通過tail -f命令去追蹤一個文件,或者像kafka這樣最新的變更會被輪詢的事件通知隊列蚪拦≌燃簦客戶端然后通過維護保持跟蹤位置偏移量進而使用它們?nèi)ポ喸儭O胍_啟一個跟蹤你要指定起始入口驰贷,例如location為0盛嘿,或者任意的時間戳,或者某位置偏移量括袒。
Schemaless triggers通過使用日志類型訪問API實現(xiàn)了相同機制的跟蹤次兆,它保持跟蹤位置偏移量,通過輪詢該API方式的最直接的好處就是Schemaless triggers使處理過程具有容錯性和可擴展性锹锰。通過配置Schemaless實例及配置哪些列去輪詢數(shù)據(jù)芥炭,然后就可以通過客戶端程序去連接Schemaless triggers框架漓库。所有的函數(shù)和回調(diào)函數(shù)都被綁定到框架的數(shù)字流上,在適當?shù)臅r刻將被Schemaless triggers調(diào)用园蝠,或者說被觸發(fā)渺蒿,而這個適當?shù)臅r刻就是當一個新的單元被插入到Schemaless實例時。作為回報彪薛,框架會將增加運行在主機上的程序需要的工作進程編號茂装。框架優(yōu)雅地通過可用進程和不可用進程進行分工善延,將失敗的進程上面的工作傳播到健康進程去處理少态。這種分工模式意味著程序員只需編寫好處理者即可,例如trigger函數(shù)挚冤,只要保證這個函數(shù)是冪等性的就可以了,剩下的就交給Schemaless triggers來處理赞庶。
架構(gòu)
在這部分中训挡,我們將討論Schemaless triggers是如何做到可擴展,如何做到讓故障影響最小化歧强。下面的圖從一個高層次的角度展示了其架構(gòu)澜薄,拿了前面結(jié)賬服務(wù)的例子:
結(jié)算服務(wù)使用了運行在三個不同主機上的Schemaless triggers,為簡單起見摊册,我們假設(shè)每個主機上有一個工作進程肤京,Schemaless triggers框架將切片從多個工作進程中分開,所以每個工作進程負責一個特定的切片茅特。注意到忘分,工作進程1從切片1拉取數(shù)據(jù),而工作進程2則則負責切片2和切片5白修,最后工作進程3負責切片3和切片4妒峦。一個工作進程只處理指定切片的單元,通過獲取新的單元去調(diào)用這些切片上注冊上來的回調(diào)函數(shù)兵睛。其中一個工作進程被指定為leader肯骇,它負責分配切片給各個工作進程。如果有一個工作進程掛了祖很,leader將失敗進程的切片重新分配給其他工作進程笛丙。
在一個切片中,單元都是按照寫入的順序被進行觸發(fā)的假颇,這也就意味著如果某個觸發(fā)單元總是因為程序錯誤而總是失敗胚鸯,那它就會在相應(yīng)的切片上阻礙單元處理。為了避免這種延遲笨鸡,你可以配置Schemaless triggers標記多次失敗的單元蠢琳,并將他們放到單獨的隊列中啊终。這樣做以后Schemaless triggers就會跳過出錯的單元接著處理下一個單元。如果被標記的單元超過了某一閥值傲须,trigger就會停止蓝牲,這通常表明是系統(tǒng)錯誤,需要人工進行修復泰讽。
Schemaless triggers通過保存每個切片最新成功被觸發(fā)的單元的已添加ID去跟蹤整個觸發(fā)過程例衍,框架將這些位置偏移保存在一個共享存儲中,例如zookeeper或者Schemaless 實例本身已卸。這也就意味著如果程序被重啟了佛玄,trigger將會從公共存儲中讀取獲取位置偏移量后繼續(xù)運行,公共存儲同樣用于保存一些meta信息累澡,例如協(xié)調(diào)leader選舉的工作梦抢,工作進程的發(fā)現(xiàn)及移除。
可擴展性和容錯性
Schemaless triggers在剛開始設(shè)計時就充分考慮其可擴展性愧哟,對于任意客戶端程序奥吩,我們可以在被追蹤的Schemaless 實例中添加最多與切片數(shù)量相等的工作進程,通常這個數(shù)量為4096蕊梧。除此之外霞赫,我們可以在線添加或移除工作進程來處理Schemaless 實例中其他trigger客戶端變化的負載。僅僅通過跟蹤框架里面的進度肥矢,我們就可以給發(fā)送數(shù)據(jù)的Schemaless 實例添加盡可能多的客戶端端衰。在服務(wù)器端沒有跟蹤客戶端并推送狀態(tài)給他們的邏輯。
Schemaless triggers同樣也具備容錯性的甘改,任何一個進程發(fā)生故障都不會影響到系統(tǒng)的運行旅东。
1、如果一個客戶端進程發(fā)生錯誤了十艾,leader會將失敗進程相關(guān)的工作重新分配給其他監(jiān)控進程玉锌,確保所有切片都會分配到處理進程。
2疟羹、如果Schemaless triggers節(jié)點上的leader發(fā)生故障了主守,一個新的節(jié)點將會被選舉作為leader,在leader選舉的過程中榄融,單元仍然會被執(zhí)行参淫,但是工作不能被重新分配,而且不能添加或移除工作進程愧杯。
3涎才、如果公共存儲(例如zookeeper)發(fā)生故障了,單元仍然會被處理,但是這種情況也像leader選舉期間耍铜,不能重新分配工作邑闺,工作進程也不能添加或移除。
4棕兼、最后陡舅,Schemaless triggers框架與Schemaless 實例里面的故障是互相隔離的,任意數(shù)據(jù)庫節(jié)點宕了都沒問題伴挚,因為Schemaless triggers可以從他們的備份節(jié)點上讀取靶衍。
總結(jié)
從運維的角度來看,Schemaless triggers是一個非常好的伙伴茎芋。Schemaless 是一個實時數(shù)據(jù)源理想的存儲颅眶,因為這里的數(shù)據(jù)可以通過隨機訪問API或者通過日志類型訪問API去訪問。另外使用Schemaless triggers的日志類型訪問API可以將數(shù)據(jù)從生產(chǎn)者和消費者中解耦出來田弥,讓開發(fā)者只要關(guān)注邏輯處理而不必關(guān)心如何保證其擴展性和容錯性涛酗。最后,我們可以在運行時添加更多的存儲服務(wù)器去提升我們的性能和內(nèi)存偷厦。如今商叹,Schemaless triggers框架是整個行程處理流中的核心驅(qū)動,包括將數(shù)據(jù)收進我們的分析數(shù)據(jù)倉庫和跨數(shù)據(jù)中心的復制沪哺。我們對2016及以后未來的前景充滿期待沈自。
更多內(nèi)容: