Apache DolphinScheduler使用圖關係解決核心鏈路告警問題,減輕任務運維負擔!

海豚调度發表於2024-07-30

轉載自程式設計師小陶

Apache DolphinScheduler 在使用過程中,肯定會有任務出現失敗的情況,那麼問題來了:排程任務的告警是需要人為配置的,在生產環境中,面對海量的任務,如何找到重要的任務,並且在失敗的時候,第一時間告警呢?

先思考一下。

先看思路

本文提供一個思路,接著往下看吧。

不賣關子了。

本質是路徑查詢,本文這裡使用了圖資料庫,或者你也可以自己使用Java實現路徑查詢。

下面是需要實現的目標,看一組任務的關係,如下圖所示,存在 A/B/C/D/E 五個任務,E 任務被配置為核心任務,當 B 任務報錯時,檢測到 B 和 E 之前存在路徑,則需要電話告警。

file

所以在配置核心鏈路告警的時候,我們只需要配置葉子節點,在實際生產中,一般是應用層的任務,比如報表、標籤、介面資料等任務。

清洗依賴資料

核心邏輯就是把所有工作流內部、跨工作流以及跨專案的依賴全部清洗出來,生成一張關係表。具體清洗邏輯,可以看:海豚排程監控:新增依賴缺失巡檢,上游改動再也不用擔心了!

file

最終生成了
t_ds_task_node_base_data 任務基礎表,後續會用於 Nebula Graph,這個後面會講。

t_ds_dag_task_relation_data_df 關係最終表,後續會用於 Nebula Graph,這個後面會講。

t_ds_dag_task_relation_data_df 這個表結構如下:

file

關係匯入圖資料庫

這裡用的國產圖資料庫 Nebula Graph,當然你也可以自己使用 Java 實現路徑查詢。

為什麼我們一定要引入圖資料庫呢?有下面幾方面考慮:

  • 可以減輕排程系統Mysql的壓力,把負責的路徑計算放在圖資料庫裡面。
  • 探索更多排程任務資料治理和運維的可能性,比如任務權重,影響分析等。

file

用到的元件是 Nebula Graph,最關鍵的函式是 find path 查詢最短鏈路
① 用到的語法是:FIND SHORTEST PATH需要注意的是,注意查詢步長,UPTO <N> {STEP|STEPS}:路徑的最大跳數。預設值為5。
② 3.3.0 開始,子圖支援了邊的條件限制了,查詢的時候只拿最新的一批關係。

  • 建立圖空間
CREATE SPACE s_schedule_job (partition_num = 225, replica_factor = 3, vid_type = FIXED_STRING(180)) COMMENT = "大資料平臺排程系統任務的血緣關係";
  • 建立邊和點
## 任務標籤

DROP tag if exists t_task;
CREATE tag if not exists t_task(  id string NULL COMMENT "project_code,dag_code,task_code,拼接,",  project_name string NULL COMMENT "project_name",  project_code string NULL COMMENT "project_code",  dag_name string NULL COMMENT "dag_name",  dag_code string NULL COMMENT "dag_code",  dag_version string NULL COMMENT "dag_version",  task_code string NULL COMMENT "task_code",  task_version string NULL COMMENT "task_version",  task_name string NULL COMMENT "task_name",  task_type string NULL COMMENT "task_type",  create_time string NULL COMMENT "時間戳") comment='排程任務節點';

## 排程任務關係
drop edge if exists e_task;
create edge if not exists e_task(  pre_project_name string NULL COMMENT "project_name",  pre_project_code string NULL COMMENT "project_code",  pre_dag_name string NULL COMMENT "dag_name",  pre_dag_code string NULL COMMENT "dag_code",  pre_dag_version string NULL COMMENT "dag_version",  pre_task_code string NULL COMMENT "task_code",  pre_task_version string NULL COMMENT "task_version",  pre_task_name string NULL COMMENT "task_name",  pre_task_type string NULL COMMENT "task_type",  post_project_name string NULL COMMENT "project_name",  post_project_code string NULL COMMENT "project_code",  post_dag_name string NULL COMMENT "dag_name",  post_dag_code string NULL COMMENT "dag_code",  post_dag_version string NULL COMMENT "dag_version",  post_task_code string NULL COMMENT "task_code",  post_task_version string NULL COMMENT "task_version",  post_task_name string NULL COMMENT "task_name",  post_task_type string NULL COMMENT "task_type",  create_time string NULL COMMENT "時間戳") comment='排程任務關係';
  • 匯入資料
同步點:

{
  spark: {
    app: {
      name: Nebula_Exchange_t_task
    }
    driver: {
      cores: 2
      maxResultSize: 5G
    }
  }

  nebula: {
    address:{
      graph:["10.1.x.xx:9669","10.1.x.xx:9669","10.1.x.xx:9669","10.1.x.xx3:9669","10.1.x.xx:9669"]
      meta:["10.1.x.xx:9559","10.1.x.xx:9559","10.1.x.xx:9559"]
    }
    user: root
    pswd: "nebula密碼"
    space: s_schedule_job
    connection {
      timeout: 60000
      retry: 3
    }
    execution {
      retry: 3
    }
    error: {
      max: 32
      output: /tmp/errors/t_task
    }
    rate: {
      limit: 1024
      timeout: 10000
    }
  }
  tags: [

    {
      name: t_task
      type: {
        source: mysql
        sink: client
      }
      host:"排程系統MYSQL資料庫IP"
      port:3307
      database:"排程系統MYSQL資料庫"
      table:"t_ds_task_node_base_data"
      user:"排程系統MYSQL使用者"
      password:"排程系統MYSQL使用者密碼"
      sentence:"SELECT concat(project_code,'_',dag_code,'_',task_code) as id,project_name,project_code,dag_name,dag_code,dag_version,task_code,task_version,task_name,task_type,create_time FROM t_ds_task_node_base_data"
      fields: [project_name,project_code,dag_name,dag_code,dag_version,task_code,task_version,task_name,task_type,create_time]
      nebula.fields: [project_name,project_code,dag_name,dag_code,dag_version,task_code,task_version,task_name,task_type,create_time]
      vertex:{
        field:id
      }
      batch: 256
      partition: 32
    }

  ]


}

同步邊:

{
  spark: {
    app: {
      name: Nebula_Exchange_e_task
    }
    driver: {
      cores: 2
      maxResultSize: 5G
    }
  }

  nebula: {
    address:{
      graph:["10.1.x.xx:9669","10.1.x.xx:9669","10.1.x.xx:9669","10.1.x.xx3:9669","10.1.x.xx:9669"]
      meta:["10.1.x.xx:9559","10.1.x.xx:9559","10.1.x.xx:9559"]
    }
    user: root
    pswd: "aD@VX2018#"
    space: s_schedule_job
    connection {
      timeout: 60000
      retry: 3
    }
    execution {
      retry: 3
    }
    error: {
      max: 32
      output: /tmp/errors/e_task
    }
    rate: {
      limit: 1024
      timeout: 10000
    }
  }
  edges: [

    {
      name: e_task
      type: {
        source: mysql
        sink: client
      }
      host:"排程系統MYSQL資料庫IP"
      port:3307
      database:"排程系統MYSQL資料庫"
      table:"t_ds_task_node_base_data"
      user:"排程系統MYSQL使用者"
      password:"排程系統MYSQL使用者密碼"
      sentence:"SELECT concat(pre_project_code,'_',pre_dag_code,'_',pre_task_code) as from_id,concat(post_project_code,'_',post_dag_code,'_',post_task_code) as to_id,pre_project_name,pre_project_code,pre_dag_name,pre_dag_code,pre_dag_version,pre_task_code,pre_task_name,pre_task_type,pre_task_version,post_project_name,post_project_code,post_dag_name,post_dag_code,post_dag_version,post_task_code,post_task_name,post_task_type,post_task_version,create_time FROM t_ds_dag_task_relation_data_df"
      fields: [pre_project_name,pre_project_code,pre_dag_name,pre_dag_code,pre_dag_version,pre_task_code,pre_task_name,pre_task_type,pre_task_version,post_project_name,post_project_code,post_dag_name,post_dag_code,post_dag_version,post_task_code,post_task_name,post_task_type,post_task_version,create_time]
      nebula.fields: [pre_project_name,pre_project_code,pre_dag_name,pre_dag_code,pre_dag_version,pre_task_code,pre_task_name,pre_task_type,pre_task_version,post_project_name,post_project_code,post_dag_name,post_dag_code,post_dag_version,post_task_code,post_task_name,post_task_type,post_task_version,create_time]
      source: {
        field: from_id
      }
      target: {
        field: to_id
      }
      batch: 256
      partition: 225
    }

  ]

}

定時指令碼: 使用 Nebula Graph 社群提供的 exchange 工具把資料從 mysql 匯入 Nebula Graph。

#!/bin/bash
# 作業引數
basepath='/opt/vcredit-graph-db/s_schedule_job/exchange'
tmpdir='/tmp/nebula/s_schedule_job'
mkdir -p $tmpdir
sourcefile=${basepath}/${jobname}.conf
targetfile=${tmpdir}/${jobname}_${vardate}.conf
cat ${sourcefile} > ${targetfile}
sed -i "s/vardate/${vardate}/g" ${targetfile}
sed -i "s/varhivetable/${varhivetable}/g" ${targetfile}

# 執行環境
export JAVA_HOME=/usr/java/jdk1.8.0_181-cloudera
spark_submit="/opt/spark-2.4.8-bin-hadoop2.7/bin/spark-submit"
# 開始執行
${spark_submit} \
--principal hive@VCREDIT.COM \
--keytab /etc/security/hive.keytab \
--master "local[*]" \
--class com.vesoft.nebula.exchange.Exchange /opt/nebula/nebula-exchange_spark_2.4-3.0.0.jar  -c ${targetfile} -h

Java 服務

/**
 * 判斷這個任務是否會影響核心任務
 * @param projectName
 * @param dagName
 * @param taskName
 * @return
 */
@ApiOperation(value = "dolphinTaskIsOnCall", notes = "判斷這個任務是否會影響核心任務,是 1 ,否 0")
@ApiImplicitParams({
        @ApiImplicitParam(name = "projectName", value = "T-1", required = false, dataType = "String", example = "BigData"),
        @ApiImplicitParam(name = "dagName", value = "T-1", required = false, dataType = "String", example = "公共和自定義域(pub)_daily"),
        @ApiImplicitParam(name = "taskName", value = "T-1", required = false, dataType = "String", example = "dwd_pub_screen_zxd_cust_df")
})
@GetMapping("/dolphinTaskIsOnCall")
@ResponseBody
public DataResult dolphinTaskIsOnCall(
        @RequestParam(value = "projectName", required = true) String projectName,
        @RequestParam(value = "dagName", required = true) String dagName,
        @RequestParam(value = "taskName", required = true) String taskName) throws GraphDatabaseException, UnsupportedEncodingException {

    HashMap<String,Object> res = dolphinService.dolphinTaskIsOnCall(projectName, dagName, taskName);
    return DataResult.ok(res);
}

核心程式碼,在第 17 行:

@Override
public HashMap<String, Object> dolphinTaskIsOnCall(String projectName, String dagName, String taskName) throws GraphDatabaseException, UnsupportedEncodingException {
    HashMap<String,Object> resMap = new HashMap<>();
    // 查詢該任務 codes
    HashMap<String,Object> task = dolphinTaskInstanceMapper.getTaskCode(projectName,dagName,taskName);
    if (task == null){
        resMap.put("res","任務不存在!");
        return resMap;
    }
    String fromCodes = task.get("project_code") + "_" + task.get("dag_code") + "_" + task.get("task_code");
    // 查詢核心任務 codes
    List<HashMap<String,Object>> tasks = dolphinTaskInstanceMapper.getOnCallTasks();
    // 查詢最短鏈路
    for (HashMap<String,Object> t : tasks){
        String toCodes = t.get("project_code") + "_" + t.get("dag_code") + "_" + t.get("task_code");
        // 查詢Nebula
        String NgSql = "FIND SHORTEST PATH with PROP FROM \"" + fromCodes + "\" TO \"" + toCodes + "\" OVER * WHERE e_task.create_time > '" + DateUtils.dayToString(DateUtils.getSomeDay(new Date(), -1)) + "' UPTO 100 STEPS  YIELD path AS p;";
        int res = nebulaService.isOnCallTask("s_schedule_job",NgSql);
        if (res > 0){
            resMap.put("res",res);
            return resMap;
        }
    }
    resMap.put("res",0);
    return resMap;
}

返回值說明:

① 影響核心任務,需要打電話

② 不影響核心任務,不需要打電話

③ 任務不存在,忽略

④ code 不等於 0 ,介面異常,忽略。

封裝好介面之後,任務失敗的程式調這個介面,判斷失敗任務是否影響核心任務,如果影響就打電話。

釘釘告警樣式:

file

電話告警,直接給對應負責人打電話。

至此,我們減少了很多工告警的配置工作,只需要關注核心的葉子節點是什麼,也就是核心的應用任務是什麼,大大提高了任務告警的配置效率!!!

  • 注意:清洗資料 和 匯入圖資料庫,在每天的 23:30 分進行,一天初始化一次,確保凌晨的任務關係是最新的,主要是用於凌晨告警。

以上就使用圖關係網路解決核心鏈路告警的全部內容,如果有任何疑問,都可以與我交流,希望可以幫到你,下次見。

原文連結:https://blog.csdn.net/qq_31975963/article/details/139839102

本文由 白鯨開源 提供釋出支援!

相關文章