作者:京東科技 王長春
小編工作中負責業務的一個伺服器端系統,使用了 Elasticsearch 服務做資料儲存,業務運營人員反饋,使用者在使用該產品時發現,使用者後臺統計的訂單筆數和匯出的訂單筆數不一致!
交易訂單筆數不對,出現差錯訂單了?這一聽極為震撼!出現這樣的問題,在金融科技公司裡面是絕對不允許發生的,得馬上定位問題並解決!
小編馬上聯絡業務和相關人員,通過梳理上游系統的呼叫關係,發現業務系統使用到的是我這邊的 ES 的儲存服務,然後對線上情況進行復現,基本瞭解問題的現象:
這兩個查詢數量不一致,首先看查詢條件是否一致呢?
經過一番排查,業務系統在呼叫查詢訂單總數和匯出訂單總數的這兩個查詢條件是一致的,也就是請求到我這邊 ES 服務時,統計聚合的查詢和分頁匯出的查詢條件是一致的,但是為什麼會在 ES 裡面查詢的結果是不一致的呢?難道 ES 裡面的資料不全?統計聚合或分頁匯出的其中有一個不準了?
為了具體排查哪個操作可能存在問題,於是通過相同條件下查詢資料庫的總數和 ES 裡面的資料進行對比。發現相同條件下,資料庫裡面的資料和 ES 條件查詢的總數是一致的, 同時業務的 orerId 欄位是沒有重複,所以可以確定的是:通過 orderId 進行統計聚合去重的操作是有問題的。
資料庫查詢:資料庫是做分庫分表,此處資料庫查詢使用的是公司內的資料部銀河大表——公司資料部會 T+1日從業務從庫資料庫中抽取 T 日的增量資料放在建立的"大表"中, 方便各業務進行資料使用。
運營後臺查詢:運營後臺查詢是直接查詢 ES 儲存服務。
資料部大表數量 = MySQL 資料庫分庫分表表裡數量 = 運營控制檯查詢數量 = ES 儲存檔案數量
問題定位:
ES 儲存服務對外給業務提供的: 通過 orderId 進行統計聚合去重(cardinality)的功能應該是有問題的。
上面說過,小編負責的 ES 儲存服務對外給業務提供了通過指定業務欄位進行統計聚合去重的功能,統計聚合去重使用的是 ES 的 cardinality 功能。通過業務的查詢的條件,使用 ES 的聚合功能 cardinality 操作,對映到 ES 層的操作命令如下程式碼所示,
執行業務的查詢條件操作,從 ES 的管理端後臺裡面查詢竟然復現了和線上生產一樣的結果,聚合統計的是 21514,條件查詢的是 21427!!!
可以確定的就是這個 cardinality 操作,導致了兩個查詢的資料不一致,如下圖所示:
GET datastore_big_es_1_index/datastore_big_es_1_type/_search
{
"size": 3,
"query": {
"bool": {
"must": [
{
"match": {
"v021.raw": "selfhelp"
}
},
{
"match": {
"v012.raw": "1001"
}
},
{
"match": {
"typeId": "00029"
}
},
{
"range": {
"createdDate": {
"gte": "2021-02-01",
"lt": "2021-03-01"
}
}
},
{
"bool": {
"should": [
{
"match": {
"v031.raw": "113692300"
}
}
]
}
}
]
}
},
"aggs": {
"distinct_orderId": {
"cardinality": {
"field": "v033.raw"
}
}
}
}
為什麼 cardinality 操作會出現這樣的結果呢?
小編開始陷入了想當然的陷阱—— 以為這就是一個簡簡單單的統計去重的功能,ES 做的多好,幫你去重並統計數量了。然後事實並不是,通過 Elasticsearch 對 cardinality 官方檔案解釋,終於找到了原因。
可以參考Elasticsearch 2.x 版本官方檔案對 cardinality的解釋:cardinality
其中對 cardinality 演演算法核心解釋是:
可以總結如下:
下面對 ES 的 cardinality 的precision_threshold引數進行驗證:
1、巨量資料量下,設定最高精度及其以上,仍然會存在誤差:
2、小資料量下,設定最高精度,可以和實際數量保持一致:
那麼線上的為什麼聚合統計的是 21514,條件查詢的是 21427?
線上程式碼執行和ES叢集設定都沒有主動設定過 precision_threshold 引數,那麼可以知道,這個應該是 ES 叢集設定的預設值。線上 ES 叢集版本為 5.4x 因此找到 5.4 版本的官方檔案,發現 5.4 版本中設定的是預設值 precision_threshold=3000, 在此條件下查詢的統計聚合出來的值是 21514。
另外 ES 官方對 cardinality 操作中的precision_threshold引數也做了研究,研究了官方檔案中precision_threshold設定和cardinality查詢失敗率、查詢資料量級的關係,可作為我們在業務開發中進行參考,如下圖所示:
Elasticsearch 5.4版本官方檔案對cardinality中precision_threshold引數的研究檔案:precision_threshold
通過對 cardinality 的原理探究, 需要明白的是 : 我們使用 cardinality 是需要區分使用場景的。
基於小編的這個業務場景,對商戶訂單進行統計,是屬於精確統計場景,那 cardinality 操作就不適合了。又因為業務的 orderId 是不會重複的,理論上在我們 ES 叢集中每個記錄的 orderId 都是唯一的,因此可以不用進行去重,而可以直接使用 ES 的 count 操作,將訂單數統計彙總出,對應 Elasticsearch 開發包中 COUNT API 如下:
org.springframework.data.elasticsearch.core.ElasticsearchTemplate
#count(org.springframework.data.elasticsearch.core.query.SearchQuery, java.lang.Class<T>)
public <T> long count(SearchQuery searchQuery, Class<T> clazz) {
QueryBuilder elasticsearchQuery = searchQuery.getQuery();
QueryBuilder elasticsearchFilter = searchQuery.getFilter();
return elasticsearchFilter == null ? this.doCount(this.prepareCount(searchQuery, clazz), elasticsearchQuery) : this.doCount(this.prepareSearch(searchQuery, clazz), elasticsearchQuery, elasticsearchFilter);
}
最後歡迎大家點贊、收藏、評論,轉發!❤️❤️❤️