2、問題拆解
第一:晚上 11:14發出問題,說明問題很着急,極大可能是線上問題。
第二:球友周大哥是資深架構師,可能問題會有一點彎或是有一些不常用的細節。
第三:看問題。
一句話概括問題:對于給定的時間字段值,用 painless 腳本實作減去 5 分鐘的處理。
painless 腳本的處理,我想到的是:ingest 預處理管道的使用。
當然之前我們也講過還有直接處理腳本的方式,但是:結合管道預處理會更友善、更好了解。
3、開搞,實戰一把
3.1 步驟 1:建立索引,并導入一批含日期類型的資料。
DELETE logs
PUT logs
{
"mappings": {
"properties": {
"name": {
"type": "text"
},
"start_date": {
"type": "date"
"close_date": {
}
}
}
}
PUT logs/_bulk?refresh
{"index":{"_id":1}}
{"name":"Person AA","start_date":"2015-05-06T02:49:40.894Z","close_date":"2015-11-01T18:10:30Z"}
{"index":{"_id":2}}
{"name":"Person CC","start_date":"2015-05-06T02:49:40.894Z","close_date":"2015-11-02T13:10:30Z"}
3.2 步驟 2:更新處理嘗試。
我的初始了解,擷取時間,然後 - 5(代表 5 分鐘的意思),不就搞定了嗎。
試試看?simulate 仿真執行一下:
POST /_ingest/pipeline/_simulate
"pipeline": {
"description": "_description",
"processors": [
{
"script": {
"description": "add time",
"lang": "painless",
"source": """
ctx.start_date = ctx.start_date - 5
"""
}
]
},
"docs": [
{
"_index": "index",
"_id": 1,
"_source": {
"name": "Person AA",
"start_date": "2015-05-06T02:49:40.894Z",
"close_date": "2015-11-01T18:10:30Z"
]
報錯啦!報錯如下:

類型錯誤。
是的,一種方案就是繼續:在類型轉換上下功夫。搞定類型轉換就搞定上面的腳本。
我做了很多嘗試,效果不大理想。看到這裡的同學,可以自己嘗試一下,歡迎留言交流。
這時候,我想:是不是得轉換一下思路。
3.3 步驟 3:換個思路,從腳本部分再切入。
官方文檔的如下部分,引起我的注意:
換個思路思考,既然:官方文檔拿出 1 篇文章的篇幅講解 Datetime 時間類型的 painless 的應用,說明這裡還是有“文章”的。
那就翻翻看吧。
宏觀上看:在 painless “無痛”腳本中,Datetime 時間類型共有如下三種類型:
類型1:numeric 時間戳類型,舉例:
本質上,這是一種 long 類型的值。
類型2:string 類型。舉例:"2015-05-06T02:49:40.894Z"。
我們上面寫入的資料就是這種類型。
類型3:complex 類型。這種我們不常見,它是一種複雜對象類型。在 painless 中通常為:ZonedDateTime。
單看 ZonedDateTime 幹巴巴的,不知道是啥意思?
但是,跳轉連結看到如下内容的時候,頓時感覺豁然開朗。
要強調的是如下一段話,切中選型要害!
在日期時間格式上述三種不同類型之間切換通常是實作腳本目标所必需的。
腳本中的典型應用是:将數字(numeric)或字元串(string)格式切換為 complex 日期格式,基于complex 日期格式做修改或比較,然後将其切換回數字或字元串日期格式進行存儲或傳回結果。
繼續開搞吧:
PUT /_ingest/pipeline/time_pipeline
"processors": [
"script": {
"description": "add time",
"lang": "painless",
"source": """
String datetime = ctx.start_date;
ZonedDateTime zdt = ZonedDateTime.parse(datetime);
zdt = zdt.minusMinutes(5);
ctx.start_date = zdt;
POST logs/_update_by_query?pipeline=time_pipeline
"query": {
"match_all": {}
GET logs/_search
上結果:
至此,問題解決完畢。
4、核心實作的文法解讀
第一:擷取時間字段,指派為字元串。
String datetime = ctx.start_date;
第二:字元串類型轉 complex 類型。
ZonedDateTime zdt = ZonedDateTime.parse(datetime);
第三:執行減法操作。
zdt = zdt.minusMinutes(5);
第四:減法操作後的結果重新指派回去。
ctx.start_date = zdt;
有了 painless 腳本,後面借助:update_by_query 批量更新結合 painless 腳本就能友善的實作已有資料的更新操作。
更多 Datetime painless 腳本的使用細節,推薦參考閱讀文末的官方連結位址。
5、小結
第一:對于不是特别熟悉或者非常小衆的知識點,找到突破口很關鍵。找到了突破口,就找到了解決問題的思路。
第二:一個小的知識點,能引發很多知識點甚至知識鍊條。
就拿本文舉例:我自己之前肯定也遇到過日期時間的查詢、聚合、更新處理操作,但是沒有系統化的翻看過 painless 時間處理的文檔。是以,當遇到新的問題的時候,依然是沒有方法論,隻是憑經驗去猜、嘗試,“猜、嘗試”花的時間總體算下來比檢視官方文檔還要長。
“前事不忘後事之師”,今天的實踐和小結尤其方法論部分是未來學習、實戰環節要改進的點。
參考
https://www.elastic.co/guide/en/elasticsearch/painless/master/painless-datetime.html https://www.elastic.co/guide/en/elasticsearch/painless/master/painless-api-reference-shared-java-time.html#painless-api-reference-shared-ZonedDateTime