cassandra百億級數據庫遷移實踐

遷移背景

cassandra集羣隔段時間出現rt飆高的問題,帶來的影響就是請求cassandra短期內出現大量超時,這個問題發生已經達到了平均兩週一次的頻率,已經影響到正常業務了。而出現這些問題的緣由主要有如下3點:html

  1. 當初設計表的時候partition key設計的不是很合理,當數據量上去(最大的單錶行數達到百億級)以後,出現了一些數據量比較大的partition。單partition最多的數據量達到了上百萬行(cassandra不支持mysql的limit m, n的查詢),當查詢這個partition的數據時,會帶來比較大的壓力。
  2. cassandra自己的墓碑機制,cassandra的一大特性就是快速寫入,若是遇到delete一條記錄時,cassandra並不會實時的對這條記錄作物理刪除,而是在這行記錄上添加一個邏輯刪除的標誌位,而下次查詢會load出這些已經刪除了的記錄,再作過濾。這樣就可能帶來及時某個partition的查詢出的數據量不大,可是墓碑比較多的時候會帶來嚴重的性能問題。
  3. 公司dba也不推薦使用cassandra,出現問題的時候,難於定位解決問題。因此決定將cassandra數據庫遷移至社區比較成熟的關係型數據庫mysql。

遷移方案

整個遷移方案主要分爲如下5個步驟:mysql

  1. 全量遷移:搬遷當前庫中全部的歷史數據(該過程會搬掉庫中大部分數據)
  2. 增量遷移:記錄全量遷移開始的時間,搬遷全量遷移過程當中變動了的數據
  3. 數據比對:經過接口比對cassandra和mysql中的數據,最終數據一致性達到必定99.99%以上
  4. 開雙寫:經過數據比對確保全量遷移和增量遷移沒問題之後,打開雙寫。若是雙寫有問題,數據比對還能夠發現雙寫中的問題。
  5. 切mysql讀:確保雙寫沒問題之後,而後根據服務的重要性級別,逐步按服務切mysql讀。全部服務切mysql讀之後,確保沒問題後關閉cassandra寫,最終下線cassandra。

mysql的分庫分表方案

  1. 分多少張表?在DBA的推薦下,單表的數據最好不要超過200w,估算了下最大一張表數據量100億左右,再考慮到數據將來數據增加的狀況,最大的這張表分了8192張表,單表的數據量120w左右,總共分了4個物理庫,每一個庫2048張表。
  2. 字段對應的問題? 這裏須要權衡一個問題,cassandra有List、Set、Map等結構,到mysql這邊怎麼存?這裏能夠根據本身實際狀況選擇,
    • 集合結構的轉成json以後長度都在1000個字符之內的,能夠直接轉成json用varchar來保存,優勢:處理起來簡單。缺點:須要考慮集合的數據增加問題。
    • 轉成json以後長度比較長,部分已經達到上萬個字符了,用單獨的一張表來保存。優勢:不用考慮集合的數據增加問題。缺點:處理起來麻煩,須要額外維護新的表。
  3. mysql分片鍵的選擇,咱們這裏直接採用的cassandra的partition key。
  4. mysql表的主鍵和cassandra表保持一致。

全量遷移方案調研

  1. copy導出:經過cqlsh提供的copy命令,把keyspace導出到文件。
    缺陷:
    • 在測試過程當中,導出速度大概4500行每秒,在導出過程當中偶爾會有超時,導出若是中斷,不能從中斷處繼續。
    • 若是keyspace比較大,則生成的文件比較大。因此這種方式不考慮
  2. sstableloader方式:這種方式僅支持從一個cassandra集羣遷移到另外一個cassandra集羣。因此該方式也不考慮
  3. token環遍歷方式:cassandra記錄的存儲原理是採用的一致性hash的策略
    整個環的範圍是[Long.MIN_VALUE, Long.MAX_VALUE],表的每條記錄都是經過partition key進行hash計算,而後肯定落到哪一個位置。
    • 例若有這樣一張表:
    CREATE TABLE test_table ( a text, b INT, c INT, d text, PRIMARY KEY ( ( a, b ), c ) );
    • 經過如下兩個cql就能夠遍歷該張表:
    cqlsh:> select token(a,b), c, d from test_table where token(a,b) >= -9223372036854775808 limit 50;
    
    
     token(a, b)          | c | d
    ----------------------+---+----
     -9087493578437596993 | 2 | d1
     ...
     -8987733732583272758 | 9 | x1
    
    (50 rows)
    cqlsh:> select token(a,b), c, d from test_table where token(a,b) >= -8987733732583272758 limit 50
    • 循環以上兩個過程,直到token(a, b) = LONG.MAX_VALUE,表示整個表遍歷完成。最終採用了該方式。以上幾個方案都有一個共同的問題,在遷移過程當中,數據有變動,這種狀況須要額外考慮。

