1. 主页
  2. 大数据技术
  3. 十五、ClickHouse
  4. 2. Win10 系统下使用 Docker 搭建 ClickHouse 开发环境

2. Win10 系统下使用 Docker 搭建 ClickHouse 开发环境

软件版本

软件 版本 备注
Windows 10 确保使用了 Windows10 并且开启了 Hyper-V 才能使用 Docker
Docker Desktop 任意 Docker的Windows桌面版
ClickHouse Server 20.3.x 直接拉取latest的镜像即可
ClickHouse Client 20.3.x 直接拉取latest的镜像即可
  • Windows10 下可以通过:控制面板 -> 程序 -> 启用或关闭 Windows 功能 -> Hyper-V(勾选 Hyper-V 管理平台和 Hyper-V 平台,然后重启生效)开启 Hyper-V 特性:
    clickhouse-hyper-v
  • Docker 安装:https://www.docker.com/get-started

安装和使用 ClickHouse

注意需要先初步了解 ClickHouse 的核心目录,再进行容器安装启动。

镜像拉取和核心目录

先下载 ClickHouse Server 和 ClickHouse Client 的镜像:

$ docker pull yandex/clickhouse-server
$ docker pull yandex/clickhouse-client

$ docker images
REPOSITORY                 TAG                 IMAGE ID            CREATED             SIZE
yandex/clickhouse-server   latest              c85f84ea6550        10 days ago         515MB
yandex/clickhouse-client   latest              f94470cc9cd9        10 days ago         488MB

两个镜像其实都是包裹在一个微型的 Ubuntu 系统中,所以启动后的容器可以使用当作是一个 Linux 系统这样操作。ClickHouse Server 在容器中的核心目录部分如下:

  • /etc/clickhouse-server:这个是 ClickHouse Server 默认的配置文件目录,包括全局配置 config.xml 和用户配置 users.xml 等等。
  • /var/lib/clickhouse:这个是 ClickHouse Server 默认的数据存储目录。
  • /var/log/clickhouse-server:这个是 ClickHouse Server 默认的日志输出目录。

为了方便管理配置、查看数据和搜索日志,可以把上面这三个目录直接映射到宿主机的具体目录,笔者在本开发机做了如下的映射:

Docker 容器目录 宿主机目录
/etc/clickhouse-server E:/Docker/images/clickhouse-server/single/conf
/var/lib/clickhouse E:/Docker/images/clickhouse-server/single/data
/var/log/clickhouse-server E:/Docker/images/clickhouse-server/single/log

ClickHouse Server 启动前需要注意几点:

  • ClickHouse Server 服务本身依赖三个端口,这三个端口的默认值是 9000(TCP 协议)、8123(HTTP 协议)和 9009(集群数据复制),映射到宿主机的时候尽可能一一对应,所以需要确保宿主机的这三个端口没有被占用,可以使用 Docker 的参数 -p 指定容器和宿主机的端口映射。
  • ClickHouse Server 正常使用需要修改容器系统的文件句柄数量配置 ulimit nofile,可以使用 Docker 参数 --ulimit nofile=262144:262144 指定文件句柄数。
  • 可以运用一个技巧,使用 Docker 的 --rm 参数创建临时容器,先获取到 /etc/clickhouse-server 目录下配置文件,通过 docker cp 容器目录 宿主机目录命令可以拷贝容器的配置文件到宿主机目录下,容器停止之后会被直接删除,这样就能保留宿主机的配置文件模板。

临时容器拷贝配置

先执行命令 docker run --rm -d --name=temp-clickhouse-server yandex/clickhouse-server 运行一个临时容器,成功后通过下面的命令拷贝容器的 config.xmlusers.xml 文件到宿主机:

  • docker cp temp-clickhouse-server:/etc/clickhouse-server/config.xml E:/Docker/images/clickhouse-server/single/conf/config.xml
  • docker cp temp-clickhouse-server:/etc/clickhouse-server/users.xml E:/Docker/images/clickhouse-server/single/conf/users.xml

这两个命令执行完毕后,可以看到宿主机的磁盘目录已经生成了config.xml和users.xml,接着需要做几项配置:

  • 创建 default 账号的密码。
  • 创建一个新的 root 账号。
  • 开放客户端监听的 Host,避免后面使用 JDBC 客户端或者 ClickHouse Client 的时候无法连接 ClickHouse Server。

通过 docker exec -it temp-clickhouse-server /bin/bash 命令进入临时容器,然后在临时容器中执行

$ PASSWORD=$(base64 < /dev/urandom | head -c8); echo "default"; echo -n "default" | sha256sum | tr -d '-'
default
37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f

$ PASSWORD=$(base64 < /dev/urandom | head -c8); echo "root"; echo -n "root" | sha256sum | tr -d '-'
root
4813494d137e1631bba301d5acab6e7bb7aa74ce1185d456565ef51d737677b2

这样就得到了 default:defaultroot:root 两个账号密码的 SHA256 摘要。修改宿主机上的 users.xml 文件:

clickhouse-users-xml

然后修改宿主机上的 config.xml 文件:

clickhouse-config-xml

最后通过 docker stop temp-clickhouse-server 停止和销毁临时容器。

运行 ClickHouse 服务

接着使用下面的命令创建和运行一个 ClickHouse Server 容器实例(确保 config.xml 和 users.xml 已经存在):

docker run -d \
 --name=single-clickhouse-server \
 -p 8123:8123 -p 9000:9000 -p 9009:9009 \
 --ulimit nofile=262144:262144 \
 -v E:/Docker/images/clickhouse-server/single/data:/var/lib/clickhouse:rw \
 -v E:/Docker/images/clickhouse-server/single/conf:/etc/clickhouse-server:rw \
 -v E:/Docker/images/clickhouse-server/single/log:/var/log/clickhouse-server:rw \
 yandex/clickhouse-server

上面的命令执行完后,Docker Desktop会有几个弹出框确认是否共享宿主机的目录,直接按share it按钮即可。

最后使用原生的命令行客户端 ClickHouse Client 进行连接

$ docker run -it --rm --link single-clickhouse-server:clickhouse-server yandex/clickhouse-client -uroot --password root --host clickhouse-server
ClickHouse client version 20.10.3.30 (official build).
Connecting to clickhouse-server:9000 as user root.
Connected to ClickHouse server version 20.10.3 revision 54441.

f5abc88ff7e4 :) select 1;

SELECT 1

┌─1─┐
│ 1 │
└───┘

1 rows in set. Elapsed: 0.004 sec.

下次如果电脑重启 ClickHouse Server 的容器没有启动,只需要使用命令 docker (re)start single-clickhouse-server 拉起容器实例即可。

使用 JDBC 连接 ClickHouse 服务

ClickHouse 的 JDBC 驱动目前有三个:

  • clickhouse-jdbc(官方):地址是 https://github.com/ClickHouse/clickhouse-jdbc,目前版本是基于 Apache Http Client 实现。
  • ClickHouse-Native-JDBC(第三方):地址是 https://github.com/housepower/ClickHouse-Native-JDBC,基于 Socket 实现。
  • clickhouse4j(第三方):地址是 https://github.com/blynkkk/clickhouse4j,比官方驱动轻量级。

说实话有点尴尬,官方的驱动包竟然没有对接 TCP 私有协议栈,而是使用了 HTTP 协议进行交互,这里不知道性能会下降多少,但是基于”官方更好”的思维这里还是选用官方的驱动包进行 Demo 演示。引入 clickhouse-jdbc 依赖:

<dependency>
    <groupId>ru.yandex.clickhouse</groupId>
    <artifactId>clickhouse-jdbc</artifactId>
    <version>0.2.4</version>
</dependency>

编写一个测试类:

public class ClickHouseTest {

    @Test
    public void testCh() throws Exception {
        ClickHouseProperties props = new ClickHouseProperties();
        props.setUser("root");
        props.setPassword("root");
        // 不创建数据库的时候会有有个全局 default 数据库
        ClickHouseDataSource dataSource = new ClickHouseDataSource("jdbc:clickhouse://localhost:8123/default", props);
        ClickHouseConnection connection = dataSource.getConnection();
        ClickHouseStatement statement = connection.createStatement();
        // 创建一张表,表引擎为 Memory,这类表在服务重启后会自动删除
        boolean execute = statement.execute("CREATE TABLE IF NOT EXISTS t_test(id UInt64,name String) ENGINE  = Memory");
        if (execute) {
            System.out.println("创建表 default.t_test 成功");
        } else {
            System.out.println("表 default.t_test 已经存在");
        }
        ResultSet rs = statement.executeQuery("SHOW TABLES");
        List<String> tables = Lists.newArrayList();
        while (rs.next()) {
            tables.add(rs.getString(1));
        }
        System.out.println("default 数据库中的表:" + tables);
        PreparedStatement ps = connection.prepareStatement("INSERT INTO t_test(*) VALUES (?,?),(?,?)");
        ps.setLong(1, 1L);
        ps.setString(2, "throwable");
        ps.setLong(3, 2L);
        ps.setString(4, "doge");
        ps.execute();
        statement = connection.createStatement();
        rs = statement.executeQuery("SELECT * FROM t_test");
        while (rs.next()) {
            System.out.println(String.format("查询结果,id:%s,name:%s", rs.getLong("id"), rs.getString("name")));
        }
    }
}

执行结果如下:

表 default.t_test 已经存在   # <--- 这里估计是驱动包的实现有 BUG,首次创建成功返回结果为 false
default 数据库中的表:[t_test]
查询结果,id:1,name:throwable
查询结果,id:2,name:doge

参考