Logstash:處理重複的文檔

這篇文章介紹了使用 Logstash 在 Elasticsearch中 對數據進行重複數據刪除的方法。 根據你的用例,Elasticsearch中 的重複內容可能不被接受。 例如,如果你要處理指標,則 Elasticsearch中 的重複數據可能會導致錯誤的聚合和不必要的警報。 即使對於某些搜索用例,重複的數據也可能導致不良的分析和搜索結果。

 

背景:Elasticsearch 索引

在介紹重複數據刪除解決方案之前,讓我們簡要介紹一下 Elasticsearch 的索引編制過程。 Elasticsearch 提供了一個 REST API 來爲你的文檔建立索引。你可以選擇提供唯一代表您的文檔的 ID,也可以讓 Elasticsearch 爲你生成ID。如果您將 HTTP PUT 與索引API 一起使用,Elasticsearch 希望您提供一個ID。如果已經存在具有相同 ID 的文檔,Elasticsearch 將用你剛纔提供的文檔替換現有內容-最後索引的文檔將獲勝。如果使用 POST 動詞,則即使語料庫中已經存在內容,Elasticsearch 也會生成具有新ID的新文檔。例如,假設你剛在一秒鐘之前爲博客文章建立了索引,並使用 POST 動詞重新發送了同一篇博客文章,Elasticsearch 創建了另一個具有相同內容但新具有 ID 的文檔。你可以參閱我之前的文章 “開始使用Elasticsearch (1)”以瞭解更多。

雖然 Elasticsearch 提供了一個顯式的 _update API,可以將其用作潛在的解決方法,但我們將把本文重點放在索引 API 上。

Logstash 的 Elasticsearch 輸出使用索引API,並且默認情況下不希望提供 ID。因此,它將每個單個事件視爲單獨的文檔。但是,有一個選項可讓你輕鬆爲 Logstash 中的每個事件設置唯一的 ID。

 

定義你的 ID

如果你的數據源已經有一個ID,那麼在索引到 Elasticsearch 之前很容易將其設置爲文檔ID。 例如,JDBC 輸入的用戶可以輕鬆地將源表中的主鍵用作 Elasticsearch ID。 使用字段引用語法,可以在輸出部分中直接設置文檔 ID:

output {
  elasticsearch {
    hosts => "example.com"
    document_id => "%{[upc_code]}"
  }
}

其中 upc_code 是數據中的字段。 該字段可能來自結構化日誌格式的字段,也可能是使用 grok 過濾器提取的。

 

刪除重複的相似內容

如前所述,在你的用例中,重複的內容可能是不可接受的。使用稱爲指紋的概念和 Logstash 指紋過濾器(fingerprint),您可以創建一個稱爲指紋的新字符串字段,以唯一地標識原始事件。指紋過濾器可以將原始事件中的一個或多個字段(默認爲消息字段)作爲源來創建一致的哈希值 (hash)。一旦創建了這些指紋,你就可以將其用作下游Elasticsearch輸出中的文檔ID。這樣,Elasticsearch 將僅在比較指紋後更新或覆蓋現有文檔內容,但絕不會複製它們。如果你想考慮更多字段以進行刪除重複數據,則可以使用 concatenate_sources 選項。

指紋過濾器具有多種算法,您可以選擇創建此一致的哈希(hash)。請參閱文檔,因爲每個函數的哈希強度不同,可能需要其他選項。在下面的示例中,我們使用 MURMUR3 方法從消息字段創建哈希並將其設置在元數據字段中。元數據字段不會發送到輸出,因此它們提供了一種在處理管道中的事件時臨時存儲數據的有效方法。

filter {
  fingerprint {
    source => "message"
    target => "[@metadata][fingerprint]"
    method => "MURMUR3"
  }
}

output {
  elasticsearch {
    hosts => "example.com"
    document_id => "%{[@metadata][fingerprint]}"
  }
}

如果使用任何加密哈希函數算法(例如SHA1,MD5),則需要提供密鑰選項。 密鑰可以是用於計算 HMAC 的任意字符串。

filter {
  fingerprint {
    source => "message"
    target => "[@metadata][fingerprint]"
    method => "SHA1",
    key => "Log analytics",
    base64encode => true
  }
}

output {
  elasticsearch {
    hosts => "example.com"
    document_id => "%{[@metadata][fingerprint]}"
  }
}

密鑰的其他示例可以是 departmentID,組織 ID 等。

 

意外重複:從 Logstash 生成 UUID

