天天看點

GitHub 為 MySQL 貢獻線上更改表定義工具 gh-ost

GitHub 為 MySQL 貢獻線上更改表定義工具 gh-ost

github正式宣布以開源的方式釋出gh-ost:github的mysql無觸發器線上更改表定義工具!

gh-ost是github最近幾個月開發出來的,目的是解決一個經常碰到的問題:不斷變化的産品需求會不斷要求更改mysql表結構。gh-ost通過一種影響小、可控制、可審計、操作簡單的方案來改變線上表結構。

在介紹gh-ost之前,我們先了解一下各種現有方案,以及為什麼要自己開發一個新工具。

已有的線上修改表定義方案

目前,線上修改表定義的任務主要是通過這三種途徑完成的:

在從庫上修改表定義,修改之後再提升為新的主庫。

通過mysql的innodb線上ddl功能。

使用修改表定義工具。現在最流行的是percona公司的pt-online-schema-change和facebook的osc,也有人使用lhm或最早的oak-online-alter-table。

還有其它的比如galera cluster的rolling schema upgrade,或者非innodb引擎的表等。github的mysql資料庫用的都是主從複制架構,使用可靠的innodb引擎。

為什麼我們決定去設計一個新解決方案,而不是直接從上面的幾種方案中選一個用?現有的解決方案都有着自身的局限性,下面就對它們的不足之處做個簡單分析。我們會主要深入地分析基于觸發器的線上修改表定義工具的不足之處。

在從庫上修改表定義的方案需要付出許多運維代價,這需要更多的伺服器、更長的完成時間和更複雜的管理工作。修改操作是直接應用在具體的某個從庫或者整個拓撲架構的一些子樹上。伺服器當機、從庫資料不夠新、新部署的伺服器等各種問題都需要有非常嚴密的跟蹤系統來跟進單個資料庫上的操作。一個改變操作可能會需要多次反複,也就需要更長時間。而把一個從庫升為主庫也會導緻短暫的停服。如果同時需要做多個更改就更難協調。我們每天都要改好幾張表,是以在考慮解決方案時,我們不希望有這樣的管理開銷。

mysql的innodb線上ddl隻能是在你敲指令的那個mysql上才是“線上”修改的。二進制檔案中的日志把修改操作序列化了,從庫應用日志時會導緻複制延遲。但如果嘗試在每個從庫上挨個去改的話又會導緻上面分析的管理代價。而且ddl還是不可中斷的,要是在修改時把操作殺掉的話還需要更長的時間去復原,甚至導緻資料字典崩潰。這種方案也不“友好”,在系統負載高時也不能限速或者暫停。這樣的操作還有可能會耗盡你的系統資源。

我們用了pt-online-schema-change好幾年了。可是,當我們的資料增多、業務壓力增大之後,我們就碰到了越來越多的問題,甚至到了許多修改操作都被認為是“危險操作”的地步。有一些操作隻敢在非業務高峰期或者周末才敢執行,其它的總是會導緻mysql停止服務。所有現有的線上修改表定義工具都是用mysql觸發器來遷移資料的,是以本身就存在着一些問題。

基于觸發器的解決方案有什麼不好?

所有線上修改表定義的工具運作原理都是相似的:建立一張與原始表定義相同的臨時表,趁上面沒有資料時先改好表定義,然後慢慢地、用增量方式把資料從原始表拷到臨時表,同時不斷的把進行中的原始表上的資料操作(所有應用在原始表上的插入、删除、更新操作)也應用過來。當工具把所有資料都拷貝完畢,兩邊資料同步了之後,它就用這張臨時表來替代原始表。修改過程就結束了。

像pt-online-schema-change、lhm和oak-online-alter-table這些工具用的都是同步複制的方式,對表的每一條資料修改都會立刻在同一個事務裡就應用到臨時表上。facebook的工具用的則是異步模式,先把修改操作都記在一張修改日志表裡,然後再取出來執行,把修改操作應用到臨時表上。這些工具全都使用觸發器來提取那些應用在目标表上的操作。

