138 Matching Annotations
  1. Feb 2025
    1. Suites

      这三个看起来都是在删除的时候超时导致的broken status

    1. test_snapshot_20233

      看起来似乎是application id为空字符,也就是没拿到application id,然后尝试kill,获取job的application id是通过bash命令获取的,那意味着可能job会执行完毕

  2. Jan 2025
    1. 手里拿着锤子,看什么都像钉子

      所以程序员总是想着用代码解决问题

    2. 要让程序员知道要开发产品的细节,可以在任务上描述出软件各种场景给出的各种行为

      用户故事,边界

    3. 可以用原型工具把它做出来,而不是非得把完整功能开发出来

      想办法告知 dev design,尽量对齐理解

    4. 许多人都是刚刚听到别人要求做的一个功能,就开始脑补接下来的一切。导致的结果,就是付出的努力毫无意义。

      太形象了

    1. 如果一个 RDD 的一个分区,只会影响到下游的一个节点,那么我们就称这样的上下游依赖关系为窄依赖。而如果一个 RDD 的一个分区,会影响到下游的多个节点,那么我们就称这样的上下游关系为宽依赖。

      如果宽依赖前面的节点数据丢失或者故障,那么后面所有执行都会受到影响

    1. 则是叶子节点本身不负责存储,而是采用一个共享的存储层,比如 GFS。Dremel 从 2009 年开始,就逐步把存储层全部迁移到了 GFS 上。

      存算分离,动态调度

    1. 由于数据格式可能存在嵌套和 repeat,基于列存,那么某行的该列的值是哪些很难判定,因此通过 dremel 的 repetition level 和 definition level 的方式进行编码后就能重组数据

    2. 对于很多取值为 NULL 的字段来说,我们并不知道它为空,是因为自己作为一个 Optional 字段,所以没有值,还是由于上一层的 Optional 字段的整个对象为空。

      当前字段本身可能是 null,也可能由于上一级是 null 导致当前字段是 null,所以需要 definition level

    3. 而这个解决方案我们之前也已经用过了,那就是像 Bigtable 一样对数据进行分区。只要行键在同一区间的列存储的数据,存储在相同服务器的硬盘上,我们也就不需要通过网络传输,来把基于列存储的数据,组装成一行行的数据了

      同一区间的行的多个列存放在同一个服务器上

    4. 更合理的解决方案是行列混合存储。因为,我们需要把一批行相同的数据,放在同一个服务器上。

      可能希望一行的一些列存在一起,避免网络传输

    5. WAL+MemTable+SSTable 组合的解决方案

      对于列存这种不便于写入的情况,可以先利用 wal 暂存数据,最后批量写

    6. 而在这种大量、小文件的场景下,是发挥不出 MapReduce 进行顺序文件读写的吞吐量的优势的。

      拆分多个文件之后,就不再是顺序读取了

    1. 《Hive SQL 的编译过程》
    2. 数据存储里,Hive 的数据是直接放在 HDFS 上,通过 HDFS 上不同的目录和文件来做到分区和分桶。分区和分桶,使得 Hive 下很多的 MapReduce 任务可以减少需要扫描的数据,提升了整个系统的性能。

      分区可以减少数据的扫描量,而分桶则能够便于进行采样,以及数据负载均衡 https://www.51cto.com/article/753462.html

    1. 科研人员注重在特定领域的深入了解,追求深入,想要寻求新的想法、突破,找到新的研究方向。而作为工程师,更多是学习理解,并和现有的工程系统加以联系,看看是否可以把整个领域最新的进展和自己的工作结合起来。

      看论文的关注点

    1. 我们设计系统的时候,不能光考虑对应系统的功能,如何让整个系统对于其他团队的开发者和使用者易用,也非常关键

      系统的简单性

    1. **如果raft 的客户端网络波动没收到成功响应而重试岂不是会多次执行操作? **

      对于这个问题,Raft协议中有一种机制管理这种网络不稳定的情况。

      在Raft协议中,客户端请求是通过集群的领导者节点来处理的。当一个客户端提交一个请求,领导者节点将这个请求作为一个新的日志条目添加到自己的日志中。然后领导者向其他节点复制这个条目。当这个条目被大多数节点保存到日志中之后,然后领导者就执行这个请求并向客户端返回结果。

      为了解决客户端因网络问题接收不到结果而重试的问题,Raft设计了client interaction的机制。也就是说,客户端在发送请求时会附加一个唯一的请求ID。这样,即使一个请求由于网络问题重复发送,由于请求ID相同,领导者能识别出这个重复的请求并且忽略它。

      在这个机制下,即使客户端因为网络问题重试请求,由于有请求ID作为标识,整个操作仍然只会执行一次。另外,即使返回结果因网络问题没有送达客户端,领导者也能根据请求ID重新发送应答而不会重复执行操作。

      因此,虽然在网络不稳定的情况下客户端可能会重试,但是通过Raft协议的设计,这个操作不会被重复执行多次。

    2. accept 阶段之后,并没有所有节点都同步数据,那么这些数据将在什么时候进行同步呢?如果新的请求到来,那么有旧数据的节点能否接受请求呢?<br /> 似乎数据持久化是在其他节点上,比如 learner 节点上,当获得了大多数的 accept 就相当于拿到了整个集群锁,然后往 learner 中写数据

    3. 如果数据依赖于当前的数据,那 paxos 算法的重试不就没用了吗?需要重新读取数据并自己进行重试

    1. 在最后的提交执行阶段,三阶段提交为了提升系统的可用性也做了一点小小的改造。在进入最后的提交执行阶段的时候,如果参与者等待协调者超时了,那么参与者不会一直在那里死等,而是会把已经答应的事务执行完成。

      不确定是否是正确的,正确的做法不应该是 rollback 吗?

    2. 两阶段提交也没法保证一致性,一旦在决定提交事务时部分节点宕机那么只有一部分节点成功 commit,就出现数据的不一致

    3. 三阶段提交相比两阶段提交会先检查各节点资源是否可用<br /> 同时在提交执行阶段,如果参与者等待协调者超时了,那么参与者会继续完成事务的执行 (回滚或者提交),因此不同节点数据会存在不一致<br /> 更多请看这里

    1. 所以,TCompactProtocol 对于所有的整数类型的值,都采用了可变长数值(VQL,Variable-length quantity)的表示方式,通过 1~N 个 byte 来表示整数。

      变长编码存储数据

    2. 而且通常两个字段的编号是连续的。所以这个协议在存储编号的时候,存储的不是编号的值,而是存储编号和上一个编号的差。

      通过存储编号间的差值Delta Encoding,减少存储编号需要的空间。只需要一个字节即可存储编号和类型

    3. 我们可以废弃字段,并且这些废弃的字段不会占用存储空间。我们会随着需求变更不断新加字段,数十乃至上百个字段的 struct 在真实的工程场景中是非常正常的。而在这个过程中,很多字段都会被逐步废弃不再使用。如果这些字段仍然要占用存储空间的话,也会是一大浪费。

      如果需要废弃字段,那么需要保证不再需要向前兼容了,当前所有的相关程序都已经切换到了不需要这个字段的版本

    1. 数据兼容主要处理两种情况:<br /> 向前兼容: 旧代码解析新数据<br /> 向后兼容: 新代码解析就数据

    1. 布隆过滤器还是很适合 LSM Tree 的

    2. 第二个地方,是在我们读取数据的时候。在读取数据的时候,我们其实是读取 MemTable 加上多个 SSTable 文件合并在一起的一个视图。也就是说,我们从 MemTable 和所有的 SSTable 中,拿到了对应的行键的数据之后,会在内存中合并数据,并根据时间戳或者墓碑标记,来对数据进行“修改”和“删除”,并将数据返回给到客户端。

      是否可以理解为内存中是最新的数据,而磁盘上的日志则是整个修改过程,如果用户需要获取历史版本,那么就从磁盘数据上恢复得到

    3. Major Compaction

      合并的是同一行数据多个版本,不需要的版本可以删除

    4. 最近被读取到的数据,会存放到缓存(Cache)里,而不存在的行键,也会以一个在内存里的布隆过滤器(BloomFilter)进行快速过滤,尽一切可能减少真正需要随机访问硬盘的次数

      布隆过滤器无法支持删除操作,同时每次启动都需要加载,或者需要持久化

    5. 因为 Bigtable 支持单行事务,所以这两个修改,要么都完成了,要么都没有完成,不会存在一个成功,一个失败的情况。

      同一行的数据连续存储,所以单行事务还是好实现的,并没有分布式问题

    1. 对于列族,更合理的解读是,它是一张“物理表”,同一个列族下的数据会在物理上存储在一起。而整个表,是一张“逻辑表”

      由于一张表会存在大量的列,但是可能会是不同的业务的数据,因此通过列族进行标识切分。<br /> 不同的列族物理上也是连续的,只是是否是一个整体而已,比如压缩缓存的时候。同一列族的数据连续存储

    2. 在Google Cloud Bigtable中,列族有以下几个主要作用:

      数据组织:列族被用来组织、分组一系列有相关性的列。这样的结构不仅提供了更高效的查询方式,也使得对于业务上有关联的数据能够更好地进行管理。 数据存储和压缩优化:同一列族中的数据在物理存储上相互靠近,这样可以确保相同类型的数据一起被读取、写入或者压缩,从而提高效率。 读写性能:通过将相关的数据存储在同一个列族中,可以提高读写性能,因为只需要访问一个列族而不是多个列。 安全性和权限控制:列族能够为权限管理提供边界,比如可以对不同的列族设置不同的访问权限。 灵活性和扩展性:Bigtable的列族模型允许动态地向每一行添加或删除列,使得Bigtable非常适应变化多端的需求。 垃圾回收:在Bigtable中,基于时间的垃圾回收策略是按列族来设定的,可以指定保存特定列族中某一时间段之内的数据版本。 总的来说,列族在Bigtable中发挥着重要的角色,它在数据管理、性能优化、安全控制以及灵活性等多个方面都起着关键的作用。

    3. Each random read involves the transfer of a 64 KB SSTable block over the network from GFS to a tablet server, out of which only a single 1000-byte value is used…… Random and sequential writes perform better than random reads since each tablet server appends all incoming writes to a single commit log and uses group commit to stream these writes efficiently to GFS.

      随机写和顺序写的性能旗鼓相当,不仅在单台tablet server上表现类似,在tablet server集群上表现也几乎一样。原因是它们都会先写commit log,同步修改内存中的数据,异步修改SSTable中的数据。随机写和顺序写的差异在于SSTable的变更,由于这个操作被异步执行,所以它们的性能没有差异。 如果SSTable的不缓存在tablet server上,随机读的性能非常差,比写入操作几乎慢一个数量级。原因是不管要实际需要多小的数据,都需要从GFS上加载64k的SSTable块数据。它的瓶颈不在于tablet server的多少,而在于从GFS上读取数据。

    4. BigTable 通过 Chubby paxos 协议来保证全局只有一个 master,同时存储一些全局数据,比如 Root Tablet 的位置信息

    5. 而且 Master 可以根据每个 Tablet Server 的负载进行动态的调度,也就是 Master 还能起到负载均衡(load balance)的作用

      Master 负载均衡,动态重分区

    6. Bigtable 里,数据存储和在线服务的职责是完全分离的。我们调度 Tablet 的时候,只是调度在线服务的负载,并不需要把数据也一并搬运走。

      存算分离的鼻祖

    7. 这么早,google 就已经在存算分离了

    8. 在 Bigtable 里,我们就采用了另外一种分区方式,也就是动态区间分区。我们不再是一开始就定义好需要多少个机器,应该怎么分区,而是采用了一种自动去“分裂”(split)的方式来动态地进行分区。

      类似于一致性 hash ,只是这里是直接对按 key 排好序的数据进行均匀切分,如果插入或删除导致不均衡则进行合并或分裂

    9. 架构师的工作不是作出决策,而是尽可能久地推迟决策,在现在不作出重大决策的情况下构建程序,以便以后有足够信息时再作出决策

      如果前期不合理考虑,岂不是给后面增加技术债?

    10. 列下面如果有值的话,可以存储多个版本

      列有多个版本,而不是行

    1. “我怎么早没想到”的数据分区

      分区建的选择可能导致数据倾斜,从而导致整体翻倍扩容部分倾斜度低的服务器资源过剩

    2. 不得不进行的“翻倍扩容”

      常规分库分表策略需要进行翻倍扩容,且扩容后缩容同样麻烦,原本想要加部分服务器也不得不翻倍进行 如果换成一致性 hash 确实能改善翻倍扩容问题

    3. 额外开销(overhead)
    1. 既然只是对访问次数计数,我们自然就可以通过一个 Combiner,把 1 万条相同域名的访问记录做个化简。把它们变成 Key 还是域名,Value 就是有多少次访问的数值这样的记录就好了。而这样一化简,reduce 所在的 worker 需要抓取的数据,就从 1 万条变成了 1 条。

      map 函数并不能减少数据量,因此通过 combiner 可以在同一个 map 节点进行 reduce 合并数据来减少传输数据

    1. 5400 转,或者 7200 转的机械硬盘。如果你读过我的《深入浅出计算机组成原理》,你一定还记得机械硬盘的 IOPS 也就是在 70 左右

      5400转或者 7200 转的硬盘只有 70iops

    2. 而对于数据可能重复写入多次的问题,你也可以对每一条要写入的数据生成一个唯一的 ID,并且在里面带上当时的时间戳

      追加写时,在写入数据的时候带上时间戳,数据最终的顺序按照时间戳排序去重能够一定程度上保证数据的正确性

    3. GFS 的客户端里面自带了对写入的数据去添加校验和(checksum),并在读取的时候计算来验证数据完整性的功能。

      数据写入可能存在部分写入的脏数据,通过校验和排除

    4. 这个“至少一次”的机制,其实很适合 Google 的应用场景。你想像一下,如果你是一个搜索引擎,不断抓取网页然后存到 GFS 上。其实你并不会太在意这个网页信息是不是被重复存了两次,你也不太会在意不同的两个网页存储的顺序

      gfs 的至少一次保证并不适用于常规应用,只是对于谷歌搜索引擎的业务比较适合

    5. 第二种因素是随机的数据写入极有可能要跨越多个 chunk。

      考虑两个客户端 A、B往一个 hdfs 文件上随机写入,分别写 chunk1 和 chunk2,但 chunk1 主副本定序为A、B,而 chunk2 的主副本定序为 B、A,那么 chunk1 中的数据是 B 覆盖 A,而 chunk2 中是 A 覆盖 B,因此不能得到一份完整的 A或者 B

    1. 独特的 Snapshot 操作

      当需要复制一份数据进行追加写的时候性能好,数据复制不需要经过网络,而是通过控制指令直接在 chunserver 上进行

    2. 分离控制流和数据流

      kafka也有这个,元信息的读写和文件的读写分开,文件的读写不需要经过 master

    3. 等到所有次副本都接收完数据后,客户端就会发送一个写请求给到主副本。我在上节课一开始就说过,GFS 面对的是几百个并发的客户端,所以主副本可能会收到很多个客户端的写入请求。主副本自己会给这些请求排一个顺序,确保所有的数据写入是有一个固定顺序的。接下来,主副本就开始按照这个顺序,把刚才 LRU 的缓冲区里的数据写到实际的 chunk 里去。

      对于同一个 chunk 来说,主、次副本是固定的,当多个客户端并发写chunk 时,需要主副本确定写入的顺序,从而保持各个副本之间的一致性

    1. 保障读数据的可用性而设立的 Shadow Master

      shadow master 相当于读副本

    2. 追加写入(Append),GFS 也只是作出了“至少一次(At Least Once)”这样宽松的保障。

      在客户端进行数据校验、去重

    1. mit 6.824

      mit

    2. 首先是 Storm 的作者南森·马茨(Nathan Marz)的“Big Data”,现在也有中译本叫做《大数据系统构建》。对于人为错误的容错问题的思考,为我们带来了著名的 Lambda 架构。在我看来,即使到今天 Lambda 架构也并不过时。其次是俗称 DDIA 的这本《数据密集型应用系统设计》,这本书梳理了整个大数据领域的核心技术脉络,是一本非常合适的架构入门书。第三本是专注于流式处理的《Streaming System》,不过目前还没有中译本上市。如果你更喜欢通过视频课程学习,那么去看一看来自 MIT 的课程 6.824 的 Distributed System 绝对错不了。我在这里放上了Youtube和B 站的视频链接。最后是一份很容易被人忽视的资料,就是 2009 年 Jeff Dean 在 Cornell 大学的一个讲座“Designs, Lessons and Advice from Building Large Distributed Systems”的 PPT,我也推荐你去看一看,对于理解大数据系统的真实应用场景很有帮助。

      论文资料

    3. 多做交叉阅读和扩展阅读。论文本身往往只有 10 来页,非常精炼,对于很多知识点,往往就只有一个小片段,甚至只有一两句话,所以交叉阅读和扩展阅读少不了。根据你需要深入了解的知识点,你可能要回顾之前已经解读过的论文,也可能需要去阅读一些开源项目的代码,或者是一些计算机经典书籍中相关的章节,帮你彻底理解对应的问题

      扩展阅读和交叉阅读

    4. 一篇篇的大数据论文,并不是教科书里的一个章节或者一个知识点,而是对于一个重要的系统问题的解决方案。在读论文之前,先尝试自己去思考和解决对应的问题,有助于你更深刻地理解问题和解决方案的重点。

      思考自己如何做

  3. Dec 2024
    1. Spark与hive的集成分为两种,spark with hive和hive on spark

    2. spark cli需要与hive部署在一起,但生产环境hive都是独立部署,因此spark cli的方式一般不使用

    1. 当多个rdd的数据进行交互的时候,rdd前一步的计算会进行shuffle,并shuffle到对应的executor上

    2. 倾斜分区判定完毕之后,下一步,就是根据 advisoryPartitionSizeInBytes 参数指定的目标尺寸,对大分区进行拆分

      将倾斜分区拆分成多个分区

    3. Spark SQL 必须要仰仗运行时的执行状态,而 Shuffle 中间文件,则是这些状态的唯一来源。

      AQE需要获取数据的统计信息来决定是否将数据进行广播

    1. Shuffle 在 Map 阶段往往会对数据做排序,而这恰恰正中 SMJ 机制的下怀。对于已经排好序的两张表,SMJ 的复杂度是 O(M + N),这样的执行效率与 HJ 的 O(M) 可以说是不相上下。再者,SMJ 在执行稳定性方面,远胜于 HJ,在内存受限的情况下,SMJ 可以充分利用磁盘来顺利地完成关联计算。因此,考虑到 Shuffle SMJ 的诸多优势,Shuffle HJ 就像是关公后面的周仓,Spark SQL 向来对之视而不见,所以对于 HJ 你大概知道它的作用就行。

      由于 Shuffle 会对分区内的数据进行排序,因此 SMJ性能和 HJ 一样,那 Shuffle HJ 就没有优势了

    2. 在任何情况下,不论数据的体量是大是小、不管内存是否足够,Shuffle Join 在功能上都能够“不辱使命”,成功地完成数据关联的计算。

      那如果是非等值条件关联查询呢?

    3. 与前两者相比,Nested Loop Join 看上去有些多余,嵌套的双层 for 循环带来的计算复杂度最高:O(M * N)。不过,尺有所短寸有所长,执行高效的 HJ 和 SMJ 只能用于等值关联,也就是说关联条件必须是等式,像 salaries(“id”) < employees(“id”) 这样的关联条件,HJ 和 SMJ 是无能为力的

      非等值的连表关联还得使用 NLJ

    1. 考虑到 SMJ 对于排序的苛刻要求,后来又有人推出了 HJ 算法。HJ 的设计初衷是以空间换时间,力图将基表扫描的计算复杂度降低至 O(1)。

      使用 Hash算法做关联似乎在大数据领域比较好用

    1. 我们知道,使用 Java Object 来存储数据会引入大量额外的存储开销。为此,Tungsten 设计并实现了一种叫做 Unsafe Row 的二进制数据结构。Unsafe Row 本质上是字节数组,它以极其紧凑的格式来存储 DataFrame 的每一条数据记录,大幅削减存储开销,从而提升数据的存储与访问效率。

      使用 Java对象会存在大量的存储开销,因此使用本地内存加 byte数组进行存储

    2. 而使用 DataFrame API 开发的应用,则会先过一遍 Spark SQL,由 Spark SQL 优化过后再交由 Spark Core 去做执行

      DataFrame 使用的 Spark SQL 进行解析执行,因此其也存在逻辑计划、物理计划

    1. 数据块的概念与 RDD 数据分区(Partitions)是一致的

      通过blockId能够获取需要的数据,无论在内存还是磁盘中

    2. LinkedHashMap[BlockId, MemoryEntry]。

      spark内存数据存储结构

    3. Spark 存储系统负责维护所有暂存在内存与磁盘中的数据,这些数据包括 Shuffle 中间文件、RDD Cache 以及广播变量。

      spark存储服务的对象

    1. 广播变量与累加器的区别在于一个是共享读取,一个是共享写入

    2. Executor是进行干活的工人,而task是spark计算的最小粒度,工人不断地执行task来完成每一阶段的task然后转向下一阶段,直到所有的阶段执行完毕

    3. 如果使用广播变量,那么数据只会在driver和executor上都存一份,task在executor上执行,如果使用到广播变量则直接从executor上获取即可

    4. 如果在任务执行的过程中引用了外部的变量,那么driver需要将变量传输给每一个相关的task,因此传输开销很大

    1. coalesce 会降低同一个 stage 计算的并行度,导致 cpu 利用率不高,任务执行时间变长。我们目前有一个实现是需要将最终的结果写成单个 avro 文件,前面的转换过程可能是各种各样的,我们在最后阶段加上 repartition(1).write().format('avro').mode('overwrite').save('path')。最近发现有时前面的转换过程中有排序时,使用 repartition(1) 有时写得单文件顺序不对,使用 coalesce(1) 顺序是对的,但 coalesce(1) 有性能问题。目前想到可以 collect 到 d
    2. collect 算子有两处性能隐患,一个是拉取数据过程中引入的网络开销,另一个 Driver 的 OOM(内存溢出,Out of Memory)

      收集数据会导致Driver的内存占用

    3. 其中 spark.executor.memory 是绝对值,它指定了 Executor 进程的 JVM Heap 总大小。另外两个配置项,spark.memory.fraction 和 spark.memory.storageFraction 都是比例值,它们指定了划定不同区域的空间占比。spark.memory.fraction 用于标记 Spark 处理分布式数据集的内存总大小,这部分内存包括 Execution Memory 和 Storage Memory 两部分,也就是图中绿色的矩形区域。(M – 300)* (1 – mf)刚好就是 User Memory 的区域大小,也就是图中蓝色区域的部分。spark.memory.storageFraction 则用来进一步区分 Execution Memory 和 Storage Memory 的初始大小。我们之前说过,Reserved Memory 固定为 300MB。(M – 300)* mf * sf 是 Storage Memory 的初始大小,相应地,(M – 300)* mf * (1 – sf)就是 Execution Memory 的初始大小。

      内存划分配置,Reserved Memory为300M,总内存量为M,剩余内存M - 300,然后划分为User Memory(1 - mf)和共享内存[Execution Memory+ Storage Memory(mf * sf)]

    4. 当一个 RDD 在代码中的引用次数大于 1 时,你可以考虑通过给 RDD 加 Cache 来提升作业性能

      Cache并不是自动的,而是人手动添加的,将RDD物化到内存中

    5. 对于 Storage Memory 抢占的 Execution Memory 部分,当分布式任务有计算需要时,Storage Memory 必须立即归还抢占的内存,涉及的缓存数据要么落盘、要么清除;对于 Execution Memory 抢占的 Storage Memory 部分,即便 Storage Memory 有收回内存的需要,也必须要等到分布式任务执行完毕才能释放。

      Execution Memory在共享内存中的优先级更高,如果Storage Memory抢占了EM的内存且EM的内存需要则必须归还

    6. 在 1.6 版本之后,Spark 推出了统一内存管理模式,在这种模式下,Execution Memory 和 Storage Memory 之间可以相互转化

      内存共享

    7. sortByKey:排序

      排序算子与其他聚合算子不同https://blog.csdn.net/raintungli/article/details/73663733

    8. aggregateByKey

      在Map端聚合后,在Reduce端基于Map端的聚合结果再进行聚合,从而实现在Map端减少传输的数据量,而在Reduce端又能实现目的

    9. 一个 Map 端聚合函数 f1,以及一个 Reduce 端聚合函数 f2

      指定Map、Reduce端的聚合函数

    10. 从图中你可以看出来,尽管 reduceByKey 也会引入 Shuffle,但相比 groupByKey 以全量原始数据记录的方式消耗磁盘与网络,reduceByKey 在落盘与分发之前,会先在 Shuffle 的 Map 阶段做初步的聚合计算。比如,在数据分区 0 的处理中,在 Map 阶段,reduceByKey 把 Key 同为 Streaming 的两条数据记录聚合为一条,聚合逻辑就是由函数 f 定义的、取两者之间 Value 较大的数据记录,这个过程我们称之为“Map 端聚合”。相应地,数据经由网络分发之后,在 Reduce 阶段完成的计算,我们称之为“Reduce 端聚合”。

      Map端聚合能够减少数据的分发数量(原本需要传输原数据,而聚合后则可以将多条数据合并为聚合数据)

    11. 对于所有 Map Task 生成的中间文件,Reduce Task 需要通过网络从不同节点的硬盘中下载并拉取属于自己的数据内容。不同的 Reduce Task 正是根据 index 文件中的起始索引来确定哪些数据内容是“属于自己的”。Reduce 阶段不同于 Reduce Task 拉取数据的过程,往往也被叫做 Shuffle Read。

      shuffle过程并不是Map阶段的节点将数据发送给Reduce阶段的节点,而是Reduce节点主动去Map阶段的节点上拉取数据,根据index文件来获取自己需要的数据

    12. (Reduce Task Partition ID,Record Key)

      基于这个逐渐可以将相同分区相同key的数据进行合并

    13. 当 Map 结构被灌满之后,Spark 根据主键对 Map 中的数据记录做排序,然后把所有内容溢出到磁盘中的临时文件

      基于溢出机制,在受限内存中处理大量数据,将处理过程中的部分结果进行排序写入临时文件,最终将所有文件进行归并排序得到任务最终的data文件和index文件

    14. shuffle过程不需要直接进行排序,而只需要基于key进行hash分区然后分发。而分发完成后,在进行排序聚合是合理的

    15. Map 阶段与 Reduce 阶段的计算过程相对清晰明了,二者都是利用 reduce 运算完成局部聚合与全局聚合。在 reduceByKey 的计算过程中,Shuffle 才是关键。

      在Shuffle的时候,Map阶段可以进行局部聚合,而局部聚合后可以确定第一轮数据分发的节点,基于hash或者什么算法进行分区后,再一次进行全局聚合,从而将相同key的数据进行聚合

    16. 在不同的工地上有不同类型的砖块,需要将相同类型的砖块分发给对应的节点,因此需要砖头在集群范围内跨节点、跨进程的数据分发

      数据在不同节点上处理之后,需要基于key进行分发

    17. SchedulerBackend 与集群内所有 Executors 中的 ExecutorBackend 保持周期性通信,双方通过 LaunchedExecutor、RemoveExecutor、StatusUpdate 等消息来互通有无、变更可用计算资源

      SchedulerBackend通过Executor上的agent ExecutorBackend获取机器上的计算资源信息.ExecutorBackend还负责执行代码

    18. 像上面这种定向到计算节点粒度的本地性倾向,Spark 中的术语叫做 NODE_LOCAL。除了定向到节点,Task 还可以定向到进程(Executor)、机架、任意地址,它们对应的术语分别是 PROCESS_LOCAL、RACK_LOCAL 和 ANY。

      本地倾向性,包括机架感知、节点感知、进程感知,进程感知最高

    19. 以 Actions 算子为起点,从后向前回溯 DAG,以 Shuffle 操作为边界去划分 Stages

      构建DAG时,会将shuffle之间的一系列RDD操作划分为一个TaskSets,他们之间是有依赖关系的,但是并不需要进行shuffle

    20. DAGScheduler 是任务调度的发起者,DAGScheduler 以 TaskSet 为粒度,向 TaskScheduler 提交任务调度请求

      TaskScheduler对任务进行调度和分配资源,资源信息WorkOffer来源于SchedulerBackend,任务信息TaskSets来源于TaskScheduler

    1. 这里有一种特殊的生命周期值得讨论:'static,其生命周期能够存活于整个程序期间。

      static变量也是

    1. 现在换一种方式思考这个关系,父节点应该拥有其子节点:如果父节点被丢弃了,其子节点也应该被丢弃。然而子节点不应该拥有其父节点:如果丢弃子节点,其父节点应该依然存在。这正是弱引用的例子!

      如果父节点没人引用了,而子节点还有人引用,这时候可能父节点就会直接销毁掉,看起来挺奇怪的

    1. 结合 Rc<T> 和 RefCell<T> 来拥有多个可变数据所有者

      一个地方修改每个引用可见

    2. 在任意给定时刻,只能拥有一个可变引用或任意数量的不可变引用 之一(而不是两者)。
      • 要么一个可变引用
      • 要么0个可变引用 + 1或多个不可变引用
    1. 那Box是如何销毁掉堆上的对象的呢?

    2. 所以 Message 值需要的最大空间是存储其最大成员所需的空间大小。

      对于枚举其实和union类似,取需要最大空间的一个类型的空间即可

    3. 使用 Box<T> 指向堆上的数据

      Box感觉可以理解为Reference

    4. 当希望拥有一个值并只关心它的类型是否实现了特定 trait 而不是其具体类型的时候

      支持上转型?

    5. 当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候

      转移所有权并不会被拷贝,拷贝也不会转移所有权,所以这里是一大堆的数据相互关联,然后通过转移或者拷贝栈上的引用来转移所有权?

    6. 当有一个在编译时未知大小的类型,而又想要在需要确切大小的上下文中使用这个类型值的时候

      由于栈上的数据是需要知道确切大小的,因此如果大小不确定则只能放在堆上,而栈上使用引用

    1. 版本向量

      看不懂

    2. r 和 w 都常都选择超过半数,如 (n+1)/2

      由于r和w都超过半数,因此是能够保证写入和读取是最新的

    3. 由于 w + r > n 时,总会至少有一个节点(读写子集至少有一个节点的交集)

      因为读写的节点比总结点多,读的节点中一定会存在上一次写的节点,但是上一次写的节点能保证写入时这些节点相对那些不写的节点更新吗

    4. 两个有因果依赖的(先插入,后更新)的语句,在复制到 Leader 2 时,由于速度不同,导致其接收到的数据违反了因果一致性。

      由于支持多点写入,那么多个操作之间如果有依赖关系,但是后面的操作写入时写入的还未同步的节点,那么就会出问题

    5. 多主模型应用场景

      实际上应该会在业务上进行妥协,对不同数据中心的用户或者数据进行隔离。或者直接使用一个数据中心也不是不可以,比如国内访问油管发送评论,那应该是在同一个数据中心进行的操作。如果是离线业务,那么就更不需要多主写入了

    6. 让所有有因果关系的事件路由到一个分区。

      分区策略,如基于hash分区或者前缀分区

    7. 写后读保证的是写后读顺序,单调读保证的是多次读之间的顺序。

      由于不同副本的同步进度不同,因此多次读取如果是不同节点,则可能出现不同的结果

    8. 客户端记下本客户端上次改动时的时间戳,在读从副本时,利用此时间戳来看某个从副本是否已经同步了改时间戳之前内容

      记录客户端的上一次修改,从而保证从已经同步该修改的节点上获取数据

    9. 字段标号不能修改,只能追加。这样旧代码在看到不认识的标号时,省略即可。

      向前兼容

    10. 在更改模式时(比如 alter table),数据库不允许增加既没有默认值、也不允许为空的列

      添加列时向后兼容

    11. Avro

      Avro相对Protobuf和Thrift来讲没有使用字段标号(原本是为了标识数据属于哪个字段),也没有显示指定类型,其会显式写入write schema版本,同时解析时使用read schema读取获取数据类型等信息

    12. 向后兼容:新加的字段需为 optional。这样在解析旧数据时,才不会出现字段缺失的情况。

      数据兼容

  4. Nov 2024
    1. 列式存储的写入

      所以列式存储的底层是使用什么数据结构?看起来像是顺序存储的样子

    2. 不同副本,不同排序

      好主意,不过似乎对于复杂查询实际上帮助不大,毕竟只能按三个列的顺序存储

    3. 不可能同时对多列进行排序。因为我们需要维护多列间的下标间的对应关系,才可能按行取数据。

      这是很疑惑的地方,每一列分开存储,应该可以分开排序才对,同时存储对应的行索引即可,而主键列也同样如此。这样每个列都进行顺序存储,并且行索引顺序且与列值位置映射,是否能实现这样的功能呢?只是说会额外浪费一定的空间,同时对于多列的排序无法实现

    4. 聚集索引(clustered index)

      感觉聚簇索引相对来讲在读取多个数据节点时顺序读效率更高

    5. 性能优化

      LSM-Tree进行范围查询性能很差,需要遍历所有的SSTable

    6. 图模型和网络模型

      网状模型相对图模型有着更严格的约束,而图模型则更宽松可扩展,不过看起来图的查询效率依然很低

    7. 无论是 BFS、DFS 还是剪枝等实现细节

      图的查询一般基于图的遍历操作,只是基于相关元数据进行了剪枝加速

    8. 数据类型和结构由外部决定,你没办法控制数据的变化。

      既然无法确定数据类型,那么能对数据做的操作其实也有限,只能在应用能够通过配置操作、计算指定列数据时能够很好的利用。否则只能存储。

    9. 关系模型

      相对网状模型进一步抽象,使得访问方式统一,同时基于用户需求统一生成执行计划进行数据访问

    10. 元组(tuples)

      奇特的视角

    11. 在每一层,通过对外暴露简洁的数据模型,我们隔离和分解了现实世界的复杂度。

      通过分层简化问题

    12. 弱 Schema(读时解析)

      非关系型数据库比如hive使用

    13. 强 Schema(写时约束)

      一般关系型数据库使用