全量遷移詳細過程

最終採用了以上方案3,經過遍歷cassandra表的token環的方式,遍歷表的全部數據,把數據搬到mysql中。具體以下:程序員

  1. 把整個token環分爲2048段,這麼作的目的是爲了,把每張表的一個大的遷移任務,劃分爲2048個小任務,當單個遷移任務出現問題的時候,不用全部數據重頭再來,
    只須要把出問題的一個小任務重跑就行了。這裏採用多線程。
  2. 遷移模式:主要有single和batch兩種模式:
    • single模式:逐一insert至mysql。數據量不大的狀況選擇,單表億級別如下選擇,在64個線程狀況下,16個線程讀cassandra的狀況下,速度能夠達到1.5w行每秒。
    • batch模式:batch insert至mysql。數據量比較大的狀況下選擇,單表過億的狀況下選擇。最大的一張100億數據量的表,遷移過程實際上峯值速度只有1.6w行每秒的速度。這是由於cassandra讀這部分達到瓶頸了。自己線上應用耗掉了部分資源。若是cassandra讀沒有達到瓶頸,速度翻倍是沒問題的。
  3. 遷移性能問題:這時候cassandra和mysql和應用機器自己均可能成爲瓶頸點,數據量比較大,儘可能採用性能好一點的機器。咱們當時遷移的時候,採用的一臺40核、100G+內存的機器。
  4. 該過程遇到的一些問題:
    • 異常處理問題:因爲自己cassandra和mysql的字段限制有必定區別。在這個過程確定會遇到部分記錄由於某列不符合mysql列的限制,致使寫入失敗,寫入失敗的記錄會記錄到文件。這一過程最好是在測試過程當中覆蓋的越全越好。具體的一些case以下:
      • cassandra text長度超過mysql的限制長度
      • cassandra爲null的狀況,mysql字段設置爲is not null(這種狀況須要建立表的時候多考慮)
      • cassandra的timestamp類型超過了mysql的datetime的範圍(eg:1693106-07-01 00:00:00)
      • cassandra的decimail類型超過了mysql的decimail範圍(eg:6232182630000136384.0)
    • 數據遺漏問題:因爲部分表的字段比較多,代碼中字段轉換的時候最好仔細一點。咱們這邊遇到過字段錯亂、字段漏掉等問題。再加上該過程沒有測試接入,本身開發上線了,數據遷移完成後才發現字段漏掉,而後又重頭再來,其中最大的一張表,從頭遷一次差很少須要花掉2周的時間。如今回過頭去看,這張表當初遷移的時候,還不止返工一次。這個過程其實是很是浪費時間的。
    • 慢查詢問題:在最大的一張表的遷移過程當中,超時比其餘小表要嚴重一些。而且在跑的過程當中發現,速度越跑越慢,排查發現是部分線程遇到了某個token查詢始終超時的狀況。而後線程一直死循環查詢查token。當把cassandra超時時間設置爲30s時,這種狀況有所改善,但還存在極個別token存在該問題。此處有一點奇怪的是,經過登陸到線上cassandra機器,經過cqlsh直接查詢,數據是可以查詢出來的。最終處理方案是針對該token加了5次重試,若是仍是不成功,則記錄日誌單獨處理。

增量遷移詳細過程

記錄全量遷移開始的時間,以及記錄這段時間全部變動的account(一個user包含多個account),把這部分數據發往kafka。再經過額外的增量遷移程序消費kakfa的方式把這部分數據搬到mysql,循環往復該過程,直到mysql中的數據追上cassandra中的數據。sql

  1. 消費兩個kafka隊列,一個爲全量遷移這段時間離線變動的account隊列,另外一個是當前業務實時變動的account隊列。
  2. 處理過程當中須要考慮兩個隊列中account衝突的問題,能夠根據accountid進行加鎖。
  3. 起初是按照user維度,進行增量遷移。實際上線後發現,按照user維度搬遷速度根本追不上正常業務數據變動的速度。而後選擇了比user低一個維度的account(一個user包含多個account)進行遷移。

數據比對

爲何有該步驟?爲了確保cassandra和mysql數據源儘量的一致。數據庫

  1. 在全量遷移完成之後,增量遷移過程當中,便上線了該比對功能。如何比對?當線上業務產生了數據變動,根據accountid,把該accountid下的cassandra的全部數據和mysql的全部數據經過調接口的形式查詢出來進行比對。精確到具體字段的值
  2. 本來認爲全量遷移和增量遷移基本沒什麼問題了,可是經過數據比對還發現了很多的數據不一致地方。排查發現有全量遷移過程致使的,也有增量遷移過程致使的,都是代碼bug致使。發現了問題若是某張表全量遷移過程都出了問題,除了須要從新全量遷移該表。而且增量遷移也須要重頭再來。
  3. 全部的比對結果存入數據庫,而後定時任務發現比對不過的數據,再按照account維度進行增量遷移。
  4. 遇到的主要問題以下:
    • 時間精度的問題:cassandra的timestamp時間戳精確到毫秒(cassandra的一個客戶端工具DevCenter查詢出來的時間只精確到秒,毫秒部分被截斷了,若是經過該工具肉眼比對,不容易發現該問題),而mysql的datetime默認條件只精確到了秒。
    • decimal小數位問題:cassandra中採用的decimal,對應mysql的字段類型是decimal(18,2),cassandra中若是是0或者0.000,遷移到mysql中會變成0.00,須要注意該精度問題。
    • 兩張表來保存同一份數據致使髒數據問題:因爲cassandra查詢有不少限制,爲了支持多種查詢類型。建立了兩張字段如出一轍的表,除了primary key不同。而後每次增刪改的時候,兩張表分別都增刪改,雖然這種方式帶來了查詢上的遍歷,可是產生髒數據的概率很是大。在比對的過程當中,發現同一份數據兩張表的數據量相差不小,排查發現因爲早期代碼bug致使表一寫成功,表二寫失敗這種狀況(好在的是這些數據都是很早以前的數據,因此直接忽略該問題)。而遷移至mysql,只遷移一張表過去。若是兩張表的數據不能徹底一致,必然有接口表現不一致。我我的對這種一份數據保存兩份用法也是不推薦的,若是不在物理層作限制,只經過代碼邏輯層來保證數據的一致性,是幾乎不可能的事。
    • 空字符和NULL的問題:cassandra中""空字符串的狀況下轉換至mysql變爲了NULL,這種狀況會帶來接口返回的數據不一致的問題,在不肯定下游如何使用該數據的時候,最好保證徹底一致。
    • 字段漏掉的問題:比對發現有張表的一個字段漏掉了,根本沒有遷移過去,除了須要從新全量遷移該表。而且增量遷移也須要重頭再來(儘可能避免該問題,該過程是很是耗時的)。
    • cassandra數據不一致的問題:同一條select查詢語句,連續查詢兩次返回的結果數不一致。這個比例在萬分之一-千分之一,帶來的問題就是有的數據始終是比較不過的。
    • 應用本地時鐘不一致致使的問題:現象就是隨着應用的發版,某張表的lastModifyTime的時間,出現了cassandra比mysql小的狀況,而從業務角度來講,mysql的時間是正確的。大概有5%的這種狀況,而且不會降下去。可能隨着下一次發版,該問題就消失了。近10次發版有3次出現了該問題,最終排查發現,因爲部署線上應用機器的本地時鐘相差3秒,而cassandra會依賴客戶端的時間,帶來的問題就是cassandra後提交的寫入,可能被先提交的寫入覆蓋。爲何該問題會隨着發版而偶然出現呢?由於應用是部署在容器中,每次發版都會分配新的容器。

開雙寫

通過以上步驟,基本能夠認爲cassandra和mysql的數據是一致的。而後打開雙寫,再關閉增量遷移。這時候若是雙寫有問題,經過比對程序也可以發現。json

切mysql讀

雙寫大概一週後,沒什麼問題的話,就能夠逐步按服務切mysql讀,而後就能夠下線cassandra數據庫了。後端

總結

  1. cassandra的使用:
    • 表的設計:特別須要注意partition key的設計,儘可能要保證單個partition的數據量不要太大。
    • 墓碑機制:須要注意cassandra的自己的墓碑機制,主要產生的墓碑的狀況,主要是delete操做和insert null字段這兩種狀況。咱們這裏曾經由於某個用戶頻繁操做本身app的某個動做,致使數據庫這邊頻繁的對同一個partition key執行delete操做再insert操做。用戶執行操做接近上百次後,致使該partition產生大量墓碑,最終查詢請求打到該partition key。形成慢查詢,應用超時重試,致使cassandra cpu飆升,最終致使其餘partition key也受到影響,大量查詢超時。
    • cassandra客戶端時鐘不一致的問題,可能致使寫入無效。
  2. 遷移相關:
    • 全量遷移和增量遷移,最好在上線以前測試充分,千萬注意字段漏掉錯位的問題,儘量的讓測試參與。在正式遷移以前,最好在線上建立一個預備庫,先能夠預跑一次。儘量的發現線上正式遷移時遇到的問題。不然正式遷移的時候遇問題的時候,修復是比較麻煩的。
    • 在切或關閉讀寫過程當中,必定要有回滾計劃。

版權聲明
做者:wycm
出處:http://www.javashuo.com/article/p-qsxjpjtb-dz.html
您的支持是對博主最大的鼓勵,感謝您的認真閱讀。
本文版權歸做者全部,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。
一個程序員平常分享,包括但不限於爬蟲、Java後端技術,歡迎關注多線程

相關文章
相關標籤/搜索