觸發器都是存儲過程,在表上有插入、删除、修改操作時就會被觸發。觸發器可能包括好多條語句,這些語句都是和引發觸發器的那條操作在相同的事務空間内運作的,是以保證了這些操作的原子性。

一般意義上的觸發器,尤其是基于觸發器的表定義修改操作,都有如下問題:

觸發器就是存儲過程,都是解釋型代碼,mysql不會做預編譯。把它們硬嵌入到業務操作的事務空間中,會給你要修改的表上執行的每條操作都增加指令分析和解釋的開銷。

鎖:觸發器與操作語句分享相同的事務空間,當操作語句釋放了原始表上的鎖之後,觸發器再去釋放另一張表上的鎖。在同步模式下這樣行為的後果尤其嚴重。主庫上的鎖競争與寫并發有直接關系。我們在生産環境中曾經遇到過鎖競争導緻的幾乎乃至完全鎖住的情況,完全無法通路表或者整個資料庫。觸發器導緻的另一種鎖是在建立或銷毀觸發器時對中繼資料的鎖。在完成修改表定義之後從比較忙的表上删除觸發器時,我們曾經碰到幾十秒甚至幾分鐘無法提供服務的情況。

無法暫停:當主庫業務負載開始增高時,你可能會想要暫停或者取消還沒完成的修改表定義的任務。可是基于觸發器的方案沒辦法這麼做。也許你可以暫停行拷貝的操作,但卻不能暫停觸發器,因為把觸發器停掉會導緻臨時表中丢資料。是以,在整個過程中觸發器都必須一直處于工作狀态。在一些繁忙的伺服器上,我們曾經見過即使把線上操作全停掉,最後主庫還是被觸發器給拖死的情況。

并發修改:大家都希望能同時修改多張表的定義。考慮到上面分析的觸發器的代價,我們并不敢以觸發器的模式同時修改多張表的定義,我們也沒聽說有哪家公司真的線上上這麼幹。

測試:大家也許想測試一下修改方案是否可行,評估一下負載。基于觸發器的方案隻能在從庫上通過基于語句的複制來模拟一下,由于從庫上的複制操作是單線程的(即使用了多線程複制的方案,大部分情況下也還是這樣的),這樣遠不能模拟出在主庫上修改過程中的真實情況。

gh-ost

gh-ost是github’s online schema transmogrifier/transfigurator/transformer/thingy的縮寫,意思是github的線上表定義轉換器。

gh-ost有以下特點:

無觸發器

輕量級

可暫停

動态可控

可審計

可測試

可靠

gh-ost不使用觸發器,它跟蹤二進制日志檔案,在對原始表的修改送出之後,用異步方式把這修改内容應用到臨時表中去。

gh-ost希望二進制檔案使用基于行的日志格式,但這并不表示如果主庫上使用的是基于語句的日志格式,就不能用它來線上修改表定義了。事實上,我們常用的方式是用一個從庫把日志的語句模式轉成行模式,再從這個從庫上去讀日志。搭一個這樣的從庫并不複雜。

因為不需要使用觸發器,gh-ost把修改表定義的負載和正常的業務負載解耦開了。它不需要考慮被修改的表上的并發操作和競争等,這些在二進制日志中都被序列化了,gh-ost隻操作臨時表,完全與原始表不相幹。事實上,gh-ost也把行拷貝的寫操作與二進制日志的寫操作序列化了,這樣,對主庫來說隻是有一條連接配接在順序的向臨時表中不斷寫入資料,這樣的行為與常見的etl相當不同。

因為所有寫操作都是gh-ost生成的,而讀取二進制檔案本身就是一個異步操作,是以在暫停時,gh-ost是完全可以把所有對主庫的寫操作全都暫停的。暫停就意味着對主庫沒有寫入和更新。不過gh-ost也有一張内部狀态跟蹤表,即使在暫停狀态下也會向那張表中不斷寫入心跳資訊,寫入量可以忽略不計。

