声明:本系列博客是根据SGG的视频整理而成,非常适合大家入门学习。

《2021年最新版大数据面试题全面开启更新》

pom.xml

 

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>flink-learning-connectors</artifactId>
        <groupId>com.zhisheng.flink</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>flink-learning-connectors-clickhouse</artifactId>

    <properties>
        <async.client.version>2.0.39</async.client.version>
        <typesafe.config.version>1.3.3</typesafe.config.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.asynchttpclient</groupId>
            <artifactId>async-http-client</artifactId>
            <version>${async.client.version}</version>
        </dependency>
        <dependency>
            <groupId>com.typesafe</groupId>
            <artifactId>config</artifactId>
            <version>${typesafe.config.version}</version>
        </dependency>
    </dependencies>


</project>

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

工程目录:

src/main

  java/com/zhisheng/connectors/clickhouse

    applied

      ClickhouseSinkBuffer.java

      ClickhouseSinkManager.java

      ClickhouseSinkScheduledChecker.java

      ClickhouseWriter.java

    model

      ClickhouseClusterSettings.java

      ClickhouseRequestBlank.java

      ClickhouseSinkCommonParams.java

      ClickhouseSinkConsts.java

    util

      ConfigUtil.java

      ThreadUtil.java

    ClickhouseSink.java

       resources

README.md

pom.xml

代码:

ClickhouseSinkBuffer.java

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_Flink学习_03

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

package com.zhisheng.connectors.clickhouse.applied;

import com.google.common.base.Preconditions;
import com.zhisheng.connectors.clickhouse.model.ClickhouseRequestBlank;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Desc:

 */
public class ClickhouseSinkBuffer implements AutoCloseable {
    private static final Logger logger = LoggerFactory.getLogger(ClickhouseSinkBuffer.class);

    private final ClickhouseWriter writer;
    private final String targetTable;
    private final int maxFlushBufferSize;
    private final long timeoutMillis;
    private final List<String> localValues;

    private volatile long lastAddTimeMillis = 0L;

    private ClickhouseSinkBuffer(
            ClickhouseWriter chWriter,
            long timeout,
            int maxBuffer,
            String table
    ) {
        writer = chWriter;
        localValues = new ArrayList<>();
        timeoutMillis = timeout;
        maxFlushBufferSize = maxBuffer;
        targetTable = table;

        logger.info("Instance Clickhouse Sink, target table = {}, buffer size = {}", this.targetTable, this.maxFlushBufferSize);
    }

    String getTargetTable() {
        return targetTable;
    }

    public void put(String recordAsCSV) {
        tryAddToQueue();
        localValues.add(recordAsCSV);
        lastAddTimeMillis = System.currentTimeMillis();
    }

    synchronized void tryAddToQueue() {
        if (flushCondition()) {
            addToQueue();
        }
    }

    private void addToQueue() {
        List<String> deepCopy = buildDeepCopy(localValues);
        ClickhouseRequestBlank params = ClickhouseRequestBlank.Builder
                .aBuilder()
                .withValues(deepCopy)
                .withTargetTable(targetTable)
                .build();

        logger.debug("Build blank with params: buffer size = {}, target table  = {}", params.getValues().size(), params.getTargetTable());
        writer.put(params);

        localValues.clear();
    }

    private boolean flushCondition() {
        return localValues.size() > 0 && (checkSize() || checkTime());
    }

    private boolean checkSize() {
        return localValues.size() >= maxFlushBufferSize;
    }

    private boolean checkTime() {
        if (lastAddTimeMillis == 0) {
            return false;
        }

        long current = System.currentTimeMillis();
        return current - lastAddTimeMillis > timeoutMillis;
    }

    private static List<String> buildDeepCopy(List<String> original) {
        return Collections.unmodifiableList(new ArrayList<>(original));
    }

    @Override
    public void close() {
        if (localValues != null && localValues.size() > 0) {
            addToQueue();
        }
    }

    public static final class Builder {
        private String targetTable;
        private int maxFlushBufferSize;
        private int timeoutSec;

        private Builder() {
        }

        public static Builder aClickhouseSinkBuffer() {
            return new Builder();
        }

        public Builder withTargetTable(String targetTable) {
            this.targetTable = targetTable;
            return this;
        }

        public Builder withMaxFlushBufferSize(int maxFlushBufferSize) {
            this.maxFlushBufferSize = maxFlushBufferSize;
            return this;
        }

        public Builder withTimeoutSec(int timeoutSec) {
            this.timeoutSec = timeoutSec;
            return this;
        }

        public ClickhouseSinkBuffer build(ClickhouseWriter writer) {

            Preconditions.checkNotNull(targetTable);
            Preconditions.checkArgument(maxFlushBufferSize > 0);
            Preconditions.checkArgument(timeoutSec > 0);

            return new ClickhouseSinkBuffer(
                    writer,
                    TimeUnit.SECONDS.toMillis(this.timeoutSec),
                    this.maxFlushBufferSize,
                    this.targetTable
            );
        }
    }
}

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

ClickhouseSinkManager.java

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_Flink学习_03

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

package com.zhisheng.connectors.clickhouse.applied;


import com.google.common.base.Preconditions;
import com.zhisheng.connectors.clickhouse.model.ClickhouseSinkCommonParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.Properties;

import static com.zhisheng.connectors.clickhouse.model.ClickhouseSinkConsts.MAX_BUFFER_SIZE;
import static com.zhisheng.connectors.clickhouse.model.ClickhouseSinkConsts.TARGET_TABLE_NAME;

/**
 * Desc:

 */
public class ClickhouseSinkManager implements AutoCloseable {
    private static final Logger logger = LoggerFactory.getLogger(ClickhouseSinkManager.class);

    private final ClickhouseWriter clickhouseWriter;
    private final ClickhouseSinkScheduledChecker clickhouseSinkScheduledChecker;
    private final ClickhouseSinkCommonParams sinkParams;

    private volatile boolean isClosed = false;

    public ClickhouseSinkManager(Map<String, String> globalParams) {
        sinkParams = new ClickhouseSinkCommonParams(globalParams);
        clickhouseWriter = new ClickhouseWriter(sinkParams);
        clickhouseSinkScheduledChecker = new ClickhouseSinkScheduledChecker(sinkParams);
        logger.info("Build sink writer's manager. params = {}", sinkParams.toString());
    }

    public ClickhouseSinkBuffer buildBuffer(Properties localProperties) {
        String targetTable = localProperties.getProperty(TARGET_TABLE_NAME);
        int maxFlushBufferSize = Integer.valueOf(localProperties.getProperty(MAX_BUFFER_SIZE));

        return buildBuffer(targetTable, maxFlushBufferSize);
    }

    public ClickhouseSinkBuffer buildBuffer(String targetTable, int maxBufferSize) {
        Preconditions.checkNotNull(clickhouseSinkScheduledChecker);
        Preconditions.checkNotNull(clickhouseWriter);

        ClickhouseSinkBuffer clickhouseSinkBuffer = ClickhouseSinkBuffer.Builder
                .aClickhouseSinkBuffer()
                .withTargetTable(targetTable)
                .withMaxFlushBufferSize(maxBufferSize)
                .withTimeoutSec(sinkParams.getTimeout())
                .build(clickhouseWriter);

        clickhouseSinkScheduledChecker.addSinkBuffer(clickhouseSinkBuffer);
        return clickhouseSinkBuffer;
    }

    public boolean isClosed() {
        return isClosed;
    }

    @Override
    public void close() throws Exception {
        clickhouseWriter.close();
        clickhouseSinkScheduledChecker.close();
        isClosed = true;
    }
}

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

ClickhouseSinkScheduledChecker.java

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_Flink学习_03

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

