0%

存储架构

Clickhouse 存储中的最小单位是 DataPart,写入链路为了提升吞吐,放弃了部分写入实时可见性,即数据攒批写入,一次批量写入的数据会落盘成一个 DataPart.

它不像 Druid 那样一条一条实时摄入。但 ClickHouse 把数据延迟攒批写入的工作交给来客户端实现,比如达到 10 条记录或每过 5s 间隔写入,换句话说就是可以在用户侧平衡吞吐量和时延,如果在业务高峰期流量不是太大,可以结合实际场景将参数调小,以达到极致的实时效果。

查询架构

计算能力方面

Clickhouse 采用向量化函数和 aggregator 算子极大地提升了聚合计算性能,配合完备的 SQL 能力使得数据分析变得更加简单、灵活。

数据扫描方面

ClickHouse 是完全列式的存储计算引擎,而且是以有序存储为核心,在查询扫描数据的过程中,首先会根据存储的有序性、列存块统计信息、分区键等信息推断出需要扫描的列存块,然后进行并行的数据扫描,像表达式计算、聚合算子都是在正规的计算引擎中处理。从计算引擎到数据扫描,数据流转都是以列存块为单位,高度向量化的。

高并发服务方面

Clickhouse 的并发能力其实是与并行计算量和机器资源决定的。如果查询需要扫描的数据量和计算复杂度很大,并发度就会降低,但是如果保证单个 query 的 latency 足够低(增加内存和 cpu 资源),部分场景下用户可以通过设置合适的系统参数来提升并发能力,比如 max_threads 等。其他分析型系统(例如 Elasticsearch)的并发能力为什么很好,从 Cache 设计层面来看,ES 的 Cache 包括 Query Cache, Request Cache,Data Cache,Index Cache,从查询结果到索引扫描结果层层的 Cache 加速,因为 Elasticsearch 认为它的场景下存在热点数据,可能被反复查询。反观 ClickHouse,只有一个面向 IO 的 UnCompressedBlockCache 和系统的 PageCache,为了实现更优秀的并发,我们很容易想到在 Clickhouse 外面加一层 Cache,比如 redis,但是分析场景下的数据和查询都是多变的,查询结果等 Cache 都不容易命中,而且在广投业务中实时查询的数据是基于 T 之后不断更新的数据,如果外挂缓存将降低数据查询的时效性。

技巧

唯一键约束

1
2
3
4
5
6
7
8
9
10
CREATE TABLE IF NOT EXISTS qilu.t_01(
C1 String,
C2 String,
C3 String,
C4 Date,
PRIMARY KEY (C1) # 要设置主键
) engine=ReplacingMergeTree() # 引擎要用ReplacingMergeTree
ORDER BY C1; # 要设置排序

optimize table t_01 FINAL; # 要强制合并分区

Change Data Capture(变更数据获取)

核心思想是,监测并捕获数据库的变动(包括数据或数据表的插入、更新以及删除等),将这些变更按发生的顺序完整记录下来,写入到消息中间件中以供其他服务进行订阅及消费。

应用场景

  • 数据同步,用于备份,容灾;
  • 数据分发,一个数据源分发给多个下游;
  • 数据采集(E),面向数据仓库/数据湖的 ETL 数据集成。

分类

主要分为基于查询基于 Binlog 两种方式

传统 CDC ETL

性能点

大数据领域的 4 类场景:

B batch 离线计算

A Analytical 交互式分析

S Servering 高并发的在线服务

T Transaction 事务隔离机制

离线计算通常在计算层,所以应该重点考虑 A、S 和 T

考虑点

  • 保证端到端的数据一致性,包括维度一致性以及全流程数据一致性;

  • 实时流处理过程中数据到达顺序无法预知时,如何保证双流 join 时数据能及时关联同时不造成数据堵塞;

  • Oracle

    1
    2
    1.Oracle 是第三方厂商维护的,不允许对线上系统有过多的侵入,容易造成监听故障甚至系统瘫痪,
    2.归档日志是在开启那一刻起才开始生成的,之前的存量数据难以进入 kafka,但是后来实时数据又必须依赖前面的计算结果

实时数仓方案

Lambda 架构

目前主流的一套实时数仓架构,存在离线和实时两条链路。实时部分以消息队列的方式实时增量消费,一般以 Flink+Kafka 的组合实现,维度表存在关系型数据库或者 HBase;离线部分一般采用 T+1 周期调度分析历史存量数据,每天凌晨产出,更新覆盖前一天的结果数据,计算引擎通常会选择 Hive 或者 Spark。

Kappa 架构

相较于 Lambda 架构,它移除了离线生产链路,思路是通过传递任意想要的 offset(偏移量)来达到重新消费处理历史数据的目的。优点是架构相对简化,数据来源单一,共用一套代码,开发效率高;缺点是必须要求消息队列中保存了存量数据,而且主要业务逻辑在计算层,比较消耗内存资源。

OLAP 变体架构

是 Kappa 架构的进一步演化,它的思路是将聚合分析计算由 OLAP 引擎承担,减轻实时计算部分的聚合处理压力。优点是自由度高,可以满足数据分析师的实时自助分析需求,减轻了计算引擎的处理压力;缺点是必须要求消息队列中保存存量数据,且因为是将计算部分的压力转移到了查询层,对查询引擎的吞吐和实时摄入性能要求较高。

数据湖架构

存储、计算和查询,分别由三个独立产品负责,分别是数据湖、Flink 和 Clickhouse。数仓分层存储和维度表管理均由数据湖承担,Flink SQL 负责批流任务的 SQL 化协同开发,Clickhouse 实现变体的事务机制,为用户提供离线分析和交互查询。CDC 到消息队列这一链路将来是完全可以去掉的,只需要 Flink CDC 家族中再添加 Oracle CDC 一员。未来,实时数仓架构将得到极致的简化并且性能有质的提升。

checkpoint

基于 Chandy-Lamport 算法实现了一个分布式的一致性的快照,从而提供了一致性的语义。

Chandy-Lamport 算法实际上在 1985 年的时候已经被提出来,但并没有被很广泛的应用,而 Flink 则把这个算法发扬光大了。

state

丰富的State API:ValueState、ListState、MapState、 BroadcastState

time

实现了 Watermark 的机制,能够支持基于事件的时间的处理,或者说基于系统时间的处理,能够容忍数据的延时、容忍数据的迟到、容忍乱序的数据。

  • Event Time:是事件创建的时间。它通常由事件中的时间戳描述,例如采集的日志数据中,每一条日志都会记录自己的生成时间,Flink 通过时间戳分配器访问事件时间戳。

  • Ingestion Time:是数据进入 Flink 的时间。

  • Processing Time:是每一个执行基于时间操作的算子的本地系统时间,与机器相关,默认的时间属性就是 Processing Time。

    例如,一条日志进入 Flink 的时间为 2019-08-12 10:00:00.123,到达 Window 的系统时间为 2019-08-12 10:00:01.234,日志的内容如下:

    2019-08-02 18:37:15.624 INFO Fail over to rm2

    对于业务来说,要统计 1min 内的故障日志个数,哪个时间是最有意义的?—— eventTime,因为我们要根据日志的生成时间进行统计。

window

Flink 提供了开箱即用的各种窗口,比如滑动窗口、滚动窗口、会话窗口以及非常灵活的自定义的窗口。

  • 滚动窗口(Tumbling Window)

    将数据依据固定的窗口长度对数据进行切片, 滚动窗口分配器将每个元素分配到一个指定窗口大小的窗口中,滚动窗口有一个固定的大小,并且不会出现重叠

    特点:时间对齐,窗口长度固定,没有重叠

    适用场景:适合做 BI 统计等(做每个时间段的聚合计算)

    例如:如果你指定了一个 5 分钟大小的滚动窗口,窗口的创建如下图所示:

  • 滑动窗口(Sliding Window)

    滑动窗口是固定窗口的更广义的一种形式,滑动窗口由固定的窗口长度和滑动间隔组成。

    特点:时间对齐,窗口长度固定,有重叠

    滑动窗口分配器将元素分配到固定长度的窗口中,与滚动窗口类似,窗口的大小由窗口大小参数来配置,另一个窗口滑动参数控制滑动窗口开始的频率。因此,滑动窗口如果滑动参数小于窗口大小的话,窗口是可以重叠的,在这种情况下元素会被分配到多个窗口中。

    适用场景:对最近一个时间段内的统计(求某接口最近 5min 的失败率来决定是否要报警)。

    例如,你有 10 分钟的窗口和 5 分钟的滑动,那么每个窗口中 5 分钟的窗口里包含着上个 10 分钟产生的数据,如下图所示:

  • 会话窗口(Session Window)

    由一系列事件组合一个指定时间长度的 timeout 间隙组成,类似于 web 应用的 session,也就是一段时间没有接收到新数据就会生成新的窗口。

    特点:时间无对齐

    session 窗口分配器通过 session 活动来对元素进行分组,session 窗口跟滚动窗口和滑动窗口相比,不会有重叠和固定的开始时间和结束时间的情况,相反,当它在一个固定的时间周期内不再收到元素,即非活动间隔产生,那个这个窗口就会关闭。一个 session 窗口通过一个 session 间隔来配置,这个 session 间隔定义了非活跃周期的长度,当这个非活跃周期产生,那么当前的 session 将关闭并且后续的元素将被分配到新的 session 窗口中去。


flink API

Flink 分别提供了面向流式处理的接口(DataStream API)和面向批处理的接口(DataSet API)。因此,Flink 既可以完成流处理,也可以完成批处理。Flink 支持的拓展库涉及机器学习(FlinkML)、复杂事件处理(CEP)、以及图计算(Gelly),还有分别针对流处理和批处理的 Table API。

checkpoint

基于 Chandy-Lamport 算法实现了一个分布式的一致性的快照,从而提供了一致性的语义。

Chandy-Lamport 算法实际上在 1985 年的时候已经被提出来,但并没有被很广泛的应用,而 Flink 则把这个算法发扬光大了。

state

丰富的State API:ValueState、ListState、MapState、 BroadcastState

time

实现了 Watermark 的机制,能够支持基于事件的时间的处理,或者说基于系统时间的处理,能够容忍数据的延时、容忍数据的迟到、容忍乱序的数据。

  • Event Time:是事件创建的时间。它通常由事件中的时间戳描述,例如采集的日志数据中,每一条日志都会记录自己的生成时间,Flink 通过时间戳分配器访问事件时间戳。

  • Ingestion Time:是数据进入 Flink 的时间。

  • Processing Time:是每一个执行基于时间操作的算子的本地系统时间,与机器相关,默认的时间属性就是 Processing Time。

    例如,一条日志进入 Flink 的时间为 2019-08-12 10:00:00.123,到达 Window 的系统时间为 2019-08-12 10:00:01.234,日志的内容如下:

    2019-08-02 18:37:15.624 INFO Fail over to rm2

    对于业务来说,要统计 1min 内的故障日志个数,哪个时间是最有意义的?—— eventTime,因为我们要根据日志的生成时间进行统计。

window

Flink 提供了开箱即用的各种窗口,比如滑动窗口、滚动窗口、会话窗口以及非常灵活的自定义的窗口。

  • 滚动窗口(Tumbling Window)

    将数据依据固定的窗口长度对数据进行切片, 滚动窗口分配器将每个元素分配到一个指定窗口大小的窗口中,滚动窗口有一个固定的大小,并且不会出现重叠

    特点:时间对齐,窗口长度固定,没有重叠

    适用场景:适合做 BI 统计等(做每个时间段的聚合计算)

    例如:如果你指定了一个 5 分钟大小的滚动窗口,窗口的创建如下图所示:

  • 滑动窗口(Sliding Window)

    滑动窗口是固定窗口的更广义的一种形式,滑动窗口由固定的窗口长度和滑动间隔组成。

    特点:时间对齐,窗口长度固定,有重叠

    滑动窗口分配器将元素分配到固定长度的窗口中,与滚动窗口类似,窗口的大小由窗口大小参数来配置,另一个窗口滑动参数控制滑动窗口开始的频率。因此,滑动窗口如果滑动参数小于窗口大小的话,窗口是可以重叠的,在这种情况下元素会被分配到多个窗口中。

    适用场景:对最近一个时间段内的统计(求某接口最近 5min 的失败率来决定是否要报警)。

    例如,你有 10 分钟的窗口和 5 分钟的滑动,那么每个窗口中 5 分钟的窗口里包含着上个 10 分钟产生的数据,如下图所示:

  • 会话窗口(Session Window)

    由一系列事件组合一个指定时间长度的 timeout 间隙组成,类似于 web 应用的 session,也就是一段时间没有接收到新数据就会生成新的窗口。

    特点:时间无对齐

    session 窗口分配器通过 session 活动来对元素进行分组,session 窗口跟滚动窗口和滑动窗口相比,不会有重叠和固定的开始时间和结束时间的情况,相反,当它在一个固定的时间周期内不再收到元素,即非活动间隔产生,那个这个窗口就会关闭。一个 session 窗口通过一个 session 间隔来配置,这个 session 间隔定义了非活跃周期的长度,当这个非活跃周期产生,那么当前的 session 将关闭并且后续的元素将被分配到新的 session 窗口中去。


flink API

Flink 分别提供了面向流式处理的接口(DataStream API)和面向批处理的接口(DataSet API)。因此,Flink 既可以完成流处理,也可以完成批处理。Flink 支持的拓展库涉及机器学习(FlinkML)、复杂事件处理(CEP)、以及图计算(Gelly),还有分别针对流处理和批处理的 Table API。

时间属性

像窗口(在 Table APISQL )这种基于时间的操作,需要有时间信息。因此,Table API 中的表就需要提供逻辑时间属性来表示时间,以及支持时间相关的操作。

每种类型的表都可以有时间属性,时间属性可以通过

  1. 用CREATE TABLE DDL创建表的时候指定
  2. 可以在 DataStream 中指定
  3. 可以在定义 TableSource 时指定。

一旦时间属性定义好,它就可以像普通列一样使用,也可以在时间相关的操作中使用,只要时间属性没有被修改,而是简单地从一个表传递到另一个表,它就仍然是一个有效的时间属性。

时间属性可以像普通的时间戳的列一样被使用和计算。一旦时间属性被用在了计算中,它就会被物化,进而变成一个普通的时间戳。普通的时间戳是无法跟 Flink 的时间以及watermark等一起使用的,所以普通的时间戳就无法用在时间相关的操作中(这句话是只限于被修改的普通时间戳,还是包含未被修改的时间戳)。

处理时间

处理时间是基于机器的本地时间来处理数据,它是最简单的一种时间概念,但是它不能提供确定性。它既不需要从数据里获取时间,也不需要生成 watermark。

定义处理时间的三种方法:

  1. 在创建表的 DDL 中定义

    PROCTIME() 就可以定义处理时间,函数 PROCTIME() 的返回类型是 TIMESTAMP_LTZ

    1
    2
    3
    4
    5
    6
    7
    CREATE TABLE user_actions (
    user_name STRING,
    data STRING,
    user_action_time AS PROCTIME() -- 声明一个额外的列作为处理时间属性
    ) WITH (
    ...
    );
  2. 在 DataStream 到 Table 转换时定义

    处理时间属性可以在 schema 定义的时候用 .proctime 后缀来定义。时间属性一定不能定义在一个已有字段上,所以它只能定义在 schema 定义的最后。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    DataStream<Tuple2<String, String>> stream = ...;

    // 声明一个额外的字段作为时间属性字段
    Table table = tEnv.fromDataStream(stream, $("user_name"), $("data"), $("user_action_time").proctime());

    WindowedTable windowedTable = table.window(
    Tumble.over(lit(10).minutes())
    .on($("user_action_time"))
    .as("userActionWindow"));
  3. 使用 TableSource 定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // 定义一个由处理时间属性的 table source
    public class UserActionSource implements StreamTableSource<Row>, DefinedProctimeAttribute {

    @Override
    public TypeInformation<Row> getReturnType() {
    String[] names = new String[] {"user_name" , "data"};
    TypeInformation[] types = new TypeInformation[] {Types.STRING(), Types.STRING()};
    return Types.ROW(names, types);
    }

    @Override
    public DataStream<Row> getDataStream(StreamExecutionEnvironment execEnv) {
    // create stream
    DataStream<Row> stream = ...;
    return stream;
    }

    @Override
    public String getProctimeAttribute() {
    // 这个名字的列会被追加到最后,作为第三列
    return "user_action_time";
    }
    }

    // register table source
    tEnv.registerTableSource("user_actions", new UserActionSource());

    WindowedTable windowedTable = tEnv
    .from("user_actions")
    .window(Tumble
    .over(lit(10).minutes())
    .on($("user_action_time"))
    .as("userActionWindow"));

事件时间

事件时间允许程序按照数据中包含的时间来处理,这样可以在有乱序或者晚到的数据的情况下产生一致的处理结果。为了能够处理乱序的事件,并且区分正常到达和晚到的事件,Flink 需要从事件中获取事件时间并且产生 watermarks

定义事件时间的三种方法:

  1. 在 DDL 中定义

    WATERMARK 语句在一个已有字段上定义一个 watermark 生成表达式,同时标记这个已有字段为时间属性字段。

    Flink 支持和在 TIMESTAMP 列和 TIMESTAMP_LTZ 列上定义事件时间。

    • 如果源数据中的时间戳数据表示为年-月-日-时-分-秒,则通常为不带时区信息的字符串值,例如 2020-04-15 20:13:40.564,建议将事件时间属性定义在 TIMESTAMP 列上,

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      CREATE TABLE user_actions (
      user_name STRING,
      data STRING,
      user_action_time TIMESTAMP(3),
      -- 声明 user_action_time 是事件时间属性,并且用 延迟 5 秒的策略来生成 watermark
      WATERMARK FOR user_action_time AS user_action_time - INTERVAL '5' SECOND
      ) WITH (
      ...
      );

      SELECT TUMBLE_START(user_action_time, INTERVAL '10' MINUTE), COUNT(DISTINCT user_name)
      FROM user_actions
      GROUP BY TUMBLE(user_action_time, INTERVAL '10' MINUTE);
    • 如果源数据中的时间戳数据为带时区信息的字符串值,例如源数据中的时间戳数据表示为一个纪元 (epoch) 时间,通常是一个 long 值,例如 1618989564564,建议将事件时间属性定义在 TIMESTAMP_LTZ 列上:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      CREATE TABLE user_actions (
      user_name STRING,
      data STRING,
      ts BIGINT,
      time_ltz AS TO_TIMESTAMP_LTZ(ts, 3),
      -- declare time_ltz as event time attribute and use 5 seconds delayed watermark strategy
      WATERMARK FOR time_ltz AS time_ltz - INTERVAL '5' SECOND
      ) WITH (
      ...
      );

      SELECT TUMBLE_START(time_ltz, INTERVAL '10' MINUTE), COUNT(DISTINCT user_name)
      FROM user_actions
      GROUP BY TUMBLE(time_ltz, INTERVAL '10' MINUTE);
  2. 在 DataStream 到 Table 转换时定义

    事件时间属性可以用 .rowtime 后缀在定义 DataStream schema 的时候来定义。

    时间戳和 watermark 在这之前一定是在 DataStream 上已经定义好了。 在从 DataStream 转换到 Table 时,由于 DataStream 没有时区概念,因此 Flink 总是将 rowtime 属性解析成 TIMESTAMP WITHOUT TIME ZONE 类型,并且将所有事件时间的值都视为 UTC 时区的值。

    • Option 1
    1
    2
    3
    4
    5
    // 基于 stream 中的事件产生时间戳和 watermark
    DataStream<Tuple2<String, String>> stream = inputStream.assignTimestampsAndWatermarks(...);

    // 声明一个额外的逻辑字段作为事件时间属性
    Table table = tEnv.fromDataStream(stream, $("user_name"), $("data"), $("user_action_time").rowtime());
    • Option 2
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
      // 从第一个字段获取事件时间,并且产生 watermark
    DataStream<Tuple3<Long, String, String>> stream = inputStream.assignTimestampsAndWatermarks(...);

    // 第一个字段已经用作事件时间抽取了,不用再用一个新字段来表示事件时间了
    Table table = tEnv.fromDataStream(stream, $("user_action_time").rowtime(), $("user_name"), $("data"));

    // Usage:

    WindowedTable windowedTable = table.window(Tumble
    .over(lit(10).minutes())
    .on($("user_action_time"))
    .as("userActionWindow"));
  3. 使用 TableSource 定义

    事件时间属性可以在实现了 DefinedRowTimeAttributesTableSource 中定义。getRowtimeAttributeDescriptors() 方法返回 RowtimeAttributeDescriptor 的列表,包含了描述事件时间属性的字段名字、如何计算事件时间、以及 watermark 生成策略等信息。

    同时需要确保 getDataStream 返回的 DataStream 已经定义好了时间属性。

    只有在定义了 StreamRecordTimestamp 时间戳分配器的时候,才认为 DataStream 是有时间戳信息的。 只有定义了 PreserveWatermarks watermark 生成策略的 DataStream 的 watermark 才会被保留。反之,则只有时间字段的值是生效的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    // 定义一个有事件时间属性的 table source
    public class UserActionSource implements StreamTableSource<Row>, DefinedRowtimeAttributes {

    @Override
    public TypeInformation<Row> getReturnType() {
    String[] names = new String[] {"user_name", "data", "user_action_time"};
    TypeInformation[] types =
    new TypeInformation[] {Types.STRING(), Types.STRING(), Types.LONG()};
    return Types.ROW(names, types);
    }

    @Override
    public DataStream<Row> getDataStream(StreamExecutionEnvironment execEnv) {
    // 构造 DataStream
    // ...
    // 基于 "user_action_time" 定义 watermark
    DataStream<Row> stream = inputStream.assignTimestampsAndWatermarks(...);
    return stream;
    }

    @Override
    public List<RowtimeAttributeDescriptor> getRowtimeAttributeDescriptors() {
    // 标记 "user_action_time" 字段是事件时间字段
    // 给 "user_action_time" 构造一个时间属性描述符
    RowtimeAttributeDescriptor rowtimeAttrDescr = new RowtimeAttributeDescriptor(
    "user_action_time",
    new ExistingField("user_action_time"),
    new AscendingTimestamps());
    List<RowtimeAttributeDescriptor> listRowtimeAttrDescr = Collections.singletonList(rowtimeAttrDescr);
    return listRowtimeAttrDescr;
    }
    }

    // register the table source
    tEnv.registerTableSource("user_actions", new UserActionSource());

    WindowedTable windowedTable = tEnv
    .from("user_actions")
    .window(Tumble.over(lit(10).minutes()).on($("user_action_time")).as("userActionWindow"));

format

  • timestamp可以将时间戳类型数据最高精确微秒(百万分之一秒),数据类型定义为timestamp(N),N取值范围为0-6,默认为0,如需要精确到毫秒则设置为Timestamp(3),如需要精确到微秒则设置为timestamp(6),数据精度提高的代价是其内部存储空间的变大,但仍未改变时间戳类型的最小和最大取值范围。

connector kafka

PORT

  • 查看指定进程pid的占用端口

    1
    lsof -i -P -n | grep LISTEN | grep {pid}

Install .pkg

a.

1
sudo installer -pkg /path/to/package.pkg -target /

will install the package in /Applications.

is all that’s needed. Here / is the mount point of Macintosh HD volume. -target accepts path like "/Volumes/Macintosh HD", or /dev/disk0 also.

b.

1
installer -pkg myapp.pkg -target CurrentUserHomeDirectory

will install the package in ~/Applications.

mysql

Regenerate

mysql镜像制作

  1. 需要备份当前需要同步的全量数据

    1
    docker exec -it dlabel_mysql mysqldump -uroot -p123456 dls > /path/to/backup.sql

    注意事项:

    其中dlabel_mysql,是在第二步中设置的name的名称

    /path/to/backup.sql是导出sql的地址路径,根据操作系统不同,需要自行更改

    假定以下操作是在/path/to的目录下

  1. 在/path/to目录下创建Dockerfile文件

    1
    2
    3
    4
    5
    6
    # Derived from official mysql image (our base image)
    FROM mysql:5.7.30
    # Add the content of the sql-scripts/ directory to your image
    # All scripts in docker-entrypoint-initdb.d/ are automatically
    # executed during container startup
    COPY ./backup.sql /docker-entrypoint-initdb.d/

    注意COPY指令中,backup.sql需要和操作1中的导出文件名保持一致

  2. 创建镜像

    1
    docker build -t dlabel:mysql20211216 .

    dlabel:mysql20211216是 REPOSITORY:TAG格式,可自行更改

  3. 登录远程仓库

    1
    docker login hostAddress

    根据提示,输入用户名admin,密码Harbor12345

  4. 映射远程仓库REPOSITORY:TAG

    1
    docker image tag dlabel:mysql20211216 hostAddress/dlabel/service:mysql20211216

    其中dlabel:mysql20211216和操作3中保持一致

    hostAddress/dlabel/service:mysql20211216,格式为hostAddress/library/REPOSITORY:TAG,其中可自行修改service:mysql20211216名称

  5. 推送当地镜像到远程仓库

    1
    docker push hostAddress/dlabel/service:mysql20211216
  6. 登录http://hostAddress查看镜像上传情况

  7. 在镜像详情界面,点击“拉取命名”按钮进行命令复制,在终端执行命令即可拉取该镜像

文中hostAddress需要替换具体的ip地址

环境准备

查看现有环境相关参数ulimit -a

  • 设置文件句柄数,在**/etc/security/limits.conf**中设置

    1
    2
    3
    # End of file
    * hard nofile 65536
    * soft nofile 65536
  • 修改max user processes进程数,在**/etc/security/limits.conf**中设置

    1
    2
    * soft nproc 65536
    * hard nproc 65536
  • 调整vm.max_map_count的大小,该参数会限制一个进程可以拥有的VMA(虚拟内存区域)的数量

    通过修改**/etc/sysctl.conf**参数

    1
    vm.max_map_count=655360

    然后执行sysctl -p

  • 调整stack size的大小(可选),在**/etc/security/limits.conf**中设置

    1
    2
    * soft stack 1024
    * hard stack 1024

manual init data

  • create index

    1
    curl -H 'Content-Type: application/json' -d '@/data/es_mapping.json' -X PUT 'http://localhost:9200/indexName'
  • import data

    1
    curl -H 'Content-Type: application/json' --data-binary '@/data/es_init_data.txt' 'http://localhost:9200/_bulk'
  • 拉取远程仓库镜像文件

    1
    docker pull hostAddress/dlabel/service:elasticsearch
  • 启动容器

    1
    sudo docker run -d --name es_origin -e ES_JAVA_POTS="-Xms6g -Xmx6g" -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.16.1

制作elasticsearch镜像

  1. 导出ES的已有索引和数据
  • 环境准备

    • 安装nodejs,安装文件地址nodejs
    • 安装elasticdump,安装命令npm install -g elasticdump
  • 导出es索引文件es_mapping.json

    1
    2
    3
    /$nodejs_home/lib/node_modules/elasticdump/bin/elasticdump \               --input=http://127.0.0.1:9200/indexName \
    --output=/data/es_mapping.json \
    --type=mapping

    注意:$nodejs_home代表nodejs的安装目录

  • 导出es数据es_init_data.txt

    1
    2
    3
    4
    /$nodejs_home/lib/node_modules/elasticdump/bin/elasticdump \           
    --input=http://127.0.0.1:9200/indexName \
    --output=/data/es_init_data.txt \
    --searchBody '{"query":{"match_all":{ }}}'
  1. 编写es数据初始化脚本 initEs.sh

    1
    2
    3
    4
    #create index
    curl -H 'Content-Type: application/json' -d '@/data/es_mapping.json' -X PUT 'http://127.0.0.1:9200/indexName'
    #import data
    curl -H 'Content-Type: application/json' --data-binary '@/data/es_init_data.txt' 'http://127.0.0.1:9200/_bulk'

    initEs.sh文件同1,2操作中的文件存放路径保持一致,均放在/data目录下

  2. 在/data目录下创建Dockerfile文件

    1
    2
    3
    4
    5
    FROM elasticsearch:7.16.1
    COPY ./data/* /data/
    RUN chown -R elasticsearch:root /data
    USER elasticsearch
    RUN elasticsearch -E discovery.type=single-node -p /tmp/epid & /bin/bash /data/wait-for-it.sh -t 0 localhost:9200 -- /data/initEs.sh; kill $(cat /tmp/epid) && wait $(cat /tmp/epid); exit 0;
  3. 创建镜像

    1
    docker build -t dlabel:elasticsearch .

    dlabel:es是 REPOSITORY:TAG格式,可自行更改

  4. 登录远程仓库

    1
    docker login hostAddress

    根据提示,输入用户名admin,密码Harbor12345

  5. 映射远程仓库REPOSITORY:TAG

    1
    docker image tag dlabel:elasticsearch hostAddress/dlabel/service:elasticsearch

    其中dlabel:elasticsearch和操作3中保持一致

  6. 推送当地镜像到远程仓库

    1
    docker push hostAddress/dlabel/service:elasticsearch

Docker File vs Docker Compose

Dockerfile is what’s used to create a container image, and a Compose file is what’s used to deploy an instance of that image as a container.

Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,您可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务。

Dockerfile

Dockerfile the predecessor of a container image. You build an image from a Dockerfile. A typical Dockerfile contains special build instructions, commands like RUNADDCOPYENTRYPOINT, etc.

Compose file

Compose files are used in two types of deployments: in the non-cluster deployment with docker-compose and a cluster deployment with docker swarm.

Compose files are used in two types of deployments: in the non-cluster deployment with docker-compose and a cluster deployment with docker swarm.

To distinguish the two types, I’m going to address the compose file responsible for cluster deployment as stack files. I’ll talk about stack files in a moment.

Compose files are part of a tool called docker-compose. It’s a client application to the docker daemon server, kind of like the docker CLI client, but instead of typing the whole run commands every time, with docker-compose you can re-use the same YAML file over and over again, and deploy the same container with the same configuration as you did in the first time.

It’s more readable, more maintainable, more intuitive. A single compose file can contain multiple container deployment configurations.

  • 执行docker-compose up,报错

    1
    Couldn’t connect to Docker daemon at http+docker://localhost - is it running?

    其中docker-compose.yml信息如下:

    1
    2
    3
    4
    5
    6
    7
    8
    version: "3.7"
    services:
    web:
    build: .
    ports:
    - "5000:5000"
    redis:
    image: "redis:alpine"
    • 解决,使用sudo权限

      1. Add user to docker group (if not already added)

        1
        sudo usermod -aG docker $USER
      2. create a symbolic link to /usr/bin using the following command

        1
        sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
      3. Restart docker service

        1
        sudo service docker restart
      4. execute

        1
        sudo docker-compose up

运行docker compose up或docker compose up -d(后台运行)运行您的整个应用程序。 注意:每次修改任一配置文件后,都要使用 docker-compose up --build 重新构建

有了docker-compose,当我们想启动多个服务时,无需再一个一个进行docker run操作,而只需要编写docker-compose.yml配置文件,即可一次运行你的全部服务。


属性 描述
docker-compose build (构建yml中某个服务的镜像)
docker-compose ps (查看已经启动的服务状态)
docker-compose kill (停止某个服务)
docker-compose logs (可以查看某个服务的log)
docker-compose port (打印绑定的public port)
docker-compose pull (pull服务镜像)
docker-compose up (启动yml定义的所有服务)
docker-compose stop (停止yml中定义的所有服务)
docker-compose start (启动被停止的yml中的所有服务)
docker-compose kill (强行停止yml中定义的所有服务)
docker-compose rm (删除yml中定义的所有服务)
docker-compose restart (重启yml中定义的所有服务)
docker-compose scale (扩展某个服务的个数,可以向上或向下)
docker-compose version (查看compose的版本)

日志输出

终端输出:docker-compose --verbose up $service_name

或者docker-compose.yml配置

1
2
stdin_open: true
tty: true

镜像重新编译

如果修改了 Dockerfile内容里面相关的信息,需要重新编译镜像,如果使用docker compose,则需要使用命令

1
docker-compose up --build

后台运行

1
docker-compose up -d