gh-ost提供了比簡單的暫停更多的功能,除了暫停之外還可以做:

負載:與pt-online-schema-change相近的一個功能,使用者可以設定mysql名額的門檻值,比如設定threads_running=30。

複制延遲:gh-ost内置了心跳功能來檢查複制延遲。使用者可以指定檢視哪個從庫的延遲,gh-ost預設是直接檢視它連上的那個從庫。

指令:使用者可以寫一些指令,根據輸出結果來決定要不要開始操作。比如:select hour(now()) between 8 and 17.

上述所有名額即使在修改表定義的過程中也可以動态修改。

标志位檔案:生成一個标志位檔案,gh-ost就會立刻暫停。删除檔案,gh-ost又會恢複工作。

使用者指令:通過網絡連上gh-ost,通過指令讓它暫停。

如果别的工具在修改過程中産生了比較高的負載,dba隻好把它停掉再修改配置,比如把一次拷貝的資料量改小些,然後再從頭開始修改過程。這樣的反複操作代價非常大。

gh-ost通過監聽tcp或者unix socket檔案來擷取指令。即使有正在進行中的修改工作,使用者也可以向gh-ost發出指令修改配置,比如可以這樣做:

echo throttle | socat - /tmp/gh-ost.sock:這是暫停指令。也可以輸入no-throttle

修改運作參數,gh-ost可以接受這樣的修改方式來改變它的行為:chunk-size=1500, max-lag-millis=2000, max-load=thread_running=30

用上面所說的相同接口也可以檢視gh-ost的狀态,檢視目前任務進度、主要配置參數、相關mysql執行個體的情況等。這些資訊通過網絡發送指令就可以得到,是以就給了運維人員極大的靈活性,如果是使用别的工具的話一般隻能是通過共享螢幕或者不斷跟蹤日志檔案最新内容。

讀取二進制檔案内容的操作完全不會增加主庫的負載,在從庫上做修改表結構的操作也和在主庫上做是非常相象的(當然并不完全一樣,但主要來說還是差不多的)。

gh-ost自帶了--test-on-replica選項來支援測試功能,它允許你在從庫上運作起修改表結構操作,在操作結束時會暫停主從複制,讓兩張表都處于同步、就緒狀态,然後切換表、再切換回來。這樣就可以讓使用者從容不迫地對兩張表進行檢查和對比。

我們在github是這樣在生産環境測試gh-ost的:我們有許多個指定的生産從庫,在上面不提供服務,隻是周而複始地不斷地把所有表定義都改來改去。對于我們生産環境地每一張表,小到空表,大到幾百gb,都會通過修改存儲引擎的方式來進行修改(engine=innodb),這樣并不會真正修改表結構。在每一次這樣的修改操作最後我們都會停掉主從複制,再把原始表和臨時表的全量資料都各做一次校驗和,然後比較兩個校驗和,要求它們是一緻的。然後我們恢複主從複制,再繼續測試下一張表。我們生産環境的每一張表都這樣用gh-ost在從庫上做過好多次修改測試。

可靠的

所有上述講到的和沒講到的内容,都是為了讓你對gh-ost的能力建立信任。畢竟,大家在做這件事的時候已經使用類似工具做了好多年,而gh-ost隻是一個新工具。

我們在從庫上對gh-ost進行測試,在去主庫上做第一次真正改動之前我們在從庫上成功地試了幾千次。是以,請你也在從庫上開始測試,驗證資料是完好無損的,然後再把它用到生産環境。我們希望你可以放手去試。

當你執行了gh-ost之後,也許你會看見主庫的負載變高了,那你可以發出暫停指令。用echo throttle指令生成一個檔案,看看主庫的負載會不會又變得正常。試一下這些指令,你就可以知道你可以怎樣控制它的行為,你的心裡就會安定許多。

你發起了一次修改操作,然後估計完成時間是淩晨2點鐘,可是你又非常關心最後的切換操作,非常想看着它切換,這可怎麼辦?隻需要一個标志位檔案就可以告訴gh-ost推遲切換了,這樣gh-ost會隻做完拷貝資料的操作,但不會切換表。它還會仍然繼續同步資料,保持臨時表的資料處于同步狀态。等第二天早上你回到辦公室之後,删除标志位檔案或者向gh-ost發送指令echo unpostpone,它就會做切換了。我們不希望軟體強迫我們看着它做事情,它應該把我們解放出來,讓人去做人該做的事。

談到估計完成時間,--exact-rowcount選項非常有用。在最開始時要在目标表上做個代價比較大的select count(*)操作查出具體要拷多少行資料,gh-ost就會對它要做多少工作有了一個比較準确的估計。接下來在拷貝的過程中,它會不斷地嘗試更新這個估計值。因為預計完成的時間點總是會不斷變化,是以已經完成的百分比就反而比較精确。如果你也曾經有過非常痛苦的經曆,看着已經完成99%了可是剩下的一點操作卻繼續了一個小時也沒完,你就會非常喜歡我們提供的這個功能。

gh-ost工作模式

gh-ost工作時可以連上多個mysql執行個體,同時也把自己以從庫的方式連上其中一個執行個體來擷取二進制日志事件。根據你的配置、資料庫叢集架構和你想在哪裡執行修改操作,可以有許多種不同的工作模式。

1、連上從庫,在主庫上修改

這是gh-ost預設的工作模式,它會檢視從庫情況,找到叢集的主庫并且連接配接上去。修改操作的具體步驟是:

在主庫上讀寫行資料;

在從庫上讀取二進制日志事件,将變更應用到主庫上;

在從庫上檢視表格式、字段、主鍵、總行數等;

在從庫上讀取gh-ost内部事件日志(比如心跳);

在主庫上完成表切換;

如果主庫的二進制日志格式是statement,就可以使用這種模式。但從庫就必須配成啟用二進制日志(log_bin, log_slave_updates),還要設成row格式(binlog_format=row),實際上gh-ost會在從庫上幫你做這些設定。

事實上,即使把從庫改成row格式,這仍然是對主庫侵入最少的工作模式。

2、連上主庫

如果沒有從庫,或者不想在從庫上操作,那直接用主庫也是可以的。gh-ost就會在主庫上直接做所有的操作。仍然可以在上面檢視主從複制延遲。

主庫必須産生row格式的二進制日志;

啟動gh-ost時必須用--allow-on-master選項來開啟這種模式;

3、在從庫上修改和測試

這種模式會在從庫上做修改。gh-ost仍然會連上主庫,但所有操作都是在從庫上做的,不會對主庫産生任何影響。在操作過程中,gh-ost也會不時地暫停,以便從庫的資料可以保持最新。

--migrate-on-replica選項讓gh-ost直接在從庫上修改表。最終的切換過程也是在從庫正常複制的狀态下完成的。

--test-on-replica表明操作隻是為了測試目的。在進行最終的切換操作之前,複制會被停止。原始表和臨時表會互相切換,再切換回來,最終相當于原始表沒被動過。主從複制暫停的狀态下,你可以檢查和對比這兩張表中的資料。

gh-ost在github的應用

現在github生産環境的表修改操作全都是用gh-ost完成的。每天隻要有需求來了,就将它運作起來,有時候一天會做好多次。因為它有審計和控制功能,是以我們還可以把它和我們的chatops系統整合起來。技術人員可以對它的工作進度有非常清晰的了解,是以可以控制它的行為。在生産環境中各種名額和事件都被收集起來,讓大家可以以圖形化的方式看到操作情況。

開源

gh-ost按照mit許可協定向開源社群釋出。

盡管現在已經穩定了,但是還是有一些想要繼續改進的方面。github現在把它釋出出來,希望能得到來自于社群的參與和貢獻。他們也會不斷把社群提供的建議等公布出來。