package com.zhisheng.connectors.clickhouse.applied;

import com.zhisheng.connectors.clickhouse.model.ClickhouseSinkCommonParams;
import com.zhisheng.connectors.clickhouse.util.ThreadUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

/**
 * Desc:


 */
public class ClickhouseSinkScheduledChecker implements AutoCloseable {
    private static final Logger logger = LoggerFactory.getLogger(ClickhouseSinkScheduledChecker.class);

    private final ScheduledExecutorService scheduledExecutorService;
    private final List<ClickhouseSinkBuffer> clickhouseSinkBuffers;
    private final ClickhouseSinkCommonParams params;

    public ClickhouseSinkScheduledChecker(ClickhouseSinkCommonParams props) {
        clickhouseSinkBuffers = new ArrayList<>();
        params = props;

        ThreadFactory factory = ThreadUtil.threadFactory("clickhouse-writer-checker");
        scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(factory);
        scheduledExecutorService.scheduleWithFixedDelay(getTask(), params.getTimeout(), params.getTimeout(), TimeUnit.SECONDS);
        logger.info("Build Sink scheduled checker, timeout (sec) = {}", params.getTimeout());
    }

    public void addSinkBuffer(ClickhouseSinkBuffer clickhouseSinkBuffer) {
        synchronized (this) {
            clickhouseSinkBuffers.add(clickhouseSinkBuffer);
        }
        logger.debug("Add sinkBuffer, target table = {}", clickhouseSinkBuffer.getTargetTable());
    }

    private Runnable getTask() {
        return () -> {
            synchronized (this) {
                logger.debug("Start checking buffers. Current count of buffers = {}", clickhouseSinkBuffers.size());
                clickhouseSinkBuffers.forEach(ClickhouseSinkBuffer::tryAddToQueue);
            }
        };
    }

    @Override
    public void close() throws Exception {
        ThreadUtil.shutdownExecutorService(scheduledExecutorService);
    }
}

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

ClickhouseWriter.java

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_Flink学习_03

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

package com.zhisheng.connectors.clickhouse.applied;

import com.google.common.collect.Lists;
import com.zhisheng.connectors.clickhouse.model.ClickhouseRequestBlank;
import com.zhisheng.connectors.clickhouse.model.ClickhouseSinkCommonParams;
import com.zhisheng.connectors.clickhouse.util.ThreadUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.handler.codec.http.HttpHeaders;
import org.asynchttpclient.*;

import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.*;

/**
 * Desc:
 * Created by zhisheng on 2019/9/28 上午10:09

 */
public class ClickhouseWriter implements AutoCloseable {
    private static final Logger logger = LoggerFactory.getLogger(ClickhouseWriter.class);

    private ExecutorService service;
    private ExecutorService callbackService;
    private List<WriterTask> tasks;
    private BlockingQueue<ClickhouseRequestBlank> commonQueue;
    private AsyncHttpClient asyncHttpClient;

    private ClickhouseSinkCommonParams sinkParams;

    public ClickhouseWriter(ClickhouseSinkCommonParams sinkParams) {
        this.sinkParams = sinkParams;
        initDirAndExecutors();
    }

    private void initDirAndExecutors() {
        try {
            initDir(sinkParams.getFailedRecordsPath());
            buildComponents();
        } catch (Exception e) {
            logger.error("Error while starting CH writer", e);
            throw new RuntimeException(e);
        }
    }

    private static void initDir(String pathName) throws IOException {
        Path path = Paths.get(pathName);
        Files.createDirectories(path);
    }

