成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

進來抄作業:一次完美的分庫分表實踐!

運維 數據庫運維
幾年前我曾經服務過的一家電商公司,隨著業務增長我們每天的訂單量很快從 30 萬單增長到了 100 萬單,訂單總量也突破了一億。

[[360800]] 

圖片來自 Pexels

當時用的 MySQL 數據庫。根據監控,我們的每秒最高訂單量已經達到了 2000 筆(不包括秒殺,秒殺 TPS 已經上萬了。秒殺我們有一套專門的解決方案,詳見《秒殺系統設計~億級用戶》)。

不過,直到此時,訂單系統還是單庫單表,幸好當時數據庫服務器配置不錯,我們的系統才能撐住這么大的壓力。

業務量還在快速增長,再不重構系統早晚出大事,我們花了一天時間快速制定了重構方案。

重構?說這么高大上,不就是分庫分表嗎?的確,就是分庫分表。不過除了分庫分表,還包括管理端的解決方案,比如運營,客服和商務需要從多維度查詢訂單數據,分庫分表后,怎么滿足大家的需求?

分庫分表后,上線方案和數據不停機遷移方案都需要慎重考慮。為了保證系統穩定,還需要考慮相應的降級方案。

為什么要分庫分表?

當數據庫產生性能瓶頸:IO 瓶頸或 CPU 瓶頸。兩種瓶頸最終都會導致數據庫的活躍連接數增加,進而達到數據庫可承受的最大活躍連接數閾值。

終會導致應用服務無連接可用,造成災難性后果。可以先從代碼,SQL,索引幾方面進行優化。如果這幾方面已經沒有太多優化的余地,就該考慮分庫分表了。

IO 瓶頸

第一種:磁盤讀 IO 瓶頸。

由于熱點數據太多,數據庫緩存完全放不下,查詢時會產生大量的磁盤 IO,查詢速度會比較慢,這樣會導致產生大量活躍連接,最終可能會發展成無連接可用的后果。

可以采用一主多從,讀寫分離的方案,用多個從庫分攤查詢流量。或者采用分庫+水平分表(把一張表的數據拆成多張表來存放,比如訂單表可以按 user_id 來拆分)的方案。

第二種:磁盤寫 IO 瓶頸。

由于數據庫寫入頻繁,會產生頻繁的磁盤寫入 IO 操作,頻繁的磁盤 IO 操作導致產生大量活躍連接,最終同樣會發展成無連接可用的后果。

這時只能采用分庫方案,用多個庫來分攤寫入壓力。再加上水平分表的策略,分表后,單表存儲的數據量會更小,插入數據時索引查找和更新的成本會更低,插入速度自然會更快。

CPU 瓶頸

SQL 問題:如果 SQL 中包含 join,group by,order by,非索引字段條件查詢等增加 CPU 運算的操作,會對 CPU 產生明顯的壓力。

這時可以考慮 SQL 優化,創建適當的索引,也可以把一些計算量大的SQL邏輯放到應用中處理。

單表數據量太大:由于單張表數據量過大,比如超過一億,查詢時遍歷樹的層次太深或者掃描的行太多,SQL 效率會很低,也會非常消耗 CPU。這時可以根據業務場景水平分表。

分庫分表方案

分庫分表主要有兩種方案:

①利用 MyCat,KingShard 這種代理中間件分庫分表。

好處是和業務代碼耦合度很低,只需做一些配置即可,接入成本低。缺點是這種代理中間件需要單獨部署,所以從調用連路上又多了一層。

而且分庫分表邏輯完全由代理中間件管理,對于程序員完全是黑盒,一旦代理本身出問題(比如出錯或宕機),會導致無法查詢和存儲相關業務數據,引發災難性的后果。

如果不熟悉代理中間件源碼,排查問題會非常困難。曾經有公司使用 MyCat,線上發生故障后,被迫修改方案,三天三夜才恢復系統。CTO 也廢了!

②利用 Sharding-Jdbc,TSharding 等以 Jar 包形式呈現的輕量級組件分庫分表。

缺點是,會有一定的代碼開發工作量,對業務有一些侵入性。好處是對程序員透明,程序員對分庫分表邏輯的把控會更強,一旦發生故障,排查問題會比較容易。

穩妥起見,我們選用了第二種方案,使用更輕量級的 Sharding-Jdbc。

做系統重構前,我們首先要確定重構的目標,其次要對未來業務的發展有一個預期。這個可以找相關業務負責人了解,根據目標和業務預期來確定重構方案。

