一道面試題: 訂單表按照訂單ID分庫分表之后,如何根據其他字段進行高性能查詢



前言

前幾天滴滴第三輪面試的時候, 遇到一道面試題, 大意是說現在給定一個訂單表, 是按照訂單ID來進行分庫分表的, 那么如果想要根據訂單的商戶ID來進行查詢某個商戶的一些訂單, 或者查詢某個用戶的訂單, 該如何處理? 當時回答的不是很好, 后來詢問了原來的同事, 發現可以通過Elasticsearch來構建二級索引或者干脆直接把全量數據存儲在ES中的方式來處理分庫分表之后的多條件查詢以及JOIN查詢

Elasticsearch構建二級索引

可以將所需要的進行判斷的字段, 簡單理解也就是SQL語句中WHERE語句中進行判斷的字段, 以及分庫分表的分區字段, 在這道題中就是訂單ID, 存儲進Elasticsearch中. 構建一個二級索引, 每次查詢的時候現在Elasticsearch中進行查詢, 獲取到訂單ID, 然后再根據訂單ID去MySQL執行查詢就好了, 如果是聚合查詢, 那么就需要自行在server中進行聚合

整個過程類似於MySQL中的非主鍵索引在執行非覆蓋索引查詢的時候, 執行的回表操作

Elasticsearch存儲全部字段

其實還可以將數據表的全部字段以及數據再存儲一份到Elasticsearch, 查詢的時候直接查詢Elasticsearch中的數據就好了, 也不需要再查詢數據庫, 缺點是Elasticsearch中存儲的數據會很多

MySQL與Elsaticsearch中數據的增刪改查如何保證事務?

上面提到的是使用Elasticsearch構建MySQL二級索引的查詢過程, 但是這中間還有一個問題, 就是Server在寫入或者修改數據的時候, 如何保證MySQL與Elasticsearch修改的事務性. 也就是MySQL中執行了增刪改的動作, Elasticsearch如何保證也同步的增刪改等. 這個如果不能保證, 那么就會造成數據不一致的情況.

我們在代碼中最直觀的一種做法就是同步寫入, 偽代碼如下:

@Transcational
public void saveData() {
    ...
    orderDAO.insert(data);
    esClient.insert(data);
    ...
}

但是上述的偽代碼存在的問題就是, 事務性其實是不能得到保障的, 就算是先寫MySQL, 然后寫入ES, ES拋了異常導致MySQL回滾也不行, 我們在實際生產環境的代碼沒有偽代碼這么簡單, ES客戶端的操作后面也可能還有業務邏輯, 這個saveData方法也有可能被別的事務調用, 所以這種方式其實是不行的.

訂閱binlog

基於阿里巴巴開源的canal, 訂閱MySQL的binlog, 把數據近實時同步到Elasticsearch中, 寫入到Elasticsearch的這個過程只需要保證at least onece就可以了, 在寫入Elasticsearch時自行保證冪等性

canal地址: https://github.com/alibaba/canal

編程式事務+補償

前面的偽代碼采取的是聲明式的方式, 無法保證回滾了MySQL數據之后Elasticsearch中的數據也能回滾, 可以采用編程式事務的方式來操作, 回滾了MySQL數據之后自行控制Elasticsearch的回滾

Elasticsearch集群也有可能發生問題或者故障, 為了避免Elasticsearch的問題導致整個業務的失敗, JD的一篇文章提到一種做法, 每當Elasticsearch寫入失敗之后, 就把數據寫入到MySQL中, 然后構建一個Worker任務定時掃描這些數據, 做補償

JD的文章鏈接: https://dbaplus.cn/news-11-2397-1.html

使用消息隊列同步更改數據庫

我們還可以使用消息隊列來傳輸數據的更改, 但是值得注意的是, 需要保證消息的有序消費, 構造同一行數據的增刪改亂序, 也會導致數據錯亂的問題出現

弊端

上文提到的兩種方案也有一些弊端

  1. 數據不一致
    1. 數據同步需要時間
    2. 數據寫入到Elasticsearch之后, 保存進去的數據不能立即被查詢到, 需要等待刷新周期(1s)
  2. 如果借助的canal或者消息隊列這種中間件出現故障, 還需要有降級的手段


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM