All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.wicp.tams.common.http.flink.SqlGateWay Maven / Gradle / Ivy

The newest version!
package net.wicp.tams.common.http.flink;

import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.tuple.Pair;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;

import lombok.extern.slf4j.Slf4j;
import net.wicp.tams.common.Result;
import net.wicp.tams.common.apiext.StringUtil;
import net.wicp.tams.common.apiext.TimeAssist;
import net.wicp.tams.common.apiext.json.JSONUtil;
import net.wicp.tams.common.constant.StrPattern;
import net.wicp.tams.common.exception.ExceptAll;
import net.wicp.tams.common.exception.ProjectExceptionRuntime;
import net.wicp.tams.common.http.HttpClient;
import net.wicp.tams.common.http.HttpResult;

@Slf4j
public class SqlGateWay {
	private final String sessionHandle;
	private final String url;
	private final String VERSION = "v1";
	private final String host;
	private final int port;
	private final SqlCmdWrapper sqlCmdWrapper = new SqlCmdWrapper();// "mycatalog", "duckula"
	private volatile ScheduledExecutorService timerService;

	public String getHost() {
		return host;
	}

	public int getPort() {
		return port;
	}

	/***
	 * 初始化构造
	 *
	 * @param url
	 */
	public SqlGateWay(String url) {
		this.url = url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
		String callSessionUrl = String.format("%s/%s/sessions", this.url, this.VERSION);

		HttpResult sessionRes = null;
		while (true) {
			try {
				sessionRes = HttpClient.doPost(callSessionUrl, null);
				if (sessionRes == null || !sessionRes.getResult().isSuc()) {// read timeout
					throw new ProjectExceptionRuntime(ExceptAll.flink_gateway_nosession,
							"拿不到session" + callSessionUrl + ":" + sessionRes.getErrMsg());
				}
				break;
			} catch (Throwable e) {
				boolean reDoWait = TimeAssist.reDoWait("connect sqlgateway", 6);// 4次还没做完?
				if (reDoWait) {// 达到最大值就出
					log.error("重试6次,未获取到sessionHandle!");
					break;
				} else {
					continue;
				}
			}
		}
		ObjectNode parseObject = JSONUtil.parserStr(new ObjectMapper(), sessionRes.getBodyStr());
		this.sessionHandle = parseObject.get("sessionHandle").asText();
		// 初始化host和port
		String[] group = StrPattern.weburl.group(this.url);
		String[] hostport = group[2].split(":");
		if (hostport.length == 2) {
			this.host = hostport[0];
			this.port = Integer.parseInt(hostport[1]);
		} else {
			this.host = hostport[0];
			this.port = 80;
		}
		heartbeatPer();
//		this.sqlCmdWrapper = initCurCatalogAndDb();
	}

	// 通过k8s的服务名组装url
	public SqlGateWay(String serviceNamForK8s, String namespaceForK8s, int port) {
		// 参考K8sAssit.getInnerUrl
		this(port == 80 ? String.format("http://%s.%s", serviceNamForK8s, namespaceForK8s)
				: String.format("http://%s.%s:%s", serviceNamForK8s, namespaceForK8s, port));
	}

	// 通过主机端口
	public SqlGateWay(String host, int port) {
		this(port == 80 ? String.format("http://%s", host) : String.format("http://%s:%s", host, port));
	}

	public SqlGateWay(String url, String sessionHandle) {
		this.url = url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
		this.sessionHandle = sessionHandle;
		// 初始化host和port
		String[] group = StrPattern.weburl.group(this.url);
		String[] hostport = group[2].split(":");
		if (hostport.length == 2) {
			this.host = hostport[0];
			this.port = Integer.parseInt(hostport[1]);
		} else {
			this.host = hostport[0];
			this.port = 80;
		}
		heartbeatPer();
		// gateway做过了初始化就不需要再做了。
//		init(initContext, params);
		// 得到默认的catalog和db
//		this.sqlCmdWrapper = initCurCatalogAndDb();
	}

//此方法不能在构造函数里调用,因为catalog_get、db_get都需要sessionid。
//	private SqlCmdWrapper initCurCatalogAndDb() {
//		String catalogSql = SqlCmd.catalog_get.getSql(null, null);
//		Result catalogSqlrs = statementsResultForDDL(catalogSql);
//		String dbSql = SqlCmd.db_get.getSql(null, null);
//		Result dbSqlrs = statementsResultForDDL(dbSql);
//		if (!catalogSqlrs.isSuc() || !dbSqlrs.isSuc()||dbSqlrs.getMessage().contains("\"errors\"")) {//包有错误
//			throw new ProjectExceptionRuntime(ExceptAll.project_other, "连接失败,没有拿到当前catalog和db");
//		}
//		return new SqlCmdWrapper(catalogSqlrs.getMessage(),dbSqlrs.getMessage());
//	}

	// 是否存在session
	public boolean isExit() {
		HttpResult sessionRes = HttpClient
				.doGet(String.format("%s/%s/sessions/%s", this.url, this.VERSION, this.sessionHandle));
		ObjectNode res = JSONUtil.parserStr(new ObjectMapper(), sessionRes.getBodyStr());
		return res.has("properties");
	}

	// TODO 关闭session
	public void close() {
		try {
			if (this.timerService != null) {
				this.timerService.shutdown();
			}
		} catch (Exception e) {
		}

	}

	// 得到info信息
	public Pair getInfo() {
		HttpResult sessionRes = HttpClient.doGet(String.format("%s/%s/info", this.url, this.VERSION));
		ObjectNode res = JSONUtil.parserStr(new ObjectMapper(), sessionRes.getBodyStr());
		return Pair.of(res.get("productName").asText(), res.get("version").asText());
	}

	public synchronized void cancelJob(String operationHandle) {
		String url = String.format("%s/%s/sessions/%s/operations/%s/cancel", this.url, this.VERSION, this.sessionHandle,
				operationHandle);
		HttpClient.doPost(url, null);
	}

	// 续命timeout时间,默认为10 min
	public boolean heartbeat() {
		HttpResult sessionRes = HttpClient
				.doPost(String.format("%s/%s/sessions/%s/heartbeat", this.url, this.VERSION, this.sessionHandle), null);
		ObjectNode res = JSONUtil.parserStr(new ObjectMapper(), sessionRes.getBodyStr());
		return !res.has("errors");
	}

	// 每个构造函数都会调用,8分钟续命一次
	private void heartbeatPer() {
		// 设置为批模式
		statementsResultForDDL("RESET 'execution.checkpointing.interval'", false);
		statementsResultForDDL("SET 'execution.runtime-mode' = 'batch'", false);
		// 流模式
		// statementsResultForDDL("SET 'execution.checkpointing.interval'='30s'");
		// statementsResultForDDL("SET 'execution.runtime-mode' = 'streaming'");

		// 添加心跳
		this.timerService = Executors.newScheduledThreadPool(1);
		this.timerService.scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
				heartbeat();
			}

		}, 400, 400, TimeUnit.SECONDS);
	}

	public void init(String initContext, Map params) {
		if (StringUtil.isNotNull(initContext)) {
			List sqls = StringUtil.sqlScriptToLine(initContext, System.lineSeparator(), params);
			for (int i = 0; i < sqls.size(); i++) {
				log.info("sql:{}", sqls.get(i));
				String statements = statements(sqls.get(i));
				log.info("index:{} handle:{}", i, statements);
			}
			// 设置默认的catalog和db
			this.sqlCmdWrapper.setCatalog("mycatalog");
			this.sqlCmdWrapper.setDb("duckula");
		}
	}

	// 初始化catalog和db
	public void init(String catalog, String db) {
		setCatalog(catalog);
		setDb(db);
	}

	protected String statements(String sql) {
		String url = String.format("%s/%s/sessions/%s/statements/", this.url, this.VERSION, this.sessionHandle);
		ObjectNode param = JsonNodeFactory.instance.objectNode();
		param.put("statement", sql);
		HttpResult httpResult = HttpClient.doPost(url, param);
		String operationHandle = httpResult == null ? ""
				: JSONUtil.parserStr(new ObjectMapper(), httpResult.getBodyStr()).get("operationHandle").asText();
		return operationHandle;
	}

	// 得到执行状态
	private String statementsStatus(String operationHandle) {// FINISHED和RUNNING
		String url = String.format("%s/%s/sessions/%s/operations/%s/status", this.url, this.VERSION, this.sessionHandle,
				operationHandle);
		HttpResult httpResult = HttpClient.doGet(url);
		String retstr = null;
		if (httpResult != null) {
			ObjectNode parseObject = JSONUtil.parserStr(new ObjectMapper(),httpResult.getBodyStr());
			retstr = parseObject.get("status").asText();
		}
		return retstr;
	}

	// 异步改同步
	protected Result statementsResultByPage(String operationHandle, int page, boolean needWaite) {
		// 由于是异步的,不知道啥时候拿到结果。
		ObjectNode parseObject = null;
		int statuscode = 200;
		int num = 0;
		while (true) {
			num++;
			if (num > 90) {// 最多等90*2秒,防止无限等待
				log.error("查询超过60秒,退出");
				break;
			}
			if (needWaite) {// 这个参数用于区分catalog操作与flink表的查询。catalog操作不需要等待,flink表的查询需要等待,区别对待于加快执行速度
				String status = statementsStatus(operationHandle);
				if (!"FINISHED".equals(status)) {// 只有完成后才能进去查询
					log.info("已查询{}次,运行结果是:{}", num, status);
					// 先做再等,这样翻页的时候就不需要等了
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
					}
					continue;
				} else {
					// !!!sqlgateway什么垃圾bug,查询到FINISHED还不能拉到数据,需等待一段时间,再拉不到就不知道怎么弄了。
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
					}
				}
			}
			String url = String.format("%s/v1/sessions/%s/operations/%s/result/%s", this.url, this.sessionHandle,
					operationHandle, page);
			HttpResult httpResult = HttpClient.doGet(url);
			parseObject = JSONUtil.parserStr(new ObjectMapper(),httpResult.getBodyStr());
			statuscode = httpResult.getHttpStatus();
			// {"nextResultUri":"/v1/sessions/d7b7853f-055e-4cfe-9e58-b98e42c7377e/operations/cfec1d00-2422-4a19-af87-980970dcfcaf/result/0","results":{"data":[],"columns":[]},"resultType":"NOT_READY"}
			if (!ResultType.NOT_READY.name().equals(parseObject.get("resultType").asText())) {// 没有准备好,再次循环,NOT_READY
				log.info("operationHandle:{} result:{}", operationHandle, parseObject);
				break;
			}
			if (!needWaite) {// 不需要等时走这条路,不至于空载最大次数
				// 先做再等,这样翻页的时候就不需要等了
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
				}
			}
		}
		Result ret = statuscode == 200 ? Result.getSuc() : Result.getError("");
		if (ret.isSuc()) {
			SqlGateResultPo parseResult = parseResult(parseObject);
			if (parseResult == null) {// 超过我们限定的时间60秒还是没有得到结果
				parseResult = new SqlGateResultPo();
				parseResult.setErrors("查询结果超时:" + num * 2 + "秒");
				ret = Result.getError("");
				ret.setRetObjs(parseResult);
			} else {
				ret.setRetObjs(parseResult);
			}
		} else {// 错误的结果
			SqlGateResultPo parseResult = new SqlGateResultPo();
			String msg = null;
			try {
				ArrayNode errorObj =(ArrayNode) parseObject.get("errors");
				int beginIndex = errorObj.get(1).asText().indexOf("org.apache.flink.table.api.ValidationException");
				int endIndex = errorObj.get(1).asText().indexOf("\n", beginIndex);
				msg = String.format("%s,原因:%s", errorObj.get(0).asText(),
						errorObj.get(1).asText().subSequence(beginIndex + 47, endIndex));
			} catch (Exception e) {
				msg = parseObject.get("errors").asText();
			}
			parseResult.setErrors(msg);
			ret.setRetObjs(parseResult);
		}
		return ret;
	}

	// 翻到下一页
	public SqlGateResultPo next(SqlGateResultPo curPageDate) {
		Result result = statementsResultByPage(curPageDate.getOperationHandle(), curPageDate.getCurPage() + 1, true);
		if (result.isSuc()) {
			SqlGateResultPo parseResult = (SqlGateResultPo) result.retObjs(0);
			return parseResult;
		}
		return null;
	}

	private static SqlGateResultPo parseResult(ObjectNode retJson) {
		if (retJson == null) {
			return null;
		}
		SqlGateResultPo ret = new SqlGateResultPo();
		ResultType resultType = ResultType.find(retJson.get("resultType").asText());
		ret.setResultType(resultType);
		ret.setNextResultUri(retJson.get("nextResultUri").asText());
		ret.setJobID(retJson.get("jobID").asText());
		ObjectNode resultsJson =  (ObjectNode)retJson.get("results");
		ArrayNode cols = (ArrayNode)resultsJson.get("columns");
		for (int i = 0; i < cols.size(); i++) {
			ObjectNode colJson = (ObjectNode)cols.get(i);
			SqlGateResultCol temp = new SqlGateResultCol(colJson.get("name").asText(),
					colJson.get("logicalType").get("type").asText());
			temp.setComment(colJson.get("comment").asText());
			temp.setLogicalNullable(colJson.get("logicalType").get("nullable").asBoolean());
			if (colJson.get("logicalType").has("length")) {// {"nullable":true,"type":"BOOLEAN"}
				temp.setLogicalLength(colJson.get("logicalType").get("length").asLong());
			}
			ret.getCols().add(temp);
		}
		ArrayNode datas = (ArrayNode)resultsJson.get("data");
		for (int i = 0; i < datas.size(); i++) {
			ObjectNode dataJson = (ObjectNode)datas.get(i);
			// kind 暂不做处理
			ArrayNode jsonArray = (ArrayNode)dataJson.get("fields");
			String[] singleData = new String[jsonArray.size()];
			for (int j = 0; j < jsonArray.size(); j++) {
				if (jsonArray.get(j) != null) {
					singleData[j] = String.valueOf(jsonArray.get(j));
				}
			}
			ret.getCurData().add(singleData);
		}
		return ret;
	}

	public Result statementsResultForDDL(String sql, boolean needWaite) {
		String operationHandle = statements(sql);
		Result statementsRes = statementsResultByPage(operationHandle, 0, needWaite);
		return statementsRes;
	}

	public String getCatalog() {
		if (StringUtil.isNull(this.sqlCmdWrapper.getCatalog())) {
			Result result = statementsResultForDDL(this.sqlCmdWrapper.getSql(SqlCmd.catalog_get), false);
			if (result.isSuc()) {
				SqlGateResultPo parseResult = (SqlGateResultPo) result.retObjs(0);
				this.sqlCmdWrapper.setCatalog(parseResult.getSimpleData()[0]);
			}
		}
		return this.sqlCmdWrapper.getCatalog();
	}

	public String getDb() {
		if (StringUtil.isNull(this.sqlCmdWrapper.getDb())) {
			Result result = statementsResultForDDL(this.sqlCmdWrapper.getSql(SqlCmd.db_get), false);
			if (result.isSuc()) {
				SqlGateResultPo parseResult = (SqlGateResultPo) result.retObjs(0);
				this.sqlCmdWrapper.setDb(parseResult.getCurData().get(0)[0]);
			}
		}
		return this.sqlCmdWrapper.getDb();
	}

	public void setCatalog(String catalog) {
		Result result = statementsResultForDDL(this.sqlCmdWrapper.getSql(SqlCmd.catalog_set, catalog), false);
		if (result.isSuc()) {
			this.sqlCmdWrapper.setCatalog(catalog);
		} else {
			throw new ProjectExceptionRuntime(ExceptAll.Project_default, "设置catalog失败:" + result.getMessage());
		}
	}

	public void setDb(String db) {
		Result result = statementsResultForDDL(this.sqlCmdWrapper.getSql(SqlCmd.db_set, db), false);
		if (result.isSuc()) {
			this.sqlCmdWrapper.setDb(db);
		} else {
			throw new ProjectExceptionRuntime(ExceptAll.Project_default, "设置db失败:" + result.getMessage());
		}
	}

	public Result execRs(SqlCmd sqlCmd, Object... params) {
//		synchronized (this) {// 初始化catalog和db
//			String catalog = getCatalog();
//			this.sqlCmdWrapper.setCatalog(catalog);
//			String db = getDb();
//			this.sqlCmdWrapper.setDb(db);
//		}
		String sql = this.sqlCmdWrapper.getSql(sqlCmd, params);
		return statementsResultForDDL(sql, true);// errors
	}

	public SqlGateResultPo exec(SqlCmd sqlCmd, Object... params) {
		Result execRs = execRs(sqlCmd, params);
		return (SqlGateResultPo) execRs.retObjs(0);
	}

	public SqlGateResultPo execPutCols(SqlCmd sqlCmd, String[] cols, Object... params) {
		SqlGateResultPo retPo = exec(sqlCmd, params);
		if (!retPo.isSuc()) {
			return retPo;
		}
		retPo.getCols().clear();
		for (String col : cols) {
			SqlGateResultCol temp = new SqlGateResultCol(col);
			retPo.getCols().add(temp);
		}
		return retPo;
	}

	public String getSessionHandle() {
		return sessionHandle;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy