多表同步 ES 的問題

2022-01-13 08:26:18 字數 4482 閱讀 1080

對跨業務域資料提供聯查搜尋能力

為什麼要上溯需求層面 ?要優化現有方案,容易侷限在現有方案的框架裡。上溯到需求層面,能夠跳出現有方案框架,在更大的範圍內搜尋解決方案,亦可對現有方案的部分設計與實現的前提和約束有更為清晰的認識。

將多源資料儲存 (s1,s2,...,sn) 的資料同步到具備聯查能力的目標資料儲存 t,且必須滿足:

t 中的不變資料與 si 中對應的不變資料應當在指定計算關係下保持一致;

對於每一次同步,t 中的可變資料與 si 中對應的可變資料在最新狀態下保持一致。

在現實場景下,源資料儲存 si 通常是資料庫 db , 而 t 則有不同選擇。 比如,有讚選擇了 es 。選擇不同的儲存,對解決方案的影響也是非常大的。

注意:需求和目標是存在差異的。目標是原始需求在當前環境約束下所能轉化的第二層需求。如果跨業務域資料都儲存在一張源資料儲存中,也就是 源資料儲存-目標資料儲存 是 1:1 的關係,那麼目標就是單資料儲存的同步了,解決方案也會簡單得多。

但是,在規範的設計理念的指引下,不同業務域的資料通常儲存在不同的資料表裡,因此才會有多表同步的需求。

制定目標時,需要考慮當前環境約束的影響。如果當前環境約束過強,則應適當考慮能否改造環境約束,使得設計方案有更靈活的空間可供選擇。環境約束改變了,同樣需求的目標也會發生變化。

資料狀態變更的每一次最新狀態同步必須保持強一致性。

這裡的難點在於可變狀態的同步。因為不可變狀態只需要原樣同步即可,且在任何時刻進行均可。

可變狀態的同步怎麼弄呢 ? 假設狀態是不可逆的,且數值是遞增的,比如訂單狀態的數值只能從 99 -> 100 ,不可能從 100 -> 99, 這樣,通過數值的比較(而不是時間戳),就可以過濾掉那些過時的狀態。解決方案也會相對簡單。

現實場景中,狀態的逆轉往往不可避免,狀態數值的遞增關係也不一定滿足,因此,通用的方案不能以狀態數字的遞增關係為前提(否則就會削弱方案的通用性),而要以狀態變更的原始時間戳為準。

es 的前提

es 儲存的資料是乙個大的 json 串,沒有字段級別的版本控制,只有針對整個 json 串的乙個版本控制。 在多表情形下,同步 es 需要考慮全域性版本控制問題。

es 資料儲存:

es 版本樂觀控制:

多表同步的現有方案:

情形一假設有乙個 索引 e 含有 (refund_id, order_no, rp_status, version)

有乙個 db 表 r (refund_id, order_no, rp_status, version)

同步方案:

只要按照 version 字段,同步到 es 即可。對於 t1 m1, t2 m2 ,如果 t1.version < t2.verison ,將 t2 時刻的記錄同步寫入。 此時,使用非順序佇列即可。

結論:理想的情形下, 乙個 es 表完全僅對應乙個 db , db 含有 version 字段,可以作為 es 表的 version 版本控制,使用非順序佇列。 做搜尋索引,應盡可能符合這種情形。

情形二現在假設有 t (order_no, shop_name) ,要把 shop_name 同步到 e 中。 shop_name 是不變的。

這時,只要在同步處理訊息的時候, 關聯 t 表,將 shop_name 讀取,寫入到 e 即可。 只是增加了一次 db 訪問,仍然可以使用非順序佇列。

結論:將 db1 , db2 同步到 e 中, 以 db1 為主, 獲取 db2 的不變字段,依然可以使用非順序佇列。db1 的 version 字段作為版本控制。

需要注意的是,對於分庫分表來說, 這裡的 version 必須是全域性遞增的 version ,而不是某個分表的 version 。因為某個分表的 version 是不能滿足遞增特性的。

情形三現在假設有 t (order_no, order_status) , 需要把 order_status 同步到索引 e 中。這時候,如果用 version 是不夠的。

存在這樣的情形:

假設退款單 r001 (t1 m1)→ (t2,m2) 發生了 rp_status = 1 → 3 ; 訂單狀態 order_status (t1', m1') → (t2', m2') 發生了 order_status = 1 → 5。

現在 t2 時刻的退款單訊息 m2 先於 m1 抵達,實時獲取了 t1' 的訂單狀態 1 ;得到了 t2r = (r001, e001, rp_status=3, order_status=1, version =3)

然後 t1 時刻的退款單訊息 m1 抵達,實時獲取了 t2' 的訂單狀態 5, 得到了 t1r =(r001, e001, rp_status=1, order_status=5, version=1)

由於 t2r.version > t1r.verison, 因此 t2r 寫入。 然而,此時,order_status 的同步是錯誤的。

結論:當 db1 (主),db2 (輔) 均要同步到索引 e 時,如果 db1 和 db2 所需同步的字段都存在變化,那麼,使用 db1 的 version 字段控制版本號是不可行的。這將導致 db2 的字段同步變更存在錯誤。

此時,就同步 es 來說,應當盡量避免這種情形。在設計方案的時候:

避免將兩個表的可變狀態同時同步到乙個索引裡。

在業務層就能做到把 db2 的可變字段冗餘到 db1 裡。不過,這樣會增加 db1 設計和業務更新的複雜度,且事先也不會想到這種冗餘方法。

要解決多表同步的問題,有兩種現有方案:

使用順序佇列

上述的問題引發的原因是, db1 的後更新的訊息先抵達先處理。 使用順序佇列,使得 db1 的先更新的訊息始終先處理,這樣,就不會導致 後更新的訊息獲取到過時的 db2 的字段狀態了。 使用順序佇列的原理是,設定 db1 的主鍵 id 作為順序佇列的排序 key 。 順序佇列的優勢,是讓業務方處理容易了;但順序佇列的併發吞吐量取決於佇列分割槽數,且容易因為一條訊息處理出錯而阻塞後續的處理。

使用非順序佇列

使用非順序佇列,需要中間儲存,自定義的全域性版本號 g 和 乙個全域性的 儲存 s

理想情況下,無論誰先到達,都應該寫入 最新的資料。那麼,這個全域性儲存 s 和 全域性版本號 應該具備什麼特徵呢 ?

全域性儲存 s,應當含有同步 es 所需的所有字段;

每次通過全域性儲存 s 和 全域性版本號 g 的處理 globalhandler, 總能拿到所有欄位的最新值 fvnew;

每次通過全域性儲存 s 和 全域性版本號 g 的處理 globalhandler, 總能拿到遞增的全域性版本號 g;

以 g 為 es 的版本號控制,總能將遞增的 g 和 最新值 fvnew 寫入。

實現思路:

第一點相對容易,梳理一下所需要同步的字段即可。舉上為例:

第二點:全域性儲存 s,具備字段級別的過濾能力,能夠根據時間戳過濾掉狀態過時的字段值;也就是說,對於要同步的字段 fi (i= 1,2,..., n), 如果 fi.timestamp_t1 > fi.timestamp_t2 ,則 fi_value_timestamp_t2 被丟棄。每次寫入 s 之後,再取出 s 的最新值。 這正是 hbasefilter 的功能。

比如說, m2 先抵達,t2 rp_status = 3 被同步到 s ,然後取出最新的值寫入 e; 接著, m1 抵達,由於 rp_status = 1 的時間戳 t1 < t2 ,因此, rp_status = 1 會被 s 過濾掉,不起作用,然後取出最新的 s 寫入 es ; 接著 m3 抵達,將 order_status = 5 寫入 s,再取出 s 的最新值寫入 e。

第三點: 全域性版本號 g 的計算,保證訊息寫入的遞增性。 同乙個表 比如 db1 的時間戳是有先後的,比如 t2 > t1 , 但是 不同表的時間戳是沒有先後的,比如 db1 t2 與 t3 是無法確定誰大雖小的。

假設 gf 是訊息所帶時間戳 t 的函式,gf = g(t,ginit) ,ginit 是全域性版本號 g 的初始值,那麼 gf 應當滿足什麼條件呢 ?

首先 g1 = gf(t1, ginit) , g2 = gf(t2,g1) ,此時 應始終滿足: g1 > ginit , g2 > g1。

即:gm = gf(t1, ginit) > ginit, gf(t2, gm) > gm 。

也就是說,對於任意的 t 及 gm, 都有 gf(t, gm) > gm。乙個簡單的實現是,gf(t, gm) = a * t + gm + b, a 和 b 為常數。

考慮 t = 0 , 則 b > 0 。 取 b = 1. gf(t, gm) = a * t + gm + 1

方案對比:

順序佇列方案更簡單,只需要乙個任務,所有同步邏輯都在這個任務裡,且流程更符合自然思維;但順序佇列方案容易阻塞,吞吐量有瓶頸。適合中小型業務量。

非順序佇列方案,吞吐量更優,不會因為某個訊息消費阻塞;不過方案也更複雜一點,需要多個任務,額外全域性儲存,且同步邏輯較為分散,不容易直接理解。適合大型業務量。

【未完待續】

es同步mysql方案 ES資料同步方案

當業務量上公升後,由於mysql對全文檢索或模糊查詢支援的能力不強,在系統中查詢的地方,往往會出現慢sql等,拖累系統其他模組,造成效能低下。隨著es使用普及率的公升高,es是mysql的乙個有效補充。我們可以將資料傳送到搜尋引擎 如es 上,由搜尋引擎來提供專業的服務。接下來,就結合工作中實際用到...

百萬級資料多表同步

只說思路!只說思路!只說思路!應用場景 百萬級資料多表同步 實現思路 我用的是redis的list型別,我當初的應用場景是因為平台開始設計時候並沒有打算把所有流水記錄放在乙個表中,而是一種幣種,乙個流水表。像這種 假如說我想對所有幣種進行乙個查詢 條件搜尋 修改 分頁 該怎麼實現?觸發器?unin ...

多表連線問題

前端時間有同行找到我讓我給他看一段sql,說要優化,呢是個多表連線的sql,我看了下,就簡單對sql語句進行了優化,對此說下多表連線優化思路 按照正常的邏輯假如,abc三個表有關係,一般都是select from a left join b left join c where 一般都是這樣的結構,其...