欢迎点击此处关注公众号。
ClickHouse 的引擎分数据库引擎和数据表引擎。
数据库引擎举例:
表引擎:ClickHouse设计实现中的一大特色,数据表拥有何种特性、数据以何种形式被存储以及如何被加载。
在所有的表引擎中,最为核心的当属MergeTree系列表引擎,这些表引擎拥有最为强大的性能和最广泛的使用场合。对于非MergeTree系列的其他引擎而言,主要用于特殊用途,场景相对有限。而MergeTree系列表引擎是官方主推的存储引擎,支持几乎所有ClickHouse核心功能。
MergeTree在写入一批数据时,数据总会以数据片段的形式写入磁盘,且数据片段不可修改。为了避免片段过多,ClickHouse会通过后台线程,定期合并这些数据片段,属于相同分区的数据片段会被合成一个新的片段。这种数据片段往复合并的特点,也正是合并树名称的由来。
MergeTree作为家族系列最基础的表引擎,主要有以下特点:
分区表实际上是在表的目录下再以分区命名,建子目录。
作用:进行分区裁剪,避免全表扫描,减少 MapReduce 处理的数据量,提高效率。
分区:分区表是指按照数据表的某列或某些列分为多个区,区从形式上可以理解为文件夹。
分桶:相对分区进行更细粒度的划分。指定分桶表的某一列,让该列数据按照哈希取模的方式随机、均匀地分发到各个桶文件中。因为分桶操作需要根据某一列具体数据来进行哈希取模操作,故指定的分桶列必须基于表中的某一列(字段)。因为分桶改变了数据的存储方式,它会把哈希取模相同或者在某一区间的数据行放在同一个桶文件中。如此一来便可提高查询效率,如:我们要对两张在同一列上进行了分桶操作的表进行 JOIN 操作的时候,只需要对保存相同列值的桶进行JOIN操作即可。
分桶表 + map join。
对两张在同一列上进行了分桶操作的表进行 JOIN 操作的时候,只需要对保存相同列值的桶进行 JOIN 操作即可。
列式存储。优点:
1)reduce:将RDD中的数据做聚合操作,先聚合分区内元素,再聚合分区间元素。
2)collect:将RDD中的元素以数组的形式收集到Driver端。
3)first:返回RDD中的第一个元素。
4)take:取出RDD的前n个元素。
5)aggregate:先通过分区内的逻辑聚合分区内的元素,再通过分区间的逻辑聚合分区间元素和初始值。
6)countByKey:统计RDD中同一个key出现的次数。
7)foreach:遍历RDD,将函数 f 应用于每一个元素。
8)saveAsTextFile:将RDD以文本文件的格式存储到文件系统中。
在 Spark 的 RDD 算子中,Transformations 算子都属于惰性求值操作,仅参与 DAG 计算图的构建、指明计算逻辑,并不会被立即调度、执行。
惰性求值的特点是当且仅当数据需要被物化(Materialized)时才会触发计算的执行,RDD 的 Actions 算子提供各种数据物化操作,其主要职责在于触发整个 DAG 计算链条的执行。
当且仅当 Actions 算子触发计算时, DAG 从头至尾的所有算子(前面用于构建 DAG 的 Transformations 算子)才会按照依赖关系的先后顺序依次被调度、执行。
好处:可以优化后再执行。
宽依赖。
shuffle。
map 和 reduce 之间混洗的过程。为了让来自相同 Key 的所有数据都在同一个 reduce 中处理, 需要执行一个 all-to-all 的操作, 需要在不同的节点(不同的分区)之间拷贝数据,必须跨分区聚集相同 Key 的所有数据, 这个过程叫做 Shuffle。
在 Spark 中,DataFrame 是一种以 RDD 为基础的分布式数据集,类似于传统数据库中的二维表格。DataFrame 与 RDD 的主要区别在于,前者带有 schema 元信息,即 DataFrame 所表示的二维表数据集的每一列都带有名称和类型。这使得Spark SQL得以洞察更多的结构信息,从而对藏于DataFrame背后的数据源以及作用于DataFrame之上的变换进行了针对性的优化,最终达到大幅提升运行时效率的目标。
反观RDD,由于无从得知所存数据元素的具体内部结构,Spark Core只能在stage层面进行简单、通用的流水线优化。
创建表:
删除表:
选择:内部表与外部表没有太大区别。如果所有的数据都由hive处理,则创建内部表;如果数据的处理由hive和其他工具一起处理,则创建外部表。
数据倾斜只会发生在shuffle过程中。
通过观察spark UI的界面,定位数据倾斜发生在第几个stage中。
可以在Spark Web UI上看一下当前这个stage各个task分配的数据量,从而进一步确定是不是task分配的数据不均匀导致了数据倾斜。
基于HashMap实现的。所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。
class LRUCache {
class Node {
int key;
int value;
Node pre;
Node next;
public Node(){};
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
private int size;
private int capacity;
private HashMap<Integer, Node> map;
Node head;
Node tail;
public LRUCache(int capacity) {
size = 0;
this.capacity = capacity;
map = new HashMap<>();
head = new Node();
tail = new Node();
head.next = tail;
tail.pre = head;
}
public int get(int key) {
Node node = map.get(key);
if (node == null) {
return -1;
}
moveToTail(node);
return node.value;
}
public void put(int key, int value) {
Node node = map.get(key);
if (node != null) {
node.value = value;
moveToTail(node);
} else {
node = new Node(key, value);
addToTail(node);
}
}
private void addToTail(Node node) {
map.put(node.key, node);
node.pre = tail.pre;
node.next = tail;
tail.pre.next = node;
tail.pre = node;
size++;
if (size > capacity) {
removeNode(head.next);
}
}
private void moveToTail(Node node) {
removeNode(node);
addToTail(node);
}
private void removeNode(Node node) {
node.next.pre = node.pre;
node.pre.next = node.next;
map.remove(node.key);
size--;
}
}
两个栈来实现,stack1 和 stack2。我们把首次浏览的页面依次压入栈 stack1 。点击后退按钮时,从 stack1 中 pop 栈顶元素,并将其 push 到 stack2。而点击前进按钮时,从 stack2 中 pop 栈顶元素,并将其 push 到 stack1。
效率:
栈:
1)栈的存取速度比堆快,仅次于直接位于CPU的寄存器。
2)栈中的数据的大小和生存周期是确定的。
3)栈中的数据可以共享。
堆:
1)堆可以动态的分配内存大小,生存期也不必告诉编译器。
2)堆在运行时动态分配内存,存取速度慢。