例如,我們希望經過本次重構,系統能支撐兩年,兩年內不再大改。業務方預期兩年內日單量達到 1000 萬,相當于兩年后日訂單量要翻 10 倍。

根據上面的數據,我們分成了 16 個數據庫。按日訂單量 1000 萬來算,每個庫平均的日訂單量就是 62.5 萬(1000 萬/16),每秒最高訂單量理論上在 1250 左右( 2000*(62.5/100) )。

這樣數據庫的壓力基本上是可控的,而且基本不會浪費服務器資源。

每個庫分了 16 張表,即便按照每天 1000 萬的訂單量,兩年總單量是 73 億(73 億=1000 萬*365*2),每個庫的數據量平均是 4.56 億(4.56 億=73 億/16),每張表的數據量平均是 2850 萬(2850 萬=4.56 億/16)。

可以看到未來兩到三年每張表的數據量也不算多,完全在可控范圍。

分庫分表主要是為了用戶端下單和查詢使用,按 user_id 的查詢頻率最高,其次是 order_id。

所以我們選擇 user_id 做為 sharding column,按 user_id 做 hash,將相同用戶的訂單數據存儲到同一個數據庫的同一張表中。

這樣用戶在網頁或者 App 上查詢訂單時只需要路由到一張表就可以獲取用戶的所有訂單了,這樣就保證了查詢性能。

另外我們在訂單 ID(order_id)里摻雜了用戶 ID(user_id)信息。

簡單來說,order_id 的設計思路就是,將 order_id 分為前后兩部分,前面的部分是 user_id,后面的部分是具體的訂單編號,兩部分組合在一起就構成了 order_id。

這樣我們很容易從 order_id 解析出 user_id。通過 order_id 查詢訂單時,先從 order_id 中解析出 user_id,然后就可以根據 user_id 路由到具體的庫表了。

另外,數據庫分成 16 個,每個庫分 16 張表還有一個好處。16 是 2 的 N 次冪,所以 hash 值對 16 取模的結果與 hash 值和 16 按位“與運算”的結果是一樣的。

我們知道位運算基于二進制,跨過各種編譯和轉化直接到最底層的機器語言,效率自然遠高于取模運算。

有讀者可能會問,查詢直接查數據庫,會不會有性能問題?是的。所以我們在上層加了 Redis,Redis 做了分片集群,用于存儲活躍用戶最近 50 條訂單。

這樣一來,只有少部分在 Redis 查不到訂單的用戶請求才會到數據庫查詢訂單,這樣就減小了數據庫查詢壓力,而且每個分庫還有兩個從庫,查詢操作只走從庫,進一步分攤了每個分庫的壓力。

有讀者可能還會問,為什么沒采用一致性 hash 方案?用戶查詢最近 50 條之前的訂單怎么辦?請繼續往后看!

管理端技術方案

分庫分表后,不同用戶的訂單數據散落在不同的庫和表中,如果需要根據用戶 ID 之外的其他條件查詢訂單。

例如,運營同學想從后臺查出某天 iphone7 的訂單量,就需要從所有數據庫的表中查出數據然后在聚合到一起。

這樣代碼實現非常復雜,而且查詢性能也會很差。所以我們需要一種更好的方案來解決這個問題。

我們采用了 ES(ElasticSearch)+HBase 組合的方案,將索引與數據存儲隔離。

可能參與條件檢索的字段都會在 ES 中建一份索引,例如商家,商品名稱,訂單日期等。所有訂單數據全量保存到 HBase 中。

我們知道 HBase 支持海量存儲,而且根據 Rowkey 查詢速度超快。而 ES 的多條件檢索能力非常強大。可以說,這個方案把 ES 和 HBase 的優點發揮地淋漓盡致。

看一下該方案的查詢過程:先根據輸入條件去 ES 相應的索引上查詢符合條件的 Rowkey 值,然后用 Rowkey 值去 HBase 查詢,后面這一步查詢速度極快,查詢時間幾乎可以忽略不計。

如下圖:

 

該方案,解決了管理端通過各種字段條件查詢訂單的業務需求,同時也解決了商家端按商家 ID 和其他條件查詢訂單的需求。如果用戶希望查詢最近 50 條訂單之前的歷史訂單,也同樣可以用這個方案。

每天產生數百萬的訂單數據,如果管理后臺想查到最新的訂單數據,就需要頻繁更新 ES 索引。在海量訂單數據的場景下,索引頻繁更新會不會對 ES 產生太大壓力?

ES 索引有一個 segment(片段)的概念。ES 把每個索引分成若干個較小的 segment 片段。

每一個 segement 都是一個完整的倒排索引,在搜索查詢時會依次掃描相關索引的所有 segment。

每次 refresh(刷新索引) 的時候,都會生成一個新的 segement,因此 segment 實際上記錄了索引的一組變化值。由于每次索引刷新只涉及個別 segement 片段,更新索引的成本就很低了。

所以,即便默認的索引刷新(refresh)間隔只有 1 秒鐘,ES 也能從容應對。

不過,由于每個 segement 的存儲和掃描都需要占用一定的內存和 CPU 等資源,因此 ES 后臺進程需要不斷的進行 segement 合并來減少 segement 的數量,從而提升掃描效率以及降低資源消耗。

MySQL 中的訂單數據需要實時同步到 Hbase 和 ES 中,那么同步方案是什么?

我們利用 Canal 實時獲取 MySQL 庫表中的增量訂單數據,然后把訂單數據推到消息隊列 RocketMQ 中,消費端獲取消息后把數據寫到 Hbase,并在 ES 更新索引。

 

上面是 Canal 的原理圖:

  • Canal 模擬 mysql slave 的交互協議,把自己偽裝成 mysql 的從庫。
  • 向 mysql master 發送 dump 協議。
  • mysql master 收到 dump 協議,發送 binary log 給 slave(Canal)。
  • Canal 解析 binary log 字節流對象,根據應用場景對 binary log 字節流做相應的處理。

為了保證數據一致性,不丟失數據。我們使用了 RocketMQ 的事務型消息,保證消息一定能成功發送。另外,在 Hbase 和 ES 都操作成功后才做 ack 操作,保證消息正常消費。

不停機數據遷移

在互聯網行業,很多系統的訪問量很高,即便在凌晨兩三點也有一定的訪問量。由于數據遷移導致服務暫停,是很難被業務方接受的!下面就聊一下在用戶無感知的前提下,我們的不停機數據遷移方案!

數據遷移過程我們要注意哪些關鍵點呢?

  • 保證遷移后數據準確不丟失,即每條記錄準確而且不丟失記錄。
  • 不影響用戶體驗,尤其是訪問量高的 C 端業務需要不停機平滑遷移。
  • 保證遷移后的系統性能和穩定性。

常用的數據遷移方案主要包括:掛從庫,雙寫以及利用數據同步工具三種方案,下面分別做一下介紹。

掛從庫

在主庫上建一個從庫。從庫數據同步完成后,將從庫升級成主庫(新庫),再將流量切到新庫。

這種方式適合表結構不變,而且空閑時間段流量很低,允許停機遷移的場景。一般發生在平臺遷移的場景,如從機房遷移到云平臺,從一個云平臺遷移到另一個云平臺。

大部分中小型互聯網系統,空閑時段訪問量很低。在空閑時段,幾分鐘的停機時間,對用戶影響很小,業務方是可以接受的。

所以我們可以采用停機遷移的方案,步驟如下:

  • 新建從庫(新數據庫),數據開始從主庫向從庫同步。
  • 數據同步完成后,找一個空閑時間段。為了保證主從數據庫數據一致,需要先停掉服務,然后再把從庫升級為主庫。
  • 如果訪問數據庫用的是域名,直接解析域名到新數據庫(從庫升級成的主庫),如果訪問數據庫用的是 IP,將 IP 改成新數據庫 IP。

最后啟動服務,整個遷移過程完成。

這種遷移方案的優勢是遷移成本低,遷移周期短。缺點是,切換數據庫過程需要停止服務。

我們的并發量比較高,而且又做了分庫分表,表結構也變了,所以不能采取這種方案!

雙寫

老庫和新庫同時寫入,然后將老數據批量遷移到新庫,最后流量切換到新庫并關閉老庫讀寫。

這種方式適合數據結構發生變化,不允許停機遷移的場景。一般發生在系統重構時,表結構發生變化,如表結構改變或者分庫分表等場景。

有些大型互聯網系統,平常并發量很高,即便是空閑時段也有相當的訪問量。幾分鐘的停機時間,對用戶也會有明顯的影響,甚至導致一定的用戶流失,這對業務方來說是無法接受的。

所以我們需要考慮一種用戶無感知的不停機遷移方案,聊一下我們的具體遷移方案,步驟如下:

①代碼準備。在服務層對訂單表進行增刪改的地方,要同時操作新庫(分庫分表后的數據庫表)和老庫,需要修改相應的代碼(同時寫新庫和老庫)。

準備遷移程序腳本,用于做老數據遷移。準備校驗程序腳本,用于校驗新庫和老庫的數據是否一致。

②開啟雙寫,老庫和新庫同時寫入。

注意:

  • 任何對數據庫的增刪改都要雙寫;對于更新操作,如果新庫沒有相關記錄,需要先從老庫查出記錄,將更新后的記錄寫入新庫。
  • 為了保證寫入性能,老庫寫完后,可以采用消息隊列異步寫入新庫。

③利用腳本程序,將某一時間戳之前的老數據遷移到新庫。

注意:

  • 時間戳一定要選擇開啟雙寫后的時間點,比如開啟雙寫后 10 分鐘的時間點,避免部分老數據被漏掉。
  • 遷移過程遇到記錄沖突直接忽略,因為第 2 步的更新操作,已經把記錄拉到了新庫。
  • 遷移過程一定要記錄日志,尤其是錯誤日志,如果有雙寫失敗的情況,我們可以通過日志恢復數據,以此來保證新老庫的數據一致。

④第 3 步完成后,我們還需要通過腳本程序檢驗數據,看新庫數據是否準確以及有沒有漏掉的數據。

⑤數據校驗沒問題后,開啟雙讀,起初給新庫放少部分流量,新庫和老庫同時讀取。

由于延時問題,新庫和老庫可能會有少量數據記錄不一致的情況,所以新庫讀不到時需要再讀一遍老庫。

⑥然后再逐步將讀流量切到新庫,相當于灰度上線的過程。遇到問題可以及時把流量切回老庫。

⑦讀流量全部切到新庫后,關閉老庫寫入(可以在代碼里加上熱配置開關),只寫新庫。

⑧遷移完成,后續可以去掉雙寫雙讀相關無用代碼。


利用數據同步工具 

我們可以看到上面雙寫的方案比較麻煩,很多數據庫寫入的地方都需要修改代碼。有沒有更好的方案呢?

我們還可以利用 Canal,DataBus 等工具做數據同步。以阿里開源的 Canal 為例。

利用同步工具,就不需要開啟雙寫了,服務層也不需要編寫雙寫的代碼,直接用 Canal 做增量數據同步即可。

相應的步驟就變成了:

①代碼準備。準備 Canal 代碼,解析 binary log 字節流對象,并把解析好的訂單數據寫入新庫。

準備遷移程序腳本,用于做老數據遷移。準備校驗程序腳本,用于校驗新庫和老庫的數據是否一致。

②運行 Canal 代碼,開始增量數據(線上產生的新數據)從老庫到新庫的同步。

③利用腳本程序,將某一時間戳之前的老數據遷移到新庫。注意:時間戳一定要選擇開始運行 Canal 程序后的時間點(比如運行 Canal 代碼后 10 分鐘的時間點),避免部分老數據被漏掉。

遷移過程一定要記錄日志,尤其是錯誤日志,如果有些記錄寫入失敗,我們可以通過日志恢復數據,以此來保證新老庫的數據一致。

④第 3 步完成后,我們還需要通過腳本程序檢驗數據,看新庫數據是否準確以及有沒有漏掉的數據。

⑤數據校驗沒問題后,開啟雙讀,起初給新庫放少部分流量,新庫和老庫同時讀取。

由于延時問題,新庫和老庫可能會有少量數據記錄不一致的情況,所以新庫讀不到時需要再讀一遍老庫。

逐步將讀流量切到新庫,相當于灰度上線的過程。遇到問題可以及時把流量切回老庫。

⑥讀流量全部切到新庫后,將寫入流量切到新庫(可以在代碼里加上熱配置開關:由于切換過程 Canal 程序還在運行,仍然能夠獲取老庫的數據變化并同步到新庫,所以切換過程不會導致部分老庫數據無法同步新庫的情況)。

⑦關閉 Canal 程序。

⑧遷移完成。

擴容縮容方案

需要對數據重新 hash 取模,再將原來多個庫表的數據寫入擴容后的庫表中。整體擴容方案和上面的不停機遷移方案基本一致。采用雙寫或者 Canal 等數據同步方案都可以。

更好的分庫分表方案

通過前面的描述,不難看出我們的分庫分表方案有一些缺陷,比如采用 hash 取模的方式會產生數據分布不均勻的情況,擴容縮容也非常麻煩。

