刚接触实时数据的时候,那时候比较时兴叫“流式数据”。我就非常怀疑实时数据的应用场景极其有限。别的不说,单说 Join 这个非常常用的数据操作,你说咋实现?
Join 是咋回事?
在关系型数据库中,两个表的 Join 原理其实还是比较容易理解的。我们用最朴素的方式方法去理解,就是这个样子:
主表和副表进行 Join ,其实就是拿主表的每一条记录跟副表的每一条记录进行查询、匹配。有人说了,匹配上一条是不是就可以了?当然不行了,因为可能会存在多对多的关系,所以必须要每一条进行匹配。
匹配结束后,把匹配的结果进行合并,然后输出结果,这就是全表 Join 的原理了。不过这种方式简单且容易理解,但是这开销也太大了吧!相当于 N 个全表扫描了。
这种情况在一般的场景不多见,因为有经验的数据工程师会用第二种方法:
其实就是在副表的关联字段上建一个索引。这样,主表在去找数据的时候,就不用做全表扫描了,索引里有直接拿,然后回查一下就好了。速度那是嗷嗷快啊!
不过,对于那些就是没有索引的咋办?我们玩数据的最喜欢加一层中间层。Join 也是这么弄的,加一层 Join Buffer :
这样呢,一来可以减少对主表的访问频次,二来呢,在合并的时候也能减少对副表的访问次数。这样至少比两边都全表扫描快一些了。
不过,在这里你可能也发现了,这个主表很关键。如果主表里的数据也非常多,那效率影响还是会很大。如果主表里的id还会重复的话,那就更费劲了。
所以 Join 的几个优化原则就出来了:
- 小表驱动大表,原理是减少主表的循环次数;
- 副表Join字段必须有索引,原理是减少匹配的次数;
- 尽量增加 JoinBuffer ,应对那些确实没有索引的情况。
如果是在 Hive 等大数据环境,还可以用一些分桶等优化的方式加快 Join 的效率。
这时候你可能会说了,你这都是离线数据的 Join ,咱都能理解。这离线数据都是已经放在那里的,怎么 Join 都行啊。但是实时数据,都是流动的,想关联一个 id ,副表里的数据还没到呢,咋关联啊?是啊,愁人!
实时数据咋 Join ?
实时数据,也叫流式数据。顾名思义啊,数据都是流动的,你都不知道下一个数据是啥时候来,不能做全表的排序和匹配,所以没办法跟离线数据一样直接 Join ,这个的确是比较复杂了一些。既然不能直接 Join ,那么我们能不能分不同的情况不同对待呢?当然是可以的了!我们来分析一下哈~~
我们在做 Join 的时候基本上有几种情况:
1、业务表和维表关联;
2、业务表和业务表关联;
2.1、大表和小表关联;
2.2、大表和大表关联;
2.3、当前数据和当前数据关联;
2.4、当前数据和历史数据关联。
你看,是不是基本就这些情况啊?好,我们一个一个来分析,看看咋解决。
业务表和维表关联
实时数据基本都是以 MQ 为传输和存储介质,用 Spark Streaming 、 Flink 作为计算引擎。所以呢,Join 的过程也就是在Spark Streaming、Flink 里进行了。
既然如此,我们能不能在这些计算引擎里把各种需要 Join 的不经常变的维表数据先读到 Redis 或者 HBase 等查询非常快的存储介质里,业务数据过来了,跟存储里的数据 Join 一下是不是就可以了?
当然可以了!这就是流表与维表的 Join 方案了!
不过这里还有一个小问题,就是维表是会发生变化的,这个好解决,我们整一个流程实时更新一下,这样就把 Redis 里的维表变成实时维表了,这样问题就解决。
业务表和业务表关联
这里分 4 种不同的情况,咱先说大表和小表关联。既然是小表,那这个还是比较好办的。咱的机器内存都不小,假设服务器都是 32G 内存的,空出 1G 放小表是不是 OK 啊?影响不会太大。所以大表和小表 Join 的解决方案就出来了,把小表通过广播的方式在大表所在的节点内存中,是不是就可以了?这样大表 Join 小表的时候,直接去内存里读取就好了么。
大表和大表关联呢?这个没办法。这种常规的全量 Join ,就只能把所有数据都在状态中存储所有的数据了。这种方法的资源占用是最大的,也是最没办法的。
当前数据和当前数据 join , Spark Streaming 和 Flink 都有办法解决。Flink 用的是窗口的逻辑。在同一个窗口中,两边的数据都到齐了,直接 join 就好了。我们需要操心的是数据迟到咋办,大致的思路就要么多等一会,要么之后再补齐一下。
当前数据和历史数据 join 其实跟上面的做法大致差不多,就是得加一个指定时间。
总结
离线数据 Join 其实就是无数个查询、匹配的过程。优化思路也比较清晰,减少主表数据量、增加副表索引等。
实时数据就比较费劲,因为没法做全量数据的排序啥的,所以得分很多种情况。但是都有解决方案。我们需要关注的是数据迟到了咋办,应对办法一般是多等会,或者之后再补齐。
这里借用一下鸣宇淳同学画的图作为总结,这张图上写的非常清晰。