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

com.huaweicloud.dws.client.worker.ActionExecutor Maven / Gradle / Ivy

package com.huaweicloud.dws.client.worker;

import com.huaweicloud.dws.client.DwsConfig;
import com.huaweicloud.dws.client.action.AbstractAction;
import com.huaweicloud.dws.client.action.PutAction;
import com.huaweicloud.dws.client.action.SqlAction;
import com.huaweicloud.dws.client.exception.DwsClientException;
import com.huaweicloud.dws.client.exception.ExceptionCode;
import com.huaweicloud.dws.client.exception.InvalidException;
import com.huaweicloud.dws.client.handler.AbstractActionHandler;
import com.huaweicloud.dws.client.handler.PutActionHandler;
import com.huaweicloud.dws.client.handler.SqlActionHandler;
import com.huaweicloud.dws.client.util.AssertUtil;
import com.huaweicloud.dws.client.util.LogUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.Closeable;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @ProjectName: dws-connector
 * @ClassName: ActionExecutor
 * @Description: 事件执行者
 * @Date: 2023/1/9 18:56
 * @Version: 1.0
 */
@Slf4j
public class ActionExecutor implements Runnable, Closeable {

    public static final Set RETRY_EXCEPTION_CODES = new HashSet<>(Arrays.asList(ExceptionCode.CONNECTION_ERROR,
            ExceptionCode.READ_ONLY, ExceptionCode.TIMEOUT,
            ExceptionCode.TOO_MANY_CONNECTIONS, ExceptionCode.LOCK_ERROR,
            ExceptionCode.STARTING_UP));

    public static final int MAX_AWAIT_ACTION_TIME = 2;

    private final SecureRandom SLEEP_RANDOM;

    /**
     * 感知是否被关闭
     */
    private final AtomicBoolean started;

    /**
     * 记录当前正在被执行的action,使用生产消费模式
     */
    private final ActionWrapper actionWrapper = new ActionWrapper<>();

    /**
     * 每个并发一个数据库连接
     */
    private final ConnectionProvider connectionProvider;
    private final DwsConfig config;

    /**
     * 记录最后执行的异常
     */
    private final AtomicReference lastError = new AtomicReference<>(null);

    private final Map, AbstractActionHandler> handlerMap = new HashMap<>();

    public ActionExecutor(AtomicBoolean started, DwsConfig config) {
        this.started = started;
        this.config = config;
        connectionProvider = new ConnectionProvider(config);
        handlerMap.put(PutAction.class, new PutActionHandler(config, connectionProvider));
        handlerMap.put(SqlAction.class, new SqlActionHandler(connectionProvider, config));
        try {
            SLEEP_RANDOM = SecureRandom.getInstanceStrong();
        } catch (NoSuchAlgorithmException e) {
            throw new InvalidException(e);
        }
    }

    @Override
    public void run() {
        // 只要没被关闭一直执行
        while (started.get()) {
            try {
                // 每次尝试获取任务,最多等待2秒
                AbstractAction action = actionWrapper.get(MAX_AWAIT_ACTION_TIME, TimeUnit.SECONDS);
                if (action != null) {
                    try {
                        handle(action);
                    } catch (Exception e) {
                        if (!action.getFuture().isDone()) {
                            action.getFuture().completeExceptionally(e);
                        }
                    } finally {
                        // 执行完不管如何消费掉数据,不然该线程将无法接受新的事件
                        actionWrapper.clear();
                        // 每次任务之心完更新所持有 连接最后使用时间,避免被释放
                        connectionProvider.refresh();
                    }
                }
                // 连接空闲超时
                if ((System.currentTimeMillis() - connectionProvider.getLastActive()) > config.getConnectionMaxIdleMs()) {
                    connectionProvider.close();
                }
            } catch (Throwable e) {
                lastError.set(e);
                log.error("handle error.", e);
            }
        }
    }

    private > void handle(T action) throws DwsClientException {
        // 获取该时间的执行者
        AbstractActionHandler handler = handlerMap.get(action.getClass());
        AssertUtil.nonNull(handler, new DwsClientException(ExceptionCode.INVALID_CONFIG, "handler is null"));
        for (int i = 0; i < config.getMaxFlushRetryTimes(); i++) {
            try {
                LogUtil.withLogSwitch(config, () -> log.info("will hand action action type is {}", action.getClass().getName()));
                // 报任何异常均将 重试
                handler.handle(action);
                break;
            } catch (Exception e) {
                log.error("handle action fail. times = {}, maxTimes = {}", i + 1, config.getMaxFlushRetryTimes(), e);
                ExceptionCode code = DwsClientException.fromException(e).getCode();
                if (i == config.getMaxFlushRetryTimes() - 1 || !RETRY_EXCEPTION_CODES.contains(code)) {
                    log.warn("failed after retry {} times, the action will end", i);
                    // 最后一次重试依旧失败 或者异常是不需要重试的, 将失败返回给调用者
                    action.getFuture().completeExceptionally(e);
                    break;
                }
                try {
                    if (!connectionProvider.isConnectionValid() || code == ExceptionCode.CONNECTION_ERROR) {
                        connectionProvider.restConnection();
                    }
                } catch (Exception exception) {
                    // 不抛出异常 避免因数据库重启等短暂故障失败
                    log.error("JDBC connection is not valid, and reestablish connection failed.", exception);
                }
                try {
                    long sleepTime = config.getRetryBaseTime() * i + SLEEP_RANDOM.nextInt(config.getRetryRandomTime());
                    log.info("this handler fail. will sleep {}", sleepTime);
                    LogUtil.withLogSwitch(config, () -> log.info("this handler fail. will sleep {}", sleepTime));
                    Thread.sleep(sleepTime);
                } catch (InterruptedException ex) {
                    log.error("unable to flush; interrupted while doing another attempt", ex);
                    Thread.currentThread().interrupt();
                    throw DwsClientException.fromException(ex);
                }
            }
        }
    }

    /**
     * 提交事件到执行器
     * @throws DwsClientException
     */
    public synchronized boolean submit(AbstractAction action) throws DwsClientException {
        AssertUtil.nonNull(action, new DwsClientException(ExceptionCode.INVALID_CONFIG, "action is null"));
        AssertUtil.isNull(lastError.get(), DwsClientException.fromException(lastError.get()));
        AssertUtil.isTrue(started.get(), new DwsClientException(ExceptionCode.ALREADY_CLOSE, "executor is closed"));
        return actionWrapper.set(action);
    }

    @Override
    public void close() throws IOException {
        connectionProvider.close();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy