|
|
@@ -0,0 +1,157 @@
|
|
|
+package com.tzld.piaoquan.recommend.feature.produce;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.squareup.okhttp.MediaType;
|
|
|
+import com.squareup.okhttp.OkHttpClient;
|
|
|
+import com.squareup.okhttp.Request;
|
|
|
+import com.squareup.okhttp.RequestBody;
|
|
|
+import com.tzld.piaoquan.recommend.feature.common.model.DTSConfig;
|
|
|
+import com.tzld.piaoquan.recommend.feature.common.util.JSONUtils;
|
|
|
+import com.tzld.piaoquan.recommend.feature.produce.service.CMDService;
|
|
|
+import com.tzld.piaoquan.recommend.feature.produce.service.DTSConfigService;
|
|
|
+import com.tzld.piaoquan.recommend.feature.produce.service.ODPSService;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.collections.MapUtils;
|
|
|
+import org.apache.commons.lang3.math.NumberUtils;
|
|
|
+import org.apache.commons.lang3.tuple.Pair;
|
|
|
+import org.apache.spark.SparkConf;
|
|
|
+import org.apache.spark.api.java.JavaRDD;
|
|
|
+import org.apache.spark.api.java.JavaSparkContext;
|
|
|
+
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+public abstract class AbstractODPSSyncJob {
|
|
|
+
|
|
|
+ protected final static CMDService cmdService = new CMDService();
|
|
|
+ protected final static ODPSService odpsService = new ODPSService();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 模板方法:固化 ODPS 数据同步到下游存储的公共编排流程。
|
|
|
+ * 子类只需实现 appName / loadConfig / validate / sink,并按需重写 afterSink。
|
|
|
+ */
|
|
|
+ protected void run(String[] args) {
|
|
|
+ log.info("args {}", JSONUtils.toJson(args));
|
|
|
+
|
|
|
+ Map<String, String> argMap = cmdService.parse(args);
|
|
|
+ if (MapUtils.isEmpty(argMap)) {
|
|
|
+ log.error("args is empty");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ SparkConf sparkConf = new SparkConf()
|
|
|
+ //.setMaster("local")
|
|
|
+ .setAppName(appName(argMap));
|
|
|
+ for (Map.Entry<String, String> e : argMap.entrySet()) {
|
|
|
+ sparkConf.set(e.getKey(), e.getValue());
|
|
|
+ }
|
|
|
+ JavaSparkContext jsc = new JavaSparkContext(sparkConf);
|
|
|
+
|
|
|
+ // ODPS
|
|
|
+ log.info("read odps");
|
|
|
+ String env = argMap.get("env");
|
|
|
+ DTSConfigService dtsConfigService = new DTSConfigService(env);
|
|
|
+ DTSConfig config = loadConfig(dtsConfigService, argMap);
|
|
|
+ if (config == null || !validate(config)) {
|
|
|
+ log.error("dts config error");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ Pair<Long, JavaRDD<Map<String, String>>> pair = readODPS(jsc, config, argMap);
|
|
|
+ Long count = pair.getKey();
|
|
|
+ JavaRDD<Map<String, String>> records = pair.getValue();
|
|
|
+
|
|
|
+ if (records == null) {
|
|
|
+ log.info("odps empty");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("odps count {}", count);
|
|
|
+
|
|
|
+ long odpsBatchSize = NumberUtils.toLong(argMap.getOrDefault("odpsBatchSize", "1000000"));
|
|
|
+ int calcPartitionNum = (int) (count / odpsBatchSize + 1);
|
|
|
+
|
|
|
+ int maxPartitionNum = NumberUtils.toInt(argMap.get("maxPartitionNum"), 30);
|
|
|
+ int finalPartitionNum = Math.min(maxPartitionNum, calcPartitionNum);
|
|
|
+
|
|
|
+ log.info("argMap.maxPartitionNum: {} calcPartitionNum: {}, finalPartitionNum: {}", maxPartitionNum, calcPartitionNum, finalPartitionNum);
|
|
|
+ log.info("config: {}", config);
|
|
|
+
|
|
|
+ // RDD
|
|
|
+ JavaRDD<Map<String, String>> repartitionedRdd = records.repartition(finalPartitionNum);
|
|
|
+ sink(repartitionedRdd, config, argMap);
|
|
|
+
|
|
|
+ afterSink(config, argMap);
|
|
|
+
|
|
|
+ jsc.stop();
|
|
|
+ }
|
|
|
+
|
|
|
+ protected abstract String appName(Map<String, String> argMap);
|
|
|
+
|
|
|
+ protected abstract DTSConfig loadConfig(DTSConfigService dtsConfigService, Map<String, String> argMap);
|
|
|
+
|
|
|
+ protected abstract boolean validate(DTSConfig config);
|
|
|
+
|
|
|
+ protected abstract void sink(JavaRDD<Map<String, String>> repartitionedRdd, DTSConfig config, Map<String, String> argMap);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步完成后的收尾钩子(如设置监控标记),默认空实现,子类按需重写。
|
|
|
+ */
|
|
|
+ protected void afterSink(DTSConfig config, Map<String, String> argMap) {
|
|
|
+ }
|
|
|
+
|
|
|
+ protected static Pair<Long, JavaRDD<Map<String, String>>> readODPS(JavaSparkContext jsc, DTSConfig config, Map<String, String> argMap) {
|
|
|
+
|
|
|
+ long count = 0;
|
|
|
+ int retry = NumberUtils.toInt(argMap.getOrDefault("retry", "50"), 50);
|
|
|
+ while (count <= 0 && retry-- >= 0) {
|
|
|
+ count = odpsService.count(config, argMap);
|
|
|
+ if (count <= 0) {
|
|
|
+ if (retry <= 0) {
|
|
|
+ sendFeishuAlert(argMap);
|
|
|
+ Map<String, String> partitionMap = odpsService.getLastestPartition(config, argMap);
|
|
|
+ argMap.putAll(partitionMap);
|
|
|
+ log.info("partitionMap {} argMap {}", JSONUtils.toJson(partitionMap), JSONUtils.toJson(argMap));
|
|
|
+ count = odpsService.count(config, argMap);
|
|
|
+ if (count <= 0) {
|
|
|
+ return Pair.of(count, null);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ try {
|
|
|
+ log.info("sleep {}", retry);
|
|
|
+ Thread.sleep(60000);
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ log.error("sleep error ", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ JavaRDD<Map<String, String>> records = odpsService.read(jsc, config, argMap);
|
|
|
+ return Pair.of(count, records);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void sendFeishuAlert(Map<String, String> argMap) {
|
|
|
+ try {
|
|
|
+ // TODO 报警
|
|
|
+ String feishuWebhook = "https://open.feishu.cn/open-apis/bot/v2/hook/540d4098-367a-4068-9a44-b8109652f07c";
|
|
|
+ MediaType mediaType = MediaType.parse("application/json");
|
|
|
+ JSONObject param = new JSONObject();
|
|
|
+ param.put("msg_type", "text");
|
|
|
+ Map<String, String> map = new HashMap<>();
|
|
|
+ map.put("text", argMap.get("table") + " 使用兜底数据");
|
|
|
+ param.put("content", map);
|
|
|
+ RequestBody body = RequestBody.create(mediaType, param.toJSONString());
|
|
|
+ Request request = new Request.Builder()
|
|
|
+ .url(feishuWebhook)
|
|
|
+ .method("POST", body)
|
|
|
+ .addHeader("Content-Type", "application/json")
|
|
|
+ .build();
|
|
|
+ OkHttpClient client = new OkHttpClient();
|
|
|
+ client.newCall(request).execute();
|
|
|
+ } catch (Throwable e) {
|
|
|
+ log.error("send feishu alert error ", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|