    private void buildComponents() {
        asyncHttpClient = Dsl.asyncHttpClient();

        int numWriters = sinkParams.getNumWriters();
        commonQueue = new LinkedBlockingQueue<>(sinkParams.getQueueMaxCapacity());

        ThreadFactory threadFactory = ThreadUtil.threadFactory("clickhouse-writer");
        service = Executors.newFixedThreadPool(sinkParams.getNumWriters(), threadFactory);

        ThreadFactory callbackServiceFactory = ThreadUtil.threadFactory("clickhouse-writer-callback-executor");

        int cores = Runtime.getRuntime().availableProcessors();
        int coreThreadsNum = Math.max(cores / 4, 2);
        callbackService = new ThreadPoolExecutor(
                coreThreadsNum,
                Integer.MAX_VALUE,
                60L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(),
                callbackServiceFactory);

        tasks = Lists.newArrayList();
        for (int i = 0; i < numWriters; i++) {
            WriterTask task = new WriterTask(i, asyncHttpClient, commonQueue, sinkParams, callbackService);
            tasks.add(task);
            service.submit(task);
        }
    }

    public void put(ClickhouseRequestBlank params) {
        try {
            commonQueue.put(params);
        } catch (InterruptedException e) {
            logger.error("Interrupted error while putting data to queue", e);
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
    }

    private void stopWriters() {
        if (tasks != null && tasks.size() > 0) {
            tasks.forEach(WriterTask::setStopWorking);
        }
    }

    @Override
    public void close() throws Exception {
        logger.info("Closing clickhouse-writer...");
        stopWriters();
        ThreadUtil.shutdownExecutorService(service);
        ThreadUtil.shutdownExecutorService(callbackService);
        asyncHttpClient.close();
        logger.info("{} is closed", ClickhouseWriter.class.getSimpleName());
    }

    static class WriterTask implements Runnable {
        private static final Logger logger = LoggerFactory.getLogger(WriterTask.class);

        private static final int HTTP_OK = 200;

        private final BlockingQueue<ClickhouseRequestBlank> queue;
        private final ClickhouseSinkCommonParams sinkSettings;
        private final AsyncHttpClient asyncHttpClient;
        private final ExecutorService callbackService;

        private final int id;

        private volatile boolean isWorking;

        WriterTask(int id,
                   AsyncHttpClient asyncHttpClient,
                   BlockingQueue<ClickhouseRequestBlank> queue,
                   ClickhouseSinkCommonParams settings,
                   ExecutorService callbackService
        ) {
            this.id = id;
            this.sinkSettings = settings;
            this.queue = queue;
            this.callbackService = callbackService;
            this.asyncHttpClient = asyncHttpClient;
        }

        @Override
        public void run() {
            try {
                isWorking = true;

                logger.info("Start writer task, id = {}", id);
                while (isWorking || queue.size() > 0) {
                    ClickhouseRequestBlank blank = queue.poll(300, TimeUnit.MILLISECONDS);
                    if (blank != null) {
                        send(blank);
                    }
                }
            } catch (Exception e) {
                logger.error("Error while inserting data", e);
                throw new RuntimeException(e);
            } finally {
                logger.info("Task id = {} is finished", id);
            }
        }

        private void send(ClickhouseRequestBlank requestBlank) {
            Request request = buildRequest(requestBlank);

            logger.debug("Ready to load data to {}, size = {}", requestBlank.getTargetTable(), requestBlank.getValues().size());
            ListenableFuture<Response> whenResponse = asyncHttpClient.executeRequest(request);

            Runnable callback = responseCallback(whenResponse, requestBlank);
            whenResponse.addListener(callback, callbackService);
        }

        private Request buildRequest(ClickhouseRequestBlank requestBlank) {
            String resultCSV = String.join(" , ", requestBlank.getValues());
            String query = String.format("INSERT INTO %s VALUES %s", requestBlank.getTargetTable(), resultCSV);
            String host = sinkSettings.getClickhouseClusterSettings().getRandomHostUrl();

            BoundRequestBuilder builder = asyncHttpClient
                    .preparePost(host)
                    .setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=utf-8")
                    .setBody(query);

            if (sinkSettings.getClickhouseClusterSettings().isAuthorizationRequired()) {
                builder.setHeader(HttpHeaders.Names.AUTHORIZATION, "Basic " + sinkSettings.getClickhouseClusterSettings().getCredentials());
            }

            return builder.build();
        }

        private Runnable responseCallback(ListenableFuture<Response> whenResponse, ClickhouseRequestBlank requestBlank) {
            return () -> {
                Response response = null;
                try {
                    response = whenResponse.get();

                    if (response.getStatusCode() != HTTP_OK) {
                        handleUnsuccessfulResponse(response, requestBlank);
                    } else {
                        logger.info("Successful send data to Clickhouse, batch size = {}, target table = {}, current attempt = {}",
                                requestBlank.getValues().size(),
                                requestBlank.getTargetTable(),
                                requestBlank.getAttemptCounter());
                    }
                } catch (Exception e) {
                    logger.error("Error while executing callback, params = {}", sinkSettings, e);
                    try {
                        handleUnsuccessfulResponse(response, requestBlank);
                    } catch (Exception error) {
                        logger.error("Error while handle unsuccessful response", error);
                    }
                }
            };
        }

        private void handleUnsuccessfulResponse(Response response, ClickhouseRequestBlank requestBlank) throws Exception {
            int currentCounter = requestBlank.getAttemptCounter();
            if (currentCounter > sinkSettings.getMaxRetries()) {
                logger.warn("Failed to send data to Clickhouse, cause: limit of attempts is exceeded. Clickhouse response = {}. Ready to flush data on disk", response);
                logFailedRecords(requestBlank);
            } else {
                requestBlank.incrementCounter();
                logger.warn("Next attempt to send data to Clickhouse, table = {}, buffer size = {}, current attempt num = {}, max attempt num = {}, response = {}",
                        requestBlank.getTargetTable(),
                        requestBlank.getValues().size(),
                        requestBlank.getAttemptCounter(),
                        sinkSettings.getMaxRetries(),
                        response);
                queue.put(requestBlank);
            }
        }

        private void logFailedRecords(ClickhouseRequestBlank requestBlank) throws Exception {
            String filePath = String.format("%s/%s_%s",
                    sinkSettings.getFailedRecordsPath(),
                    requestBlank.getTargetTable(),
                    System.currentTimeMillis());

            try (PrintWriter writer = new PrintWriter(filePath)) {
                List<String> records = requestBlank.getValues();
                records.forEach(writer::println);
                writer.flush();
            }
            logger.info("Successful send data on disk, path = {}, size = {} ", filePath, requestBlank.getValues().size());
        }

        void setStopWorking() {
            isWorking = false;
        }
    }
}

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

ClickhouseClusterSettings.java

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_Flink学习_03

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

package com.zhisheng.connectors.clickhouse.model;

import com.google.common.base.Preconditions;
import com.zhisheng.connectors.clickhouse.util.ConfigUtil;
import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;

/**
 * Desc:
 * Created by zhisheng on 2019/9/28 上午12:40

 */
public class ClickhouseClusterSettings {
    public static final String CLICKHOUSE_HOSTS = "clickhouse.access.hosts";
    public static final String CLICKHOUSE_USER = "clickhouse.access.user";
    public static final String CLICKHOUSE_PASSWORD = "clickhouse.access.password";

    private final List<String> hostsWithPorts;
    private final String user;
    private final String password;
    private final String credentials;
    private final boolean authorizationRequired;

    private int currentHostId = 0;

    public ClickhouseClusterSettings(Map<String, String> parameters) {
        Preconditions.checkNotNull(parameters);

        String hostsString = parameters.get(CLICKHOUSE_HOSTS);
        Preconditions.checkNotNull(hostsString);

        hostsWithPorts = buildHostsAndPort(hostsString);
        Preconditions.checkArgument(hostsWithPorts.size() > 0);

        String usr = parameters.get(CLICKHOUSE_USER);
        String pass = parameters.get(CLICKHOUSE_PASSWORD);

        if (StringUtils.isNotEmpty(usr) && StringUtils.isNotEmpty(pass)) {
            user = parameters.get(CLICKHOUSE_USER);
            password = parameters.get(CLICKHOUSE_PASSWORD);

            credentials = buildCredentials(user, password);
            authorizationRequired = true;
        } else {
            // avoid NPE
            credentials = "";
            password = "";
            user = "";
            authorizationRequired = false;
        }
    }

    private static List<String> buildHostsAndPort(String hostsString) {
        return Arrays.stream(hostsString
                .split(ConfigUtil.HOST_DELIMITER))
                .map(ClickhouseClusterSettings::checkHttpAndAdd)
                .collect(Collectors.toList());
    }

    private static String checkHttpAndAdd(String host) {
        String newHost = host.replace(" ", "");
        if (!newHost.contains("http")) {
            return "http://" + newHost;
        }
        return newHost;
    }

    private static String buildCredentials(String user, String password) {
        Base64.Encoder x = Base64.getEncoder();
        String credentials = String.join(":", user, password);
        return new String(x.encode(credentials.getBytes()));
    }

    public String getRandomHostUrl() {
        currentHostId = ThreadLocalRandom.current().nextInt(hostsWithPorts.size());
        return hostsWithPorts.get(currentHostId);
    }

    public String getNextHost() {
        if (currentHostId >= hostsWithPorts.size() - 1) {
            currentHostId = 0;
        } else {
            currentHostId += 1;
        }
        return hostsWithPorts.get(currentHostId);
    }

    public List<String> getHostsWithPorts() {
        return hostsWithPorts;
    }

    public String getUser() {
        return user;
    }

    public String getPassword() {
        return password;
    }

    public String getCredentials() {
        return credentials;
    }

    public boolean isAuthorizationRequired() {
        return authorizationRequired;
    }

    @Override
    public String toString() {
        return "ClickhouseClusterSettings{" +
                "hostsWithPorts=" + hostsWithPorts +
                ", credentials='" + credentials + '\'' +
                ", authorizationRequired=" + authorizationRequired +
                ", currentHostId=" + currentHostId +
                '}';
    }
}

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

ClickhouseRequestBlank.java

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_Flink学习_03

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

package com.zhisheng.connectors.clickhouse.model;

import java.util.List;

/**
 * Desc:
 * Created by zhisheng on 2019/9/28 上午10:14

 */
public class ClickhouseRequestBlank {
    private final List<String> values;
    private final String targetTable;
    private int attemptCounter;

    public ClickhouseRequestBlank(List<String> values, String targetTable) {
        this.values = values;
        this.targetTable = targetTable;
        this.attemptCounter = 0;
    }

    public List<String> getValues() {
        return values;
    }

    public void incrementCounter() {
        this.attemptCounter++;
    }

    public int getAttemptCounter() {
        return attemptCounter;
    }

    public String getTargetTable() {
        return targetTable;
    }

    public static final class Builder {
        private List<String> values;
        private String targetTable;

        private Builder() {
        }

        public static Builder aBuilder() {
            return new Builder();
        }

        public Builder withValues(List<String> values) {
            this.values = values;
            return this;
        }

        public Builder withTargetTable(String targetTable) {
            this.targetTable = targetTable;
            return this;
        }

        public ClickhouseRequestBlank build() {
            return new ClickhouseRequestBlank(values, targetTable);
        }
    }

    @Override
    public String toString() {
        return "ClickhouseRequestBlank{" +
                "values=" + values +
                ", targetTable='" + targetTable + '\'' +
                ", attemptCounter=" + attemptCounter +
                '}';
    }
}

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

ClickhouseSinkCommonParams

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_Flink学习_03

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

package com.zhisheng.connectors.clickhouse.model;

import com.google.common.base.Preconditions;

import java.util.Map;

import static com.zhisheng.connectors.clickhouse.model.ClickhouseSinkConsts.*;

/**
 * Desc:
 * Created by zhisheng on 2019/9/28 上午10:05

 */
public class ClickhouseSinkCommonParams {
    private final ClickhouseClusterSettings clickhouseClusterSettings;

    private final String failedRecordsPath;
    private final int numWriters;
    private final int queueMaxCapacity;

    private final int timeout;
    private final int maxRetries;

    public ClickhouseSinkCommonParams(Map<String, String> params) {
        this.clickhouseClusterSettings = new ClickhouseClusterSettings(params);
        this.numWriters = Integer.valueOf(params.get(NUM_WRITERS));
        this.queueMaxCapacity = Integer.valueOf(params.get(QUEUE_MAX_CAPACITY));
        this.maxRetries = Integer.valueOf(params.get(NUM_RETRIES));
        this.timeout = Integer.valueOf(params.get(TIMEOUT_SEC));
        this.failedRecordsPath = params.get(FAILED_RECORDS_PATH);

        Preconditions.checkNotNull(failedRecordsPath);
        Preconditions.checkArgument(queueMaxCapacity > 0);
        Preconditions.checkArgument(numWriters > 0);
        Preconditions.checkArgument(timeout > 0);
        Preconditions.checkArgument(maxRetries > 0);
    }

    public int getNumWriters() {
        return numWriters;
    }

    public int getQueueMaxCapacity() {
        return queueMaxCapacity;
    }

    public ClickhouseClusterSettings getClickhouseClusterSettings() {
        return clickhouseClusterSettings;
    }

    public int getTimeout() {
        return timeout;
    }

    public int getMaxRetries() {
        return maxRetries;
    }

    public String getFailedRecordsPath() {
        return failedRecordsPath;
    }

    @Override
    public String toString() {
        return "ClickhouseSinkCommonParams{" +
                "clickhouseClusterSettings=" + clickhouseClusterSettings +
                ", failedRecordsPath='" + failedRecordsPath + '\'' +
                ", numWriters=" + numWriters +
                ", queueMaxCapacity=" + queueMaxCapacity +
                ", timeout=" + timeout +
                ", maxRetries=" + maxRetries +
                '}';
    }
}

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

ClickhouseSinkConsts.java

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_Flink学习_03

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

package com.zhisheng.connectors.clickhouse.model;

/**
 * Desc:
 * Created by zhisheng on 2019/9/28 上午10:03

 */
public final class ClickhouseSinkConsts {
    private ClickhouseSinkConsts() {
    }

    public static final String TARGET_TABLE_NAME = "clickhouse.sink.target-table";
    public static final String MAX_BUFFER_SIZE = "clickhouse.sink.max-buffer-size";

    public static final String NUM_WRITERS = "clickhouse.sink.num-writers";
    public static final String QUEUE_MAX_CAPACITY = "clickhouse.sink.queue-max-capacity";
    public static final String TIMEOUT_SEC = "clickhouse.sink.timeout-sec";
    public static final String NUM_RETRIES = "clickhouse.sink.retries";
    public static final String FAILED_RECORDS_PATH = "clickhouse.sink.failed-records-path";
}

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

ConfigUtil.java

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_Flink学习_03

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

package com.zhisheng.connectors.clickhouse.util;

import java.util.*;

import com.typesafe.config.Config;
import com.typesafe.config.ConfigValue;

/**
 * Desc:
 * Created by zhisheng on 2019/9/28 上午12:38

 */
public class ConfigUtil {
    public static final String HOST_DELIMITER = ", ";

    private ConfigUtil(){

    }
    public static Properties toProperties(Config config) {
        Properties properties = new Properties();
        config.entrySet().forEach(e -> properties.put(e.getKey(), unwrapped(config.getValue(e.getKey()))));
        return properties;
    }

    public static Map<String, String> toMap(Config config) {
        Map<String, String> map = new HashMap<>();
        config.entrySet().forEach(e -> map.put(e.getKey(), unwrapped(e.getValue())));
        return map;
    }

    private static String unwrapped(ConfigValue configValue) {
        Object object = configValue.unwrapped();
        return object.toString();
    }

    static public String buildStringFromList(List<String> list) {
        return String.join(HOST_DELIMITER, list);
    }

    static public List<String> buildListFromString(String string) {
        return Arrays.asList(string.split(" "));
    }
}

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

ThreadUtil.java

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_Flink学习_03

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

package com.zhisheng.connectors.clickhouse.util;

import com.google.common.util.concurrent.ThreadFactoryBuilder;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

/**
 * Desc:
 * Created by zhisheng on 2019/9/28 上午12:39

 */
public class ThreadUtil {
    private ThreadUtil() {
    }

    public static ThreadFactory threadFactory(String threadName, boolean isDaemon) {
        return new ThreadFactoryBuilder()
                .setNameFormat(threadName + "-%d")
                .setDaemon(isDaemon)
                .build();
    }

    public static ThreadFactory threadFactory(String threadName) {
        return threadFactory(threadName, true);
    }

    public static void shutdownExecutorService(ExecutorService executorService) throws InterruptedException {
        shutdownExecutorService(executorService, 5);
    }

    public static void shutdownExecutorService(ExecutorService executorService, int timeoutS) throws InterruptedException {
        if (executorService != null && !executorService.isShutdown()) {
            executorService.shutdown();
            if (!executorService.awaitTermination(timeoutS, TimeUnit.SECONDS)) {
                executorService.shutdownNow();
                executorService.awaitTermination(timeoutS, TimeUnit.SECONDS);
            }
        }
    }
}

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

ClickhouseSink.java

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_Flink学习_03

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术

package com.zhisheng.connectors.clickhouse;

/**
 * Desc:
 * Created by zhisheng on 2019/9/28 上午12:38

 */

import com.zhisheng.connectors.clickhouse.applied.ClickhouseSinkBuffer;
import com.zhisheng.connectors.clickhouse.applied.ClickhouseSinkManager;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.sink.RichSinkFunction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.Properties;

/**
 * Desc:
 * Created by zhisheng on 2019/9/28 上午12:38

 */
public class ClickhouseSink extends RichSinkFunction<String> {

    private static final Logger logger = LoggerFactory.getLogger(ClickhouseSink.class);

    private static final Object DUMMY_LOCK = new Object();

    private final Properties localProperties;

    private volatile static transient ClickhouseSinkManager sinkManager;
    private transient ClickhouseSinkBuffer clickhouseSinkBuffer;

    public ClickhouseSink(Properties properties) {
        this.localProperties = properties;
    }

    @Override
    public void open(Configuration config) {
        if (sinkManager == null) {
            synchronized (DUMMY_LOCK) {
                if (sinkManager == null) {
                    Map<String, String> params = getRuntimeContext()
                            .getExecutionConfig()
                            .getGlobalJobParameters()
                            .toMap();

                    sinkManager = new ClickhouseSinkManager(params);
                }
            }
        }

        clickhouseSinkBuffer = sinkManager.buildBuffer(localProperties);
    }

    /**
     * Add csv to buffer
     *
     * @param recordAsCSV csv-event
     */
    @Override
    public void invoke(String recordAsCSV, Context context) {
        try {
            clickhouseSinkBuffer.put(recordAsCSV);
        } catch (Exception e) {
            logger.error("Error while sending data to Clickhouse, record = {}", recordAsCSV, e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public void close() throws Exception {
        if (clickhouseSinkBuffer != null) {
            clickhouseSinkBuffer.close();
        }

        if (sinkManager != null) {
            if (!sinkManager.isClosed()) {
                synchronized (DUMMY_LOCK) {
                    if (!sinkManager.isClosed()) {
                        sinkManager.close();
                    }
                }
            }
        }

        super.close();
    }
}

Flink实例(六十一): connectors(十二)clickhouse 写 入 (一)_大数据技术