這些問題可以用一致性 hash 方案解決。基于虛擬節點設計原理的一致性 hash 可以讓數據分布更均勻。

而且一致性 hash 采用環形設計思路,在增減節點時,使得數據遷移的成本會更低,只需要遷移臨近節點的數據。

不過需要擴容時基本上要成倍擴容,在 hash 環上每個節點間隙都增加新的節點,這樣才能分攤所有原有節點的訪問和存儲壓力。

由于篇幅原因,這里不詳細介紹一致性 hash 了,網上有很多相關資料,大家有興趣可以仔細研究一下。

降級方案

在大促期間訂單服務壓力過大時,可以將同步調用改為異步消息隊列方式,來減小訂單服務壓力并提高吞吐量。

大促時某些時間點瞬間生成訂單量很高。我們采取異步批量寫數據庫的方式,來減少數據庫訪問頻次,進而降低數據庫的寫入壓力。

詳細步驟:后端服務接到下單請求,直接放進消息隊列,訂單服務取出消息后,先將訂單信息寫入 Redis,每隔 100ms 或者積攢 10 條訂單,批量寫入數據庫一次。

前端頁面下單后定時向后端拉取訂單信息,獲取到訂單信息后跳轉到支付頁面。

用這種異步批量寫入數據庫的方式大幅減少了數據庫寫入頻次,從而明顯降低了訂單數據庫寫入壓力。

不過,因為訂單是異步寫入數據庫的,就會存在數據庫訂單和相應庫存數據暫時不一致的情況,以及用戶下單后不能及時查到訂單的情況。

因為畢竟是降級方案,可以適當降低用戶體驗,我們保證數據最終一致即可。

根據系統壓力情況,可以在大促開始時開啟異步批量寫的降級開關,大促結束后再關閉降級開關。

流程如下圖:

 

作者:二馬讀書

編輯:陶家龍

出處:轉載自公眾號二馬讀書(ID:ermadushu)

 

責任編輯:武曉燕 來源: 二馬讀書
相關推薦

2020-10-15 14:05:30

PostgreSQL升級開發

2021-10-25 09:16:27

MySQL分庫分表

2020-12-11 10:40:13

PostgreSQL數據庫GitLab

2019-11-12 09:54:20

分庫分表數據

2019-04-18 14:06:35

MySQL分庫分表數據庫

2021-10-21 06:39:41

限流熔斷系統

2021-07-27 06:37:03

HTTP協議消息

2024-07-26 00:16:11

2024-05-21 08:40:21

分庫分表源碼

2022-07-03 10:21:19

MYSQL存儲緩沖池

2021-05-08 18:50:57

分庫分表中間件

2020-07-30 17:59:34

分庫分表SQL數據庫

2020-09-27 08:00:49

分庫分表

2019-01-29 15:25:11

阿里巴巴數據庫分庫分表

2017-06-12 11:09:56

計數架構數據庫

2022-06-22 07:32:53

Sharding分庫數據源

2024-08-13 17:09:00

架構分庫分表開發

2021-08-31 20:21:11

VitessMySQL分庫

2022-06-30 07:34:46

分庫分表外賣訂單系統

2023-08-11 08:59:49

分庫分表數據數據庫
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 九九av| 日韩在线中文字幕 | 97国产一区二区精品久久呦 | 蜜桃精品在线 | 一区二区三区福利视频 | 日韩高清不卡 | 国产成人精品在线 | 一区二区免费视频 | 午夜国产一区 | 免费亚洲婷婷 | 国产伊人精品 | 精品中文在线 | 国产视频第一页 | 国产精品久久久久无码av | 欧美一级淫片007 | 国产美女自拍视频 | 亚洲第一av | 99久久国产综合精品麻豆 | 久久久久无码国产精品一区 | 国产精品1区2区 | 欧美日韩久久精品 | 国产偷录视频叫床高潮对白 | 亚洲一区视频在线 | 超黄视频网站 | 国产精品久久久久久久久久免费 | 亚洲一区二区免费 | 国产精品美女在线观看 | 色在线免费视频 | 久久国产日韩欧美 | 久久99精品久久久久久青青日本 | 久久精品aaa| 欧美日韩网站 | 伊人久久麻豆 | 国产免费一区二区三区免费视频 | 国产激情一区二区三区 | 久久国产精品无码网站 | 久久高清| 成人一区二 | 嫩草视频在线免费观看 | 亚洲一区自拍 | 三级在线免费观看 |