先前的用例涉及內容的有意識地刪除重複數據。在某些部署中,尤其是 Logstash 與可確保至少交付一次的持久性隊列或其他排隊系統一起使用時,Elasticsearch 中可能存在重複項。如果 Logstash 在處理過程中崩潰,則重新啓動時將重播隊列中的數據-這可能導致重複。爲了減少這種情況造成的重複,可以對每個事件使用 UUID。這裏的重點是,在將數據序列化到消息隊列之前,需要在生產方(即發佈到排隊系統的 Logstash 實例)上生成UUID。這樣,Logstash使用者在從崩潰還原或重新啓動時需要重新處理事件時,將保留相同的事件ID。

如果你的源數據沒有唯一標識符,則可以使用同一指紋過濾器來生成 UUID。請記住,此方法不考慮事件本身的內容,而是爲每個事件生成 version 4 UUID

filter {
  fingerprint {
    target => "%{[@metadata][uuid]}"
    method => "UUID"
  }
}

output {
  elasticsearch {
    hosts => "example.com"
    document_id => "%{[@metadata][uuid]}"
  }
}

如果在 Logstash 生產者和使用者之間使用隊列,則必須顯式複製@metadata字段,因爲它們不會持久化到輸出中。 另外,你可以使用以下常規字段:

filter {
  fingerprint {
    target => "generated_id"
    method => "UUID"
  }
}

output {
  kafka {
    brokers => "example.com"
    ...
  }
}

從消費者方面,您可以只使用:

input {
  kafka {
    brokers => "example.com"
  }
}

output {
  elasticsearch {
    hosts => "example.com"
    document_id => "%{[generated_id]}"
  }
}

例子

在下面,我們用一個實際的例子來展示,這個是如工作的。首先讓我們先創建一個叫做 logstash_fingerprint.conf 的 Logstash 配置文件:

logstash_fingerprint.conf

input {
    http {
        id => "data_http_input"
    }
}

filter {
    fingerprint {
        source => [ "sensor_id", "date"]
        target => "[@metadata][fingerprint]"
        method => "SHA1"
        key => "liuxg"
        concatenate_sources => true
        base64encode => true
    }
}

output {
    stdout {
        codec => rubydebug
    }

  elasticsearch {
        manage_template => "false"
        index => "fingerprint"
     	hosts => "localhost:9200"
        document_id => "%{[@metadata][fingerprint]}"
    }
}

在這裏,我們使用 http input 來收集數據。在這裏,我們使用 sensor_id 及 date 這兩個字段來生成一個 fingerprint。也就是說,只有這兩個字段是一樣的,那麼無論我們輸入多少次數據,那麼在 Elasticsearch 中將不會有新的數據生成,因爲它們的 ID 都是一樣的。 我們啓動 Logstash:

sudo ./bin/logstash -f ~/data/fingerprint/logstash_fingerprint.conf 

我們可以在另外一個 console 中打入如下的命令:

curl -XPOST --header "Content-Type:application/json"http://localhost:8080/" -d '{"sensor_id":1, "date": "2015-01-01", "reading":16.24}'

這個時候,我們可以在 Logstash 的 console 中查看到:

我們在 Kibana 的 Dev Tools 中進行查看:

GET _cat/indices

我們可以看到有一個新的 fingerprint 的索引已經生產了。

我們查看 fingerprint 的文檔數:

GET fingerprint/_count

結果顯示:

{
  "count" : 1,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  }
}

我們在另外一個 console 中打入無數次的如下的命令:

我們發現,只要是 sensor_id 和 date 的值都是一樣的,那麼 fingerprint 的文檔數永遠是 1。當然你也可以更新其它字段的值,比如 reading 字段的值爲20,那麼新的值將會在裏面得以體現。這個操作相當於更新的操作。

如果我們改動一下 sensor_id 的值爲2,也就是:

curl -XPOST --header "Content-Type:application/json"http://localhost:8080/" -d '{"sensor_id":2, "date": "2015-01-01", "reading":16.24}'

那麼我們重新查看 fingerprint 索引的文檔數:

GET fingerprint/_count

上面顯示文檔的數值爲2。也就是說,在索引 fingerprint  中,只要是 sensor_id 及 date 的數值是一樣的,那麼我們將永遠只有一個文檔,而且是永遠不會重複的。

 

結論

如你在本文中所看到的,指紋過濾器可以用於多種用途,並且是你應該在Logstash生態系統中熟悉的插件。它可以很方便地讓我們保持我們的文檔的唯一性,而不招致重複的數據生成。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章