Elasticsearch 對外提供了一個 _tasks
接口,用于獲取當前各個節點正在執行的任務,這里要避免和 pending_tasks
搞混,后者是用于獲取在 master leader 節點排隊等待修改 cluster state 的處理任務。
WHY
Task 管理功能不是一開始就有的,是從 5.0 開始推出并不斷完善的,如下是相關的 issue 鏈接。
摘取上面的部分描述如下:
We have identified several potential features of elasticsearch that can spawn long running tasks and therefore require a common management mechanism for this tasks.
This issue will introduce task management API that will provide a mechanism for communicating with and controlling currently running tasks.
The task management API will be based on the top of existing TransportAction framework, which will allow any transport action to become a task.
The tasks will maintain parent/child relationship between tasks running on the coordinating nodes and subtasks that are spawn by the coordinating node on other nodes.
上面這段話把引入 Task 的原因、作用都講解的很清楚了,簡單總結如下:
- 因為現在 es 里面會有一些
長時間運行的任務
,所以需要構建一個通用的管理機制,方便去查看這些任務的運行狀態、進度,甚至能進行一定的管理操作,比如取消。 - 該機制會提供一個
task management
的 API 來查看和獲取當前在執行的所有任務 - 實現上會基于現有的
TransportAction
框架,這樣就可以將所有的 transport action 請求都變成 task 進行管理。 - 還會提供
父子關系
的 Task 類型,主要是由協調節點觸發的父任務和下發到其他節點的子任務組成。
看到這里,相信大家已經對于 Task 管理的來歷以及要實現的功能有了大致的理解,最后再補充一句。
Task 管理功能也極大提升了 Elasticsearch 系統的可觀測性
,對于各個節點當前在執行的任務有了統一觀測的手段,不用再猜了。
接下來我們就看看什么是 Task。
WHAT
Task 通常包含如下信息:
- id,id of the task with the node
- Node 級別的 id,每個 node 獨立維護的單調遞增的整型
- task_id ,unique task id
- Cluster 級別的 id,由 Node Id 和 Node 級別的 Task Id 組成,格式為:{Node Id}:{Task Node Id}
- node,運行任務的 node
- parent_task_id,parent task id
- 如果該任務有父子關系,那么該 id 不為空
- type,task type,任務類型,主要有下面幾種:
- transport: 其他 Node 發送來的 transport 請求任務
- direct:直接在本地 node 執行的請求任務
- persistent:持久化的 task,這些 task 是存儲在 cluster state 里面的,比如 rollup/transform 等,不會因為 node 掛掉而丟失
- action,task action
- 創建該 task 的 transport action 名字
- 通過 action 可以知道這個 task 在具體做的任務,比如 indices:data/write/bulk 是一個寫請求,cluster:monitor/tasks/lists 是一個獲取 task 列表的請求
- 有些 action 后面有一些后綴,比如
[n]
[s]
等,這些后綴有一定的標識作用,這里簡單解釋下- [n],TransportNodesAction,節點與節點間通過 transport 請求發送的 action
- [s],transportShardAction,發生在分片上的 action 操作
- [p],PrimaryShardAction,發生在主分片上的 action 操作
- [r],ReplicaShardAction,發生在副本分片上的 action 操作
- start_time,任務開始的時間戳
- running_time,任務已經運行的總時間
- x_opaque_id,client 發起請求時可以設定該 http header,這樣便可以跟蹤該請求發起的所有 task
- description,任務的詳情描述
- status,描述任務的狀態
- cancellable,是否可以取消
Task 的相關接口主要是下面兩個:
-
_cat/tasks
,以列表
的形式展現所有節點的任務,只展現部分內容,屬于簡潔模式
-
_tasks
,以JSON
的形式展現所有節點的任務詳情,屬于詳情模式
下面是通過 GET _tasks?detailed=true
獲取的一個樣例數據:
{
"-Tws1PJEQ4WW_GSsRLTSLg:35061634": {
"node": "-Tws1PJEQ4WW_GSsRLTSLg",
"id": 35061634,
"type": "transport",
"action": "indices:data/write/bulk[s]",
"status": {
"phase": "waiting_on_primary"
},
"description": "requests[1], index[.monitoring-es-7-2022.12.11][0]",
"start_time_in_millis": 1670740946462,
"running_time_in_nanos": 359046,
"cancellable": false,
"parent_task_id": "_Z3jXvvvQnqR6pye8zwZtg:69312208",
"headers": {}
}
}
-
-Tws1PJEQ4WW_GSsRLTSLg:35061634
是 task id,這個 task 運行在 node id 為-Tws1PJEQ4WW_GSsRLTSLg
的 node 上,id 為35061634
- 這個 task 是一個子任務
subtask
,其 parent task id 為_Z3jXvvvQnqR6pye8zwZtg:69312208
- task type 為
transport
,這說明是其他 node 發送來的請求 - action 為
indices:data/write/bulk[s]
,是在 shard 上執行的一個 task,從 description 上可以看到是在 index.monitoring-es-7-2022.12.11
shard0
上執行的
其他字段大家可以自行解釋,這里不再展開講解。
通過這一小節,我們已經知道了 Task 的組成,那么接下來我們看看 Task 管理是如何實現的。
HOW
TaskManager
是核心管理類,它提供兩個核心方法供外部調用。
- register,注冊一個新的 task
- unregister,在 task 執行結束后,注銷之前的 task
TaskManager
內部使用一個 Map<Long,Task>
的結構維護當前 Node 的所有 task,即 task node id
到 task
的映射管理。
Task 創建的時機主要是兩個地方:
- client 通過 rest api 接口發起請求后,大部分請求都會轉成一個 transport action 請求發送出去再處理,那么在這個新的 transport action request 發送之前會生成一個 task
- Node 與 Node 之間一直在不停地互相通信,這個通信也是通過 transport action 請求完成的,在目標 Node 收到請求后,也會生成一個 task
client rest 請求的處理流程大概如下:
- client 發起 http 請求(e.g.
GET _tasks
),被路由到相關的 RestAction 中,比如RestListTasksAction
。 - RestAction 對請求做驗證和處理后,會轉換成對應的 transport action request 發送出去,轉交本地對應的 TransportAction 處理,比如
TransportListTasksAction
- TransportAction 在執行任務之前會通過 TaskManager 注冊一個 task
- TransportAction 處理相關的請求
- 在第 4 步處理結束后通過
TaskManger
注銷之前注冊的 task - 一路返回給 client 相關結果
TransportAction 中的代碼如下,注 1 為 register,注 2 為 unregister。
Node 與 Node 之間請求的處理流程大致如下:
- Node1 通過
TransportService
的 sendReqeust 方法向 Node2 發送請求,這其中使用的 actionName 是相關 TransportAction 中定義的 transportNodeAction,比如TransportListTasksAction
發送的 action name 是cluster:monitor/tasks/lists[n]
- Node2 接收到請求,在
InboudHandler
中,通過 action name 從 ReqeustHandlers 中獲取對應的 request handler,然后進行處理 - 在 RequestHandler 的
processMessageReceived
方法中,會通過 TaskManager 注冊一個 task - RequestHandler 處理相關的請求
- 在第 4 步處理結束后通過 TaskManger 注銷之前注冊的 task
- 一路返回給 Node1 相關結果
RequestHandlerRegistry 中的代碼如下,注 1 為 register,注 2 為 unregister。
關于 register 和 unregister 的實現邏輯,這里就不展開講了,感興趣的同學可以自行去查看相關代碼。
其他
持久化結果:.tasks 索引
細心的同學可能會發現在 elasticsearch 中有一個系統索引 .tasks
,如果去查詢這個索引的內容,會得到類似如下的文檔內容。
GET .tasks/_search
{
"_index" : ".tasks",
"_type" : "task",
"_id" : "9m1T5Qx6RnaRXER7Z:1715505780",
"_score" : 2.8134105,
"_source" : {
"completed" : true,
"task" : {
"node" : "9m1T5Qx6RnaRXER7Z",
"id" : 1715505780,
"type" : "transport",
"action" : "indices:data/write/reindex",
"status" : {
"total" : 34556,
"updated" : 0,
"created" : 34556,
"deleted" : 0,
"batches" : 35,
"version_conflicts" : 0,
"noops" : 0,
"retries" : {
"bulk" : 0,
"search" : 0
},
"throttled_millis" : 0,
"requests_per_second" : -1.0,
"throttled_until_millis" : 0
},
"description" : "reindex from [.indexA] to [.indexA_reindex][_doc]",
"start_time_in_millis" : 1657627161222,
"running_time_in_nanos" : 7259281625,
"cancellable" : true,
"headers" : { }
},
"response" : {
"took" : 7239,
"timed_out" : false,
"total" : 34556,
"updated" : 0,
"created" : 34556,
"deleted" : 0,
"batches" : 35,
"version_conflicts" : 0,
"noops" : 0,
"retries" : {
"bulk" : 0,
"search" : 0
},
"throttled" : "0s",
"throttled_millis" : 0,
"requests_per_second" : -1.0,
"throttled_until" : "0s",
"throttled_until_millis" : 0,
"failures" : [ ]
}
}
}
這個返回的文檔記錄了一個 reindex task 的詳情和結果。
需要注意的是,并非所有的 task 都可以持久化結果到 .tasks
索引中,這只支持某些 long running task ,如下:
- DeleteByQuery
- Reindex
- UpdateByQuery
在發起相關請求時,只要加上一個參數 wait_for_completion=true
,請求會返回一個 task id
,然后該 task 的結果會被記錄到 .tasks
索引中。如果不加該參數,則不會記錄。
另外 .tasks
索引是按需創建的,只有在需要記錄結果時才會創建該索引,如果你的 cluster 里面沒有,也沒有什么問題。
取消任務 Cancel Task
部分 Task 在執行過程中可以被取消(Cancel),相關接口為 POST _tasks/[task_id/_cancel
。但不是所有 Task 都可以被取消,只有 Cancellable
為 true 的才可以。
可以取消的任務主要是一些 long running 的task,比如 reindex、update by query、delete by query、search 等,它們的 task 都繼承了 CancellableTask
。
另外 ES 還引入了自動 cancel search 任務的機制
,如下是相關 issue:
當 ES 發現 client 主動斷開連接
時,會主動 cancel
當前正在執行的 search 任務,以便減輕集群負載。
Persistent Task
Persistent Task 是一類比較特殊的任務,一般的 Task 在 Node 停止或者 Crash 后就結束了,即便 Node 重啟也無法繼續之前在執行的 Task,但是 Persistent Task 通過將自身持久化到 Cluster State
中,即便相關 Node 停止,它依然可以被重新分配到其他 Node 上繼續運行。
這部分使用的不多,主要是 x-pack 增加的一些如 ml、rollup、transform 等功能在用,了解下即可。
總結
本文主要講解了 Elasticsearch 中 Task 的來歷、組成和實現,希望能對大家有所幫助,以后可以正確的使用 Task Management API 來解決使用中的問題。