diff --git a/about/index.html b/about/index.html new file mode 100644 index 00000000..386340d0 --- /dev/null +++ b/about/index.html @@ -0,0 +1,151 @@ + + + + + + +关于 - staticor in data + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+ +
+
+
+
+ + + + + + + + + +
+
+ + diff --git a/api-content/index.html b/api-content/index.html new file mode 100644 index 00000000..1b3b5697 --- /dev/null +++ b/api-content/index.html @@ -0,0 +1 @@ +{"posts":[{"title":"Onedata-3, 节省数据成本——到数据资产","content":"数据中心是成本中心,要在有限资源内,最大化的完成精细化运营的决策支撑任务,所以会尽可能地完成业务方的合理需求; 与此同时,也会受到整体技术产研的“资源帽”的要求,要不断的节省成本。 成本分为人员成本和集群成本,人员成本可估算为60-80万(人民币一年); 集群成本可按一个<core+Memory>对来描述,和公司采买机器的偏好有关(memory: core 越高说明是内存计算型的任务更高)一般这个比例是在4:1 到2:1之间。 成本花在什么地方了? 在建设数据中台的过程中,数据仓库的建设、日常数据挖掘与分析、数据产品开发,都是由数据团队的人+机器完成的。 在开心过程中,要小心一些成本陷阱。 提防成本陷阱 能够监测到每个报表的使用情况 制定数据下线规则 其实,以我的过往经验来看,数据团队产生的数据报表有一多半是30天内无人查看的,三成以上是近90天无人查看的。 这个原因也很好理解,核心报表(与业务方的日报、周报、月报有关)是业务方每天关注的,但更多的生产报表有它的历史任务,过了那段业务高峰,业务方就会转移到新注意力; 随着时间的积累,就导致这些历史报表的地位变得很难受。 衡量一张表的投入产出比 还是那句话,对于成本优化来说,如果不能给出量化的策略,就难以优化。 在数据仓库任务中,每张表的加工成本可以根据集群使用情况用金额评定; 但难点是一张表的收益如何量化呢? 烟囱式开发 即数据重复加工,团队里每个人各自开发了若干个数据烟囱,没有复用,造成资源的巨大浪费。 数据倾斜 数据开发优化,应在开发流程上加以优化,避免不合理任务的上线。 数据存储 对数据管理来说,应对每张表设置存储策略,引入定期删除机制,降低存储。 同时还应对远期表引入冷热表分离和压缩机制,进一步降低存储成本。 调度周期 \b(图片来自 极客时间-《数据中台实战课》) 在我之前接触的团队,数据调度也是类似的,从凌晨开始,持续到上午的6点或8点,完成当天ETL任务, 在白天则基本上服务于一些ad-hoc即席查询,集群的使用率不到四分之一。 因为一般情况,业务方会默认的以为每天上班后看到的数据就是当天准确的,所以很难再和业务同事商量下午2点再看数据,这不现实。 精细化成本管理 —— 核心工作在于数据资产门录的建设。 早期数据仓库是开发开发,上线上线,修bug维护等一系列工作下来,在这个阶段是没有成本意识的,更不会量化一张数仓表到底投入了多少总成本。 \b(图片来自 极客时间-《数据中台实战课》) 一张数仓报表的财务分析示例, 依赖成本:在这个图中 表A 依赖上游的六个表、三个开发任务 = 开发任务各自的执行成本 ,存储成本是6个报表的存储成本。 开发成本:自身生产任务的执行成本,按CU占用时间评估执行成本; 存储成本,按该表存储占用一年来估算一年的成本; 数据价值如何体现? 再有价值的数据,如果只有开发人员关注,而无业务人员问津,也是不对的。 末端数据应用分类: 应用层表(对接展示类或者特定业务场景应用),使用范围(谁在使用)+使用频率(使用强度); 轻度汇总或集市层(用于探索分析),使用范围(谁在使用)+使用频率(使用强度); 一开始,并不一定需要对每个报表,每个数仓中间层表都准确维护。 但数据资产盘点工作应至少把数据的使用分为三类: 第一类:持续占用成本资源,无人过问。 第二类,ROI不匹配的数据报表,高投入低产出 第三类,高成本数据,以一个参考线往上 前两类对应的是下线报表策略。 数据下线策略 这里要注意的是,下线策略并不是突然间将报表从任务调度停止,删除历史数据,这样的一刀切太过危险。 应使用温和的下线策略: 在数据地图上标识这个表的状态为下线观察期(7-30天),如果有用户反应还会使用,则根据使用情况进行调整 (放弃下线,或者引导用户使用其他表); 观察期过后,更新状态为已下线——停止生产,新数据不再写入; 如果集群分为主集群和备用集群,则将数据移到冷备集群,一般来说冷备集群不负责数据密集型计算,主要以备份和数据存储为主。 再经历过一段时间,还无人反馈,则数据删除,更新数据字典状态。 数据存储策略 数据存储周期设置 永久级 非永久级, 短期, 只保留最近7天分区 中期, 保留最近30天分区 长期,保留最近180天, 一年分区 衡量数据成本治理的方式 开发维度同样的任务,是否可以节省更多的机器资源。 ","link":"https://staticor.github.io/post/33xsTBvWN/"},{"title":"Onedata-2 数据出错了","content":"突然发现仪表盘中的数据为0 ... ... ? 图片来自(极客时间-数据中台实战课) 由结果表顺着向上游排查,查了30分钟之后发现是某个dwd表的解析出了问题,部分脏数据影响了下游,导致没报错,但其实数据写入失败。 一套盘查下来用了半天,业务方也对数据部门感到呵呵。 这是个经典问题,暴露了数据质量不完善: 业务方发现时间早于数据部门,客户满意度直线下降; 出现问题后,数据部门无法快速定位,排查时间过长; 故障没有对应的报警; 常见问题根源: 业务源系统变更 数据开发BUG (大多数) 物理资源不足,任务失败 运维故障,基础设施 Hadoop Bug 提升质量的方法:提前发现问题 稽核校验任务 根据出表结果,按业务规则,设计校验逻辑,确保数据完整性、一致性和准确性; 规则: 数据有无, 数据值落在合理区间; 规则未通过,承接报警策略 规则设计 规则的分类 完整性, 有无数据 一致性, 两个表当中 A表的a1 a2指标 可计算出B表的b1 b2 即跨表间存在的公式, 一致性依赖。 准确性,数据记录的正确性,如商品类目的格式,IP格式 唯一性,不应产生重复数据的表,出现了重复行。 及时性,数据生产的最晚接受时间 DDL。 规则分级 强规则,弱规则取决于业务上对该表出问题的容忍度。 比如和公司金钱有关的、核心指标有关的,都得设置为强规则(电话+短信+IM+ 邮件,能提醒的都用上) 反之采用弱规则。 如何衡量数据质量 凡事都得量化,才能做到优化。 怎样衡量数据仓库的数据质量,可以采用 🟥 6点30分钟核心报表生产率 (这里6点30分钟是一个虚时间,也可以定为5点30分,取决于业务) 🟦 稽核规则命中率, 不同规则对应的表负责人处理的报警次数,反向指标。 🟩 数据产品SLA, 反向指标。 如果指标超过9点还没产出,示为不可用。 每天之中将可用数据的占比计算出来,取补即为当天可用指标, 如99.9% 数据质量中心(Data Quality Center) 即为这样的定义稽核规则、控制报警、执行任务明细、指标打分和评级 DQC首页是质量大屏,展示当前任务的核心质量指标。 (上面的🟥🟦🟩 三级指标) 一些典型的数据故障 协作事故 跨部门, 服务端修改了上游数据(换库改表) 同部门,集群内调整了运行参数 ","link":"https://staticor.github.io/post/onedata-2-shu-ju-chu-cuo-liao/"},{"title":"BookNote-大数据之路(Alibaba-OneData) Part2","content":"维度设计 measure = 事实 context = 维度 没有测量事实,就只有定性概念和分类。 维度是用来分析上下文的,只有通过维度,才能发现——数据有没有变化,变化的背后有没有问题。 维度和属性在哪里发现? 如果是刚来的数据人员,通过观察已有数据产品(报表)、与业务人员沟通中获取业务关系的context。 因为一般他们是数据的使用者,最为了解需要关注何样的分类分组数据。 维度基本设计 维度设计过程就是在确定维度属性的过程, Kimball理论中提过,数据仓库的能力直接到维度属性的质量和深度成正比。 以维度建模为核心,要保证维度的唯一性。 先从主维表开始,以之为根。 一般是ODS表,直接来自业务数据库的同步对应表。 确定相关维度,数据仓库是多个数据源的整体系统,不同业务系统或者同一业务系统之间的维表可能存在关联性。 根据对业务梳理,要确定主维表的延伸。 例如商品名称、商品ID是主维表,而商品对应的分类,所属店铺,就是关联维度。 留意数值型维度 多数情况下,维度是分类字段(categorical variable),但也会出现数值型。 比如商品价格,可以用来作为求平均、最大时以测量事实; 比如按价格区间: 0-99, 100-999,1000元以上执行分组的商品销售统计,这又是描述维度。 所以切勿将维度和事实表和数值与分类进行强硬划分。 维度沉淀 原始信息有些情况下能直接作为维度来使用,如 xxx.status 这个字段取0和1分别表示用户的两种状态。 但有一类场景是由原始字段加工而来的,如在线商品状态是指商品上架状态与发布时间限定在某个值以后。 为避免下游再次处理,或者多人使用造成口径的不一致,需要额外地将这个维度沉淀出来,对外提供这个信息。 维度层次结构 某些场景下,维度是一个多级结构或者是主从关系的。 比如商品的分类信息作为维度,一级分类、二级分类、三级分类... 这本身就是一个多级定义。 根据这个结构,可以沿着层次向下钻取。 核心维度表 商品、卖家、买家、类目等核心描述,应有且只有一个维度表,基于这些公共维度的交叉查询不会产生二义性。 这类维度表也称为共享维表。 ","link":"https://staticor.github.io/post/booknote-da-shu-ju-zhi-lu-alibaba-onedata-part2/"},{"title":"OneData与OneService 初识","content":"OneData OneService,统一数据管理,统一数据服务。 大型公司的数据部门最终发展方向是面向公司的数据使用者提供一个稳定可靠的数据平台,真正发挥数据的价值,而不单是成本。 数据资产的意义是沉淀+挖掘,可以形成有价值的业务决策。 成本是消耗性质的,难以复用和被挖掘的。 几万张表最基本的分类是按业务域和主题域进行逻辑上的划分。 例如表名: dwd.wms_inbound_order_info_di dwd是数据明细层,物理分层标识; wsm是仓储主题域的缩写; inbound表示产品入库业务过程; order_info是事实表名称; di是存量增量后缀符,表示按日增量表 数据架构 存储 + 集成 + 计算 + 服务 + 治理 以Hadoop为代表性的大数据集群,Hadoop(包含HBase)是负责数据的存储工作。 Hadoop之上是和外部系统的数据管道对接服务(集成模块):Flume,Sqoop,Datax,Kafka。 另外便是计算引擎层Hive,Spark,Flink,这些跑在Hadoop之上的数据作业,一般是以一棵棵任务调度树的形式运转,用web-ui的方式组织(Airflow,Azkaban) 数据服务层是将生产加工出的数据(离线或实时)投递到对应的展现层,包装成数据产品。 数据治理,则是围绕着元数据中心进行数据地图、血缘关系、数据字典、指标系统、权限管理进行。 我认为企业的数据仓库组织架构发展存在这几个阶段: 一草根时期,无名义上的数据仓库工作者,由运营或分析师处理公司的必要数据需求;此时基本上没有Hadoop集群; 二小型大数据部门(10人),分配3-5人进行数据仓库工作,3-5人进行数据服务与平台开发工作。 此时多是以高效完成业务数据需求为主,对数据质量和治理花的功夫不多。 三多点开花,随着业务扩张或公司规模发展,开始划分出多个业务自治的数据仓库。 多个分散小数仓各自为战,灵活效率支撑业务需求。 四分久必合。成立数据中台项目,形成统一的公共数据仓库层。自然,这个过程也就说明公司技术部门在数据上的资源是加大投入的,要有专职的数据产品部门、数据开发团队和平台架构部门的配合,才得以完成。 metadata 公司元数据应包含哪些内容? 数据字典 [data dictionary] 数据结构体为: 库名-表名,表中各字段名、数据类型和备注 血缘关系 [data lineage] 血缘关系是指表的加工依赖,一般是可以看到一张倒树图: 其它,数据特征 目前业内有影响力的元数据产品,Netflix-Metacat,Apache Atlas,Cloudera Navigator. 元数据管理的组成部门: 数据源管理: MySQL,MongoDB,Hive,Kudu ... 血缘解析:解析SQL,提出目标表和依赖的输入表。 很久以前,我自己也实现过简单的Python parser脚本(正则解析),完成一个SQL脚本的表名解析。 这也是一种最简单的解析SQL方式:静态解析。 除此之外还可以实时抓取执行SQL的计划,获取输入和输出。 以及第三种,根据任务日志再解析。 数据血缘是数据生命周期管理的前提,便于实现数据级别和数据安全(访问权限控制)。 Netflix的元数据管理实践 阅读了一篇Medium的技术文章(2018年) Metacat: Making Big Data Discoverable and Meaningful at Netflix 早期Netflix是使用Hive+Pig进行数据ETL,数据源涉及Amazon S3,Druid,ES,Redshift,Snowflake和MySQL。 Metacat是通过一套统一的REST/Thrift接口,访问各种不同的数据源。 The respective metadata stores are still the source of truth for schema metadata, so Metacat does not materialize it in its storage. It only directly stores the business and user-defined metadata about the datasets. It also publishes all of the information about the datasets to Elasticsearch for full-text search and discovery. 可见Metacast直接抽取上游的元数据,不再单独的实现。同时提供ES的检索功能。 以Hive元数据为例,Hive的元数据托管在RDBMS(MySQL),有可能成为Hive服务的瓶颈。 所以额外的数据读取服务需要重新设计,以避免对Hive作业造成影响。 (例如对元数据读写造成的锁冲突问题) OneService 数据服务是面向数据需求。 满足数据需求的方式由数据产品和业务方与开发人员协商,最终确定实现。 在这个过程中离不开数据的开发和服务的稳定。 数据研发流程要考虑的角色和阶段: ","link":"https://staticor.github.io/post/onedata/"},{"title":"Booknote,《Star Schema -The Complete Reference》","content":"Dimensional Design 维度设计 维度模型由两部分: measurements and context, 即 测量指标和上下文。 在数据仓库模型里对应的是事实表和维度表,如果引入关系数据库,那么此时的维度模型就是星型模型。 维度设计是服务于分析型系统,而非事务型系统—— 前者是面向业务过程的评估,后者是保证服务的顺利执行。 ( evaluation vs execution ) OLTP system Transaction(事务)要提供的接口: 增加,删除,修改,查询,而且应保证操作的原子性。 在OLTP系统,数据库表的设计要遵循一定的设计规范,比较著名的就是3NF(第三范式)。 这种规范也被称为 ER模型。 OLAP(Analytic System) OLAP在设计和定位、实现上,和OLTP都有着较大的差异: Measurement and Context 一月 各类商品的毛利是多少? 按教育程度分类,每类用户的账户余额平均值是多少? 供货商的退货率是多少? 这一系列问题与销售过程、账户管理、退款服务产生的存量数据有关。为了回答这些问题,要查询的不再是一条数据,而是一批次+多个表的数据的聚合查询结果。 对Measurement必须是结合context的, 比如当有人说销售额是1000时,这句话的信息量极少。1000到底是一个商品还是一类商品,时间描述也不清楚。 对范围的限定,即为context,上下文。 Fact and Dimensions 在维度设计中,测量对应的是事实表,上下文对应的是维度表。 要留意的是,并不是所有数值都是隶属于事实的测量。 比如说用户的年龄,可以作为维度。 事实和一个数值指标对应,往往“指标”是可以拆解或聚合的。 事实表与维度举例(销售过程)星形样例 商品维度:商品,SKU编码,商品描述,品牌与品牌编码,经理,分类与分类编码; 销售员维度:销售员,区域编码和区域名称 顾客维度:顾客ID,账单地址 时间日期:下单日期,下单月份,季,财务周期,订单年 Star Schema 星形模式 事实表为中心,向外放射(☢️)解释关联到每个方面的维度: 事实表的查询 数据仓库架构 😶 Data Warehouse Architectures 这一章介绍EDW(enterprise data warehouse)。 作者提示,不要生搬硬套文中提到的架构模块。 Inmon's Corporate Information Factory 🏭 Bill Inmon是数据仓库社区中多产的贡献者(prolific writer and contributor to the data warehousing community), 代表性著作(Corporate Information Factory, 企业信息工厂) 这个架构下, 分析型应用(包括BI工具)不直接查询。 EDW通常存储在关系数据库管理系统,Inmon提供使用3NF范式。 Kimball,维度数据仓库 Kimball的成就 1990年,Kimball将之前的个人思想加以提炼,提出了star schema设计。 对数据仓库Kimball给出了一套维度设计的方法论 —— 数据总线架构(bus architecture) Kimball 维度模型和前文提到EDW最大的区别是什么? 设计是遵循维度建模,而非范式的关系建模,由一系列星型表或cubes组成。 二者,维度数仓可以直接被分析系统查询,data mart (是数据仓库中的主题区域)。 ","link":"https://staticor.github.io/post/booknotelesslessstar-schema-the-complete-referencegreatergreater/"},{"title":"BookNote-大数据之路(Alibaba-OneData) Part1","content":" 成书于2017年,有些技术或许过时或者描述不再准确了,要分析地阅读。 结合我的需要,我先拆解数据模型篇和数据管理篇,然后再来看数据技术篇。 为什么,数据技术每5年一小变10年一大跳,但对于数据模型和元数据管理的理念,却还是常青。 数据模型篇 数据模型(Data Model)就是数据组织和存储方法,强调存储和使用角度合理存储数据。 良好数据模型的好处有几个方面:减少数据IO吞吐查询性能高; 减少不必要数据存储,实现结果复用;改善用户使用数据体验,提高数据使用的效率;改善数据的统计口径,提升数据质量。 关系型数据库 vs. 数据仓库 这里就不赘述了,在我的博客-对比学习(一) 第一对概念中就想到了这组。 OLAP vs. OLTP vs. HTAP OLTP即为事务处理系统,数据库建设遵循基于范式的实体建模,在操作上主要是数据的随机读写。 OLAP面向的则是批量读写,关注的是数据整合,以及复杂大数据查询和计算的性能。 建模方法论 ER 模型 vs. 维度建模 ER模型的操作步骤: 高层模型,抽象模型,描述主要主题和主题间关系; 例如 充值 - 消费 - 发货 - 学习服务 - 退款 中层模型,在高层基础上,细分主题的数据项 —— 消费细分为订单,子订单,商品, 用户 ... ... 物理模型(底层模型),在中层模型基础上,考虑物理存储,基于性能和平台特点进行物理属性设计,也可能做一些合表拆表、分区设计。 Kimball维度建模 这位大师在早些年提出了 是数据仓库工程师的必读之作了。 在目前的多数公司中,都采用了(过)维度建模。 维度建模是从分析决策的需求出发,重点关注用户如何能快速完成需求分析。 维度建模步骤: 业务过程分析,例如前面提到的交易支付过程 选择粒度,这里要非常非常小心,维度和粒度的区别,在事件分析中,要能预见到分析可能用到的程度,从而决定选择的粒度。 (粒度是维度的一种组合) 识别维表,确定粒度后,就需要基于此粒度设计维表,包括维度属性。 选择事实表,确定分析需要的衡量指标。 其它模型 Data Vault模型,了解不多。 Anchor模型 对Data Vault模型做了进一步的范式处理,将模型规范到6NF。 阿里巴巴数据建模实践 阶段一 Oracle(ODS层)+DSS层 基本没有系统化的建模体系。 阶段二 引入MPP(Greenplum) 用ER模型+维度模型,四层架构。 ODL(操作数据层)+BDL(基础数据层)+IDL(接口数据层)+ADL(应用数据层) 第一层 ODL 贴源层,和源系统保持一致; 第二层 BDL,引入ER模型 第三层 IDL,基于维度模型构建集市层 第四层,完成应用的个性化和基于展现需要的数据组装 经验: 在快速变化的业务阶段,构建ER模型风险大 阶段三:Hadoop时代,以Kimball维度建模为核心,加以升级和扩展 数据公共层建设,解决数据存储和计算的共享问题。 “OneData” , 需要长期的数据整体设计。 OneData 数据整合方法论 定位价值: 建设统一规范的数据接入层(ODS)和数据中间层(DWD和DWS),通过数据服务和数据产品,完成公司的大数据系统建设,即数据公共层建设。 (注,这里不会涉及到主题和个性化业务) 能提供标准化的,可共享的数据服务能力,降低数据互通成本。 😌 解释: 数据域:面向业务分析,将业务过程或者维度进行担负的集合。 数据域要长期维度和更新,但变动频率不高。 业务过程,指不可拆分的行为事件,在之下可以定义指标,比如销售流水额。 时间周期,明确统计时间范围或时间点的时间描述,如最近xx天,自然周,自然月,截止昨日,截止当日。 修饰类型,对修饰词的抽象划分。 修饰类型从属于业务域,如日志的访问终端类型。 修饰词,修饰词隶属于一种修饰类型,在「日志访问终端类型」下,分为修饰词PC端,无线端。 度量/原子指标,原子指标即为度量,基于业务事件行为下的度量,是业务定义中不可再拆分的指标,比如销售流水额。 维度是指度量的环境,如下单事件,用户买家是维度,商家平台也是维度。 维度属于一个数据域,如地理维度,时间维度。 维度属性是隶属于一个维度,比如地理维度中的国家,省份和城市。时间维度中的年季月周日。 派生指标,派生指标是数据产品中真正被使用的指标,它由原子指标+若干个修饰词(可没有)+时间周期。 比如原子指标-销售流水额,对业务来说要看的是xxx业务线最近30天新注册用户发生的流水额 销售流水额,原子指标 最近30天是时间周期 新注册是修饰词 用户买家是维度 如果有一棵指标树,原子指标是所有指标树的 派生指标可以选择一个或多个修饰词,也可以不指定修饰词。 例如时间修饰+业务粒度修饰+用户类型修饰——4月虚拟课程业务线首次付费用户数。 修饰词如果指定多个,它们之间的关系还可以进行逻辑运算:且运算、或运算。 派生指标的种类 事务型指标:对业务活动进行衡量的指标,新增注册用户数,订单支付金额,在此基础上创建派生指标。 存量型指标:对实体对象某些状态的统计,商品总数,注册用户总数,对应时间周期一般为“历史截止某时间”。 复合型指标:事务型指标和存量指标基础上复合计算出来的指标,例如浏览商品页面UV-下单用户数转化率 复合指标的规则: 比率型:创建原子指标,如CTR 比例型:创建原子指标,如最近7天某业务线支付金额占比 变化量型:不创建原子指标,增加修饰词。 变化率型:创建原子指标,例如最近7天新注册用户支付金额7天周比变化率,原子指标是支付金额变化率,修饰类型是买家类型,修饰词是新注册用户 统计型:不创建原子指标,增加修饰词,如均值,分位数,人均,日均,行业平均 排名型:创建原子指标, 指标命名规范 每个公司的数据团队都应有规范的数据库、数据表、字段的命名规范。 这也是数据治理和质量的一部分,切忌百花齐放。 时间周期修饰词 最近x天: 用 xd 来描述,如 最近1天 - 1d,最近3天 - 3d 7的倍数,最近7天:1w,最近14天 - 2w 30的倍数,最近30天: 1m,最近60天-2m,最近90天-3m 自然周: cw 自然月: cm 自然季: cq 年初至当日:td 零点截止当前:tt 财年:fy (financial year) 准实时: ts 未来7天:f1w 未来4周: f4w 模型设计 阿里巴巴数据公共层设计理念遵循维度建模思想,参考 Star Schema-The Complete Reference 和 The Data Warehouse Toolkit-The Definitive Guide to Dimensional Modeling. 数据模型的维度设计主要以维度建模理论为基础,基于维度数据模型总线架构,构建一致性的维度和事实。 模型层次: 操作数据层,ODS 上游数据几乎无处理的存放在数据仓库,完成以下数据任务: 数据同步,增量和全量同步到集群; 结构化,非结构化(一般为日志)结构化处理和解析存储; 累积历史和清洗,按业务需求和审计要求进行历史数据的归档和清洗。 公共维度模型层,CDM,存放明细事实数据、维度数据及公共指标汇总数据 设计思路1:维度退化,将维度表退化至事实表,减少重复关联,提高明细数据表的易用性 在汇总数据层,加强指标的维度退化,采取更多的宽表手段构建CDM 应用数据层,ADS,存放数据产品个性化的统计指标数据,由ODS和CDM层加工而成 个性指标:不通用(几乎是case by case), 复杂性(指数型,比值型,排名与统计型) 基于应用的数据组装:大宽表集市,横表转纵表,趋势指标串。 模型设计的基本原则 高内聚,低耦合:业务相近的、粒度相同的设计为一个物理或逻辑模型,低概念同时访问的数据分开存储; 核心模型与扩展模型,核心模型支持常规的数据服务, 扩展模型支持个性化或少量应用,难点——控制扩展模型对核心模型的入侵 公共处理逻辑正常,公共处理逻辑不宜暴露给应用层实现 成本 vs. 性能 适当数据冗余可换来查询的性能提升 数据处理幂等性,处理逻辑不变,不同时间多次运行数据结果确定不变 命名规范,相同含义字段在不同表中命名必须相同,命名清晰、易于理解 数据模型开发与设计的日常工作流 需求调研是起点,一般由数据产品和数据仓库工程师面向分析师和运营配合完成。 总线架构设计 数据域是面向业务的,将一个核心业务动作汇聚成一个数据集合。 总线矩阵: 明确数据域下面有哪些业务过程; 明确业务过程的context,即维度描述。 举例: 采购-分销域 业务过程(采购,发货,入库) 一致性维度:供应商,业务类型,地区,仓库,类目,采购单,发货单,入库单 规范定义与模型设计 对指标体系的定义,包括原子指标、修饰词,统计时间周期,从而推理派生指标。 模型设计包括维度定义,属性的定义,以及维度表、明细事实表和汇总事实表设计。 ","link":"https://staticor.github.io/post/booknote-da-shu-ju-zhi-lu-alibaba/"},{"title":"Spark源码学习(五)-存储与序列化","content":"存储级别 StorageLevel Spark提供了一个名为StorageLevel的单例对象。提供了控制RDD存储级别的标志信息: 标记RDD是否使用内存或ExternalBlockStore 如果RDD内存不足或ExternalBlcokStore,是否将其放入磁盘 是否将数据以序列化形式保存在内存中 是否在多个节点复制RDD分区 如果是创建自定义的存储级别对象,使用StorageLevel提供的工厂方法。 private def this(flags: Int, replication: Int) = { this((flags & 8) != 0, (flags & 4) != 0, (flags & 2) != 0, (flags & 1) != 0, replication) } 内部标志变量, 由四个Bool型和一个整型进行构造: useDisk(Boolean),是否使用磁盘 传入flags 与8取与运算; useMemory(Boolean),是否使用内存 传入flags 与4取与运算; useOffHeap,(Boolean) 是否使用堆外内存 传入flags 与2取与运算; deserialized(Boolean) 是否开启反序列化 传入flags 与1取与运算; replication: 副本数量(默认值为1),必须小于40. 根据 useOffHeap 加工的 memoryMode 方法返回的 MemoryMode.ON_HEAP 和 MemoryMode.OFF_HEAP . 参数有效性, useMemory 和 useDisk 必须至少有一个为true。 object StorageLevel DISK_ONLAY 分为 3个子级别 DISK_ONLY: 只开了 useDisk 开关 DISK_ONLY2 DISK_ONLY3 MEMORY_ONLY 细分为 ","link":"https://staticor.github.io/post/spark-yuan-ma-xue-xi-wu-cun-chu-yu-xu-lie-hua/"},{"title":"死锁入门与银行家算法","content":"定义: A有B的依赖资源,B有A的依赖资源,双方都不能抢夺走对方的资源,也不放弃目前拥有的,为了完成自己的目标采取互相等待,导致程序无法进行下去的现象。 死锁产生的条件, 四个条件都发生时,才可能会产生死锁。 互斥使用,某些资源只能交由一个线程使用,不能共享; 不可抢占,别的线程需要急需某资源,涌终止另外正在运行的线程。 占有且等待,保持对原有资源的占有,不轻易放弃 循环等待 ,多个线程间的等待关系成环 解决死锁 一般互斥使用这个条件是很难破坏的,在设计层面一开始就决定了,要避免或解决死锁,就要从另外三个条件入手。 破坏“不可抢占” 当要去尝试获取另一个资源, 获取不到,就放弃当下拥有的资源, 避免自己手上资源被人依赖。 破坏“占有且等待”: ​ 让程序每次执行时申请所有需要的资源, 如果有部分未完成 就阻塞。 破坏“循环等待” 给每个资源标上优先级序号, 用线性方式去申请资源。 循环等待是因为各资源没有线性关系。 Object a = new Object(); Object b = new Object(); new Thread( ()->{ synchronized (a) { System.out.println("i get a "); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("I want get b"); synchronized(b) { System.out.println("I get b"); } } }); new Thread( ()->{ synchronized (b) { System.out.println("i get b"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("I want get a"); synchronized(a) { System.out.println("I get a"); } } }); 数据库中的死锁 文章: https://levelup.gitconnected.com/understanding-why-a-database-deadlock-occurs-8bbd32be8026 使用jstack 可以看到 Java stack information for the threads listed above: =================================================== "Thread-0": at dealock.Example.lambda$main$0(Example.java:22) - waiting to lock <0x000000043f83b988> (a java.lang.Object) - locked <0x000000043f83b978> (a java.lang.Object) at dealock.Example$$Lambda$14/0x0000000800066840.run(Unknown Source) at java.lang.Thread.run(java.base@11.0.15/Thread.java:829) "Thread-1": at dealock.Example.lambda$main$1(Example.java:37) - waiting to lock <0x000000043f83b978> (a java.lang.Object) - locked <0x000000043f83b988> (a java.lang.Object) at dealock.Example$$Lambda$15/0x0000000800066c40.run(Unknown Source) at java.lang.Thread.run(java.base@11.0.15/Thread.java:829) Found 1 deadlock. 银行家算法 事先预防策略,避免系统进入不安全状态。 在银行中,客户申请贷款的数量是有限的,每个客户在第一次申请贷款时要声明完成该项目所需的最大资金量,在满足所有贷款要求时,客户应及时归还。银行家在客户申请的贷款数量不超过自己拥有的最大值时,都应尽量满足客户的需要。在这样的描述中,银行家就好比操作系统,资金就是资源,客户就相当于要申请资源的进程。 pseudo-code P 进程的集合 Mp 进程p的最大请求数目 Cp 进程p当前被分配资源 A 当前可用资源 while (P != ∅) { found = FALSE; foreach (p ∈ P) { if (Mp − Cp ≤ A) { /* p可以獲得他所需的資源。假設他得到資源後執行;執行終止,並釋放所擁有的資源。*/ A = A + Cp ; P = P − {p}; found = TRUE; } } if (! found) return FAIL; } return OK; ","link":"https://staticor.github.io/post/si-suo-ru-men-zhi-shi/"},{"title":"记几个有意思的智力题目","content":"赛马问题 ##25匹 25匹马,速度不相同,赛道最多同时最多允许5匹马比赛。 最少要几次找出最快的3匹? 7场。 编码为 A1-A5,B1-B5,。。。E1-E5. 前5场比出各组的第一快, 即找出A1,B1,C1,D1,E1。 第六场比 这5组的头马,假设 A1>B1>C1>D1>E1 这样D组和E组不用再考虑了 一定不是前3. 接下来第七场只用比: A2,A3,B1,B2,C1 从这5匹中找出Top2 和A1即为答案。 (A1是最快的, 不用再比了) 扩展问题:64匹 64匹马,速度不相同,赛道最多同时最多允许8匹马比赛。 最少要几次找出最快的4匹? 秤球问题 有一个没有刻度的天平,只能反馈左右是否相等。 有12个小球,其中有一个球的重量与其它11个不同,至少要称几次可以找出来? B站视频 约瑟夫环问题 很有意思, 比如你处在42人的一个环形队列当中, 从第1个人开始报数,1-2-3 这样报数, 周期为3. 每报到3时就拉出去埋了。 直到最后剩下2个人, 请问你一开始要在什么位置,能活在最后? ","link":"https://staticor.github.io/post/ji-ji-ge-you-yi-si-de-zhi-li-ti-mu/"},{"title":"Linux 文件系统权限与Find命令","content":"Rwx 拥有者, 群组, 其它组 三个权限粒度。 每个权限粒度都可以设定它的 可读,可写,可执行的权限, rwx 三个字母 分别用一个二进制位 rwx 4 , 2, 1 来表示, rwx 就是7 rw 就是6 rx 就是5 r就是4, 权限最大的场景就是 这三个权限粒度 下 三种操作都放开就是777 。 Linux 权限分配644 表示 对于文件拥有者, Owner 是可读可写 但不能执行的。 对于群组和其它组成员来说就是 只有可读的权限。 若要同时设置 rwx (可读写运行) 权限则将该权限位 设置 为 4 + 2 + 1 = 7 若要同时设置 rw- (可读写不可运行)权限则将该权限位 设置 为 4 + 2 = 6 若要同时设置 r-x (可读可运行不可写)权限则将该权限位 设置 为 4 +1 = 5 上面我们提到,每个文件都可以针对三个粒度,设置不同的rwx(读写执行)权限。即我们可以用用三个8进制数字分别表示 拥有者 、群组 、其它组( u、 g 、o)的权限详情,并用chmod直接加三个8进制数字的方式直接改变文件权限。语法格式为 : ———————————————— -rw------- (600) 只有拥有者有读写权限。 -rw-r--r-- (644) 只有拥有者有读写权限;而属组用户和其他用户只有读权限。 -rwx------ (700) 只有拥有者有读、写、执行权限。 -rwxr-xr-x (755) 拥有者有读、写、执行权限;而属组用户和其他用户只有读、执行权限。 -rwx--x--x (711) 拥有者有读、写、执行权限;而属组用户和其他用户只有执行权限。 -rw-rw-rw- (666) 所有用户都有文件读、写权限。 -rwxrwxrwx (777) 所有用户都有读、写、执行权限。 ———————————————— 可以 chown 改变owner chmod 改变 模式。 ","link":"https://staticor.github.io/post/linux-wen-jian-xi-tong-quan-xian-yu-find-ming-ling/"},{"title":"Spark源码学习(四)-内存管理","content":"查看源码的入口类, Spark的 SparkEnv: val memoryManager: MemoryManager = UnifiedMemoryManager(conf, numUsableCores) 声明语句中val memoryManager: MemoryManager MemoryManager是抽象类, 在Spark2.x(还是3.0?) 之后,只保留了唯一的实现继承类 - UnifiedMemoryManager(统一内存管理者), 去除了之前的静态内存管理者。 源码中对 MemoryManager类的注释: MemoryManager 是控制execution内存和storage内存的边界,以及借用内存的机制。 Execution与Storage共享的区域是通过 spark.memory.fraction 这个参数(默认为0.6)控制的, 它是总堆空间 与300MB之差的一部分组成。 此空间的边界位置(软边界)由spark.memory.storageFraction进一步确定,默认是0.5。 这意味着storage区域的大小默认大小是0.6*0.5 = 0.3. 存储storage区域可以借用尽可能多的执行内存,直到执行区有需求时再讨要回来。 (发生时机,缓存的block将从内存中被淘汰,直到释放出足够内存供执行区满足需求) 。 相应地,执行区内存也能借用尽可能多的存储区,以完成任务。但是,执行区内存*永远* 不会被存储区淘汰。 好处是存储内存和执行内存可以互相占用。 UnifiedMemoryManager abstract class MemoryManager( conf: SparkConf, numCores: Int, onHeapStorageMemory: Long, onHeapExecutionMemory: Long) private[spark] class UnifiedMemoryManager( conf: SparkConf, val maxHeapMemory: Long, onHeapStorageRegionSize: Long, numCores: Int) MemoryManager构造参数, 核数量, 存储区内存大小, 执行区内存大小。 UnifiedMemoryManagerr 主要构造参数: 占用的核数量, 执行最大内存,堆内存储区大小。 主要方法 getMaxMemory, 获得执行内存+存储内存, 返回数字表示多少个bytes 并指定了 minSystemMemory, 要求 Exeucotr分配内存的最小值必须要大于 保留300MB的1.5倍. memoryFraction 可用内存的比例,默认大小是0.6 (即 存储+执行内存的比例) apply方法 将获取的0.6比例 再分配执行区和存储区, 默认比例0.5, 即存储和执行区各占0.3 def apply(conf: SparkConf, numCores: Int): UnifiedMemoryManager = { val maxMemory = getMaxMemory(conf) new UnifiedMemoryManager( conf, maxHeapMemory = maxMemory, onHeapStorageRegionSize = (maxMemory * conf.get(config.MEMORY_STORAGE_FRACTION)).toLong, numCores = numCores) } 四三三分配 默认1GB的Executor内存,先指派给Reserved 300MB,然后余下的724MB 40%分配给User 区域 289.6MB 60%分配 给执行+存储区 即为434.4MB, 存储和执行分别初始分配217 MB onHeap, offHeap JVM管理的是堆内内存(onHeap-memory), 堆内内存在java中使用GC只能通知释放,并不能真正控制。 使用offHeap 堆外内存,能实现灵活的内存控制(相对来说不安全,有操作风险)。 public enum MemoryMode { ON_HEAP, OFF_HEAP } 第一次看到的Spark中的枚举类。 动态占用机制 如果存储和执行区内存都满了,那么就得执行spill-to-disk。 如果Storage不足,而Execution有空余,Storage可以占用部分Execution; 如果Execution不足,而Storage有空余,Execution可以占用部分Storage; 2和3的区别是—— Execution借了的内存涌被evict(淘汰), Storage借了的内存可以被evict(淘汰) 内存对象是否要执行Evict也取决于对象的存储级别(StorageLevel) 为什么Execution借的内存可以不还 存储内存如果不够了,再从硬盘读,只影响性能,不影响任务的运转。 执行内存如果不够了,数据就没了。 相关参数设置 TEST_MEMORY(spark.testing.memory) .createWithDefault(Runtime.getRuntime.maxMemory) TEST_RESERVED_MEMORY (spark.testing.reservedMemory) RESERVED_SYSTEM_MEMORY_BYTES 保留区内存大小, 默认300MB, 用于非存储、非执行使用 EXECUTOR_MEMORY(spark.executor.memory) 默认1个G, Executor进程占用的内存总大小 ","link":"https://staticor.github.io/post/spark-yuan-ma-xue-xi-si-nei-cun-guan-li/"},{"title":"Data Marketplace","content":" Data governance 『数据治理』 Data discovery on-time 嗅探上游的metadata变动, 新增表,字段,格式, etc。 **- Data catalog ** 业务上的数据标签 Data lineage 数据表的依赖关系 DAG。 发现数据是如何生产的,数据的流动和加工明细。 Data dictionary 较好理解,业务方定位数据或查询使用的产品。 通过关键字或数据标签或指标,定位到数据解释的含义。 有特殊编码的字段,一般需要在这里给出对应的mapping meaning。 Data taxonomy 数据分门别类的管理。 按业务划分。 Data classification 数据按安全和访问程度分级,与上面的taxonomy 不同。 比如敏感的客户数据(手机号,地址,信用卡)高严重级。 Data stewardship 数据监管和管理。 KPI definition (Core metrics) Data collection 数据集成 实时数据 近实时 离线数据 集成建议, metadata-driven data collection 数据源管理工具。 Data ingestion Schema registration(Schema 注册 Schema validation Data classification 敏感数据标记 在这里 Data transformation (ETL) 的核心过程 Data structuring Data cleansing/filtering Data integration Data storage & consumption Business intelligence Maching Learning Data warehousing, Data lake Semantic layer Sensitive data protection Role -based access control (RBAC) 根据角色定义的数据访问权限系统。 Data de- identification 数据加密和脱敏 Encryption, masking, Tokenization, Hashing Youtube视频 ","link":"https://staticor.github.io/post/data-marketplace/"},{"title":"Data warehouse - Design","content":"数据仓库 vs 数据库, 阅读我的另一篇文章 Data Warehouse 是给谁设计的, 正如Service Engineer, 他们建设的Application/Server是服务于公司的客户。 而DataWarehouse是给公司的决策层和分析层使用的, 所以面临的挑战是: where can I find the proper data I want 我怎么能够便捷的找到我想要的数据。 How good is that data? 这个数据的呈现形式是我想要的吗, 是否易用? How can I get access to that data ? 这个数据我是否能访问, 怎么访问? Gartner 曾做过一次调查,对企业内部发现进行业务决策面临的数据挑战来自于no data, poor data, complex data。 no data 压根没有数据; poor data, 数据不完全; complex data, 数据需要额外的加工和解析,无法直接成为决策依据。 Data warehouse 的联想: 数据组织成本高, 需要有专人维护 存储数据量大 计算任务多且种类繁杂 数据用度广泛 数据仓库, 公司的业务决策系统,不直接面向用户提供服务. (decision support system) 数据仓库是围绕特定业务主题的产物. 公司的业务可以细分, 不同业务系统在数仓中用"主题" (subject)进行逻辑上的划分. 数仓建设的方法论 Inmon范式建模 根据3NF自上而下建模,将数据抽取为实体和关系模型, 不强调事实表和维度表. 根据上游数据生产,加工成数据仓库,再产生数据集市关心的指标. 数据源往往是异构的,强调数据清洗工作. Kimball维度建模 自下而上建模,从数据集市->数据仓库->数据源,以最终任务为导向,将数据按照目标拆分为不同的需求. 数据会抽取为事实-维度模型. 数据源经ETL转化为事实表和维度表,导入到数据集市, 数据集市是数据仓库中的一个逻辑上的主题域. data vault modeling Data Vault(保险箱?)是 Dan Listedt提出,面向细节,可追踪历史。 由中心表,链接表和附属表三部分,核心是中心表,存储业务主键,链接表存储业务关系,附属表存储业务描述。 英文文章中用 Hub,Link和Satellites表示。 几种建模实践 如果遵循Inmon执行范式建模,要准备若干个表: 城市信息实体表 订单与用户的关联表 支付手段解释表 商品信息分类表 用户注销状态解释表 ... ... 即 实体是实体, 两个实体之间的relation 表. 这是ER设计的理念,也是 Inmon的模式. Kimball模式建模 Kimball关心业务的核心过程,在这里只有一个业务过程,即用户下单. 所以事实表只有一个: 订单事实表 用户表在这里是一张特殊信息维度 ---- 虽然用户的注册,资料更新也是用户的操作,但对于业务来说,并不是商业动作,而是产生动作的来源,和"商品"等价. 城市表,支付方式表,商品分类表,都是与支付有关的维度表. 上图,维度建模的示例,雪花模型 上图,维度建模的示例,星形模型 图片来自 towardsdatascience 分层设计-物理分层 Data Staging Area 是OLTP-上游关系型数据库进入数仓之前的缓冲层,可以进行简单的数据清理操作. ODS 层是做什么? 分主题设计,构建总线矩阵 主题域是业务过程的抽象集合。 业务过程就是公司经营过程中一个个不可拆分的行为事件,但并不一个业务过程就单独划分一个域,而会进行一些合并,如下面的流量域: 商品域: 业务过程商品的创建和上架下架,是交易的基础依赖域; 用户域:用户有关的数据,包括注册,登录,用户身份管理; 供应链域:商品内部采购和调拨业务过程; 物流域:商品配送的物流快递数据; 仓储域:仓库存储相关的数据,入库,出库; 流量域:一般是电商平台用户相关行为数据,搜索、曝光 交易域:加入购物车、下单、支付,与用户购买直接相关的过程; 售后域:电商平台交易的逆过程,包括工单申请、退货订单; 促销域:商品促销有关的数据,包括优惠券、抢杀、拼团等; 内容域:内容社区,包括发布、分享等业务过程; ... ... 主题域划分的要求: 稳定性——相对稳定,新加入一个主题域,不影响已划分的主题域; 扩展性——有一定的扩展性,允许某些主题域凋零下线; 总线矩阵: 主题域 + 业务过程 + 可分析维度 构建一致性维度。 数仓设计难题 - 不同主题的边界如何划定 Cube vs. Granularity (维度 vs. 粒度) 设计技巧 -- 数据模型设计,怎样提升复用? 先了解集群 使用率: 在一定时间范围内,对每层的所有数仓表计算它们有多少被查询任务命中🎯; ODS(原始数据层):892; DWD(明细数据层):1008; DWS(轻度汇总层):152 ADS/DM(应用层,集市层):305 DIM(维度表):862 衡量DWD层是否完善,一般看ODS层有多少被上层引用。 ODS层跨层用得占比过高,说明DWD建设度不成熟。 之前我在的公司是把所有ODS层都执行了DWD层的复制,再定期计算DWD层的跨层引用率,即由DWD层有多少比例的表被DIM、DM、ADS层有效依赖。 (无效依赖是指,有些任务曾经依赖过,但任务已经下线) DWS/ADS、DM层的完善度:考核汇总层和应用层的完善程度,则主要看满足查询需求的程度。 即在查询需求中有哪些是对这三层的表的零依赖。如果这样的任务太多,说明这三层建设要进一步提升。 (图片来自 《数据中台实战课 - 极客时间) Zipper Table 拉链表 即累积历史全量表,记录了某一实体从产生到当前的全量统计信息, 通常为用户创建的居多, 一行表示一位用户的累积统计信息: 以user_id 为唯一主键 用户注册日期, 联系方式等静态信息 用户首次付费日期, 用 9999-12-31 这种无效数字表示还未发生的数字,而不用NULL值 用户累积付费金额, 用户的xxx (关键) 统计窗口的起始日期 使用场景, 基于历史快照的统计, 比如2019年12月31日,系统累积有付费用户数是多少? 拉链表几乎是要每天更新的,动态更新这个窗口. 拉链表的开发和实现比较容易,要遵循历史+增量的merge逻辑. 可进一步参考这篇文章 ","link":"https://staticor.github.io/post/data-warehouse-design/"},{"title":"Data Architectures in companies","content":"Netflix 2018年 Shopee 2021 shopee@2021, Author: Huang Lianghui (Data warehouse architecture ) Shopee 2020年Q1季度的订单量达429.8 million 较2019年同比增长110% 数据集成层: Binlog, Service Log 数据存储层, kafka持久化消息stream, HDFS/HBase (HBase存储维度数据) 计算查询引擎: Spark, Flink, Presto SQL 调度管理层: Airflow, Streaming SQL Platform, Streaming Job Management OLAP存储层, Druid, Phonix(HBase), Elastic Search 应用层: 数据报表, 用户画像和其它商业应用 实时数据仓库: Flink, druid + hive Shopee 将DB的Binlog 同步到Kafka,通过Flink或Spark应用,计算实时的销售数据\\ 用户下单和商品浏览等行为数据.最终结果存储到Druid和HDFS. 任务正由Spark的Structured Streaming迁移到Flink. 计算架构使用Lambda: Flink只处理当天的增量数据 离线任务处理T-1 Challenges 数据规模增长 expansion of data scale , various query requrements, 实时需求的迫切性 , T+1 不再满足当下业务需要 BI teams's real-time demand real-time label training 技术开发,维护成本高 开发成本高 AirBnB 2016 数据集成: EventLogs接Kafka, RDB接Sqoop 进HDFS S3集群 数据存储: S3, 主备两个集群 (gold, silver) 计算调度: Airflow 补充: Spark集群和Presto集群 数据应用: Airpal, Panoramix, Tableau Airpal是一个Presto即席查询平台. ","link":"https://staticor.github.io/post/data-architectures-in-companies/"},{"title":"Spark源码学习(二)-阶段划分和任务执行","content":"Spark应用有这么几个概念: Job, Stage和Task Job以行动算子为界, RDD 的 action算子会执行 runJob 方法 (SparkContext类). Stage 以Shuffle依赖为界, 遇到一次Shuffle就会产生新的Stage, 默认会有一个ResultStage; Task是Stage子集,以并行度(分区数量)来衡量, 分区数是多少,就有多少个Task. 提交Job def runJob[T, U: ClassTag]( rdd: RDD[T], func: (TaskContext, Iterator[T]) => U, partitions: Seq[Int], resultHandler: (Int, U) => Unit): Unit = { if (stopped.get()) { throw new IllegalStateException("SparkContext has been shutdown") } val callSite = getCallSite val cleanedFunc = clean(func) logInfo("Starting job: " + callSite.shortForm) if (conf.getBoolean("spark.logLineage", false)) { logInfo("RDD's recursive dependencies:\\n" + rdd.toDebugString) } dagScheduler.runJob(rdd, cleanedFunc, partitions, callSite, resultHandler, localProperties.get) progressBar.foreach(_.finishAll()) rdd.doCheckpoint() } 上面的关键一行 dagScheduler.runJob(rdd, cleanedFunc, partitions, callSite, resultHandler, localProperties.get) 这是 DAGScheduler类提供的方法. 在这个方法中, 调用了 submitJob 方法 val waiter = submitJob(rdd, func, partitions, callSite, resultHandler, properties) 在submitJob中, 创建了jobId val jobId = nextJobId.getAndIncrement() 往一个事件队列中,将这个jobId 和数据,计算逻辑, 分区,扔进去. // 在DAGScheduler类submitJoby方法 eventProcessLoop.post(JobSubmitted( jobId, rdd, func2, partitions.toArray, callSite, waiter, Utils.cloneProperties(properties))) // EventLoop类. post方法 def post(event: E): Unit = { if (!stopped.get) { if (eventThread.isAlive) { eventQueue.put(event) } else { onError(new IllegalStateException(s"$name has already been stopped accidentally.")) } } } 事件类型 Event处理会用 doOnReceive(event) 完成处理, 在DAGSchedulerEvent类 有这个方法: private def doOnReceive(event: DAGSchedulerEvent): Unit = event match { case JobSubmitted(jobId, rdd, func, partitions, callSite, listener, properties) => dagScheduler.handleJobSubmitted(jobId, rdd, func, partitions, callSite, listener, properties) case MapStageSubmitted(jobId, dependency, callSite, listener, properties) => dagScheduler.handleMapStageSubmitted(jobId, dependency, callSite, listener, properties) case StageCancelled(stageId, reason) => dagScheduler.handleStageCancellation(stageId, reason) case JobCancelled(jobId, reason) => dagScheduler.handleJobCancellation(jobId, reason) case JobGroupCancelled(groupId) => dagScheduler.handleJobGroupCancelled(groupId) case AllJobsCancelled => dagScheduler.doCancelAllJobs() case ExecutorAdded(execId, host) => dagScheduler.handleExecutorAdded(execId, host) case ExecutorLost(execId, reason) => val workerHost = reason match { case ExecutorProcessLost(_, workerHost, _) => workerHost case ExecutorDecommission(workerHost) => workerHost case _ => None } dagScheduler.handleExecutorLost(execId, workerHost) case WorkerRemoved(workerId, host, message) => dagScheduler.handleWorkerRemoved(workerId, host, message) case BeginEvent(task, taskInfo) => dagScheduler.handleBeginEvent(task, taskInfo) case SpeculativeTaskSubmitted(task) => dagScheduler.handleSpeculativeTaskSubmitted(task) case UnschedulableTaskSetAdded(stageId, stageAttemptId) => dagScheduler.handleUnschedulableTaskSetAdded(stageId, stageAttemptId) case UnschedulableTaskSetRemoved(stageId, stageAttemptId) => dagScheduler.handleUnschedulableTaskSetRemoved(stageId, stageAttemptId) case GettingResultEvent(taskInfo) => dagScheduler.handleGetTaskResult(taskInfo) case completion: CompletionEvent => dagScheduler.handleTaskCompletion(completion) case TaskSetFailed(taskSet, reason, exception) => dagScheduler.handleTaskSetFailed(taskSet, reason, exception) case ResubmitFailedStages => dagScheduler.resubmitFailedStages() case RegisterMergeStatuses(stage, mergeStatuses) => dagScheduler.handleRegisterMergeStatuses(stage, mergeStatuses) case ShuffleMergeFinalized(stage) => dagScheduler.handleShuffleMergeFinalized(stage) } 第一个模式匹配. 命中了 JobSubmitted 这个case class, 再执行 handleJobSubmitted(jobId ...) 方法, // New stage creation may throw an exception if, for example, jobs are run on a // HadoopRDD whose underlying HDFS files have been deleted. finalStage = createResultStage(finalRDD, func, partitions, jobId, callSite) ... ``` 可见在在这里完成的结果Stage的创建. # 结果创建 DAGScheduler.createResultStage ```scala private def createResultStage( rdd: RDD[_], func: (TaskContext, Iterator[_]) => _, partitions: Array[Int], jobId: Int, callSite: CallSite): ResultStage = { val (shuffleDeps, resourceProfiles) = getShuffleDependenciesAndResourceProfiles(rdd) val resourceProfile = mergeResourceProfilesForStage(resourceProfiles) checkBarrierStageWithDynamicAllocation(rdd) checkBarrierStageWithNumSlots(rdd, resourceProfile) checkBarrierStageWithRDDChainPattern(rdd, partitions.toSet.size) val parents = getOrCreateParentStages(shuffleDeps, jobId) val id = nextStageId.getAndIncrement() val stage = new ResultStage(id, rdd, func, partitions, parents, jobId, callSite, resourceProfile.id) stageIdToStage(id) = stage updateJobIdStageIdMaps(jobId, stage) stage } 核心语句 val stage = new ResultStage(id, rdd, func, partitions, parents, jobId, callSite, resourceProfile.id) 结果阶段的构造参数有: id, 阶段ID rdd, 最后的结果RDD func, 途程执行的算子 partitions, 分区列表 parents, 上游依赖的阶段 getOrCreateParentStages(shuffleDeps, jobId) jobID 提交时的Job ID callSite resourceProfile.id 上一级阶段的获取方法, getOrCreateParentStages /** * Get or create the list of parent stages for the given shuffle dependencies. The new * Stages will be created with the provided firstJobId. */ private def getOrCreateParentStages(shuffleDeps: HashSet[ShuffleDependency[_, _, _]], firstJobId: Int): List[Stage] = { shuffleDeps.map { shuffleDep => getOrCreateShuffleMapStage(shuffleDep, firstJobId) }.toList 这个获取上级阶段的方法,又调用了getOrCreateShuffleMapStage // 获取 可能存在的 ShuffleIdToMapStage, 如果不存在,该方法创建一个Shuffle Map Stage. private def getOrCreateShuffleMapStage( shuffleDep: ShuffleDependency[_, _, _], firstJobId: Int): ShuffleMapStage = { shuffleIdToMapStage.get(shuffleDep.shuffleId) match { case Some(stage) => stage case None => // Create stages for all missing ancestor shuffle dependencies. getMissingAncestorShuffleDependencies(shuffleDep.rdd).foreach { dep => // Even though getMissingAncestorShuffleDependencies only returns shuffle dependencies // that were not already in shuffleIdToMapStage, it's possible that by the time we // get to a particular dependency in the foreach loop, it's been added to // shuffleIdToMapStage by the stage creation process for an earlier dependency. See // SPARK-13902 for more information. if (!shuffleIdToMapStage.contains(dep.shuffleId)) { createShuffleMapStage(dep, firstJobId) } } // Finally, create a stage for the given shuffle dependency. createShuffleMapStage(shuffleDep, firstJobId) } } 可见, 该方法使用了 createShuffleMapStage来创建 Shuffle Map的Stage. /** * Creates a ShuffleMapStage that generates the given shuffle dependency's partitions. If a * previously run stage generated the same shuffle data, this function will copy the output * locations that are still available from the previous shuffle to avoid unnecessarily * regenerating data. */ def createShuffleMapStage[K, V, C]( shuffleDep: ShuffleDependency[K, V, C], jobId: Int): ShuffleMapStage = { val rdd = shuffleDep.rdd val (shuffleDeps, resourceProfiles) = getShuffleDependenciesAndResourceProfiles(rdd) val resourceProfile = mergeResourceProfilesForStage(resourceProfiles) checkBarrierStageWithDynamicAllocation(rdd) checkBarrierStageWithNumSlots(rdd, resourceProfile) checkBarrierStageWithRDDChainPattern(rdd, rdd.getNumPartitions) val numTasks = rdd.partitions.length val parents = getOrCreateParentStages(shuffleDeps, jobId) val id = nextStageId.getAndIncrement() val stage = new ShuffleMapStage( id, rdd, numTasks, parents, jobId, rdd.creationSite, shuffleDep, mapOutputTracker, resourceProfile.id) stageIdToStage(id) = stage shuffleIdToMapStage(shuffleDep.shuffleId) = stage updateJobIdStageIdMaps(jobId, stage) if (!mapOutputTracker.containsShuffle(shuffleDep.shuffleId)) { // Kind of ugly: need to register RDDs with the cache and map output tracker here // since we can't do it in the RDD constructor because # of partitions is unknown logInfo(s"Registering RDD ${rdd.id} (${rdd.getCreationSite}) as input to " + s"shuffle ${shuffleDep.shuffleId}") mapOutputTracker.registerShuffle(shuffleDep.shuffleId, rdd.partitions.length, shuffleDep.partitioner.numPartitions) } stage } val stage = new ShuffleMapStage( id, rdd, numTasks, parents, jobId, rdd.creationSite, shuffleDep, mapOutputTracker, resourceProfile.id) 可见阶段的划分就是取决于Shuffle依赖的数量. 阶段数,就是Shuffle依赖的数量+ 1. 任务切分 阶段划分过后, 查看 submitStage 方法 /** Submits stage, but first recursively submits any missing parents. */ private def submitStage(stage: Stage): Unit = { val jobId = activeJobForStage(stage) if (jobId.isDefined) { logDebug(s"submitStage($stage (name=${stage.name};" + s"jobs=${stage.jobIds.toSeq.sorted.mkString(",")}))") if (!waitingStages(stage) && !runningStages(stage) && !failedStages(stage)) { val missing = getMissingParentStages(stage).sortBy(_.id) logDebug("missing: " + missing) if (missing.isEmpty) { logInfo("Submitting " + stage + " (" + stage.rdd + "), which has no missing parents") submitMissingTasks(stage, jobId.get) } else { for (parent <- missing) { submitStage(parent) } waitingStages += stage } } } else { abortStage(stage, "No active job for stage " + stage.id, None) } } val missing = getMissingParentStages(stage).sortBy(_.id) 这一行, 要通过 getMissingParentStages 获取可能存在的上一级阶段id, 如果没有上一级,就直接提交本阶段stage的任务. 如果有上一级,就遍历. submitMissingTasks 中出现了我们关心的任务 -- tasks. 可见, 任务对stage模式区宵只分为两类, ShuffleMapStage和ResultStage. 如果是ShuffleMapStage, 创建ShuffleMapTask. 如果是ResultStage, 创建ResultTask 任务的划分就是由 partitionsToCompute这个对象指派的. tasks 创建好之后, 将它们封装在一个TaskSet中, 调用taskScheduer 的submitTasks进行提交 taskScheduler.submitTasks(new TaskSet( tasks.toArray, stage.id, stage.latestInfo.attemptNumber, jobId, properties, stage.resourceProfileId)) TaskScheduler 是 Trait, 它的实现是 TaskSchedulerImpl private[spark] class TaskSchedulerImpl( val sc: SparkContext, val maxTaskFailures: Int, isLocal: Boolean = false, clock: Clock = new SystemClock) extends TaskScheduler ... 提交方法(jjyy) override def submitTasks(taskSet: TaskSet): Unit = { val tasks = taskSet.tasks logInfo("Adding task set " + taskSet.id + " with " + tasks.length + " tasks " + "resource profile " + taskSet.resourceProfileId) this.synchronized { val manager = createTaskSetManager(taskSet, maxTaskFailures) val stage = taskSet.stageId val stageTaskSets = taskSetsByStageIdAndAttempt.getOrElseUpdate(stage, new HashMap[Int, TaskSetManager]) // Mark all the existing TaskSetManagers of this stage as zombie, as we are adding a new one. // This is necessary to handle a corner case. Let's say a stage has 10 partitions and has 2 // TaskSetManagers: TSM1(zombie) and TSM2(active). TSM1 has a running task for partition 10 // and it completes. TSM2 finishes tasks for partition 1-9, and thinks he is still active // because partition 10 is not completed yet. However, DAGScheduler gets task completion // events for all the 10 partitions and thinks the stage is finished. If it's a shuffle stage // and somehow it has missing map outputs, then DAGScheduler will resubmit it and create a // TSM3 for it. As a stage can't have more than one active task set managers, we must mark // TSM2 as zombie (it actually is). stageTaskSets.foreach { case (_, ts) => ts.isZombie = true } stageTaskSets(taskSet.stageAttemptId) = manager schedulableBuilder.addTaskSetManager(manager, manager.taskSet.properties) if (!isLocal && !hasReceivedTask) { starvationTimer.scheduleAtFixedRate(new TimerTask() { override def run(): Unit = { if (!hasLaunchedTask) { logWarning("Initial job has not accepted any resources; " + "check your cluster UI to ensure that workers are registered " + "and have sufficient resources") } else { this.cancel() } } }, STARVATION_TIMEOUT_MS, STARVATION_TIMEOUT_MS) } hasReceivedTask = true } backend.reviveOffers() } 核心语句, val manager = createTaskSetManager(taskSet, maxTaskFailures) 创建了一个TaskSetManager, 即将TaskSet作为参数,又打了一层包. 任务调度 schedulableBuilder, 任务调度器, 在初始化时会根据模式进行初始化. def initialize(backend: SchedulerBackend): Unit = { this.backend = backend schedulableBuilder = { schedulingMode match { case SchedulingMode.FIFO => new FIFOSchedulableBuilder(rootPool) case SchedulingMode.FAIR => new FairSchedulableBuilder(rootPool, sc) case _ => throw new IllegalArgumentException(s"Unsupported $SCHEDULER_MODE_PROPERTY: " + s"$schedulingMode") } } schedulableBuilder.buildPools() } Spark默认的调度器是FIFOSchedulableBuilder. 关注 rootPool 变量, 所谓任务调度就是将若干个任务放在一个池子里,然后以某种策略取出来执行的过程. 任务池的对面, 是谁来取任务调用呢? SchedulerBackend SchedulerBackend 是个特质, 实现 class CoarseGrainedSchedulerBackend(scheduler: TaskSchedulerImpl, val rpcEnv: RpcEnv), 我管它叫调度后端录杂粮版. 调用receiverOffers 从TaskPool取任务. 最终执行, 关注方法 ~makeOffers // Make fake resource offers on all executors private def makeOffers(): Unit = { // Make sure no executor is killed while some task is launching on it val taskDescs = withLock { // Filter out executors under killing val activeExecutors = executorDataMap.filterKeys(isExecutorActive) val workOffers = activeExecutors.map { case (id, executorData) => new WorkerOffer(id, executorData.executorHost, executorData.freeCores, Some(executorData.executorAddress.hostPort), executorData.resourcesInfo.map { case (rName, rInfo) => (rName, rInfo.availableAddrs.toBuffer) }, executorData.resourceProfileId) }.toIndexedSeq scheduler.resourceOffers(workOffers, true) } if (taskDescs.nonEmpty) { launchTasks(taskDescs) } 调度算法 SchedulingAlgorithm SchedulingAlgorithm是特质, 实现也有两种, FIFO和Fair. Task 分发的策略 Task是计算逻辑, 面对三个Executor, Driver将Task发给谁呢? 移动数据不如移动计算. 在调度执行时, Spark调度总是尽量让每个task以最高的LocalityLevel来尝试获取RDD. 这也是 RDD中 getPreferredLocation 方法的对应: 计算和数据的位置,有不同的"level",这个称为本地化级别; 如果是同在一个进程当中, 称为进程本地化, 效率是最高的; (PROCESS_LOCAL) 节点本地化, 较上次之; (NODE_LOCAL) rack本地化, 较上次之; (RACK_LOCAL) else, 最次. ANY 性能最差 NO_PREF 启动任务 杂粮调度器后端 : // Launch tasks returned by a set of resource offers private def launchTasks(tasks: Seq[Seq[TaskDescription]]): Unit = { for (task <- tasks.flatten) { val serializedTask = TaskDescription.encode(task) if (serializedTask.limit() >= maxRpcMessageSize) { Option(scheduler.taskIdToTaskSetManager.get(task.taskId)).foreach { taskSetMgr => try { var msg = "Serialized task %s:%d was %d bytes, which exceeds max allowed: " + s"${RPC_MESSAGE_MAX_SIZE.key} (%d bytes). Consider increasing " + s"${RPC_MESSAGE_MAX_SIZE.key} or using broadcast variables for large values." msg = msg.format(task.taskId, task.index, serializedTask.limit(), maxRpcMessageSize) taskSetMgr.abort(msg) } catch { case e: Exception => logError("Exception in error callback", e) } } } else { val executorData = executorDataMap(task.executorId) // Do resources allocation here. The allocated resources will get released after the task // finishes. val rpId = executorData.resourceProfileId val prof = scheduler.sc.resourceProfileManager.resourceProfileFromId(rpId) val taskCpus = ResourceProfile.getTaskCpusOrDefaultForProfile(prof, conf) executorData.freeCores -= taskCpus task.resources.foreach { case (rName, rInfo) => assert(executorData.resourcesInfo.contains(rName)) executorData.resourcesInfo(rName).acquire(rInfo.addresses) } logDebug(s"Launching task ${task.taskId} on executor id: ${task.executorId} hostname: " + s"${executorData.executorHost}.") executorData.executorEndpoint.send(LaunchTask(new SerializableBuffer(serializedTask))) } } } 关键代码, executorData.executorEndpoint.send(LaunchTask(new SerializableBuffer(serializedTask))) 从任务池中获取任务 (序列化后) 发给 executor 执行. DAGScheudler的注释 The high-level scheduling layer that implements stage-oriented scheduling. It computes a DAG of stages for each job, keeps track of which RDDs and stage outputs are materialized, and finds a minimal schedule to run the job. It then submits stages as TaskSets to an underlying TaskScheduler implementation that runs them on the cluster. A TaskSet contains fully independent tasks that can run right away based on the data that's already on the cluster (e.g. map output files from previous stages), though it may fail if this data becomes unavailable. It then submits stages as TaskSets to an underlying TaskScheduler implementation that runs them on the cluster. A TaskSet contains fully independent tasks that can run right away based on the data that's already on the cluster (e.g. map output files from previous stages), though it may fail if this data becomes unavailable. ","link":"https://staticor.github.io/post/spark-yuan-ma-xue-xi-er-ren-wu-he-jie-duan-hua-fen/"},{"title":"Spark源码学习(三)-Shuffle","content":"Shuffle分成两个阶段来看待, Shuffle Write和Shuffle Read. 前者由Map端处理, 写到磁盘(数据文件+索引文件). 后者由Reduce端处理, 调用ShuffleRDD的compute方法完成read. ShuffleMapTask 将RDD中的数据划分成若干个buckets (基于ShuffleDependency中的分区器) private[spark] class ShuffleMapTask( stageId: Int, stageAttemptId: Int, taskBinary: Broadcast[Array[Byte]], partition: Partition, @transient private var locs: Seq[TaskLocation], localProperties: Properties, serializedTaskMetrics: Array[Byte], jobId: Option[Int] = None, appId: Option[String] = None, appAttemptId: Option[String] = None, isBarrier: Boolean = false) extends Task[MapStatus](stageId, stageAttemptId, partition.index, localProperties, serializedTaskMetrics, jobId, appId, appAttemptId, isBarrier) runTask 方法 override def runTask(context: TaskContext): MapStatus = { // Deserialize the RDD using the broadcast variable. val threadMXBean = ManagementFactory.getThreadMXBean val deserializeStartTimeNs = System.nanoTime() val deserializeStartCpuTime = if (threadMXBean.isCurrentThreadCpuTimeSupported) { threadMXBean.getCurrentThreadCpuTime } else 0L val ser = SparkEnv.get.closureSerializer.newInstance() val rddAndDep = ser.deserialize[(RDD[_], ShuffleDependency[_, _, _])]( ByteBuffer.wrap(taskBinary.value), Thread.currentThread.getContextClassLoader) _executorDeserializeTimeNs = System.nanoTime() - deserializeStartTimeNs _executorDeserializeCpuTime = if (threadMXBean.isCurrentThreadCpuTimeSupported) { threadMXBean.getCurrentThreadCpuTime - deserializeStartCpuTime } else 0L val rdd = rddAndDep._1 val dep = rddAndDep._2 // While we use the old shuffle fetch protocol, we use partitionId as mapId in the // ShuffleBlockId construction. val mapId = if (SparkEnv.get.conf.get(config.SHUFFLE_USE_OLD_FETCH_PROTOCOL)) { partitionId } else context.taskAttemptId() dep.shuffleWriterProcessor.write(rdd, dep, mapId, context, partition) } 核心语句是这里的 dep.shuffleWriterProcessor.write(rdd, dep, mapId, context, partition) ShuffleWriteProcessor 这是Shuffle写阶段的处理类, 核心方法是write, 创建了 ShuffleWriter 对象 (通过shuffleManager的getWriter 方法) 完成写操作, try 语句中的内容如下 val manager = SparkEnv.get.shuffleManager writer = manager.getWriter[Any, Any]( dep.shuffleHandle, mapId, context, createMetricsReporter(context)) writer.write( rdd.iterator(partition, context).asInstanceOf[Iterator[_ <: Product2[Any, Any]]]) val mapStatus = writer.stop(success = true) if (mapStatus.isDefined) { // Initiate shuffle push process if push based shuffle is enabled // The map task only takes care of converting the shuffle data file into multiple // block push requests. It delegates pushing the blocks to a different thread-pool - // ShuffleBlockPusher.BLOCK_PUSHER_POOL. if (dep.shuffleMergeEnabled && dep.getMergerLocs.nonEmpty && !dep.shuffleMergeFinalized) { manager.shuffleBlockResolver match { case resolver: IndexShuffleBlockResolver => val dataFile = resolver.getDataFile(dep.shuffleId, mapId) new ShuffleBlockPusher(SparkEnv.get.conf) .initiateBlockPush(dataFile, writer.getPartitionLengths(), dep, partition.index) case _ => } } } mapStatus.get ShuffleManager 早期还有一个HashShuffleManager的实现,目前新版本中只有一个SortShuffleManager. 该类中的getWriter 通过匹配handle的类型,创建不同的ShuffleterWriter: unsafeShuffleHandle -> UnsafeShuffleWriter; bypassMergeSortHandle -> BypassMergeSortShuffleWriter BaseShuffleHandle -> SortShuffleWriter 这三种handle是通过SortShuffleManager.registerShuffle 注册时创建的: override def registerShuffle[K, V, C]( shuffleId: Int, dependency: ShuffleDependency[K, V, C]): ShuffleHandle = { if (SortShuffleWriter.shouldBypassMergeSort(conf, dependency)) { new BypassMergeSortShuffleHandle[K, V]( shuffleId, dependency.asInstanceOf[ShuffleDependency[K, V, V]]) } else if (SortShuffleManager.canUseSerializedShuffle(dependency)) { // Otherwise, try to buffer map outputs in a serialized form, since this is more efficient: new SerializedShuffleHandle[K, V]( shuffleId, dependency.asInstanceOf[ShuffleDependency[K, V, V]]) } else { // Otherwise, buffer map outputs in a deserialized form: new BaseShuffleHandle(shuffleId, dependency) } 注释: 先判断能不能忽略归并排序, 代码注释给出了两个条件. 如果分区数量小于 spark.shuffle.sort.bypassMergeThreshold partitions 并且不需要进行 map-side 聚合, 直接写 ** numPartitions ** 个文件, 然后最终将它们连接起来, 这样避免两次序列化和反序列化. 这样人帮的缺点时会同一时间一次性打开多个文件,造成更多的内存缓冲区的使用. 如果满足这些条件, 就\b\b返回一个 Bypass的Handle. 第二层判断, 判断是否使用序列化Shuffle (SerializedShuffle), 通过 canUseSerializedShuffle 方法判断. 要求: 序列化框架要能支持重定位(RelocationOfSerializedObject) 不能使用mapSideCombine 分区数量不能大于 MAX_SHUFFLE_OUTPUT_PARTITIONS_FOR_SERIALIZED_MODE 即 16777216 最后,使用默认的BaseShuffleHandle (没有限制条件) ShuffleWriter ShuffleWriter 是抽象类, 有三个继承实现 SortShuffleWriter BypassMergeSortShuffleWriter UnsafeSHuffleWriter 三个写对象有什么区别呢? SortShuffleWriter // write 方法 /** Write a bunch of records to this task's output */ override def write(records: Iterator[Product2[K, V]]): Unit = { sorter = if (dep.mapSideCombine) { new ExternalSorter[K, V, C]( context, dep.aggregator, Some(dep.partitioner), dep.keyOrdering, dep.serializer) } else { // In this case we pass neither an aggregator nor an ordering to the sorter, because we don't // care whether the keys get sorted in each partition; that will be done on the reduce side // if the operation being run is sortByKey. new ExternalSorter[K, V, V]( context, aggregator = None, Some(dep.partitioner), ordering = None, dep.serializer) } sorter.insertAll(records) val mapOutputWriter = shuffleExecutorComponents.createMapOutputWriter( dep.shuffleId, mapId, dep.partitioner.numPartitions) sorter.writePartitionedMapOutput(dep.shuffleId, mapId, mapOutputWriter) partitionLengths = mapOutputWriter.commitAllPartitions(sorter.getChecksums).getPartitionLengths mapStatus = MapStatus(blockManager.shuffleServerId, partitionLengths, mapId) } 先创建一个排序器 sorter (sorter是 ExternalSorter 对象) , 使用sorter.insertAll (records) 排序. 核心语句是 sorter的writePartitionedMapOutput, 执行写操作. 补充, 构建sorter时也要判断是否用到了map-side 聚合 ExternalSorter.writerPartitionedMapOutput LocalDiskShuffleMapOutputWriter.commitAllPartitions IndexShuffleBlockResolver.writeMetadataFileAndCommit 在本地写磁盘时,要写的metadata包括两种信息, 索引文件和checksum文件, 其中checumSum 是可省略的. sorter.insertAll map是PartitionedAppendOnlyMap 一个映射表, 如果开启了预聚合,用map来更新对应的key-value信息. 溢写操作 maybeSpillCollection(usingMap = true) 参数为true. buffer 是 PartitionedPairBuffer 是个纯数组结构, 只做纯append, 溢写操作参数是false. ... if (shouldCombine) { // Combine values in-memory first using our AppendOnlyMap val mergeValue = aggregator.get.mergeValue val createCombiner = aggregator.get.createCombiner var kv: Product2[K, V] = null val update = (hadValue: Boolean, oldValue: C) => { if (hadValue) mergeValue(oldValue, kv._2) else createCombiner(kv._2) } while (records.hasNext) { addElementsRead() kv = records.next() map.changeValue((getPartition(kv._1), kv._1), update) maybeSpillCollection(usingMap = true) } } 如果没有支持预聚合操作, .... else { // Stick values into our buffer while (records.hasNext) { addElementsRead() val kv = records.next() buffer.insert(getPartition(kv._1), kv._1, kv._2.asInstanceOf[C]) maybeSpillCollection(usingMap = false) } } sorter.maybeSpillCollection(usingMap: Boolean) 内存缓冲区溢写到磁盘的操作. true表示map, estimatedSize = map.estimateSize() if (maybeSpill(map, estimatedSize)) { map = new PartitionedAppendOnlyMap[K, C] false表示是buffer数组. sorter.maybeSpill(collection C, currentMemory: Long) 判断是否应该要溢写? 元素数量是32的倍数 并且 当前内存大于指定阈值 (SHUFFLE_SPILL_INITIAL_MEM_THRESHOLD 默认取自于配置文件 key为 spark.shuffle.spill.initialMemoryThreshold) 5MB 尝试申请新内存(amountToRequest, acquireMemory) 如果申请资源不足以满足, 开始强制溢写. 另外一种开启溢写的机制是判断读取元素数量太多, 大于 numElementsForceSpillThreshold (SHUFFLE_SPILL_NUM_ELEMENTS_FORCE_SPILL_THRESHOLD) 即整数的最大值, 会强制溢写 protected def maybeSpill(collection: C, currentMemory: Long): Boolean = { var shouldSpill = false if (elementsRead % 32 == 0 && currentMemory >= myMemoryThreshold) { val amountToRequest = 2 * currentMemory - myMemoryThreshold val granted = acquireMemory(amountToRequest) myMemoryThreshold += granted // If we were granted too little memory to grow further (either tryToAcquire returned 0, // or we already had more memory than myMemoryThreshold), spill the current collection shouldSpill = currentMemory >= myMemoryThreshold } shouldSpill = shouldSpill || _elementsRead > numElementsForceSpillThreshold // Actually spill if (shouldSpill) { _spillCount += 1 logSpillage(currentMemory) spill(collection) _elementsRead = 0 _memoryBytesSpilled += currentMemory releaseMemory() } shouldSpill } logSpillage(currentMemory) spill(collection) 执行真正的溢写操作 溢写过后, 调用relaseMemory () 释放内存. spill(collection) -> spillMemoryIteratorToDisk(inMemoryIterator) 在ExternalSortger中, spill 方法真正调用的是 spillMemoryIteratorToDisk 方法注释, 将内存中的一部分迭代对象,写到磁盘中的一个临时文件. Spill contents of in-memory iterator to a temporary file on disk. 临时文件创建: diskBlockManager.createTempShuffleBlock() 溢写的写对象, writer: val writer: DiskBlockObjectWriter = blockManager.getDiskWriter(blockId, file, serInstance, fileBufferSize, spillMetrics) fileBufferSize 大小是 SHUFFLE_FILE_BUFFER_SIZE 32K (spark.shuffle.file.buffer) sorter.partitionedIterator 最后返回所有的数据,既包括磁盘的溢写临时文件,也包括内存缓冲区中的文件 BypassMergeSortShuffleWriter ShuffleRead阶段 想象Reduce是一个结果Stage, 那么通过ResultStage就应该可以找到读数据的逻辑 , ResultTask.runTask中有这么一段: func(context, rdd.iterator(partition, context)) 调用RDD的iterator 方法, 继而调用 RDD.getOrCompute -- computeOrReadCheckpoint -- compute 方法 因为要看的是Shuffle过程, 所以去查看ShuffleRDD的compute方法. 可以看到, SparkEnv.get.shuffleManager.getReader 这一行生成了Reader,并执行read方法. ","link":"https://staticor.github.io/post/spark-yuan-ma-xue-xi-yi-sparkcontext/"},{"title":"Spark的OOM分析","content":"若作业提交时以yarn-client模式提交,Driver运行时的JVM参数是spark在spark-env.sh 配置文件中读取,因此在spark-env.sh中加入配置:SPARK_DRIVER_MEMORY=2G 作业在每个worker上运行时报错:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 此时需要配置每个worker运行的MEMORY参数,在spark-env.sh中配置: export SPARK_WORKER_MEMORY=4g export SPARK_WORKER_CORES=4 scala> val ds1 = spark.range(1, 100000) ds1: org.apache.spark.sql.Dataset[Long] = [id: bigint] scala> val ds2 = spark.range(1, 100000, 2) ds2: org.apache.spark.sql.Dataset[Long] = [id: bigint] scala> val ds3 = ds1.repartition(7) ds3: org.apache.spark.sql.Dataset[Long] = [id: bigint] scala> val ds4 = ds2.repartition(12) ds4: org.apache.spark.sql.Dataset[Long] = [id: bigint] scala> val ds5 = ds3.selectExpr(" id * 5 as id") ds5: org.apache.spark.sql.DataFrame = [id: bigint] scala> val joined = ds5.join(ds4, "id") joined: org.apache.spark.sql.DataFrame = [id: bigint] scala> val sum = joined.selectExpr(" sum(id) as result") sum: org.apache.spark.sql.DataFrame = [result: bigint] scala> sum.show() +---------+ | result| +---------+ |500000000| 如果放大上面的数据,就会产生OOM. scala> val ds1 = spark.range(1, 10000000) ds1: org.apache.spark.sql.Dataset[Long] = [id: bigint] scala> val ds2 = spark.range(1, 10000000, 2) ds2: org.apache.spark.sql.Dataset[Long] = [id: bigint] scala> val ds3 = ds1.repartition(7) ds3: org.apache.spark.sql.Dataset[Long] = [id: bigint] scala> val ds4 = ds2.repartition(12) ds4: org.apache.spark.sql.Dataset[Long] = [id: bigint] scala> val ds5 = ds3.selectExpr(" id * 5 as id") ds5: org.apache.spark.sql.DataFrame = [id: bigint] scala> val joined = ds5.join(ds4, "id") joined: org.apache.spark.sql.DataFrame = [id: bigint] scala> val sum = joined.selectExpr(" sum(id) as result") sum: org.apache.spark.sql.DataFrame = [result: bigint] scala> sum.show() ; ","link":"https://staticor.github.io/post/spark-de-oom-fen-xi/"},{"title":"[Paper] GFS, BigTable","content":"知名的文件管理系统: GFS,HDFS,Facebook Haystack,FastDFS ... ... GFS是最著名的分布式文件系统,模仿者众多。 文件系统标准接口: (所有文件系统都必须提供) 创建, 删除, 打开, 关闭, 读取, 写入 拓展接口:(不是所有文件系统都提供) 生成快照,便于数据多版本控制管理 修改,节省修改操作效率 (GFS不支持随机修改) 追加,修改的特殊场景,Append修改效率更高; GFS GFS, 大规模的分布式文件系统,用来处理分布型数据密集型的应用. 它提供了容错性,可以在廉价的硬件上运行,性能还不错. 与先前的共享文件系统相比, 设计考量。FS满足了当前公司的需要,广泛应用在Google的平台. GFS也是为MapReduce,BigTable提供存储基础,是分布式框架的最底层依赖。 关键字: Design, reliability, performance, measurement Introduction 数据增长, 处理需求的增长, 遇到了一些问题: 组件故障(component failures) 单个文件的尺寸越来越大, 超过GB级的文件成为常态. 多数文件的修改是以追加方式的,像整个覆盖的场景相对少. 设计的Overview 文件系统需要有监测能力和自探能力,容错,可恢复 会存储大文件块, 平均大小估算是100MB或更大. 小文件也会支持, 不重点讨论. 能支持多个客户端并发在对同一文件进行追加写入,高带宽低延迟 单机文件系统过滤到分布式文件系统,再升级为GFS的过程,要解决的问题: 文件怎样分散在多台服务器上, 怎样实现多台机器间的负载均衡,自动扩容和缩容? 怎样知道一个文件在哪台节点机器上? 怎样保证服务器在故障时文件系统的可用性可选性?(CAP) 如果使用多个副本,怎样保证一致性? (CAP) 性能要求,怎么支持大型文件(单个文件过GB)的存储 机器集群规模庞大时,怎样实现监控、容错与恢复。 怎样实现快速的顺序读和追加写? 接口 基本操作是必要的, 如前面所述的六个标准接口: create, delete | open close | read write GFS 又提供了两个拓展接口: snapshot 和 append 架构 Master 设计 怎样知道一个文件存储在哪台机器上? 文件位置即为文件的元数据之一,想象有一台NameNode是专门用来管理文件元空间的。 那么设计GFS集群是使用单Master还是多节点? 单中心节点的优点是实现成本低,一致性容易保证,缺点也极明显,单点容易导致单点故障,性能也极容易造成瓶颈。 优化策略是元数据的数据设计,减少单Master的压力。 多中心节点(分布式中心节点) 实现成本高,一致性难保证,系统可靠性难验证; 优点是解决了单点性能瓶颈问题,扩展性极强。 GFS选择的是单中心节点。 Files 被划分为 固定尺寸的 chunks ,每个chunk是全局唯一的(分配一个64bit编号). Metadata存什么? file 和 chunk 的 namesapces (持久化) file到chunks的映射 (持久化) chunk副本和节点Node的映射, 不持久化 所以可推断出GFS读一个文件的过程要有: 客户端提供文件名 -> 根据文件名获得chunk列表 -> 获取所有chunk的node location list -> 设计策略建立起到这些node lsit 的通信pipeline。 Chunkserver 就是记录这些chunk地址, 由chunkserver 向MetaServer通信,所以Meta不用持久化Chunk地址。 Chunk size, 主要的设计参数, 64MB. 它的元数据是小于64bit的, 假设一个文件有三个副本。 1TB的元数据有多大? 3TB副本总大小 除以 64MB * 64B 即只有3MB; 如果1PB数据,只有3GB元数据, 完全可以用内存管理。 太大,或者太小会怎样? 可见如果chunk太小,那么chunk数量将会增大, 对master内存会是不小的影响。 如果chunk太大,又会牺牲读取和写入数据的性能。 In-Memory 数据结构 metadata存储在内存中,便于master周期性的扫描全体chunk的状态. 扫描过程中,要实现chunk的垃圾回收, 副本拷贝, chunk平衡 Chunk 位置 Master对一份chunk,不会一直保存它的信息. 它只是在启动时向chunk server轮询, 此后master让自己保持最新状态,通过HeartBeat message机制. GFS的所有数据流不经过master,而是直接由client和chunkserver交互。 Operation Log 操作日志存什么, 元数据变动的记录. Op Log非常重要,不能直接交给Client访问. 除了访问控制,还要对其进行多副本, Master恢复文件状态,要借助于Op Log的replay. 为了缩短重启的时间,要尽量保持op log的尺寸不要太大. 所以这里还另设计了checkpoint, 当op log过大, 先从硬盘上的checkpoint 读取最新的一个快照, 并且重放日志也只重放这个cehckpoint之后的操作. checkpoint 是以一种B树形式,建立一个cp会花一点时间,master希望是在不延迟的前提下完成创建. master 切换到一个新日志文件,并在一个单独线程创建新cp, 恢复只需要最新的完整的cp和后续日志文件. 一致性模型 Consistency Model GFS 的保证 File Namespace变动(创建文件,删除)是原子的, 由master排它性操作. (有锁) master的Op Log定义了这些操作的全局有序性. 尽量避免master内存成为系统瓶颈,GFS采用一系列手段节省master内存,包括增大chunk大小,节省chunk数量,对元数据进行定制化压缩。 BigTable A Distributed Storage System for Structured Data Bigtable 是分布式存储系统,管理超大规模的数据 —— PB级+上千个节点。 在Google有诸多项目使用到了BigTable, 网页索引,Google Earth, Google Fiance。 对于BigTable不同应用服务有各自的需求,覆盖不同量级的数据尺度,既有延迟查询也有实时服务。 在这篇文章中,将会介绍Bigtable的数据模型,满足不同客户的动态控制数据格式和Schema。 Introduction BigTable历经2.5年的光阴,设计果成。 用于结构化数据, 格式后面说。 达成目的,应用范围广泛,扩展能力强,高性能,高可用 成果, BigTable在Google内部已经在几十个项目应用了,如摘要所述。 4.2. 支撑离线任务 上千个 实现上,Bigtable类似一个数据库,又与常规的分布数据库和内存数据库不同 5.2Bigtable 不支持全面的关系数据模型 5.3 数据索引非常灵活 Section2: 介绍Data Model Section3: 客户端的API Section4: 对Google infrastructure的依赖 Section5: Bigtable实现的基础 Section6: 提升Bigtable性能的设计 Section7: 性能衡量 Section8: Google的应用案例 Section9: 踩过的坑 Section10: 相关工作 Section11:总结 Data Model 数据模型上, Bigtable是一个稀疏的、分布式的、持久化的多维度有序map。 map的索引: row key, column key + 时间戳; value,未被翻译的 bytes array (row: string, column: string, time: int64) --> string row key, 表中的row key 可以是任意字符串, 上限为64KB,一般来说约10-100个byte; sorted, 基于row key的字典序 partition, row range 动态分区 tablet, 每段row range 称为 tablet, BigTable的分布单元,读小段的row ranges只需要和几台节点通信,效率很高。 URL的组织技巧,类似于Java中的 package 包名, maps.google.com/index.html 会转为 com.google.maps/htmls, 存储更有效率 Column Families, column keys被分组,编成column families,提供了最基础的访问控制。 同一Column Families存储的列是相同数据类型(便于压缩) CF的创建时机,要在数据存入之前创建 设计意图: 一个表中的CF应控制在几百之内 CF中的列key命名规则, family:qualifier 访问控制,在硬盘和内存级别,按CF进行控制:添加新数据、读取数据、创建衍生的CF. Timestamps 在BigTable中每个Cell都有版本的概念,BigTable用Timestamps -- 用Int64来存储,在现实世界中精度可达毫秒级时间(Sample format 2002-11-15 14:10:13.123456) API BigTable 提供了一系列的操作表和CF的API,以及修改和更新集群的操作。举例: RowMutation 是指行级的更新操作。 Table *T = OpenOrDie("/bigtable/web/webtable"); RowMutation r1(T, "com.cnn.www"); r1.Set("anchor:www.c-span.org", "CNN") r1.Delete("anchor:www.abc.com"); Operation op; Apply(&op, &r1); Bigtable也能执行MapReduce的应用。 我们已经实现了一系列的基于Bigtable输入或输出的MR Job。 Building Blocks Bigtable 用到了Google的GFS来存储日志和数据文件。 Bigtable需要 集群管理系统,能满足任务调度,资源管理及机器状态监控的需要。 Google SSTable是用来存储Bigtable数据文件的文件格式, SSTable是持久化的,有序的不可变map(key和value 均不可变)。 SSTable是由 block sequence 组成, 每个block是64KB(可配置),并且另外设置了Index 文件,用来定位和寻址block。当SSTable被打开时,内存将会加载到内存。 内存遍历会使用对block索引的二分查找,然后将block从磁盘进行读取。 可选地,SSTable也能被完全的加载到内存,可以在不经磁盘的情况下进行查询。 Bigtable用的高可用的分布式服务称为Chubby, Chubby的一篇paper为 The Chubby lock service for loosely-coupled distributed systems . 我会在未来适当的时机再去拜读。 Chubby服务由五个活跃的副本组成:一个用来选举成为master,提供需求承接。 整体服务只要有半数活着即为可用。 Chubby使用了共识性算法Paxos,以保证一致性。 每个目录或者每个文件,都可视作是一个锁,对文件的读和写都得是原子操作。 Chubby可视作是zookeeper的前身。 Bigtable用Chubby完成一系列任务: 确保任何时刻只有一个活跃的master; 存储Bigtable bootstrap的位置 存储Bigtable schema信息(每个表的CF) 存储访问控制列表 如果Chubby不可用,Bigtable也将不可用。 Implementation Bigtable由这三个部分组成: client library,与外部的client连接,缓存一些表的位置 master server,负责将表分配到tablet server,监控tablet server数据的新增、过期废弃,tablet-server的平衡,GFS文件的GC。 除此之外,还负责处理table和CF的元数据变更。 若干tablet servers, 支持动态扩展。 每台tablet server管理一系列的表,处理客户端对table的读和写请求,当table增长得过大时,也负责拆表,每个表控制在100-200MB。 Bigtable的客户端不直接和master做数据层面的通信交换,而是和tablet server进行数据的读和写。 Tablet Location 🤓 Tablet的存储第一级是在Chubby, 包含root tablet(根表)位置; 第二级 根表 包含所有tablets, 用一张特殊的表来存储,即 METADATA table; 每个元数据表存储 user table 分表的所在位置。 METADATA 表 永远不会被拆分(以保证中间层不会裂变)。 第三级 用户表 即用户取用的表。 当client library 查询时发现缓存中没目标表,或者发现缓存位置失效,将会递归向上移动这个表的location。cache空或者信息过期了,有对应的刷新策略。 Tablet Assignment 每个Tablet在同一时间只会分配给一个tablet server. Master会时刻跟踪tablet server的状态,当前tablets的分配情况、未分配状态。 如果一个tablet未被分配,某个tablet server拥有充足的空间,master 会将这个tablet分配给它。 Bigtable用Chubby来跟踪tablet节点, 当一台tablet启动,会获得一个排它性的锁。 这个分布式锁即为tablet Chubby目录下的一个全局唯一的目录。 Master通过监控这个目录来洞察tablet server。 如果tablet server 失去了锁,也就停止对表的服务。 (Chubby提供了能够快速查验当前节点是否还持有这个锁的机制) Tablet节点如果发现自己手上还有锁文件,也会进行重试请求。 当tablet server终止服务,也会放弃锁。 如果Master发现这台Tablet server不能再为某表提供服务,会执行reassign (重分配)。 Master会周期性的底部和绐Tablet节点的锁状态。 当集群系统启动Master,它需要发现当前tablet分配,以便更新它们。 Master会在启动时执行以下步骤: master 在Chubby 获得一个唯一的 master lock , 防止并发的master实例化 master 扫描Chubby中servers目录,以找到正在活跃的server master与每台活跃的server通信,以发现哪些tablet已经被分配了 master扫描METADATA表,来掌握table集合 每当遇到尚未分配的tablet时,master就会将tablet添加到未分配的tablet。 只有在创建或删除一个表时,现有tablet集合才会发生变化,两个现有的tablet合并成为一个大表,或者一个大表拆分成两个小表。 Table Serving tablet的状态持久化使用GFS, 更新的操作写到提交日志(redo records), 近期修改使用内存表memtable, 老的操作则使用 SSTables。 当还原一个Tablet时,tablet server从METADATA表读取这个表的元数据,元数据中包含一系列SSTable,将SSTable内存读到内存,从redo 点位进行重建memtable. 当Tablet server收到写操作时,server先检查格式的有效性。 有效的写操作会被提到的commit log,将很多小型的操作commit编组能提升性能。 当Tablet server收到读操作,会进行类似的格式检查。 对操作作用于 SSTable和memtable的合并视图(mergedview )。 因为SSTable和memtable是按字典序排序的,所以mergeview是很容易形成。 Compactions 规整 minor compaction 因为写操作会造成memtable的增长,当这个内存表达到阈值,需要进行先frozen 冻结, 创建一个新memtable, Frozen memtable再被写到磁盘(GFS的SSTable)。 这样做有两个目的,减少tablet server中内存消耗; 当server挂的时候降低还原的成本。 每个这样的minor compaction 都会创建一个新的SSTable。 如果这种操作不进行限制,读操作可能需要合并来自任意数量的SSTable。 因此我们限制了此类文件的数量 —— 通过执行 merging compaction。 合并的compaction 会对所有SSTable写到一个SSTable, 这个过程称为**major compaction **。 (这个过程很像 MapReduce Mapoutput 的过程) Non-major压缩产生的SSTable可能产生特定的文件,这些文件包含了被删除的条目信息。 而Major-压缩不会包含这些删除信息。 Bigtable会循环遍历所有tablet,并定期对它们使用major-压缩。 这些major-压缩允许Bigtable回收被删除数据使用的资源,也允许它确保被删除的数据及时从系统中消失,这对于存储敏感数据的服务非常重要。 Refinements 为了提供高性能的服务,需要进行一些特殊设计。 Locality groups Client侧对CF成组,称为locality group. Compression Client可以控制 对于locality group 的对应SSTable是否要压缩。 Caching for read performance 提升读的性能。 引入二级缓存机制。 Scan缓存: 高级别缓存,提供Key-value的键值对,返回SSTable接口。 Block缓存:低级别缓存,保存SSTable block的读信息。 Bloom filters 使用Bloom过滤器,用于判定SStable是否包含特定数据。 Commit-log implementation Performance Evaluation ","link":"https://staticor.github.io/post/paper-the-google-file-system-gfs/"},{"title":"Spark数据类型系统","content":"Spark (ScalaDoc) : https://spark.apache.org/docs/3.2.0/api/scala/org/apache/spark/sql/types/IntegerType$.html Spark -SQL datatype : https://spark.apache.org/docs/latest/sql-ref-datatypes.html AbstractDataType 抽象类 所有数据类型的基类 主要继承的子类是DataType. [abstract field] defaultSize: 数据类型的默认size [concrete value]simpleString 类型的字符串表示 原子数据类型 AtomicType AtomicType 也是抽象类, 继承于DataType, 主要继承子类: NumericType DateType BooleanType String Type TimestampTYpe NumericType extends AtomicType NumericType 依旧是抽象类 由两个抽象子类: IntegralType 整数数值的抽象基类, 四个子类 Byte, Short, Integer, Long FractionalType 分数数值的抽象基类 FloatType, DoubleType, DecimalType IntegralType 整数家庭 非抽象类, 继承于IntegralType ByteType: InternalType = Byte defaultSize = 1 simpleString = "tinyint" ShortType InternalType = Short defaultSize = 2 simpleString = "smallint" IntegerType InternalType = Int defaultSize = 4 simpleString = "int" LongType InternalType = Long defaultSize = 8 simpleString = "bigint" FractionalType 分数家庭 FloatType InternalType = Float defaultSize = 4 DoubleType InternalType = Long defaultSize = 8 DecimalType 复合数据类型 ArrayType(elementType, containsNull) 数组元素类型一致. MapType 所有Key的类型要一致,所有Value类型一致. StructType 结构体数据类型. ","link":"https://staticor.github.io/post/spark-shu-ju-lei-xing-xi-tong/"},{"title":"读书Note-《DDIA》","content":"DDIA 是 Designing Data-Intensive Applications 的简称,因为此书具有一定的普遍性和推广意义, 所以在数据计算领域和分布存储,有单独的称呼. 我对这本书基于之前几位社区译者的基础上进行了再次翻译和备注的repo: https://github.com/staticor/ddia 概念与数据模型 应用可划分为数据密集型和计算密集型的,通常数据密集型包括以下组件: 冷数据存储,如数据库,便于其他应用程序跨服务间的延迟访问 热数据存储,如缓存,取用代价昂贵,所以不用数据库 索引设计与即席查询 实时处理流式数据 定期处理的批量数据 衡量密集数据系统的角度: 可靠性 (硬件故障,软件bug,人为操作) 可扩展性(伸缩性,负载均衡, 吞吐量) 易维护性(操作简化, 可迭代升级) 最著名的数据模型是基于实体关系的ER设计,使用SQL来查询 作者以一个LinkedIn-BillGates 这个实体对象的存储设计来对比了SQL与No-SQL(JSON)的差异. json文档自然是非常灵活的,但灵活的背后也会产生生产加工的额外成本. 从信息处理的角度上,或许只是将处理工序安放在哪个团队的问题,本质上没有带来新的突破性优化. 对比概念, 故障vs失败, 可用vs可靠。 容错相对于失败的最大区别是预见到了失败的发生,但并不是考虑到所有类型的错误。 第二章深入探讨了文档数据库与关系型数据的差异。 图数据模型,以后有机会我会考虑学习Neo4j和Cypher。 存储与检索 世界上最简单的数据库, 用两个Bash函数实现. #!/bin/bash db_set () { echo "$1,$2" >> database } db_get () { grep "^$1," database | sed -e "s/^$1,//" | tail -n 1 } 存储系统不光要考虑数据如何存放,还要设计好数据怎样被快速检索. 为了快速的在数据库中,根据key定位到数据的位置,引入了特定的数据结构-索引. 索引是一个"接口"层的逻辑概念,如果被实现有不同的方式: 散列索引, 树结构索引. 索引是主数据(primary data)的派生信息,很多数据库都支持灵活的索引添加和删除操作,也支持查询时指定索引. 自然,好的索引会带来额外的存储开销,所以这里就是存储和查询性能之间的trade-off了. Hash 索引 Hash索引, 容易想到,但局限性也明显,不支持范围查询和带排序分组的查询,只对单值的等值查询效能高. 像Python中的字典,Java中的HashMap就可以视作为一种微观的键值数据库. 如果一直采用Log-append,怎样避免最终用完最终硬盘? 一种解决方案是日志分为特定大小的段(segment),当日志增长到特定大小就关闭当前段,写入一个新段. 然后再针对已关闭的日志段进行压缩管理, 压缩文件再进行合并. 实现细节要考虑的因素还不少: 文件格式, 删除记录,崩溃恢复,部分写入记录,并发控制 为什么不直接在文件更新, 要采用追加写入呢? 追加和分段合并, 都是顺序写入,比随机写入快得多得多. SSTables 和 LSM树 前面的Log-segment设计并没有提到key-value的顺序.在原有基础上做个改变, 要求键值对的序列按key排序. 称这个段文件格式为 SST(Sorted String Table简写),并且每个key只在每个合并的Segment中出现一次. (合并时两个相同key冲突时的解决方案,新key覆盖旧key) 构建和维护SSTables SSTables的核心,怎么事先就让数据有序的组织进Segment? 这就需要先在内存完成有序组织,可以使用平衡树结构, 这个内存组织的树称为内存表(memtable), 当内存表大于某个阈值, 通常是几MB,将它作为SSTable文件写入硬盘, 因为这个树是key-ordering的,所以写到硬盘时,也是排好序的. 这个设计方案的小问题: 如果数据库崩溃,内存表中的数据会丢失(还没写到硬盘). 为避免这个问题,可以在硬盘上保存一个单独日志,记录每个写入操作, 这个操作日志是不要求顺序的,它的目的是解决恢复后replay. 当内存数据写出到SSTable,相应操作日志就可以被丢弃了. 用SSTables制作LSM树 LSM 的由来: 这种索引结构由O'Neil等人描述, 被命名为 Log-Structured Merge-Tree (LSM树), 基于这种合并和压缩排序文件原理的存储引擎通常被称为LSM存储引擎. SSTables何时被压缩和合并,有不同的策略设计,常见的选择是 size-tiered 和 leveled compaction. size-tiered : HBase leveled compaction: LevelDB, RocksDB Cassandra 同时支持这两种. LSM基本思想, 后台有若干SSTables, 执行压缩合并, 在内存区保留一个缓存区,负责最新写入数据. B树 和SStables一样,B树也是Key-ordering, 支持按Key的值查找和范围查询. B树和特殊之处是什么? B树将数据库分解成固定大小的块(block或数据页datapage), 一般为4KB/16KB,并且一次只能读取或写入一个页面. 这种设计也接近底层硬件的组织结构. B树的基本底层写操作是新数据覆写硬盘页面,这个前面的LSM是有差异的(前面的只追加,不修改). 一些操作如果覆写几个不同页面,这是一个危险操作,B树通常会有一个额外的硬盘数据结构: WAL. WAL, Write-Ahead log WAL也称为 redo log, 这是一个仅追加的文件,每个B树修改在数据页操作前,必须写入到该文件. 当数据库崩溃后恢复,这个日志被用来数据恢复. 比较B树和LSM树 LSM写入速度更快,B树读取速度更快. (B树写慢, 不是纯追加的写,而是覆盖写, 而且是两次写, 第一次预写WAL日志, 二次写data page) (LSM读 慢, 必须检查不同数据结构和不同压缩层级的SSTables) 进一步优化, 部值放在索引中 聚集索引, 索引中存储了所有行数据. 非聚集索引,仅在索引中存储数据的引用. 内存存储一切? 事务处理还是分析? 一个企业可能有少则几十个多则上百个数据处理系统:控制商品的商品产品中台,物料库存管理,供应链管理,用户注册管理,交易管理等等. 每个系统都需要有专人维护,这些系统都是独立运行的. 对业务数据的分析需求不能与这些系统作业共用一个系统 ---- 会影响OLTP性能. 在此背景下,数据仓库应运而生, Datawarehouse的建立之初是汇聚多个OLTP系统的数据,完成统计分析需求. 数据仓库和业务数据库有很大的区别,它们的相同点是提供了类SQL的查询接口,但系统构造和设计几乎是完全不同. 例如关系数据库的设计要遵循设计范式, 数据仓库 星形模型与雪花模型 许多数据仓库的设计是公式化的, 比较著名的是用星型维度建模. 事实表是中心,事实表外键关联维度表,解释业务过程中的描述信息. 当某个维度出现了多级维度,例如国家-城市-地区-社区这个四级地址维度,如果都放在一个表是星型维度,会有数据冗余. 如果放在四张表,都用id-parentId关联,这就产生了四级关联,从而将原来的星星变成了像雪花一样的多级散射形态, 这种模式称为雪花建模. 列式存储 事实表的列数少则几十,多则几百上千,所以对数据的逻辑设计和物理设计更高了. 在 OLTP数据库,以行式存储引擎为主,每行表示一个完整的record. 在数据仓库场景,很少用 select * from someTableWithManyColumns , 而更关注的需求涉及到部分列. 列数据压缩, 列格式存储更利于压缩, 各列中的数据类型是一致的, 使用压缩能大大减少磁盘的IO. 列存的相关技术或文章: Google Dremel, HBase, Cassandra. 列存中的排序问题 第四章,Encoding and Evolution 第五章,复制,副本 复制(副本)意味着通过网络连接的多台机器上保留相同数据的副本。 解决服务的单点故障问题,一台机器出了问题,别的机器拥有数据副本,依旧能保证服务的顺利进行。 复制的难点在于数据并不是一陈不变的 —— 数据是动态更新的。 因此需要特殊的设计 主流的变更复制算法: 单主复制。 single leader 多主复制。 multi leader 无主复制。 leaderless 无主 几乎所有的分布式数据库都使用这三种方法之一。 从机制又分为两种:同步复制、异步复制。 leader and follower 领导者与追随者 存储了数据库复制的每个节点称为副本 replica。 如果是多个副本情况,怎样保证他们的数据是一致的? 每一次向数据库的写入操作都需要广播到所有副本上,否则副本就会数据不一致。 常见的策略被称为 基于leader的复制 (也称 主动或被动复制, 或主/从复制)。 一个副本被指定为领导者 leader / master(也称为主库)。 客户端向数据库写入时,它必须将请求发送给领导者,leader将新数据写入本地存储。 其他副本被称为追随者(follower), 也被称为只读副本 read-only replica 、备库。 领导者将新数据写入本地存储,也会将数据变更发送给其它follower,称之为** 复制日志或变更流** (replication log 或 change stream)。 每个follower 从领导者拉取日志, 按相应的处理顺序也在自己本地进行修改。 (基于领导者的复制) 同步复制的优点:从库能保证与所有主库一致的最新数据副本。 缺点,如果同步从库没有响应,主库就无法处理写入操作,主库必须要等,一直阻塞,直到同步副本再次可用。 因此,所有从库设置为同步是不切实际的 —— 任何一个节点的中断都会导致整个系统停滞。 如果在数据库启用同步复制,通常意味着多个从库中,有的是同步复制(一般设置为一个),其他从库是异步复制。 这样保证至少有两个节点是拥有最新数据副本(主库和同步从库)。 这种配置有时也被称为 半同步(semi-synchronous)。 通常是完全异步复制。 链式复制(chain replication) 是同步复制的变体。 主从系统中,任何节点皆有可能宕机,怎样处理节点故障以提升系统的容错? 从库失效,要追赶落后的进度。 主从之间的网络中断、从库crash都会导致从库失效。 从库可以从日志获知最后一个事务。当从库连接到主库后,请求断开期间发生的后续变更。 主库失效,要进行故障转移(failure-over). 故障转换可以手动,也能自动执行。 确认主库有效失效,大多数是用超时(Timeout)来判断,一个节点在一段时间(如30秒、60秒)没响应,就认为它失效(挂了); 怎样选择一个新主库,主库由剩余的副本完成, 有选举算法和指派算法:比如按多数投票选举的方式选择,或者由一个指定的coordinator(协调者节点)指派 某个节点为新leader。 重新配置系统,以启用新的主库。 故障切换中容易出错的场景: 若使用异步复制,新主库可能没有收到老主库宕机前最后的写入操作。 选出新主后,老主重新加入集群,新主在此期间可能收到冲突的写入。 常见解法是丢弃老主未复制的写入。 脑裂(brain-split) 非常危险,两个节点都自以为自己是主库,接受写操作请求,而且没有冲突解决机制,就可能造成数据丢失和损坏。 复制日志 预定日志( WAL, Write-Ahead Log) 逻辑日志复制(基于行) logical 基于触发器的复制 RDB的逻辑日志是以行的粒度描述库表的更新序列: 插入新行,日志包含所有列的新值; 删除的行,日志包含唯一标识能识别被删除的行,通常是表主键; 更新的行,日志包含唯一标识更新的行,以及所有列新值; 如果修改多行,则会生成多条日志记录。 对外部应用,逻辑日志的格式更容易解析。 将数据库的内容发送到外部系统,如复制到数据仓库进行离线分析,或建立自定义索引和缓存,这种技术被称为数据变更捕获(change data capture)。 第六章:分区 分区策略, Hash分区、Range分区。 Hash策略,如Java的hashCode。 要担心的特性: 数据不均衡问题。 分区再平衡。 随时间推移,数据库会有各种变化: 查询吞吐量增加,想要添加更多CPU; 数据集大小增加,想添加更多磁盘和RAM存储它; 机器故障,其他机器需要接管故障机器的责任; 将集群负载从一个节点转移到另一个节点的过程称为再平衡(rebalancing)。 再平衡策略。 hash mod N 实际上,生产环境很少使用这种算法。 如hash(key) = 123456, 如果最初有10个节点, 则这个数据要放在 6号。 模N 方法是当N数量变化时,会产生频繁的rebalance代价。 分布式系统与事务 ","link":"https://staticor.github.io/post/du-shu-note-ddia-designing-data-intensive-applications/"},{"title":"Apache Kafka常见问题","content":"Kafka, 分布式的消息引擎(中间件,消息队列), 提供点对点或发布-订阅,主要处理数据在系统间的流转。 也提供了弱持久化和弱流式计算,但据我了解,多数场景是只把Kafka作为消息传输工具。 为什么要用到消息队列, Kafka作为消息队列有什么特色? 削峰填谷 只考虑数据系统有两个,上游和下游。 当上游数据有突发流量,下游处理会有风险时,消息队列这在中间起到了“削峰“缓冲流量的作用。 解耦和异步通信 消息队列作为一个接口层,解耦重要的业务流程。 消息队列提供了异步处理机制,允许用户把一个消息放入队列,等需要时再处理它们。 Kafka的物理架构 生产者 + 消费者 + 中间有多个broker组成的Kafka集群 + zookeeper( 老版本) broker, 消息代理商的意谓, 这个消息代理,这么理解: Producers,往Broker指定Topic写消息。 Consumers,从Brokers里拉取指定Topic的数据。 Zookeeper在这个过程起什么作用。 分布式协调组件, Kafka用ZK储存meta信息, consumer消费状态, 新版本在淡化ZK。 补充一下controller。 管理协调集群的, 有点类似 resource manager。 Kafka 分区 Partition的分类 Leader 唯一对外服务的副本, 生产者写,消费者读。 Follower 和Leader保持同步的跟随者副本。 AR, 数据分区的所有副本。 ISR, In-Sync Replicas 与Leader同步,不至于太落后的副本(集合)。 参数, replica.lag.time.max.ms OSR, ISR反面 超过阈值后从ISR剔出去的副本, AR=ISR+OSR Kafka为什么这么快? Zero-copy DMAC https://chowdera.com/2021/09/20210919064412301v.html Follower是如何与Leader同步数据 follower要从Leader同步数据: F向L发送同步数据赋诗,包含了F需要的offset 请求序列: 发请求1, 请求2,请求3, 请求4, 表示以最新的请求为准。 超10秒没有发送请求, 或者没有收到请求数据, Follower从ISR剔除队列,降级为OSR。 ISR为空时怎么办? Unclean选举。 为什么Unclean选举会造成数据丢失? 新上任的领导落后Leader太多, 有个gap, 但生产者继续发, 而老leader下线不服务。 新leader和原leader之间的差异就丢了。 数据传输的可靠性 在Producer端, send方法设置 ack的参数 正1, leader接收就算发送成功 负1, 即all 所有副本(ISR中)都收到才算成功 0, fire and shot 发了就不管 重复消费, 幂等性,事务。 丢失, 这里要采用带有回调的send方法, 并且ack要设置为负1. 关于副本的Leader选举细节 介绍介绍。 unclean选择, 是否允许 OSR的能否参加选举。 丢数据: OSR 是落后原Leader进度较多的, 如果它成为leader, spark streaming拿到的 HW 会突破变小。 每个Partition有个Controller进程, 当Leader挂了 Kafka 读写分离 主写从读 分区重分配 kafka-reassign-partitions 本质在于数据复制,增加新的副本,然后进行数据同步,最后再删除老副本。 ","link":"https://staticor.github.io/post/apache-kafka-chang-jian-wen-ti/"},{"title":"不同场景的count(1) vs count(*)","content":"Spark Spark 下 count(1) 与 count(*) 等价。 Spark下 count(*) 在AST Tree层面直接替换为了count(1). 源码: Astbuilder.scala 1830-1850行附近, (Spark版本号是3.1.) Hive MySQL - InnoDB, MyISAM MyISAM 单独维护一个表用于统计 count的表结果。 (注意, 无where条件) ","link":"https://staticor.github.io/post/bu-tong-chang-jing-de-count1-vs-count/"},{"title":"数据结构(一)-线段树与并查集","content":"线段树 - SegmentTree 为什么要使用线段树? —— Range query 一类典型问题,染色问题。 每次在区间 「a,b」中挑选一个颜色进行粉刷。 求经过m次粉刷操作后,区间「i,j」上有多少种颜色? 线段树对区间查询有一些特殊设计,所以也称为区间树。 线段树是一棵二叉树,每个节点表示一个区间。 父节点是两个子节点的超集。 线段树不是完全二叉树,是平衡二叉树 查询的时间复杂度为 O(log N)。 递归创建线段树的过程, buildSegmentTree public class SegmentTree<E> { private E[] data; private E[] tree; private Merger<E> merger; public SegmentTree(E[] arr, Merger merger){ data = (E[]) new Object[arr.length]; for(int i = 0; i < arr.length; i ++){ data[i] = arr[i]; } tree = (E[]) new Object[arr.length * 4]; this.merger = merger; buildSegmentTree(0, 0, data.length-1); } // 在treeIndex 位置创建表示区间为[l...r]的线段树 private void buildSegmentTree(int treeIndex, int l, int r){ if(l == r){ tree[treeIndex] = data[l]; return; }else { int leftTreeIndex = leftChild(treeIndex); int rightTreeIndex = rightChild(treeIndex); int mid = l + (r-l)/2; buildSegmentTree(leftTreeIndex, l, mid); buildSegmentTree(rightTreeIndex, mid+1, r); // 综合两个孩子节点的信息, 使父节点是两个区间的加总结果。 tree[treeIndex] = merger.merge( tree[leftTreeIndex], tree[rightTreeIndex]); } } ``` 注意, 区间合并的归则不一定是求和,也可以是求最大,计数,去重计数。 所以这里要放宽merge操作的逻辑,传入一个融合器接口——merger。 查询函数的递归表示: ```java // 返回区间[queryL, queryR] 的值 public E query(int queryL, int queryR){ if(queryL < 0 || queryR < 0 || queryL >= data.length || queryR >= data.length) throw new IllegalArgumentException("index error"); return query(0, 0, data.length -1, queryL, queryR); } // 以treeId 为根的线段树「L...R」的范围内,搜索子区间[queryL, queryR]的值 private E query(int treeIndex, int l, int r, int queryL, int queryR){ if(l == queryL && r == queryR){ return tree[treeIndex]; } int mid = l + (r-l) /2; int leftTreeIndex = leftChild(treeIndex); int rightTreeIndex = rightChild(treeIndex); if(queryL >= mid + 1){ // 与左子树无关 return query(rightTreeIndex, mid+1, r, queryL, queryR); } else if(queryR <= mid){ // 与右子树无关 return query(leftTreeIndex, l, mid, queryL, queryR); } else { // 与左子树 右子树都有关系 E lResult = query(leftTreeIndex, l, mid, queryL, mid); E rResult = query(rightTreeIndex, mid+1, r, mid+1, queryR); return merger.merge(lResult, rResult); } } ``` 空间复杂度, 4N。 时间复杂度, - 创建树 O(N) - 更新 O(log N) - 查询 O(log N) # 并查集 - Union Find Set ","link":"https://staticor.github.io/post/segmenttree-and-ufset/"},{"title":"Spark的执行计划","content":"A Deep Dive into Spark SQL's Catalyst Optimizer -- Yin Huai https://databricks.com/session/a-deep-dive-into-spark-sqls-catalyst-optimizer A Deep Dive into Query Execution Engine of Spark SQL - Maryann Xue Spark Execution plan 在MySQL的Server会将用户提交的query进行解析和分析优化,转变为执行任务。 在Hive和Spark SQL的工作流程中,也会有类似的过程。 Analysis Unresolved Logical plan Catalog Logical Plan Logical Optimization Optimized Logical Plan Physical Planing Physical plans Cost Model Code Generation 不妨先用一个引子来看看Plan是什么,怎样看到它。 val schemaItems: StructType = StructType(List( StructField("itemid", IntegerType, nullable = false), StructField("itemname", StringType, nullable = false), StructField("price", DoubleType, nullable = false), )) val itemRDD: RDD[Row] = spark.sparkContext.parallelize(Seq( Row(1, "Tomato", 4.5), Row(2, "Potato", 3.5), Row(0, "Watermelon", 14.5), )) val schemaOrders: StructType = StructType(List( StructField("userid", IntegerType, nullable = false), StructField("itemid", IntegerType, nullable = false), StructField("count", IntegerType, nullable = false), )) val orderRDD: RDD[Row] = spark.sparkContext.parallelize(Seq( Row(100, 0 , 1), Row(100, 1, 1), Row(101, 2, 3), Row(102, 2, 8), )) val itemDF = spark.createDataFrame(itemRDD, schemaItems) val orderDF = spark.createDataFrame(orderRDD, schemaOrders) itemDF.createTempView("items") orderDF.createTempView("orders") itemDF.show() orderDF.show() // itemDF.join(orderDF, "itemDF.itemid == orderDF.itemid").show() val x: DataFrame = spark.sql( """ |select items.itemname, sum(1) as pv, sum(orders.count) as totalcount |from items, orders |where items.itemid = orders.itemid |group by items.itemname |""".stripMargin) x.explain() 在控制台能看输出的结果: 其中explain的参数分为三类 -- 无参数类型 x.explain() // 等于 SimpleMode -- Boolean类型 explain(mode=”simple”) which will display the physical plan x.explain(true) // 开为true之后中, 会显示 物理plan+逻辑plan -- String类型: extended / codegen / cost / formatted explain(mode=”extended”) which will display physical and logical plans (like “extended” option) explain(mode=”codegen”) which will display the java code planned to be executed explain(mode=”cost”) 打印出 逻辑计划和统计。 explain(mode=”formatted”) 分为两个section: 物理计划和节点 以formatted为例的输出 == Physical Plan == AdaptiveSparkPlan (14) +- HashAggregate (13) +- Exchange (12) +- HashAggregate (11) +- Project (10) +- SortMergeJoin Inner (9) :- Sort (4) : +- Exchange (3) : +- Project (2) : +- Scan ExistingRDD (1) +- Sort (8) +- Exchange (7) +- Project (6) +- Scan ExistingRDD (5) (1) Scan ExistingRDD Output [3]: [itemid#3, itemname#4, price#5] Arguments: [itemid#3, itemname#4, price#5], MapPartitionsRDD[2] at createDataFrame at MustKnowAboutQueryPlan.scala:44, ExistingRDD, UnknownPartitioning(0) (2) Project Output [2]: [itemid#3, itemname#4] Input [3]: [itemid#3, itemname#4, price#5] (3) Exchange Input [2]: [itemid#3, itemname#4] Arguments: hashpartitioning(itemid#3, 200), ENSURE_REQUIREMENTS, [id=#62] (4) Sort Input [2]: [itemid#3, itemname#4] Arguments: [itemid#3 ASC NULLS FIRST], false, 0 (5) Scan ExistingRDD Output [3]: [userid#12, itemid#13, count#14] Arguments: [userid#12, itemid#13, count#14], MapPartitionsRDD[3] at createDataFrame at MustKnowAboutQueryPlan.scala:45, ExistingRDD, UnknownPartitioning(0) (6) Project Output [2]: [itemid#13, count#14] Input [3]: [userid#12, itemid#13, count#14] (7) Exchange Input [2]: [itemid#13, count#14] Arguments: hashpartitioning(itemid#13, 200), ENSURE_REQUIREMENTS, [id=#63] (8) Sort Input [2]: [itemid#13, count#14] Arguments: [itemid#13 ASC NULLS FIRST], false, 0 (9) SortMergeJoin Left keys [1]: [itemid#3] Right keys [1]: [itemid#13] Join condition: None (10) Project Output [2]: [itemname#4, count#14] Input [4]: [itemid#3, itemname#4, itemid#13, count#14] (11) HashAggregate Input [2]: [itemname#4, count#14] Keys [1]: [itemname#4] Functions [2]: [partial_sum(1), partial_sum(count#14)] Aggregate Attributes [2]: [sum#51L, sum#52L] Results [3]: [itemname#4, sum#53L, sum#54L] (12) Exchange Input [3]: [itemname#4, sum#53L, sum#54L] Arguments: hashpartitioning(itemname#4, 200), ENSURE_REQUIREMENTS, [id=#70] (13) HashAggregate Input [3]: [itemname#4, sum#53L, sum#54L] Keys [1]: [itemname#4] Functions [2]: [sum(1), sum(count#14)] Aggregate Attributes [2]: [sum(1)#46L, sum(count#14)#47L] Results [3]: [itemname#4, sum(1)#46L AS pv#44L, sum(count#14)#47L AS totalcount#45L] (14) AdaptiveSparkPlan Output [3]: [itemname#4, pv#44L, totalcount#45L] Arguments: isFinalPlan=false Process finished with exit code 0 Spark SQL 解析器 - Parser Spark使用 ANTLR(Another Tool for Language Recognition) 4 进行语法的解析. 关于ANTLR4,我还了解不多. 先加到TODO吧. 这部分要重点看 Expressions. LogicalPlan 逻辑计划在执行计划中是承前启后的作用,SparkSqlParser 中AstBuilder执行节点访问地,将语法树的Context节点转换成对应的LogicalPlan节点 , 生成一棵未解析的逻辑算子树(Unresolved LogicalPlan) 第二阶段, 由Analyzer将一系列规则作用在这个Unresolved LogicalPlan上,对树节点绑定各种数据信息, 生成解析后的逻辑算子树 Analyzed LogicalPlan. 第三阶段执行优化器 Optimizer. QueryPlan 是 LogicalPlan的父类, 主要操作分为6个模块: 输入输出,字符串,规划化,表达式操作,基本属性和约束. QueryPlan的方法, output 虚函数 outputSet 封装output返回值, 返回AttributeSet inputSet, 节点输入是所有子节点的输出, 返回值也是 返回AttributeSet produceAttributes 该节点产生的属性; missingInput 该节点表达式涉及的, 但子节点输出不包含的属性 各阶段Plan的角色 Unresolved Logical Plan syntactic field check 语义分析, 生成第一版logical plan 此时的plan 还不会下探到字段级别, 如下面所示。 == Parsed Logical Plan == 'Aggregate ['items.itemname], ['items.itemname, 'sum(1) AS pv#44, 'sum('orders.count) AS totalcount#45] +- 'Filter ('items.itemid = 'orders.itemid) +- 'Join Inner :- 'UnresolvedRelation [items], [], false +- 'UnresolvedRelation [orders], [], false Analyzed Logical Plan 通过Spark Catalog获取Schema信息,包括字段的名称和类型, 参考下面, 会看到RDD的字段名称已经显示出来了。 == Analyzed Logical Plan == itemname: string, pv: bigint, totalcount: bigint Aggregate [itemname#4], [itemname#4, sum(1) AS pv#44L, sum(count#14) AS totalcount#45L] +- Filter ((itemid#3 = itemid#13) AND (price#5 > cast(2 as double))) +- Join Inner :- SubqueryAlias items : +- View (`items`, [itemid#3,itemname#4,price#5]) : +- LogicalRDD [itemid#3, itemname#4, price#5], false +- SubqueryAlias orders +- View (`orders`, [userid#12,itemid#13,count#14]) +- LogicalRDD [userid#12, itemid#13, count#14], false Optimized Logical Plan ? Optimized的rule有哪些? 待补充。 观察Filter算子 在上一个Plan中出现在Join之前, Predicate Pushdown发生在这里。 优化之后,Filter算子下推到了Join内部的流程。 == Optimized Logical Plan == Aggregate [itemname#4], [itemname#4, sum(1) AS pv#44L, sum(count#14) AS totalcount#45L] +- Project [itemname#4, count#14] +- Join Inner, (itemid#3 = itemid#13) :- Project [itemid#3, itemname#4] : +- Filter (price#5 > 2.0) : +- LogicalRDD [itemid#3, itemname#4, price#5], false +- Project [itemid#13, count#14] +- LogicalRDD [userid#12, itemid#13, count#14], false Physical Plan == Physical Plan == AdaptiveSparkPlan isFinalPlan=false +- HashAggregate(keys=[itemname#4], functions=[sum(1), sum(count#14)], output=[itemname#4, pv#44L, totalcount#45L]) +- Exchange hashpartitioning(itemname#4, 200), ENSURE_REQUIREMENTS, [id=#74] +- HashAggregate(keys=[itemname#4], functions=[partial_sum(1), partial_sum(count#14)], output=[itemname#4, sum#53L, sum#54L]) +- Project [itemname#4, count#14] +- SortMergeJoin [itemid#3], [itemid#13], Inner :- Sort [itemid#3 ASC NULLS FIRST], false, 0 : +- Exchange hashpartitioning(itemid#3, 200), ENSURE_REQUIREMENTS, [id=#66] : +- Project [itemid#3, itemname#4] : +- Filter (price#5 > 2.0) : +- Scan ExistingRDD[itemid#3,itemname#4,price#5] +- Sort [itemid#13 ASC NULLS FIRST], false, 0 +- Exchange hashpartitioning(itemid#13, 200), ENSURE_REQUIREMENTS, [id=#67] +- Project [itemid#13, count#14] +- Scan ExistingRDD[userid#12,itemid#13,count#14] AQE(Adaptive Query Execution ) 在上面的计划执行过程中有一行,显示了 AdaptiveSparkPlan isFinalPlan=false 默认情况 AQE是关闭的 参考文章: Laurent Leturgez - medium ","link":"https://staticor.github.io/post/spark-de-zhi-xing-ji-hua/"},{"title":"对比学习--Big Data(二)数据倾斜","content":"什么是数据倾斜(Data Skewing) 数据倾斜的前提是分布处理,本质是Shuffle过程时不同Reducer节点的处理处理不均衡; 现象是在观看各Task运行耗时,发现绝大多数Task在很多时间内完成了,而只有部分任务一直在运行,典型数字是一直卡在了99%。 倾斜的常见场景: 业务数据特性 - Reduce Key分布不均匀 建表时考虑不周 SQL语句导致 业务数据和建表设计,通过样本分布调查, 是能快速确定或者排除。 补充: 除了MR计算, HDFS的distcp, Sqoop数据同步任务也会出现倾斜。 有机会本文再补充上述的倾斜场景 问题定位 关注Stage(Hive或Spark,都有对应的Stage描述) 分析Shuffle过程的启动算子 注意groupBy, reduceBy, combineBy, join, distinct等打散MapRDD的操作时用了哪些key。 找到了倾斜发生现场, 进一步要追源头。 分析产生倾斜的原因 源头与上游表设计 是否输入源头本来就倾斜? 业务特性导致的不均匀; 数据仓库设计导致的不均匀; 提取逻辑 是不是异常Key引起的, 比如有NULL字段、 解决思路:过滤掉NULL值的记录, 或预处理 SQL中的Shuffle处理 Join on 条件用到的key 分析 reduce join , map join group by / count distinct 字段 处理方式 本源操作 局部优化 map端, hive.map.aggr = true, 在map中做部分的聚集操作 map端, hive.groupby.skewingdata = true, 数据倾斜时启用负载均衡 引入随机key 二次mapreduce reduce端; hive.exec.reducers.bytes.per.reducer = 1000000000 Hive 默认每个节点处理1G大小数据。 Reduce Join 前置为Map Join 适用于一个表(或RDD)明显小于另一个的场景。 set hive.optimize.skewjoin = true set hive.skewjoin.key = skew_key_threshold (default ) 内存与任务规模优化: 提高shuffle并行度/ 增加Reducer个数 增加Reducer 的JVM内存 自定义分区 SQL语法优化 count(distinct a) count(1) from grouped_A 拆表查询, 将引起倾斜部分的数据单独处理。 ","link":"https://staticor.github.io/post/dui-bi-xue-xi-big-dataer-shu-ju-qing-xie/"},{"title":"数据结构进阶(一) Java中的HashMap","content":"Java1.8的优化关注点 怎么执行的散列化? Treeify 的时机和要求是什么? 数组中的元素数据至少为64个 链表元素数量至少为8个。 /** * The smallest table capacity for which bins may be treeified. * (Otherwise the table is resized if too many nodes in a bin.) * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts * between resizing and treeification thresholds. */ static final int MIN_TREEIFY_CAPACITY = 64; /** * The bin count threshold for using a tree rather than list for a * bin. Bins are converted to trees when adding an element to a * bin with at least this many nodes. The value must be greater * than 2 and should be at least 8 to mesh with assumptions in * tree removal about conversion back to plain bins upon * shrinkage. */ static final int TREEIFY_THRESHOLD = 8; /** * The bin count threshold for untreeifying a (split) bin during a * resize operation. Should be less than TREEIFY_THRESHOLD, and at * most 6 to mesh with shrinkage detection under removal. */ static final int UNTREEIFY_THRESHOLD = 6; HashMap 和HashTable有什么区别? 多线程下 HashMap不安全, HashTable不允许有空值。 迭代方式,HashTable使用Enumeration, HashMap使用Iterator 扩容方式不同。 HashTable继承于Dictionary, HashMap继承于AbstractMap类。 怎样让HashMap也变得线程安全 Map<Integer, Integer> map = new HashMap<>(); Map<Integer, Integer> syncronizedMap = Collections.synchronizedMap(map); // 返回一个SynchronizedMap // 第二种方式, 使用ConcurrentHashMap ConcurrentHashMap<Integer, Integer> chp = new ConcurrentHashMap<>(); 高并发情况下集合有哪些问题? 第一代线程安全集合类: Vector,HashTable 使用synchronized 修饰了方法; 第二代线程非安全集合类: ArrayList HashMap, 线程不安全,但性能更好。 想要安全怎么办? Collections.synchronized(list); 第三代线程安全集合类: java.util.concurrent.*; ConcurrentHashMap; CopyOnWriteArrayList CopyOnWriteArraySet 注意 不是 CopyOnWriteHashSet 底层多采用Lock锁(, ","link":"https://staticor.github.io/post/java-zhong-de-hashmap/"},{"title":"海量数据的检索和排序问题","content":"引子 有一个1G大小文件,每一行是一个词,词的大小不超过16byte, 内存限制是1M,返回频数最高的100个词。 读取每个词,计算hashCode 按hashCode 取模 5000, 将该值存到 5000个小文件,每个文件是200KB (1G/ 5000) 。 如果某个文件超过1MB(内存大小),进行二次哈希,直到每个文件大小不超过1MB。 对每个小文件,统计每个词的出现频率。 使用Trie 字典树 + 最小堆。 给40亿个不重复的unsigned int 的整数, 没有排序过,然后再给一个数, 如何快速判断这个数是否在那40亿个数当中。 100万个数中找出最大的100个数。(变形: 10亿个Long整数,怎样找到其中最大的10个)。 N log N ? 1000万个数中找出出现频率最高的100个数。 解决大数据问题的思路 裁剪问题,将大问题转为小问题。 数据剪裁的套路: Maping, 最常见的是Hashing,通过散列函数 bitmap通过位数据的特殊实现Partition Filtering, bloom-fiter Tree, 原数据入树, 例如字符串前缀查询 堆, 跳表 ","link":"https://staticor.github.io/post/hai-liang-shu-ju-de-jian-suo-he-pai-xu-wen-ti/"},{"title":"大数据生态","content":" Hadoop Evolution 2002 Nutch project 2003 - 2004 Google Paper (GFS, MapReduce) 2008 Hadoop ——Apache Foundation , top-level project 2011, Hadoop1.0 released 2012, Hadoop2.0 released + YARN 2017, Hadoop3.0 released (Java , MR Optimization) 2020, Hadoop3.1.3 MapReduce是21世纪计算机科学个有里程碑意义的技术革新,将“分治处理”应用到了大型数据处理领域。 网页爬取 日志处理 ... ... 恰好它诞生在web2.0 爆发的时期的互联网广告领域,进而影响了后面的移动互联网。 同时,MR的使用前提也需要新的文件系统。 HDFS HDFS是主从架构, 主为NameNode,从为一些DataNode,主存储了集群的数据元信息,帮助应用程序锁定位置,从是负责存储和计算的节点,并通过心跳和主节点汇报自己的健康状态和block信息。 一般集群的建议是DataNode数量小于5K。 SNN,Secondary NameNode 周期性的完成对NameNode的Editslog和FsImage的合并,减少Editslog大小。 Block(数据块)的放置策略 第一个副本放在上传文件的所在DataNode 第二个副本放在另一个rack上的节点 第三个副本放在与2号副本相同rack的另一台节点上 第四个副本: 随机。 Hadoop MR计算框架的一丿 —— WordCount // Mapper public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable> { private final statck IntWritable one = new IntWritable(1); private Text word = new Text(); public void Map(Object key, Text value, Context context) throws IOException, InterruptedException { StringTokenizer itr = new StringTokenizer(value.toString()); while (itr.hasMoreTokens()) { word.set(itr.nextToken()); context.write(word, one); } } } // Reducer public static class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWriterable> { private IntWritable result = new IntWritable(); public void Reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable val: values) { sum += val.get(); } result.set(sum); context.write(key, result); } } // Job entry public static void main(String[] args)throws Exception { Configuration conf = new Configuration(); Job job = Job.getInstance(conf, "word count"); job.setJarByClass(WordCount.class); job.setMapperClass(TokenizerMapper.class); job.setCombinerClass(IntSumReducer.class); job.setReducerClass(IntSumReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); FileInputFormat.addInputPath(job, new Path(args[0])); FileOutputFormat.addOutputPath(job, new Path(args[1])); System.exit(job.waitForCompletion(true)?0:1); } 计算如何向数据移动? 在MR计算框架中,有几个角色: 客户端应用, 任务清单(Split切片信息) split 和block 的对应关系 由NameNode存储映射 客户端要把自己的计算逻辑移动到存有block的DataNode上, 客户端任务有一个统筹管理者 —— ApplicationMaster JobTracker(资源管理+任务调度 TaskTracker(任务管理 资源汇报) 计算逻辑由客户端上传到HDFS, 副本数大于3; Spark 随着Hadoop的推广和普及,MR的性能问题也成为了诟病的地方。 MR 的问题: 操作算子单一,只有Map 和Reduce两种,开始繁琐,特别是对于多表Join; Shuffle(Map与Reduce的中间过程)必须要落盘,磁盘开销大,严重影响任务的性能; LowAPI, MR 在Hive提出之前,开发门槛很高; Spark提供了Python (PySpark),Spark SQL, 以及 Spark MLlib, 对于开发和应用上,非常友好。 Spark提供了丰富的操作算子: filter, join, flatMap, union 等。 逻辑上将代码分为逻辑处理层和物理执行层,逻辑上将应用解析成一个DAG(有向无环图),定义数据及数据间的操作关系。 每个action 定义一个job。 每次shuffle过程会创建一个新的Stage。 Spark的Shuffle Spark 的WordCount object WordCount { def main(args: Array[String]): Unit = { val conf = new SparkConf().setAppName("WordCount").setMaster("local[*]")) val spark = SparkSession.builder().config(conf).getOrCreate() import spark.implicits._ val count = spark.read.textFile("input.txt") .flatMap(_.split(" ")) .Map(s => (s, 1)) .rdd .ReduceByKey((a, b) => a + b) .collect() } } 下一站, 湖仓一体 ","link":"https://staticor.github.io/post/da-shu-ju-sheng-tai/"},{"title":"Spark 窗口查询","content":"相关代码: https://github.com/staticor/WindowQueryInSparkHive.git 什么是窗口查询 示例1 PARTITION BY country ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 示例2 PARTITION BY country ORDER BY date ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING 示例1的Spark链式实现 Window.partitionBy("country").orderBy("date").rowsBetween(Window.unboundedPreceding, Window.currentRow) 示例2的Spark链式实现 Window.partitionBy("country").orderBy("date").rowsBetween(-3, 3) 如果不引入partition by , 那将是全局排序。 用到了partition by 就会针对指定的列划分子窗口。 一般来说会对窗口执行 order by 排序算子。 定义窗口由两个类实现: Window.scala, WindowSpec.scala rowsBetween 开窗上下边界有这几种情形: unbounded preceding 输入数据第一行 unbounded following 输入数据最后一行 current row ( 即整数0) 当前行 如果是传入的整数, 正整数或负整数,对应的是间隔偏移量,表示的即为一个滑动窗口; 比如-3 表示当前行之前的3行,+4 表示接下来之后的4行, [-3, 4] 这是一个包括左右端点的窗口,一共有8条数据的滚动窗口。 WindowSpec的核心构造函数是 rowsBetween 源码 (WindowSpec.scala, rowsBetween) def rowsBetween(start: Long, end: Long): WindowSpec = { val boundaryStart = start match { case 0 => CurrentRow case Long.MinValue => UnboundedPreceding case x if Int.MinValue <= x && x <= Int.MaxValue => Literal(x.toInt) case x => throw QueryCompilationErrors.invalidBoundaryStartError(x) } val boundaryEnd = end match { case 0 => CurrentRow case Long.MaxValue => UnboundedFollowing case x if Int.MinValue <= x && x <= Int.MaxValue => Literal(x.toInt) case x => throw QueryCompilationErrors.invalidBoundaryEndError(x) } new WindowSpec( partitionSpec, orderSpec, SpecifiedWindowFrame(RowFrame, boundaryStart, boundaryEnd)) } 窗口的属性 partitionSpec orderSpec SpecifiedWindowFrame SpecifiedWindowFrame 是一个case class。 case class SpecifiedWindowFrame(frameType: FrameType, lower: Expression, upper: Expression) extends WindowFrame partitionSpec 和 orderSpec 是把数据分桶和桶内排序, 第三部分是确定窗口范围。 WindoSpecDefinition 执行计划分析 import spark.implicits._ import org.apache.spark.sql.functions._ val df: DataFrame = Seq((1, "a"), (1, "a"), (2, "a"), (1, "b"), (2, "b"), (3, "b")) .toDF("id", "category") val byCategoryOrderedById = Window.partitionBy("category") .orderBy("id") .rowsBetween(Window.currentRow, 1) val frame: DataFrame = df.withColumn("sum", sum("id") over byCategoryOrderedById) 输出结果 ================ analyzed ==================== Project [id#7, category#8, sum#12L] +- Project [id#7, category#8, sum#12L, sum#12L] +- Window [sum(id#7) windowspecdefinition(category#8, id#7 ASC NULLS FIRST, specifiedwindowframe(RowFrame, currentrow$(), 1)) AS sum#12L], [category#8], [id#7 ASC NULLS FIRST] +- Project [id#7, category#8] +- Project [_1#2 AS id#7, _2#3 AS category#8] +- LocalRelation [_1#2, _2#3] ============= optimized Plan================= Window [sum(id#7) windowspecdefinition(category#8, id#7 ASC NULLS FIRST, specifiedwindowframe(RowFrame, currentrow$(), 1)) AS sum#12L], [category#8], [id#7 ASC NULLS FIRST] +- LocalRelation [id#7, category#8] ============= executed Plan================= AdaptiveSparkPlan isFinalPlan=false +- Window [sum(id#7) windowspecdefinition(category#8, id#7 ASC NULLS FIRST, specifiedwindowframe(RowFrame, currentrow$(), 1)) AS sum#12L], [category#8], [id#7 ASC NULLS FIRST] +- Sort [category#8 ASC NULLS FIRST, id#7 ASC NULLS FIRST], false, 0 +- Exchange hashpartitioning(category#8, 200), ENSURE_REQUIREMENTS, [id=#52] +- LocalTableScan [id#7, category#8] Exchange hashpartitioning 实际例子 row_number import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan import org.apache.spark.sql.execution.SparkPlan import org.apache.spark.{SparkConf, SparkContext} import org.apache.spark.sql.{DataFrame, SparkSession} import org.apache.spark.sql.expressions.Window import org.apache.spark.sql.functions._ object Example_4_Spark { def main(args: Array[String]): Unit = { val conf: SparkConf = new SparkConf().setMaster("local[4]") val spark: SparkSession = SparkSession.builder().config(conf) .appName("CsvExample") .master("local").getOrCreate() import spark.implicits._ val scoreDF = spark.sparkContext.makeRDD(Array( Score("a", 1, 80), Score("b", 1, 78), Score("c", 1, 95), Score("d", 2, 74), Score("e", 2, 92), Score("f", 3, 99), Score("g", 3, 99), Score("h", 3, 45), Score("i", 3, 55), Score("j", 3, 78))).toDF("name","class","score") scoreDF.createOrReplaceTempView("score") scoreDF.show() println("//*************** 求每个班最高成绩学生的信息 ***************/") println(" /******* 开窗函数的表 ********/") spark.sql("select name,class,score, rank() over(partition by class order by score desc) rank from score").show() println(" /******* 计算结果的表 *******") spark.sql( """ |select name, class, score, rk |from ( | select name, class, score, rank() over(partition by class order by score desc) rk | from score |) mid where rk = 1 |""".stripMargin).show() } } case class Score(name: String, group: Int, score: Int) ","link":"https://staticor.github.io/post/spark-chuang-kou-cha-xun/"},{"title":"对比学习--Big Data(一)","content":"💚 数据仓库 vs (关系型)数据库 关系型数据库, 面向事务 OLTP 数据库, 信息的持久化,提供了库表字段设计的规范和约束,同时有索引机制,大大加速了查询的性能。 解决的是事务操作,当业务发生时,需要把这一业务事件永久的记录下来,以便后续调用查验信息。 (数据库前身, 本地结构化文件) 定位:数据库聚焦存储本业务关心的数据 建设:业务过程中产生的实体表,动作表 数据范围: 面向Application 或Service 数据类型: 主要是明细数据 设计: 尊重数据库设计的范式规范, 表和表的关系使用外键连接。 计算场景:复杂查询较少。 通常是判断一条数据是否存在, 更新或插入一条数据的值 从关系型数据到数据仓库演变 MPP? Greenplum(基于PostgreSQL) 传统数仓之外的补充,纯为OLAP解决。 数据仓库,面向分析 OLAP 定位: 满足公司级全面分析和数据计算的需求 建设: 面向主题subject的。 放什么范围的数据: 业务分析需要, 会集成所有数据库的数据; 同时包括关系数据库之外的信息, 日志采集系统,爬虫系统,离线上传数据, etc 放什么类型的数据: 既包括明细数据,也包括汇总型数据, 设计:用空间换时间的思想。 反范式 计算场景:查询复杂,多表关联是必要过程, 借助大数据 存储: 使用Hadoop分布式 Hadoop vs. Spark 关键点: Hadoop的Shuffle必须要写到磁盘, vs Spark 的Shuffle(不一定落盘,cache到内存); 编程API,MapReduce提供的是Low API, Spark拥有RDD和DataFrame的灵活算子。 JVM优化, Hadoop MR 启动一个Task就会启动一次JVM,基于进程的操作。 Spark每次MR操作是基于线程,启动Executor时启动一次JVM 定位 Hadoop 由 HDFS, Common,YARN和MapReduce 几个模块组成, 开源的大数据处理和分析框架。 HDFS负责存储, YARN是资源管理和调度,MapReduce负责具体的计算和数据处理。 Spark是计算引擎,无存储。 由Spark Core, Spark SQL, Spark Streaming, Spark MLib, Spark Graphx组成。 Hadoop解决的是批计算任务,适合于一定延迟但数据量巨大的任务; Spark则是批处理,相对来说数据任务的Dataset要少一些。 同时Spark Streaming能处理微批计算,能解决伪实时的分析需求。 性能差异 MapReduce执行过程中会有频繁的磁盘传输,Sprak把中间结果存储在内存中,减少磁盘IO,所以性能上要比MR提升明显。 编程模型 MR任务经历了几个阶段的开发模型。 传统MR任务是使用Java开发Mapper, Reducer任务。 后来用Hadoop Streaming,可以用Shell Python 等脚本实现MR, 之后Hive 引入了SQL,可以让用户用SQL的方式完成MR任务。 大大降低了MR任务的开发门槛。 Spark 提供了RDD和DataFrame,内嵌了丰富的算子。 语言支持 Hadoop Java, Spark是Scala, 但也支持Java。 Spark提供了Python R的扩展。 解释RDD的弹性: 存储弹性,内存和磁盘的切换; (RDD封装的是计算逻辑, 并不保存数据) 容错: 数据丢失可自动恢复; 分片弹性,可根据需要重新分片; 计算弹性:计算出错的重试机制。 Kafka vs. Flume Kafka为什么快? DMAC + Zero Copy 常规的实现,读磁盘中的文件,将之加到内存,再通过Socket把它发到网络中。 File.read(fileDesc, buf, len); Socket.send(socket, buf, len); 四次传输: DMA实现: 从硬盘,把文件读到内核缓冲区; CPU搬运:从内核缓冲区,复制到分配的内存; CPU搬运:从内存,写到Socket缓冲区; DMA搬运:从Socket缓冲区,写到网卡缓冲区。 Kafka 干得就是数据搬运的工作,自然要在这个流水线下了大力气优化。 优化后的成果,是将上面的四次搬运缩减为两次 —— 只有首尾的第一次和第四次,跳过了CPU工序。 因为跳过了中间的复制数据环节,所以这个过程也称为 “零拷贝”(Zero copy) // 调用 Java NIO 的 transferTo 方法 @Override public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException { return fileChannel.transferTo(position, count, socketChannel); } Kafka: broker 组成的Kafka集群作为服务者,处理生产者(Producer)和消费者组(Consumer Group)的发送消息和消费消息 ","link":"https://staticor.github.io/post/compare-study/"},{"title":"Hive的执行计划","content":"Hive的SQL是怎么转变成的MapReduce Driver端, 将HQL语句转换为AST; 借助于 ParseDriver: 将HQL语句转为Token , 对Token解析生成AST; 将AST转换为TaskTree; SemanticAnalyzer, AST转换为QueryBlock 将QB转换为OperatorTree 将OT进行逻辑优化, 生成TaskTree TaskTree执行物理优化 提交任务执行 (ExecDriver) Hive 的执行入口类 主类 CliDriver run() 方法 执行executeDriver processCmd processLocalCmd qp.run(cmd) runInternal 其中 qp 是Driver对象, Driver端的runInternal 方法, 调用了compile 完成了语法的解析和编译. compileInternal 编译 编译器, 解析器, 优化器 在这步 AST 在这里生成 compile方法中, 使用ParseUtils.parse 完成对命令的解析,生成一棵AST树,代码如下: ASTNode tree; // AST 树 tree = ParseUtils.parse(command, ctx); ... HiveLexerX lexer = new HiveLexerX(new ANTLRNoCaseStringStream(command)); TokenRewriteStream tokens = new TokenRewriteStream(lexer); Hive 使用ANTLR 完成SQL的词法解析, 在老版本的Hive中, 因为语法非常简单, 只用一个文件 Hive.g 完成,随着语法规则越来越复杂, 目前为拆分成了5个文件: 词法规则 HiveLexer.g 语法规则 四个文件: SelectClauseParser.g FromClauseParser.g IdentifiersParser.g HiveParser.g AST转为Operator Tree Implementation of the semantic analyzer. It generates the query plan. There are other specific semantic analyzers for some hive operations such as DDLSemanticAnalyzer for ddl operations. 优化器 // SemanticAnalyzer.java Optimizer optm = new Optimizer(); 轮询Optimizer中的优化策略 // Optimizer.java // Invoke all the transformations one-by-one, and alter the query plan. public ParseContext optimize() throws SemanticException { for (Transform t : transformations) { t.beginPerfLogging(); pctx = t.transform(pctx); t.endPerfLogging(t.toString()); } return pctx; } 这个Transform 就是优化策略的抽象类, 例如GroupByOptimizer 是有条件地执行map侧计算,减少shuffle IO. This transformation does group by optimization. If the grouping key is a superset of the bucketing and sorting keys of the underlying table in the same order, the group by can be performed on the map-side completely. execute() 执行 执行器 Hive- Explain 最简单的Explain计划例子 只有一个Stage, 即Stage-0, 而且是一个Fetch Operator, limit为负1 表示没有Limit操作. Processor树下面只有一个TableScan, 是对表名 logs的操作. 统计信息能看到 一共的行数,(Num rows), 数据尺寸(Data size). Select算子下面是执行的扫列, 因为执行了select *, 所以全部列的schema都展示出来了. 一个稍稍复杂的例子 再来看一个带有 分组聚合查询(count + count distinct)的例子 执行语句是 hive> explain extended select ip , count(distinct id) uv, count( doop) pv from logs group by ip; 阶段划分为2个了, Stage 1是 根阶段, Stage 0 依赖于Stage1 Stage1是一个Map Reduce阶段,分为 Map树 Map过程也是要执行TableScan, 扫的logs表-行数列数, 执行 Select, 只对 ip id 和 doop 三列扫. GroupBy 算子 STAGE DEPENDENCIES: Stage-1 is a root stage Stage-0 depends on stages: Stage-1 STAGE PLANS: Stage: Stage-1 Map Reduce Map Operator Tree: TableScan alias: logs Statistics: Num rows: 2762962 Data size: 828888768 Basic stats: COMPLETE Column stats: NONE GatherStats: false Select Operator expressions: ip (type: string), id (type: string), doop (type: string) outputColumnNames: ip, id, doop Statistics: Num rows: 2762962 Data size: 828888768 Basic stats: COMPLETE Column stats: NONE Group By Operator aggregations: count(DISTINCT id), count(doop) keys: ip (type: string), id (type: string) mode: hash outputColumnNames: _col0, _col1, _col2, _col3 Statistics: Num rows: 2762962 Data size: 828888768 Basic stats: COMPLETE Column stats: NONE Reduce Output Operator key expressions: _col0 (type: string), _col1 (type: string) null sort order: aa sort order: ++ Map-reduce partition columns: _col0 (type: string) Statistics: Num rows: 2762962 Data size: 828888768 Basic stats: COMPLETE Column stats: NONE tag: -1 value expressions: _col3 (type: bigint) auto parallelism: false Path -> Alias: file:/Users/staticor/tmp/hive/warehouse/shopee.db/logs [logs] Path -> Partition: file:/Users/staticor/tmp/hive/warehouse/shopee.db/logs Partition base file name: logs input format: org.apache.hadoop.mapred.TextInputFormat output format: org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat properties: bucket_count -1 column.name.delimiter , columns id,user_id,ip,doop,ttr columns.comments columns.types bigint:bigint:string:string:string file.inputformat org.apache.hadoop.mapred.TextInputFormat file.outputformat org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat location file:/Users/staticor/tmp/hive/warehouse/shopee.db/logs name shopee.logs numFiles 1 numRows 0 rawDataSize 0 separatorChar serialization.ddl struct logs { i64 id, i64 user_id, string ip, string doop, string ttr} serialization.format 1 serialization.lib org.apache.hadoop.hive.serde2.OpenCSVSerde totalSize 828888758 transient_lastDdlTime 1653360838 serde: org.apache.hadoop.hive.serde2.OpenCSVSerde input format: org.apache.hadoop.mapred.TextInputFormat output format: org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat properties: bucket_count -1 column.name.delimiter , columns id,user_id,ip,doop,ttr columns.comments columns.types bigint:bigint:string:string:string file.inputformat org.apache.hadoop.mapred.TextInputFormat file.outputformat org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat location file:/Users/staticor/tmp/hive/warehouse/shopee.db/logs name shopee.logs numFiles 1 numRows 0 rawDataSize 0 separatorChar serialization.ddl struct logs { i64 id, i64 user_id, string ip, string doop, string ttr} serialization.format 1 serialization.lib org.apache.hadoop.hive.serde2.OpenCSVSerde totalSize 828888758 transient_lastDdlTime 1653360838 serde: org.apache.hadoop.hive.serde2.OpenCSVSerde name: shopee.logs name: shopee.logs Truncated Path -> Alias: /shopee.db/logs [logs] Needs Tagging: false Reduce Operator Tree: Group By Operator aggregations: count(DISTINCT KEY._col1:0._col0), count(VALUE._col1) keys: KEY._col0 (type: string) mode: mergepartial outputColumnNames: _col0, _col1, _col2 Statistics: Num rows: 1381481 Data size: 414444384 Basic stats: COMPLETE Column stats: NONE File Output Operator compressed: false GlobalTableId: 0 directory: file:/Users/staticor/tmp/hive/9ae192bf-f90d-4427-ba16-54d05ac3c8a3/hive_2022-05-29_16-51-04_737_1249285988661867765-1/-mr-10001/.hive-staging_hive_2022-05-29_16-51-04_737_1249285988661867765-1/-ext-10002 NumFilesPerFileSink: 1 Statistics: Num rows: 1381481 Data size: 414444384 Basic stats: COMPLETE Column stats: NONE Stats Publishing Key Prefix: file:/Users/staticor/tmp/hive/9ae192bf-f90d-4427-ba16-54d05ac3c8a3/hive_2022-05-29_16-51-04_737_1249285988661867765-1/-mr-10001/.hive-staging_hive_2022-05-29_16-51-04_737_1249285988661867765-1/-ext-10002/ table: input format: org.apache.hadoop.mapred.SequenceFileInputFormat output format: org.apache.hadoop.hive.ql.io.HiveSequenceFileOutputFormat properties: columns _col0,_col1,_col2 columns.types string:bigint:bigint escape.delim \\ hive.serialization.extend.additional.nesting.levels true serialization.escape.crlf true serialization.format 1 serialization.lib org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe serde: org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe TotalFiles: 1 GatherStats: false MultiFileSpray: false Stage: Stage-0 Fetch Operator limit: -1 Processor Tree: ListSink Time taken: 0.159 seconds, Fetched: 123 row(s) hive.fetch.task.conversion=more 控制哪些查询走MapReduce none, 所有查询都走MR; more, 默认Level, 查询字段,分区的where, limit 时间戳, 虚拟字段 minimal, 查询字段, 分区的where, limit 不走MR 一个简单的count (distinct ) + sum 的单表执行计划: Stage 0 依赖于Stage1, Stage1 执行了MapReduce, Map阶段执行操作: 表扫描, 字段映射Project(Select Operator) 聚合 ","link":"https://staticor.github.io/post/hive-de-zhi-xing-ji-hua/"},{"title":"林群院士-关于微积分的演讲内容","content":"内容来自于一个视频: 林群院士|教科书讲得太复杂,学微积分只需要一个案例|格致 林群院士简介: 林群:1935年出生,福建连江人。中国科学院数学与系统科学研究院研究员、中国科学院院士。 在计算数学,特别是微分方程的高性能解法方面,进行了长期深入研究。 他热爱科普和教育事业,著有《画中漫游微积分》《微分方程与三角测量》《微积分快餐》等科普读物,被评为2019年十大科学传播人物。 数学太多了,我们可能全都掌握它。 挑一些最重要的,比如小学的算术,中学的代数相当于算术的发展,这是人类几千年的财富。 中学完了的大学,主要学线性代数和微积分,这是最近几百年的知识财富。 给在场孩子家长的一起学习数学的建议: 多则少 (Less is More) 不要学得很多,不要参加各种活动,但对学习来说,要记住多则少。 微积分是一切高级数学的基本功 —— 邱成桐 假传万卷书,真传一案例。 找真传,不要读很多书。 我是很笨的人,读不了很多书。 今天我就是要从哲学的角度,读学习方法。 微积分应该用什么案例? 我们经常坐高铁,在高铁车厢里会有一个屏幕显示当前的速度,这个速度怎么来的? 高铁在某一时刻的时速,或瞬时速度。 瞬时,即时间很短,趋近于零,零比零的问题。 牛顿是怎么计算瞬时速度的呢? 取一个“很短的时间”,在这个时间段产生的路程小变化 短时间积累的和就是总时间,形成了积分。 小学数学,中学数学,再到大学数学,是把一些称呼变化了,相加变成了积分。 以微积分来说,这个瞬时速度问题是个经典的案例。 我们现在的教科书,容我做一点批评, 繁琐教学。 我们首先给学生讲道理和发明,然后再将定理和证明。 当我们把道理弄清理,定理就自然出来了,比如刚刚这个微积分,如果把瞬时速度这个案例搞清楚,定理证明只用几行出来了。 现在的书上,不讲道理,只讲定理;不讲发明,只讲证明,这是学习上很忌讳的事。 我希望数学文化应该改变一下,将数学变成讲道理和讲发明的科学。 牛顿, 在乡间,不在学校,不忙碌的时候,思考才能创造出来。 现在我们的活动太多了,我认为家长要改变这样的做法,让孩子有时间去思考去创造。 考试当然了不起,我接触到了一些做题能手,但他们做久题之后就不知道解决什么问题(成了一种做题机器)。 我希望各位同学把自己看成一个平凡的学生,但我们要知道正确的学习方法。 希望用一个案例顶万卷书,不要图多,结果什么都懂,什么都不懂。 ","link":"https://staticor.github.io/post/lin-qun-yuan-shi-guan-yu-wei-ji-fen-de-yan-jiang-nei-rong/"},{"title":"「数据仓库技能树」--Hive Tuning","content":"what and why Tuning: 逻辑正确,但实现消耗的资源超出预期。 资源: 任务运行时间, 占用集群的计算资源(Memory&Cores) 解决问题,促使Tuning的契机 —— 对现行任务,虽然能顺利执行成功,但对效果希望进行改善。 当和内行人交流Hive Tuning(也是很多大数据-数据仓库岗位时会聊的一个话题),至少胸中要懂的一些场景和策略,不要眉毛胡子一把说,没有章法。 业务逻辑,如果这个任务不是自己是第一手的开发者,那么半路介入,需要先了解业务背景。 SQL语句的框架,细节有无优化空间。 输入信息: 任务执行日志,查询计划详情 嗅探点: 源数据(source) 表的格式, 比如orc 是有提升的 加载数据规模; 扫描分区个数; 元数据MySQL 负载状况(HiveServer2 Performance) 执行任务个数,单个任务消耗时间 使用的权限&锁,引起的问题 内存管理, Java堆内存 当多人操作同一数据时,可能会看到A在注入(修改)数据,B在写入数据。 概率较低, 看日志根据关键字定位。 语句分析 减少和业务需求无关的不必要操作, 例如没必要的全局排序: order by vs. sort by 避免使用order By, Order By 会使用一个全局Reducer进行整体排序 —— cause a performance bottleneck, setting the number of reducers to one, making it very inefficient for large datasets. 人工列剪裁 减少不必要的列扫描, 即不要使用 select * 人工谓词下推 先filter 再join 开启Map端聚合参数 Group By 分析执行计划 cost-based-optimizer (CBO) CBO可以给出 joins的执行顺序、并行度,量化执行任务的开销。 设置参数: set hive.cbo.enable=true; query vectorization 参数: hive.vectorized.execution.enabled 设置为true https://docs.cloudera.com/documentation/enterprise/6/6.3/topics/hive_query_vectorization.html#enabling_hive_query_vectorization 内存与Reducer个数优化 set hive.exec.reducers.max=8; set mapred.reduce.tasks=8; Join细节 大表与大表的join 之 空Key 如果某个join条件中引入了 这个空Key 处理方式, 分析业务是否能过滤掉空值,如果不能过滤,将Null 修饰为一个有效值, 或者随机值。 **要特别注意的是, 这个随机值不能与第二个表发生真实join, 否则产生错误结果。 ** -- 原sql select count(a.uid) from A left join B on A.uid = B.uid2 -- 过滤空值 select count(a.uid) from ( select uid from A where uid is not null ) A left join B on A.uid = B.uid2 -- 随机join select count(a.uid) from A left join B on nvl(A.uid, rand()) = B.uid2 join 时的倾斜问题 + groupby 倾斜 SET hive.optimize.skewjoin=true; --If there is data skew in join, set it to true. Default is false. SET hive.skewjoin.key=100000; --This is the default value. If the number of key is bigger than this, the new keys will send to the other unused reducers. groupby 时的倾斜 set hive.groupby.skewindata= false; (默认 false) 开启 true 将会实现负载均衡, 多开启 map 任务. 大表的 distinct 操作 count(distinct xx) 使用一个 reducer 计算l 等效的操作是 group by xx 再 count 但对 reducer 要求来说是不同的. 强制指定 reducer 数量 : set reducer.num 大表与小表的join 之 mapjoin map join 自动开启 hive.auto.convert.join 默认是true 看执行计划, 如果设置了该参数, 在计划中会出现一个 “Map Local Tables” —— 将小表作为本地缓存。 如果不设置, 小表是正常的MapReduce Stage。 大表小表的顺序是没有关系的。 对于大表与小表的Join能否使用 mapjoin, Hive默认大小25MB修改。 0: jdbc:hive2://localhost:10000/> set hive.mapjoin.smalltable.filesize; set hive.mapjoin.smalltable.filesize=25000000 这里 join是默认的inner join, 如果是left join 则要注意了。 left join , map join不会生效。 Stage整体 提升并行度 set hive.vectorized.execution.enabled=true; set hive.exec.parallel=true; set hive.exec.parallel.thread.number=8; 如果查询无依赖的,可以转为并行运行。 如果集群当前的利用率已经很高,开这个参数提效果不明显。 参考资料: CDH - Tuning Apache Hive in CDH ","link":"https://staticor.github.io/post/hivetuning0520/"},{"title":"Spark Tuning","content":"文章主题, Spark调优。 Spark 任务要避免成为集群的运转负担,包括不合理的占用CPU、带宽和内存资源。 官方的Tuning文档 Tuning Spark 文档介绍了调优的三个思路: Data Serialization 数据序列化 Memory Tuning 内存调优 其它考量 数据序列化 分布计算中数据的序列化非常重要. Java Serialzation, 自定义类需要继承 java.io.Serializable 要有public的构造方法, 所有属性是public, POJO 类. Kryo Serialization, 支持的范围比 Java Serialization 少, 但能支持的场景性能要高. 设置时机是在SparkConf环节: conf.set("spark.serializer","org.apache.spark.serializer.KryoSerializer") 在Spark2.0.0 之后, 使用Kryo对简单数据类型进行序列化。 内存优化 一直以为,笔者是很少关注内存的优化细节,一般用的都是别人提供的参考参数列表,对一半以上参数的调参方式也不太了解。 当出现调优的工作需求时,往往就要被动学习了。 内存使用的优化要考虑哪些问题? 你的对象占用多大的内存空间, 能否将dataset全部加载到内存中? 访问这些对象的开销是什么 GC(垃圾回收)的消耗 Java对象的特点, 每个Java对象都拥有自己专属的`对象头信息 , 大约16个byte, 包括 mark world (锁, hashcode), klass pointer 指向自己的类指针, 对于特别小的object, 这个对象头可能比Instance data的尺寸还大. Java String有额外的40bytes大小,要存储 String作为一个char数组的额外信息(长度,编码等). 例子,一个10个character的字符串,实际占用的空间可能至少要60bytes. 一般集合对象(Hashmap, LinkedList等),会对存储元素进行装饰(比如 Map.Entry), 每个对象除了对象头信息,还有指向下一个对象的指针. 存储原子类型的集合,会存储它们的装箱类(int - > java.lang.Integer) Spark内存管理 Spark将内存划分为两类, execution执行区 和 storage存储区, 执行区内存用来Shuffle, Join, Sort和Aggregation, 存储区用于缓存\\ 集群间的对象传递. 在Spark1.6之后使用统一内存管理模型,废弃了老版的静态内存管理机制, 使用统一内存管理, 即 执行+存储共用一片区域, 称为 "M" Spark官方文档-Tuning Spark 保留内存占用300MB, 余下的空间 : Execution Memory + StorageMory 是干活和存储内存, 共占用空间是 spark.memory.fraction 默认值0.6。 Storage 的占比由 另一个参数 spark.memory.storageFraction控制 默认值0.5 User Memory , 占用空间是 【1 - spark.memory.fraction】 所谓的内存管理主要是关注的上面的 Execution内存和Storage内存。 Execution内存正是用于处理shuffle,join,sorts和aggregation过程。 Storage内存是用于数据缓存。 二者是能互相占用的,这也是统一内存管理的统一(Unified)所指: M: execution + storage 的region, 即上面的 0.6 控制的空间。 R: execution 当storage memo 不够时会匀一部分给到Storage, 这个阈值由上面的storageFraction 控制。 即R表示 Storage最少占用的比例。 设置建议, spark.memory.fraction 应与JVM堆空间的老年代保持一致。 内存优化的思路 data structures Design阶段,避免 嵌套型数据结构, 尽可能使用 numeric IDs 替换字符串key JVM参数, -XX:+UseCompressedOops Serialized RDD存储 RDD StorageLevel GC调优 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps to the Java options. HeapSize调整 GC collector设置, 如 G1, -XX:+UseG1GC, -XX:G1HeapRegionSize The Young generation is further divided into three regions [Eden, Survivor1, Survivor2]. YARN的参数优化 YARN控制集群整体的计算资源,对于Spark任务来说,它对集群的资源依赖就是«核数, 内存»。 有下面3对6个参数: yarn.nodemanager.resource.memory-mb 为每个node分配的容器内存,上限是该节点的总内存 yarn.scheduler.maximum-allocation-mb 不同scheduer处理时动态处理后的最小最大要求 yarn.scheduler.minimum-allocation-mb yarn.nodemanager.resource.cpu-vcores 单个节点获得的核数 yarn.scheduler.maximum-allocation-vcores yarn.scheduler.minimum-allocation-vcores 其它优化思路 任务的并行化 关注参数, spark.default.parallelism 官方推荐, 每个CPU core 执行并行执行2-3个任务。 spark.PairRDDFunction Reduce过程的内存使用 多数情况下,我们遇到的OOM都是发生在Reduce阶段。 典型例子, 在使用 groupByKey 时,有大批量的数据在Shuffle阶段, 需要为每个task准备一个大的HashTable, 以执行数据的分组装配,通常这个HashTable会占用较多的空间,OOM也多发现在与这个大数据结构中。 大空间的广播变量 与 数据本地化 Spark SQL Tuning https://spark.apache.org/docs/latest/sql-performance-tuning.html ","link":"https://staticor.github.io/post/sparktuning/"},{"title":"0521-Data Modeling (一)数据建模的三个阶段","content":"文章主题, Data Modeling Data Modeling (数据建模)的阶段 概念模型 逻辑模型 物理模型 这三个阶段与Hive/Spark 对于SQL语句的Explain过程如出一辙。 概念模型 ( Conceptual data model) 这个过程也可以被称作是领域数据模型,通过收集初步的项目需求,明确要覆盖的实体范围(entity class space) 笔者认为,这个过程中“实体边界” (the border of entities)是一个非常非常重要的标志。 典型的实体: user product shop(store) 以画素描为例, 这是一个sketch阶段 - 数据框架必须要这个阶段中定义清晰。 逻辑模型 ( Logical data model) 较概念模型来说, 逻辑模型将下探到字段 field级别。 在这一过程中,不会讲究具体要用哪种技术来实现,而要在意实体关系用什么方式过滤、连接,以得到最终的结果。 核心区别, 字段级别的连接。 而在概念模型中,最多是体现表与表的关系。 这个过程应该要输出若干个业务指标,构建出一个简版的metrics roadmap. 物理模型 ( Physical data model) 最终物理模型基本到了数据仓库地图的蓝图状态了,往往是数据仓库名,字段名,字段类型的ER表现。 参考文章 ibm-learning-data-modeling ","link":"https://staticor.github.io/post/data-modeling/"},{"title":"MySQL要知道的知识","content":"提交语句到执行查询的过程(用户侧) 连接器 一切要从连接开始, MySQL使用TCP连接。 mysql -h$ip -P$port -u$user -p"password" 服务端经TCP握手,建立连接,鉴定用户的身份:密码+权限都在这个过程完成校验。 (多久处理idle的连接? 在MySQL对应参数是 wait_timeout, 默认值 28800 秒, 8小时) 提交查询命令, 之前总觉得MySQL有个cache机制,但后来了解到这个查询缓存功能有些鸡肋。 命中率低,失效情况普遍(前后两个查询,变动一个字符都会视作不同的查询)。 在新版8.0已经删除了cache。 分析器 parser & analyzer 接下来MySQL服务端要将客户端的提交查询进行解析,是否合法的查询。 词法分析 优化器 optimizor 执行器 executor ( Hive将query转换成MapReduce的过程,也会经parser & analyzer & optimze 的过程) redo log , binlog 数据持久化,一般有两个思路。 edit Log, 记录所有的指令操作, 当重启时通过replay log 恢复; image(snapshot), 滚动打快照,当重启时通过load快照恢复现场。 我们自然希望edit log 越小越好(恢复操作快), image离目前越近越好(恢复操作快)。 所以editLog和image需要经常合并, 在Hadoop NameNode中,它有个辅助节点称为 Secondary NameNode就是完成的这项工作。 而在MySQL中,采用的是WAL(Write-Ahead Logging),先写日志,再写磁盘。 redo log 的 容量和设计: 几块文件(环形队列), 每个文件写到末尾从到再写。 参考: https://dev.mysql.com/blog-archive/mysql-8-0-new-lock-free-scalable-wal-design/ 事务隔离 ACID(我的记忆口决: 毛利兰的朋友—— 园子 一直要抹隔离霜, 持久防晒) SQL的事务隔离级别: 读未提交 read uncommitted A事务查询时,能查到另一个事务B 还没commit的变更 读已提交 read committed 较上面, 只有B事务提交了,A事务才能查到B的变更 可重复读 repeatable read A事务执行过程中看到数据,和启动前看到的数据一致 串行化 serialized 最好理解, 所有操作都加个锁,前面没完事,后面就等着。 用 transaction_isolation 查看当前的隔离级别。 MVCC 与 undo log 每行数据是有多个版本的,每次事务更新,修改了这行数据,都会生成一个新的数据版本。 每个数据版本有一列 transaction id 就对应着修改它的事务id, 标记为 row trx_id 。 如果这行数据被改了多次,就会有多个版本,对应着不同的 row trx_id。 MySQL中的另一个日志: undo log 事务的一致性视图(read-view) InnoDB为每个事务构造了一个数组,用来保存事务启动时,当前正在活跃的所有事务ID,活跃(启动了但未提交) 数组里事务ID最小值记为 低水位,当前系统已经创建的事务ID最大值+1 记为高水位。 这个视图数组和高水位,组成了当前这个事务的一致性视图。 用之来判定数据的可见性: 如果是已提交事务, row trx_id 落在低水位以下, 这个数据看得见; 如果是未提交事务, 分2类: - row trx_id 在数组中,表示这个版本没提交, 不可见; - row trx_id 不在数组中,表示这个版本由已经提交的事务生成,可见 如果落在未开始事务(高水位以后),这个版本是由将来启动事务生成的, 不可见 索引 B+树 关于索引,描述得再多也不过份。 关于索引的生活举例,我个人更喜欢用的例子,并非是书中目录和页的关系,而是图书馆中的书架和书的关系。 有过借阅经历的我们能回忆起自己借书的步骤: 找一台机器,登录图书馆检索系统,录入自己身份; 搜索自己想查的书,根据作者、书名、类型; 得到一个书架编号,自己走到书架 在书架某层中,找到书,走借阅流程。 如没有找到,联系管理员协商解决,或放弃。 以前在工作时,和索引接触最多的场景,就是接到DBA的要求,对线上慢查进行优化。 当时内心第一反应就是“糟了,是不是索引没有用对(没走理想索引)“。 系统来看,索引就是帮助寻地址定位数据的信息量,也是存放在数据页中的。 在数据结构中,哈希表也是一种索引模型 —— 将key通过hash函数,使用hash结果可以快速回答底部元素是否存在的问题。 hash的局限性也非常明显,无法解决范围和带顺序的检索需求。 第二种想法是使用连续数组,使用二分查找,也能快速查找一个或者一片连续的数据。 但数组的问题就是添加和删除数据时的维护成本,远没有链表灵活。所以,有序数组只适用于存储静态数据。 第三种想法是引入非线性数据结构,树。 BST,AVL,RB树。 MySQL使用的是多叉树(N叉的N,一般可达到1000-2000),数据都存放在叶子节点,中间节点只放索引。 如果是4层的树高, 第三层所有叶子节点的数量是1500的三次方,超过30亿数据了。 称为B-树, 或B+树。 根节点常驻内存,中间节点的data page 是在磁盘的。 磁盘的IO 寻址是毫秒级,所以一次定位数据一般要2-3次的树中向下遍历工作。 普通索引和唯一索引 聚簇索引和非聚簇索引 聚簇索引的叶子节点内容是整行的数据。 非主键索引的叶子节点是存放的主键的值, 在InnoDB 非主键索引也称为二级索引。 回表: 二级索引,如果查询的数据是不在索引中的, 还要将查到的主键值再到聚簇索引中再查一次。 所以查询要尽量使用主键查询。 覆盖索引, 查主键树就能返回查询结果,不用回表。 索引建立 索引维护 页分裂: 新插入数据,会占掉原来的链表位置,即data page的空间。 那么如果这个页已经满了,B+树算法的设计是要申请一个新页,挪动部分数据。 就像动态数组的扩容, 页分裂显然是对性能有影响的。 有分裂也有对应的逆过程——合并页。 怎样减少分裂的操作呢。 如果说数据每次插入都是递增的,不会插入到之前的历史数据中,不就好了么? 这恰恰响应了自增主键的表设计规范。 索引下推 Index condition pushdown 在联合索引中,申请回表前增加判断, 减少回表次数。 锁 锁的划分有多种方式,比较直观的分类是按锁的影响范围,分为 全局锁 表级锁 行锁 全局锁, 对整个数据库加锁。 flush tables with read lock 场景,全库备份。 表级锁, lock tables ... read / write 分为表读锁和表写锁。 另一类表级锁是 MDL(Metadata lcok)。 行锁,不是所有的引擎都支持的。 InNoDB支持行锁,在需要的时候加上行锁,事务结束之后释放,即两阶段锁协议。 死锁 以两个事务为例,死锁满足的条件是: AB互斥 AB互不打断对方 AB有对方的依赖资源 AB循环等待对方 死锁检测的成本要在服务端。 附录:相关命令 show processlist; -- show current connections, include users, ip address and port, state show variables like '%timeout%' -- find wait_timeout 28800 seconds MySQL 的底层魅力是数据持久化,并发查询下的事务处理。 对于语义“如何得到精确想要的结果” 在简单的 select col from T 下, 背后有许多人付出的努力。 ","link":"https://staticor.github.io/post/MySQL-stuff/"},{"title":"booknote,《李光耀观天下》","content":"本文章,这本书的书摘。 书评 这本书在世界局势非常不确定的时候面世。他所详述的大议题,中东、中国、美国和欧洲,无论在今天还是在他或者我的有生之年,都充满着困难和挑战。 —— 卡林顿爵士, 北约组织秘书长 多年来,我有幸和他圣诞,并且每次都觉得增长见闻。即使不认同一两个细节,仔细留意他的看法都会让我获益良多。美国人,中国人,乃至各国人民都能从这本书的观点中受益。 —— 约瑟夫·奈博士 《权力的未来》作者 序 ... 本书是我对世界,以及在可预见的未来各股力量相互角力的看法。对正眼的局势及其起因有正确的认识,是了解未来可能如何发展的先决条件。我的观点建立在自己的观察,和过去50年参与政事与形形色色的人接触的基础上。 ... 美国和中国是在行动和决策上,最具国际影响力的两个主要国家。但新加坡必须尽可能与其他国家建立联系。 ... 无论世界怎么样,新加坡都得去接受它,因为它实在小得无法改变世界。 。。。 就新加坡而言,我们的成功故事取决于三大特性:确保这是个让人们生活与工作的最安全国家,平等对待每一个公民,以及确保每一代新加坡人能持续成功。 有些国家就像树,长得高大挺拔,不需要支持;有些国家则像匍匐植物,需要依赖树才能往上爬。 日本,中国,韩国,甚至越南都是树。 我们是美国的安全合作伙伴,因此可以买到其他买不到的武器作为回报。我们必须支持它。 我们的繁荣是因为与全世界接轨,同时接待美国与日本。 新加坡 万事难料,唯有一事我敢肯定:如果新加坡最终决定走向两党制,我们将注定平庸。 美国 陷入困境但优势仍在。 修昔底德“强者做自己想做的事,弱者任由命运摆布。” 新加坡的总体战略是确保即使搭上中国的经济增长列车,也不切割同世界其他国家的联系,尤其是美国。 通过语言和世界各地保持联系。 历史显示能讲英语并与世界沟通的能力,成为新加坡的成长故事中最为重要的因素。 吸引人才方面,中国不那么有效的原因之一是因为语言。 比起英语,华语是一门更难掌握的语言。 面对的挑战 债务与赤字问题; 教育问题,忽视了基础教育和技术教育。 基础设施; 阶级鸿沟; 种族歧视; 中国 一个强大的中央。 五千年来,中国人一直认为,只要中央政权是强大的,这个国家就安全。 这种心态已存在数千年了。 ... 在今天的中国,没有人际关系,什么事也办不成。 在新加坡,我们已接受必须像西方那样,.... , 中国人还没有接受这一观念。 在新加坡,如你是总统或总理,军队领导人自动接受你的命令,因为制度大于人。 中国将维持一个强大的中央。 讲述自己对毛,邓,江,胡,习几代领导人 Mao, 开创者,也是最伟大的革命者。 邓,改革者。“中国幸亏有了邓”。 江,邓的接班人。 胡,整合者,温和慈祥的外表下有坚强的个性。 习,韬光养晦,保持谦逊。 中国与美国 在20年至30年内,双方力量最终会对等,实现平衡。 第一次平衡,中国将美国人赶出12海里;第二次 200海里的专属经济区。 最大危机是在台海问题上。 但是我认为,美国不会为了维护台湾的‘’而与中交战,这得不偿失。 被问到,是否有一天会给中国海军提供一个后勤中心或其他形式的基地? 我不能这么说,在我有生之年不会发生。 我认为第一步将是为这两国的海军提供后勤中心,而不是只为一车。千万不要在它们之间做选择。 中国面临的挑战 财富差距,发展不平衡。 处理那些低效的国有企业。 创新与知识产权 —— 为什么中国不能发明(iPhone, iPad, 互联网), 不是缺乏人才,而欠缺某些东西。 欧洲 衰退与分歧。 福利制度。 瑞典新闻工作者 乌尔夫·尼尔森 中央公积金, CPF是新加坡的退休金制度。建立在个人账户的基础上。 我相信,要是采用了欧洲制度,我们的经济将失去不少活力,并将因此付出沉重的代价。 和欧洲相比,如三个北欧国家——瑞典、挪威和丹麦都是相当小的国家。 挪威人口有500万,比新加坡还少。 在英国London求租时,在电话对房东说“我姓李,但我是华人。 如果你的房间不想出租给华人,请跟我说,这样我不必白跑一趟” 华族社群,“不求闻达”并且被视为最不会添麻烦的种族。 美国移民社会。 台湾出生的企业家,互联网公司雅虎的联合创办人杨致远。 日本,韩国,朝鲜,印度 东南亚 东南亚 马来西亚。 “新马分家” 1965年8月9日脱离马来西亚联邦。 公民权。 马来西亚的种族主义政策。 新加坡最重要的任务,是建立和维持一个强大的武装力量,以维度国家主权。 印尼 泰国 越南 ","link":"https://staticor.github.io/post/JItOkbzRI/"},{"title":"MIT 6.006--LearnNote-算法导论(2020.MIT)","content":"2020年之后的新版课程,由 Eric Demaine 主讲。 (2010年之前老视频中的二讲教授) 主讲:Prof. Eric Demaine, Dr. Jason Ku, Prof. Justin Solomon 课程主页:https://ocw.mit.edu/6-006S20 课程来自Bilibili地址 时间复杂度 大O表示法 Sequence 数据结构 static array 静态数组的好处是知道 head 位置, 就知道它后面的所有位置的内存地址,那么对于“根据编号去访问”的场景,就去使用 array 。 随机访问(get、set random position) O(1) 构建(build) O(N) 插入 insert_at, 涉及原有元素的移动 O(N) 删除 delete_at , 涉及原有元素的移动 O(N) 数组快要满了,就要考虑扩容的策略 —— resize 设计 一般是乘数(multiple)扩容,而非常数扩容。 (发散问题, 为什么要用 2 倍扩容, 而不是 +5 扩容) (发散问题,什么时机扩容, 距离满容 80% ? 70% ?) dynamic array (linked list) item|next, next 是指向下一个元素的pointer. build 成本 O(N) insert_at 中间插入,先寻找插入位置, O(N), 如果只在头部位置, 成本就是O(1) delete_at 中间删除,先寻找插入位置, O(N), 如果只删除头部位置, 成本就是O(1) QuickSort (老版的视频资料) Divide and Conquer (分治) O(N) 的复杂度, 执行 partitioning ; 划分次数要执行 O(Log N)次 算法介绍: i = p for j <- p+1 to q do if A[j] <= x then i = i + 1 A[i] <-> A[j] A[p] <- A[i] 例题 Ex, 6, 10, 13, 5, 8, 3, 2, 11 x < -6 (pivot) j 遍历到5, 小于6 , 和10交换 6, 5, 13, 10, 8, 3, 2, 11 j遍历到3 和13 交换 6, 5, 3, 10, 8, 13, 2, 11 j遍历到2 和10交换 6, 5, 3, 2, 8, 13, 10, 11 j遍历结束 最后再交换 A[i] 和pivot 2,5,3,6,8,13,10,11 老版视频, 证明比较排序算法最好的复杂度就是 O(NlogN) 怎样在线性复杂度完成排序? 扩展介绍了几种别的非比较排序: 计数排序 counting sort 复杂度是 O(N+k) k 是输入数据的范围 基数排序 radix sort : digit by digit 排序, 复杂度是 O(N T) T 是有多少位 哈希碰撞 ? BST 二叉搜索树 好的搜索树 : 平衡 vs. 不好的搜索树,退化为链表 中序遍历, 第二次访问根节点时,对其打印输出. 时间开销, O(N) 数组 (N个元素) 建树的时间复杂度? (N log N) 定义,随机化二分搜索的树高度的期望为 log (N) 图论 Graph 由 节点和连接节点的边组成。 扩展:边是否带有方向,称为有向图和无向图; 以及边上是否带有一个数值(权重),权为有权图和无权图。 简单图: 没有自环,每条边都是唯一的,同一对顶点间只有一条边(或者没有)。 预留问题,网络中的最短路径如何求解? 最小权重路径如何求解? 单源路径(single source path),给定一个入口节点,从这个节点出发,到其它任意节点的最短路径。 动态规划 DP ","link":"https://staticor.github.io/post/learnnote-suan-fa-dao-lun-2020mit-yi/"},{"title":"让子弹飞一会儿","content":"土匪(侠者)。 师爷,骗子。 黄四爷,恶霸地主。 曾经的革命者。 经典台词 来者不善啊。。。 “你才是来者!” 反正呢,我就是想当县长夫人。谁当 你是想站着,还是想挣钱? 我是想站着,还把钱挣了! 小六子把鼓砍出来了,鼓追人,人碰了武举人。 鼓都被锤破了,看来很冤啊! 我来鵝城,只为三件事,公平,公平,还是TMD公平。 她已经成了寡妇,我不能让她再守活寡。 陷害小六子,吃粉。 他吃了两碗粉,只给了一碗钱。 卖凉粉的名字叫孙守义, 这自然不是为了响应孙正义了。 在我看来,而是在怀念逝去的道义。 花姐: 不好色的县长,不一定是好县长。 流水的县长,铁打的老爷。 别走,是不是只有一碗粉!?!? 六子的死。 三人成🐅。 杀人诛心。 杀人还要诛心。 先打哪张牌?请客?斩首,收下当狗。 精彩的鸿门宴 鸿门宴——宴无好宴,必有血光。 介错人。 在看这部电影前,我不了解什么是介错人。 我听说自裁用短刀,介错用的是长刀。 介错人是补刀的,免除自裁的人面临的痛苦。 张和黄对日本文化都有些了解。 酒一口一口喝,路一步一步走。步子迈得快了,容易扯着蛋。 二十年前,我们曾有过一面之缘。 你不会装糊涂。 改不了,天生的。 〈 美女不要,钱你也不要,你要什么啊 〉 花有重开日,人无再少年。 黛玉晴雯子。 我说我当不了这个助长,你非让我花钱买这个官,现在官倒是到手了,你倒这么走了。。。 她是我老婆,我就是县长,我就是马邦德。 这钱发给谁呢? 发给穷人。 谁是穷人呢? 谁穷,谁就是穷人! 他们不是英雄,我也不是美人。 麻匪火并,县长暴死。 枪一响,就有人死。 有人死,就有人哭。 有人哭,就有人要说心里话。 或许是你的恩人,您才是我的恩人。 翻译翻译,什么他妈的叫他妈的惊喜。 好人就得拿钱指着。 闭嘴,你这个老骗子。 出城剿菲。 明嘲暗讽。 康城县长,不是鹅城。 康城富饶,鹅城凶险。 师爷死了: 屁股疼。 屁股在树上呢。。。 师爷把老二害死了。 死在了堆元宝里。 死了,啥也没带走。 带不走。 黄四郎的替身与花姐。 老二的死。 老三,带着花姐。 上海。 等待着他们的。 lock down。 ","link":"https://staticor.github.io/post/NLZWE3893/"},{"title":"Java的GC","content":"不断更新, 预计还需要1个月完成。 Java GC概念 什么是垃圾? 在程序运行中,没有任何指针(引用)指向的对象。 An object is considered garbage when it can no longer be reached from any pointer in the running program. 为什么需要GC? 一个基本认知,是如何不进行GC,内存尽早被消耗殆尽。 如果不及时清理,这些垃圾对象占用的内存空间会一直保留到应用程序结束,被保留的空间无法被其他对象使用,而且会产生大量的不连续内存空间,影响大对象的存储,甚至导致内存溢出。 表示数据已不可见,对用户没有价值。 什么时机GC? 怎样GC? 标记 + 清理。 早期在C、C++时代, GC是人工进行的,开发人员使用new进行内存申请,使用Delete关键字内存释放。 虽然灵活,但频繁操作带来管理负担。 新语言的出现,发现了这个问题,因此提出了自动化内存分配和GC的理念,目前多数新语言都是采用自动化GC。 在有了自动GC机制后(如Java),开发人员可以解释了手脚,专注于开发核心流程,而减少对GC的关注。 识别垃圾的方法: 引用计数与可达性分析 引用计数。 一个对象A被引用了,它的计数器加个1,当引用失效就减1,当它的计数器为零时,表示这个对象A就是垃圾了。 优点:实现简单,便于标识,判定效率高,回收没有延迟性。 缺点:需要单独的字段存储计数器,占用存储空间;时间开销,每次引用变化的维护。 最为严重的缺点,无法解决循环引用的GC场景,这是一条致命缺陷,导致在Java中没有使用这种方法。 (Python用引用计数法, 使用弱引用。) 可达性分析(GC Roots) Java,C# 使用。 GC Roots 根集合是一组活跃的引用对象,由它出发直接或间接访问到的对象就不是垃圾。 也称作追踪性垃圾回收(Tracing Garbage Collection) 虚拟机栈中引用的对象引用 本地方法栈内JNI引用对象引用 方法区中静态属性引用的对象引用 方法区中常量引用的对象引用 所有被同步锁持有的对象引用 虚拟机内部的引用 有哪几种GC算法 Mark-Sweep (标记清除算法) 1960年, J. McCarthy提出,应用于Lisp语言。 堆中有效内存空间(available memory)被耗尽时,会停止整个程序(STW)。 进行两项工作,标记,清除。 标记: 从引用根节点(Root)开始遍历,标记所有被引用对象, 在对象的Header记录为可达对象。 清除: Collector 对堆内存从头到尾线性扫描,发现没有标记可达,则视作为垃圾。 并不是真清空,而是把对象地址保存在空闲的地址列表里。 下次有新对象需要加载时,判断垃圾的空间是否足够,如果足够,就直接覆盖。 缺点, 两次对全局对象的全遍历效率不高; 有STW,影响正常的工作流程; 以及比较大的问题(第三点) —— 产生不连续的内存空间(碎片),影响后续对象的分配。 需要维护一个空闲列表。 Copy (复制算法) 在内存开辟A区和B区,在A区标记为正常对象和垃圾对象,将正常对象都复制到B区。 优点和缺点比较明显: 用空间换时间的理念,换来的是运行高效,复制过去也能保证空间连续性。 局限性 系统中垃圾对象很多,复制算法需要复制的存活对象不能太多 在分代Young-old中的S1和S0就是采用的这个方法。 Mark-Compact(标记-整理算法,标记-压缩算法) 第一阶段和Mark-Sweep相同。 第二阶段(Compact), 将所有存活对象压缩到内存的一端,按顺序排放。 之后清理边界外所有空间,腾挪出所有空间。 和MS区别, MS是一种非移动式的回收算法。 MC是移动式的,效率要比MS低。 移动过程中也会STW。 System.gc() vs Runtime.gc() vs System.runFinalization System.gc() 首先看下System.gc方法的源码声明: Runs the garbage collector. Calling the gc method suggests that the Java Virtual Machine expend effort toward recycling unused objects in order to make the memory they currently occupy available for quick reuse. When control returns from the method call, the Java Virtual Machine has made a best effort to reclaim space from all discarded objects. The call System.gc() is effectively equivalent to the call: Runtime.getRuntime().gc() See Also: Runtime.gc() **建议(提醒)**JVM调用gc方法,以回收垃圾对象。 这里的"suggest" 有一些传神,因为方法不确定是否马上执行gc。 System.runFinalization 强制执行对象的finalize()方法 Java中的Garbage Collector(回收器) 按线程数分,可分为串行垃圾回收器和并行垃圾回收器。 串行,同一时间段只允许有一个CPU用于执行GC,此时STW,直到GC结束。 与串行相反, 并行GC运用多个CPU同时执行GC,提升应用的吞吐量,不过并行与串行回收一样,采用独占式,使用STW机制。 GC的迭代过程, 1999年, JDK1.3.1 Serial GC 串行,第一款GC; ParNew 是其多线程版本 2002年, JDK1.4.2, 推出Parallel GC和 CMS(Concurrent Mark Sweep GC). 在JDK6之后, Parallel GC成为HotSpot默认GC 2012年, JDK1.7u4, G1推出 2017年, JDK9中 G1成为默认GC 2018年,JDK11, 引入 Epsilon GC (No-op 无操作), 同时引入ZGC 2019年, JDK12 优化G1, 引入 Shenandoah GC 2019年, JDK13发布,增强ZGC 2020年,JDK14, 删除CMS,扩展ZGC 七款经典回收器: 串行回收: Serial , Serial Old 并行: ParNew, Parallel Scavenge, Parallel Old 并发: CMS, G1 按代际划分: 新生代: Serial, ParNew, PS 老年代:Serial Old, Parallel Old, CMS 整体: G1 垃圾收集器的组合 JVM堆空间分代,不同代有各自的回收器。 老年代来说: Serial Old 都能组合; Parallel Old 只能和PS; CMS 不能要PS (JDK10 CMS 删除) (JDK 14版 弃用了上面的红线; 绿线标记为Deprecated) G1, 既能回收新生代,也能回收老年代。 如果是目前的视角, 七种经典回收器的可用组合就是3种了 Serial GC + Serial Old PS + Parallel Old G1 回头看对象的引用 强引用,软引用,弱引用,虚引用有什么区别?(Strong/Soft/Weak/Phantom Reference) 强软弱虚 4种引用强度依次递减的引用类型。 除强引用, 剩余三种在java.lang.ref包。 new对象产生的引用对象便是强引用。 关键字: 宁肯OOM也不回收。 软引用,内存溢出之前,将这弱引用对象进行二次回收。 关键字,内存不够时,回收。 弱引用,不管内存够不够,只要发现弱引用,就回收。 虚引用, 唯一目的是在这个对象被gc时收到一个系统通知。 应用场景,大多数情况我们使用的都是强引用,在使用缓存时会用软引用和弱引用。 ","link":"https://staticor.github.io/post/GC-in-java/"},{"title":"[Paper]Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing","content":"Abstract 我们提出了RDD, 弹性分布式数据集 ---- 一种分布式内存的抽象结构, 允许程序员以容错方式在大型集群上执行内存计算. 在当前计算框架下,有两种类型的计算应用表现性能较低: 迭代式算法和交互式数据挖掘, RDD也是受到了启发,从而产生. 这两种情况下,将数据保存在内存可以将性能提高一个数量级. RDD提供了一种受限制的内存共享机制,基于对共享状态粗粒度的转换,而不是细粒度更新. 然而, 我们证明RDD的表达能力是足以覆盖很多计算场景的. Introduction MapReduce和Dryad等诸框架已经被大规模应用,这些系统让用户使用一系列高级别的算子完成并行计算,不必担心工作的分发和容错. 但它们缺少了对于内存的抽象,导致效率低. Data复用在很多地方上是必要的: PageRank, K-Means 聚类, LR回归. 交互数据挖掘, 频繁对数据进行ad-hoc. 可惜, 像MapReduce是满足不了这两类场景的. MR在HDFS上的计算任务,会产生大量的IO\\ 序列化和数据备份,严重影响了任务的开销. 认识到这个问题,研究者提出了一些data reuse的方案 : Prege, HaLoop 它们的局限性是仅适用于特定计算场景. 本论文提出的RDD具备更宽的使用范围. RDD是容错的,并行数据结构,封装了一系列的灵活算子,可以在内存完成各自任务. RDD设计的主要挑战是编程接口的容错设计. 目前存在的集群内存存储设计: DSM(Distributed Shared Memory), Database, Piccolo,是通过对状态的细粒度更新,提供了接口. RDD是基于 粗粒度的接口设计. RDD RDD的抽象设计 只读的,带有分区. 创建方式有二: 只能从指定存储的数据结构或者由别的RDD变换而来, 我们称这些变换为特定的"transformation" 算子. 典型的例子, map, filter, join. RDD 不需要在任何时刻都被实现, RDD拥有足够的信息,证明自己是怎么派生出来的(从其它数据集), 这是一个很便利的属性. 本质上, 一个程序不能引用一个RDD (如果它重建失败了) Spark 编程接口 map, filter Actions: count, collect, save 除此之外,还提供 persist 方法. Spark默认将RDD保存在内存,但如果内存不够,可以指定别的存储策略. 例子, Console Log Mining 控制台日志分析 想在HDFS已有的日志(TB级)检索信息. 使用Spark, 会将这些日志分段加载到不同的节点 lines = spark.textFile("hdfs:// ...path...") errors = lines.filter(_.startsWith("ERROR")) errors.persist() errors.filter(_.contains("MySQL")).count() errors.filter(_.contains("HDFS")) .map(_.split('\\t')(3)) .collect() pipeline 作业, 把任务分发到持有errors分区的节点上. RDD vs. Distributed Shared Memory DSM(是什么), 可以全局的进行读写. RDD和DSM的主要区别: RDD只能由粗变换创建, DSM可以对单个内存地址读写(RDD可以视作是一片连续区域?) RDD限制了批量写的程序,但可以更有效的容错. 特别地,RDD不用产生检查点,因为它们可以使用血缘来快速恢复. 只有当RDD失败时才会重算(并行的在其它节点完成),不必重头回滚整个程序. RDD的第二个特性,不可变性. 系统通过运行慢任务备份副本来缓解慢节点. RDD不适合的应用场景 通过前面的介绍,RDD是适合于批量计算的应用,也适合于一步接一步的迭代式计算. 但对于流计算, RDD不适用的. Spark Programming Interface Spark使用Scala,提供了像 DryadLINQ 一样的 API. Scala是函数式编程语言, 然而RDD倒不必须使用FP来实现. Spark, 开发者需要完成一个 driver program, 与一系列workers集群通信, 如下图: 编程接口 val points = spark.textFile(... ) .map(parseFunc).persist() var w = // random for( i <- 1 to ITERATIONS) { val gradient = points.map { p.x * (1/ ...) }.reduce( (a, b) => a+b ) w -= gradient } RDD 的表示 提供了通用的五个特征: partitions 分区, 数据集的最小操作原子单位. 返回一组分区列表 dependencies 依赖, RDD是怎么来的 返回一组依赖的列表 partitioner 返回metadata, return metadata 这个RDD是Hash分区还是Range分区. metadata 分区,schema信息 data placement 数据放置信息 preferredLocation(p) 节点列表, 参数是p(分区), 返回能访问它的节点 对RDD而言,有意思的便是设计不同RDD的依赖关系. 我们定义了两类, 窄依赖和宽依赖. 窄依赖,每个父RDD的分区,最多被一个子RDD依赖. 例如 map是窄依赖的操作 宽依赖则是一个RDD的分区被多个子RDD依赖, 例如 join是宽依赖操作. 窄依赖可以在一个节点上形成执行流水线作业(map, filter + ..) 都是element by element. 对比来说,宽依赖需要收集所有依赖的父分区数据. 窄依赖可以快速重算. 在宽依赖下,如果单节点计算失败了,就需要再次重上游依赖提取数据重计算,恢复成本较高. 实现 大约使用了14000行Scala代码完成了Spark的实现. 能运行在Mesos集群,Hadoop共享资源的应用. Job Scheduling 分配任务时, 将任务分发到哪一台机器上考虑了 data locality. 就近 对于宽依赖(shuffle 依赖), 目前我们采用的是这种方式, 在父分区所在节点,存储中间结果,以简化故障恢复, 有些类似MapReduce时对Map output的设计. 如果一个任务失败了, 会在别的节点上 rerun. 如果某一个Stage不可用了, (比如 从Mapside 产生的shuffle write 结果丢了), 会重新提交丢失的分区. Interpreter Intergration 解释器集成 two changes Class Shipping, Modified code generation Memory Management 内存管理 Spark 提供了三个RDD持久化的级别, 在内存持久化(以Java 反序列化对象) 在内存以序列化对象 在硬盘 第一种性能最好, JVM可以直接访问RDD. 第二种 允许用户在空间有限时, 比Java对象更节省空间的方式表示, 但性能不如第一种. 第三种, 当RDD太大时, 每次都用内存开销太高. 因此在内存上更为方便 为了更好利用内存,我们使用LRU的丢弃机制. 当新的RDD分区被计算, 而且没有足够空间存储它, 我们会将最久不使用的RDD丢弃. ","link":"https://staticor.github.io/post/resilient-distributed-datasets-a-fault-tolerant-abstraction-for-in-memory-cluster-computing/"},{"title":"Spark中的RDD(一)+ Paper Reading","content":"本篇文章结合 https://spark.apache.org/docs/latest/rdd-programming-guide.html#overview Paper 每个Spark应用的driver program都是由用户侧的main函数驱动的, Spark提供了一种抽象的操作集合: RDD —— 弹性分布式数据集。 RDD的特点: 不可变对象,数据集的逻辑表达; 分布存储 + 并行计算,每个数据分片称为一个分区; 容错可用,失败可重试 可缓存到内存或磁盘 可变换成其它类型的RDD 注意,Spark不支持嵌套型RDD(RDD复合RDD),见Spark-5063. Spark RDD属性, RDD是一个抽象类, 类声明为 abstract class RDD[T: ClassTag]( @transient private var _sc: SparkContext, @transient private var deps: Seq[Dependency[_]] ) extends Serializable with Logging { RDD[T: ClassTag] 是一个泛型类, RDD中元素的类型。 SparkContext 是不可或缺的参数, 定义了几个 compute 具体计算逻辑,由实现的子类完成。 getPartitions 返回这个RDD的所有分区Array(返回值是 Array[Partition]) getDependencies, RDD对父RDD的依赖关系 getPreferredLocations , specify 放置策略 partitioner : 可选的的覆写属性。 persisit, cache 持久化 RDD与基本操作 创建 RDD有三种创建方式,本地内存,外部流或由别的RDD转换而来。 从内存创建 makeRDD parallelize 从外部流创建, textFile 单个文件,或正则匹配 wholeTextFiles 读取多个小文本文件的目录 RDD操作 RDD支持两种类型操作, transform 和 action 。 transform 是将已有的RDD转换为新的RDD, action是对rdd运行计算,将值要返回给Driver。 Spark中所有转换都是惰性的。 val lines: RDD[String] = context.textFile("input/hello.txt") val lineLengths: RDD[Int] = lines.map((line: String) => line.length) val totalLength: Int = lineLengths.reduce((a, b) => a + b) RDD 的依赖血缘, 示例代码为: lines = spark.textFile("hdfs://...") errors = lines.filter(_.startsWith("ERROR") errors.persist() RDD 持久化 RDD有两个持久化(缓存)方法: cache, persist 。 cache 调用的persist 默认参数的方法(默认缓存到内存中);如果保存到磁盘,要修改StorageLevel。 /** * Persist this RDD with the default storage level (`MEMORY_ONLY`). */ def cache(): this.type = persist() def persist(): this.type = persist(StorageLevel.MEMORY_ONLY) 重点关注源码中的StorageLevel 方法. 持久化也是“懒操作”, 要等到触发action算子时才执行) RDD 和分布式共享内存(DSM)的比较 Driver 启动多个workers, worker 从HDFS读取inputdata, 分别在内存计算rdd. Spark 会将可以流水线执行的窄依赖Transformation放在一个job stage,而Job Stage间要对数据进行Shuffle. 这是Spark DAGScheduler 在生成任务时的作业划分过程完成的. 调度Task的考虑, Spark会关心Partition所在的集群位置,也有就近取数的策略. 什么操作会启动Shuffle 为什么要Shuffle? In Spark, data is generally not distributed across partitions to be in the necessary place for a specific operation. Duration computations, a single task will operate on a single partition - thus, to organize all the data for a single reduceByKey task to execute, Spark need to perform all-to-all operation. It must read from all partitions to find all the valeus for all keys, and then bring together values across partitions to compute the final result for each key -- this is called the shuffle* 这里我的理解是这样的 —— 简而言之,某些运算要把相同的Key聚合到一起才能计算出来,例如说按Key求和,少一个元素,最终结果都是不准确的。 但数据的输入来源是分散在各个partitions之间的,那么按需要,把满足聚合条件的key聚合在一起的数据移动和分配的过程就是Shuffle。 有哪些Shuffle算子? Operations which can cause a shuffle include repartition operations like repartition and coalesce, 'ByKey' operations (except for counting) like groupByKey and reduceByKey, and join operations like cogroup and join. 重分区算子, repartition coalesce ByKey操作类, groupByKey reduceByKey sortByKey combineByKey foldByKey aggregateByKey join算子, jopin cogroup 还有一个distinct countByKey 是Action , 不是 Transform 文档介绍,该方法只能用于结果的map很小的场景,返回值是Map[K, Long] 不是RDD 。 会加载到driver 的 memory. /** * Count the number of elements for each key, collecting the results to a local Map. * * @note This method should only be used if the resulting map is expected to be small, as * the whole thing is loaded into the driver's memory. * To handle very large results, consider using rdd.mapValues(_ => 1L).reduceByKey(_ + _), which * returns an RDD[T, Long] instead of a map. */ def countByKey(): Map[K, Long] = self.withScope { self.mapValues(_ => 1L).reduceByKey(_ + _).collect().toMap } distinct 也是一个shuffle算子。 def removeDuplicatesInPartition(partition: Iterator[T]): Iterator[T] = { // Create an instance of external append only map which ignores values. val map = new ExternalAppendOnlyMap[T, Null, Null]( createCombiner = _ => null, mergeValue = (a, b) => a, mergeCombiners = (a, b) => a) map.insertAll(partition.map(_ -> null)) map.iterator.map(_._1) } /** * Return a new RDD containing the distinct elements in this RDD. */ def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope { -- partitioner match { case Some(_) if numPartitions == partitions.length => mapPartitions(removeDuplicatesInPartition, preservesPartitioning = true) case _ => map(x => (x, null)).reduceByKey((x, _) => x, numPartitions).map(_._1) } } 几种ByKey算子 reduceByKey(_+_) aggregateByKey(0)(_+_)(_+_) foldByKey(0)(_+_) combineByKey( v=>, (x: Int, y) => x+y, (x:Int, y:Int) => x+y) 源码: PairRDDFunctions.scala def combineByKeyWithClassTag[C]( createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C, partitioner: Partitioner, mapSideCombine: Boolean = true, serializer: Serializer = null) 后台调用的都是这个函数, 只不过参数不一样。 combineByKey 第一个数据做什么操作 createCombiner, 相同Key 第一条数据进行的处理函数。 第二个参数,控制分区的聚合逻辑 第三个参数,控制分区间的处理逻辑 mergeCombiner 基函数 def combineByKeyWithClassTag[C]( createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C, partitioner: Partitioner, mapSideCombine: Boolean = true, serializer: Serializer = null)(implicit ct: ClassTag[C]): RDD[(K, C)] = self.withScope { require(mergeCombiners != null, "mergeCombiners must be defined") // required as of Spark 0.9.0 if (keyClass.isArray) { if (mapSideCombine) { throw new SparkException("Cannot use map-side combining with array keys.") } if (partitioner.isInstanceOf[HashPartitioner]) { throw new SparkException("HashPartitioner cannot partition array keys.") } } val aggregator = new Aggregator[K, V, C]( self.context.clean(createCombiner), self.context.clean(mergeValue), self.context.clean(mergeCombiners)) if (self.partitioner == Some(partitioner)) { self.mapPartitions(iter => { val context = TaskContext.get() new InterruptibleIterator(context, aggregator.combineValuesByKey(iter, context)) }, preservesPartitioning = true) } else { new ShuffledRDD[K, V, C](self, partitioner) .setSerializer(serializer) .setAggregator(aggregator) .setMapSideCombine(mapSideCombine) } } foldByKey的重载 /** * Merge the values for each key using an associative function and a neutral "zero value" which * may be added to the result an arbitrary number of times, and must not change the result * (e.g., Nil for list concatenation, 0 for addition, or 1 for multiplication.). */ def foldByKey( zeroValue: V, partitioner: Partitioner)(func: (V, V) => V): RDD[(K, V)] = self.withScope { // Serialize the zero value to a byte array so that we can get a new clone of it on each key val zeroBuffer = SparkEnv.get.serializer.newInstance().serialize(zeroValue) val zeroArray = new Array[Byte](zeroBuffer.limit) zeroBuffer.get(zeroArray) // When deserializing, use a lazy val to create just one instance of the serializer per task lazy val cachedSerializer = SparkEnv.get.serializer.newInstance() val createZero = () => cachedSerializer.deserialize[V](ByteBuffer.wrap(zeroArray)) val cleanedFunc = self.context.clean(func) combineByKeyWithClassTag[V]((v: V) => cleanedFunc(createZero(), v), cleanedFunc, cleanedFunc, partitioner) } ","link":"https://staticor.github.io/post/sparkkrdd1/"},{"title":"Hello Gridea","content":"👏 欢迎使用 Gridea ! ✍️ Gridea 一个静态博客写作客户端。你可以用它来记录你的生活、心情、知识、笔记、创意... ... Github Gridea 主页 示例网站 特性👇 📝 你可以使用最酷的 Markdown 语法,进行快速创作 🌉 你可以给文章配上精美的封面图和在文章任意位置插入图片 🏷️ 你可以对文章进行标签分组 📋 你可以自定义菜单,甚至可以创建外部链接菜单 💬 你可以进行简单的配置,接入 Gitalk 或 DisqusJS 评论系统 🇬🇧 你可以使用中文简体或英语 🌁 你可以任意使用应用内默认主题或任意第三方主题,强大的主题自定义能力 🖥 你可以自定义源文件夹,利用 OneDrive、百度网盘、iCloud、Dropbox 等进行多设备同步 🌱 当然 Gridea 还很年轻,有很多不足,但请相信,它会不停向前 🏃 未来,它一定会成为你离不开的伙伴 尽情发挥你的才华吧! 😘 Enjoy~ ","link":"https://staticor.github.io/post/hello-gridea/"},{"title":"Booknote,","content":"速成回顾, learnxinyminutes-c lang C语言经典书, 通过关键插图或代码块记录这本书的精华。 第一个C语言程序, #include<stdio.h> 表示引入标准IO库的头文件。 接下来定义了返回值为int的 main函数。 main函数也是应用程序的入口函数,main函数内部再调用其它函数。 参考下图, Anatomy of a C program. Question>>为什么IO这么基础的操作还要额外导入,C为什么不直接内置呢? 考量1:节简原则,并不是所有程序都要IO; 书中用的词是 The principle of economics DataTypes in C: 为什么整数类型有short, long, unsigned short, unsigned long 和 int 这么多种类型? fit the types to the machine ··· Windows, Apple 不同系统平台。 Picture, Difference Between C90, C99 and C11 Two_func, 在main函数调用 String & Char array #define 定义常量的关键词,预处理 —— 编程程序时,所有 define的变量会被批量替换成指定的字面值,这个过程称为编译时替换(compile-time substitution)。 char name[40] 声明一个 长度为40的char数组。 scanf从标准输入中获取用户的输入,保存到name数组中😥。 strlen() vs. sizeof sizeof(T) 可以返回T类型在运行的机器上占用多少个字节 , strlen方法返回这个string对象有多少个字符。 因为一个char占用一个byte,可能会想它们是相同的(对于一个char[]) 其实不然。 sizeof 作用于结构体时,不等于各分量的sizeof结果之和。 一般而言,以下断言不成立:// sizeof(struct rectangle) == sizeof(int) + sizeof(int) 这是因为structure成员之间可能存在潜在的间隙(为了对齐) 编译之前的预处理 define TAXRATE 0.015 while-循环 for-循环 尾递归 - Tail recursion 最简单的递归形式是把递归调用置于函数的末尾,即return 语句之前的位置。 这种递归称为尾递归。 指针:指向一个值为内存地址的变量(或数据对象) 指针要带有类型声明。 int * pi; 指向int类型的指针; char * pc; 指向 char类型的指针; float * pf; 指向float类型的指针。 在许多语言中,地址都不暴露给程序员。在C中,可以通过&运算符访问地址,通过*运算符获得地址上的值。 例如 &barn 表示变量barn的地址,使用变量名即可获得变量的数值。 数组和指针 \b数组名就是数组的地址头,索引[x]则表示顺着这个内存地址,访问到联排的地址。 内存管理(申请和释放), 存储级别 storage classes storage duration 存储期, 对象在内存中保留了多长时间。 scope 作用域 、linkage 链接 表明程序哪些部分可以使用它。 不同存储级别具有不同的存储期、作用域和链接。 作用域, 块作用域、函数作用域、函数原型作用域或文件作用域。 块作用域, block scope 用 { } 圈起来的一个变量; 块内的局部变量 for循环, while循环和if语句控制的变量,也是块的一部分。 文件作用域,变量定义在函数外面, 有点类似全局变量 global variable malloc() and free() 内存的分配 (memory allocation) 是怎么回事? int plates[100]; 该声明预留了100个内存位置,每个位置都用于储存int类型的值。 内存的分配工具 malloc 函数 , 接收一个参数, 所需要内存字节数。 会找到一块匿名地址,返回动态分配内存块的首字节地址。 有申请就有释放,释放指令是free(地址)。 double * ptd; ptd = (double *) malloc( 30 * sizeof(double)); ","link":"https://staticor.github.io/post/booknote-c-primer-plus/"}]} \ No newline at end of file diff --git a/api-info/index.html b/api-info/index.html new file mode 100644 index 00000000..d94b7792 --- /dev/null +++ b/api-info/index.html @@ -0,0 +1 @@ +{"posts":[{"fileName":"33xsTBvWN","abstract":"","description":"数据中心是成本中心,要在有限资源内,最大化的完成精细化运营的决策支撑任务,所以会尽可能地完成业务方的合理需求; 与此同时,也会受到整体技术产研的“资源帽”的要求,要不断的节省成本。 成本分为人员成本和集群成本,人员成本可估算为60-80万(...","title":"Onedata-3, 节省数据成本——到数据资产","tags":[],"feature":"","link":"https://staticor.github.io/post/33xsTBvWN/","stats":{"text":"6 min read","time":308000,"words":1518,"minutes":6},"isTop":false,"toc":"\n","date":"2022-06-09 08:41:22","dateFormat":"2022-06-09"},{"fileName":"onedata-2-shu-ju-chu-cuo-liao","abstract":"","description":"突然发现仪表盘中的数据为0 ... ... ? 图片来自(极客时间-数据中台实战课) 由结果表顺着向上游排查,查了30分钟之后发现是某个dwd表的解析出了问题,部分脏数据影响了下游,导致没报错,但其实数据写入失败。 一套盘查下来用了半天,...","title":"Onedata-2 数据出错了","tags":[{"name":"onedata","slug":"onedata","used":true,"link":"https://staticor.github.io/tag/onedata/"},{"name":"数据中台","slug":"Wc3ajsb70","used":true,"link":"https://staticor.github.io/tag/Wc3ajsb70/"}],"feature":"","link":"https://staticor.github.io/post/onedata-2-shu-ju-chu-cuo-liao/","stats":{"text":"3 min read","time":151000,"words":737,"minutes":3},"isTop":false,"toc":"\n","date":"2022-06-08 22:23:29","dateFormat":"2022-06-08"},{"fileName":"booknote-da-shu-ju-zhi-lu-alibaba-onedata-part2","abstract":"","description":"维度设计 measure = 事实 context = 维度 没有测量事实,就只有定性概念和分类。 维度是用来分析上下文的,只有通过维度,才能发现——数据有没有变化,变化的背后有没有问题。 维度和属性在哪里发现? 如果是刚来的数据人员,通过...","title":"BookNote-大数据之路(Alibaba-OneData) Part2","tags":[{"name":"bigdata","slug":"D6pPBGMAh","used":true,"link":"https://staticor.github.io/tag/D6pPBGMAh/"},{"name":"booknote","slug":"gIWbJvnhi","used":true,"link":"https://staticor.github.io/tag/gIWbJvnhi/"},{"name":"datasystem","slug":"fpMRWEw0UV","used":true,"link":"https://staticor.github.io/tag/fpMRWEw0UV/"}],"feature":"","link":"https://staticor.github.io/post/booknote-da-shu-ju-zhi-lu-alibaba-onedata-part2/","stats":{"text":"3 min read","time":146000,"words":719,"minutes":3},"isTop":false,"toc":"\n","date":"2022-06-06 20:41:11","dateFormat":"2022-06-06"},{"fileName":"onedata","abstract":"","description":"OneData OneService,统一数据管理,统一数据服务。 大型公司的数据部门最终发展方向是面向公司的数据使用者提供一个稳定可靠的数据平台,真正发挥数据的价值,而不单是成本。 数据资产的意义是沉淀+挖掘,可以形成有价值的业务决策。 ...","title":"OneData与OneService 初识","tags":[{"name":"数据中台","slug":"Wc3ajsb70","used":true,"link":"https://staticor.github.io/tag/Wc3ajsb70/"},{"name":"datasystem","slug":"fpMRWEw0UV","used":true,"link":"https://staticor.github.io/tag/fpMRWEw0UV/"},{"name":"数据仓库","slug":"6ykOqkgld","used":true,"link":"https://staticor.github.io/tag/6ykOqkgld/"}],"feature":"","link":"https://staticor.github.io/post/onedata/","stats":{"text":"5 min read","time":271000,"words":1238,"minutes":5},"isTop":false,"toc":"\n","date":"2022-06-05 19:17:02","dateFormat":"2022-06-05"},{"fileName":"booknotelesslessstar-schema-the-complete-referencegreatergreater","abstract":"","description":"Dimensional Design 维度设计 维度模型由两部分: measurements and context, 即 测量指标和上下文。 在数据仓库模型里对应的是事实表和维度表,如果引入关系数据库,那么此时的维度模型就是星型模型。 维...","title":"Booknote,《Star Schema -The Complete Reference》","tags":[{"name":"数据仓库","slug":"6ykOqkgld","used":true,"link":"https://staticor.github.io/tag/6ykOqkgld/"}],"feature":"https://staticor.github.io/post-images/booknotelesslessstar-schema-the-complete-referencegreatergreater.png","link":"https://staticor.github.io/post/booknotelesslessstar-schema-the-complete-referencegreatergreater/","stats":{"text":"4 min read","time":186000,"words":864,"minutes":4},"isTop":true,"toc":"\n","date":"2022-06-05 12:26:00","dateFormat":"2022-06-05"},{"fileName":"booknote-da-shu-ju-zhi-lu-alibaba","abstract":"","description":" 成书于2017年,有些技术或许过时或者描述不再准确了,要分析地阅读。 结合我的需要,我先拆解数据模型篇和数据管理篇,然后再来看数据技术篇。 为什么,数据技术每5年一小变10年一大跳,但对于数据模型和元数据管理的理念,却还是常青。 数据模...","title":"BookNote-大数据之路(Alibaba-OneData) Part1","tags":[{"name":"booknote","slug":"gIWbJvnhi","used":true,"link":"https://staticor.github.io/tag/gIWbJvnhi/"},{"name":"大数据","slug":"m5G8CnLvG","used":true,"link":"https://staticor.github.io/tag/m5G8CnLvG/"}],"feature":"","link":"https://staticor.github.io/post/booknote-da-shu-ju-zhi-lu-alibaba/","stats":{"text":"10 min read","time":580000,"words":2808,"minutes":10},"isTop":false,"toc":"\n","date":"2022-06-05 00:04:59","dateFormat":"2022-06-05"},{"fileName":"spark-yuan-ma-xue-xi-wu-cun-chu-yu-xu-lie-hua","abstract":"","description":"存储级别 StorageLevel Spark提供了一个名为StorageLevel的单例对象。提供了控制RDD存储级别的标志信息: 标记RDD是否使用内存或ExternalBlockStore 如果RDD内存不足或ExternalBlc...","title":"Spark源码学习(五)-存储与序列化","tags":[{"name":"sourcecode","slug":"gpFCBLzVp","used":true,"link":"https://staticor.github.io/tag/gpFCBLzVp/"},{"name":"spark","slug":"nj3HlfkXw","used":true,"link":"https://staticor.github.io/tag/nj3HlfkXw/"}],"feature":"https://staticor.github.io/post-images/spark-yuan-ma-xue-xi-wu-cun-chu-yu-xu-lie-hua.png","link":"https://staticor.github.io/post/spark-yuan-ma-xue-xi-wu-cun-chu-yu-xu-lie-hua/","stats":{"text":"2 min read","time":72000,"words":302,"minutes":2},"isTop":false,"toc":"\n","date":"2022-06-03 22:39:40","dateFormat":"2022-06-03"},{"fileName":"si-suo-ru-men-zhi-shi","abstract":"","description":"定义: A有B的依赖资源,B有A的依赖资源,双方都不能抢夺走对方的资源,也不放弃目前拥有的,为了完成自己的目标采取互相等待,导致程序无法进行下去的现象。 死锁产生的条件, 四个条件都发生时,才可能会产生死锁。 互斥使用,某些资源只能交由一...","title":"死锁入门与银行家算法","tags":[],"feature":"https://staticor.github.io/post-images/si-suo-ru-men-zhi-shi.jpeg","link":"https://staticor.github.io/post/si-suo-ru-men-zhi-shi/","stats":{"text":"4 min read","time":211000,"words":836,"minutes":4},"isTop":false,"toc":"\n","date":"2022-06-03 09:47:10","dateFormat":"2022-06-03"},{"fileName":"ji-ji-ge-you-yi-si-de-zhi-li-ti-mu","abstract":"","description":"赛马问题 ##25匹 25匹马,速度不相同,赛道最多同时最多允许5匹马比赛。 最少要几次找出最快的3匹? 7场。 编码为 A1-A5,B1-B5,。。。E1-E5. 前5场比出各组的第一快, 即找出A1,B1,C1,D1,E1。 第六场比 ...","title":"记几个有意思的智力题目","tags":[{"name":"puzzle","slug":"W00hF4OBW","used":true,"link":"https://staticor.github.io/tag/W00hF4OBW/"}],"feature":"https://staticor.github.io/post-images/ji-ji-ge-you-yi-si-de-zhi-li-ti-mu.jpg","link":"https://staticor.github.io/post/ji-ji-ge-you-yi-si-de-zhi-li-ti-mu/","stats":{"text":"2 min read","time":75000,"words":331,"minutes":2},"isTop":false,"toc":"\n","date":"2022-06-03 09:42:12","dateFormat":"2022-06-03"},{"fileName":"linux-wen-jian-xi-tong-quan-xian-yu-find-ming-ling","abstract":"","description":"Rwx 拥有者, 群组, 其它组 三个权限粒度。 每个权限粒度都可以设定它的 可读,可写,可执行的权限, rwx 三个字母 分别用一个二进制位 rwx 4 , 2, 1 来表示, rwx 就是7 rw 就是6 ...","title":"Linux 文件系统权限与Find命令","tags":[{"name":"linux","slug":"Qyd-OEPcb","used":true,"link":"https://staticor.github.io/tag/Qyd-OEPcb/"}],"feature":"","link":"https://staticor.github.io/post/linux-wen-jian-xi-tong-quan-xian-yu-find-ming-ling/","stats":{"text":"2 min read","time":107000,"words":484,"minutes":2},"isTop":false,"toc":"","date":"2022-06-02 20:02:17","dateFormat":"2022-06-02"},{"fileName":"spark-yuan-ma-xue-xi-si-nei-cun-guan-li","abstract":"","description":"查看源码的入口类, Spark的 SparkEnv: val memoryManager: MemoryManager = UnifiedMemoryManager(conf, numUsableCores) 声明语句中val memo...","title":"Spark源码学习(四)-内存管理","tags":[{"name":"sourcecode","slug":"gpFCBLzVp","used":true,"link":"https://staticor.github.io/tag/gpFCBLzVp/"},{"name":"spark","slug":"nj3HlfkXw","used":true,"link":"https://staticor.github.io/tag/nj3HlfkXw/"}],"feature":"https://staticor.github.io/post-images/spark-yuan-ma-xue-xi-si-nei-cun-guan-li.jpg","link":"https://staticor.github.io/post/spark-yuan-ma-xue-xi-si-nei-cun-guan-li/","stats":{"text":"4 min read","time":207000,"words":888,"minutes":4},"isTop":false,"toc":"\n","date":"2022-06-02 14:07:11","dateFormat":"2022-06-02"},{"fileName":"data-marketplace","abstract":"","description":" Data governance 『数据治理』 Data discovery on-time 嗅探上游的metadata变动, 新增表,字段,格式, etc。 **- Data catalog ** 业务上的数据标签 Data li...","title":"Data Marketplace","tags":[{"name":"data marketplace","slug":"sYbTBNDF0","used":true,"link":"https://staticor.github.io/tag/sYbTBNDF0/"}],"feature":"","link":"https://staticor.github.io/post/data-marketplace/","stats":{"text":"2 min read","time":79000,"words":325,"minutes":2},"isTop":false,"toc":"\n","date":"2022-06-02 11:00:05","dateFormat":"2022-06-02"},{"fileName":"data-warehouse-design","abstract":"","description":"数据仓库 vs 数据库, 阅读我的另一篇文章 Data Warehouse 是给谁设计的, 正如Service Engineer, 他们建设的Application/Server是服务于公司的客户。 而DataWarehouse是给公司的...","title":"Data warehouse - Design","tags":[{"name":"数据仓库","slug":"6ykOqkgld","used":true,"link":"https://staticor.github.io/tag/6ykOqkgld/"}],"feature":"https://staticor.github.io/post-images/data-warehouse-design.jpg","link":"https://staticor.github.io/post/data-warehouse-design/","stats":{"text":"7 min read","time":383000,"words":1813,"minutes":7},"isTop":false,"toc":"\n","date":"2022-06-01 18:27:27","dateFormat":"2022-06-01"},{"fileName":"data-architectures-in-companies","abstract":"","description":"Netflix 2018年 Shopee 2021 shopee@2021, Author: Huang Lianghui (Data warehouse architecture ) Shopee 2020年Q1季度的订单量达42...","title":"Data Architectures in companies","tags":[{"name":"数据架构","slug":"kdsz4v2vc","used":true,"link":"https://staticor.github.io/tag/kdsz4v2vc/"},{"name":"大数据","slug":"m5G8CnLvG","used":true,"link":"https://staticor.github.io/tag/m5G8CnLvG/"}],"feature":"https://staticor.github.io/post-images/data-architectures-in-companies.jpg","link":"https://staticor.github.io/post/data-architectures-in-companies/","stats":{"text":"2 min read","time":78000,"words":316,"minutes":2},"isTop":false,"toc":"\n","date":"2022-06-01 17:03:19","dateFormat":"2022-06-01"},{"fileName":"spark-yuan-ma-xue-xi-er-ren-wu-he-jie-duan-hua-fen","abstract":"","description":"Spark应用有这么几个概念: Job, Stage和Task Job以行动算子为界, RDD 的 action算子会执行 runJob 方法 (SparkContext类). Stage 以Shuffle依赖为界, 遇到一次Shuff...","title":"Spark源码学习(二)-阶段划分和任务执行","tags":[{"name":"spark","slug":"nj3HlfkXw","used":true,"link":"https://staticor.github.io/tag/nj3HlfkXw/"}],"feature":"https://staticor.github.io/post-images/spark-yuan-ma-xue-xi-er-ren-wu-he-jie-duan-hua-fen.jpg","link":"https://staticor.github.io/post/spark-yuan-ma-xue-xi-er-ren-wu-he-jie-duan-hua-fen/","stats":{"text":"14 min read","time":832000,"words":2581,"minutes":14},"isTop":false,"toc":"\n","date":"2022-05-31 22:03:30","dateFormat":"2022-05-31"},{"fileName":"spark-yuan-ma-xue-xi-yi-sparkcontext","abstract":"","description":"Shuffle分成两个阶段来看待, Shuffle Write和Shuffle Read. 前者由Map端处理, 写到磁盘(数据文件+索引文件). 后者由Reduce端处理, 调用ShuffleRDD的compute方法完成read. S...","title":"Spark源码学习(三)-Shuffle","tags":[{"name":"sourcecode","slug":"gpFCBLzVp","used":true,"link":"https://staticor.github.io/tag/gpFCBLzVp/"},{"name":"spark","slug":"nj3HlfkXw","used":true,"link":"https://staticor.github.io/tag/nj3HlfkXw/"}],"feature":"https://staticor.github.io/post-images/spark-yuan-ma-xue-xi-yi-sparkcontext.jpg","link":"https://staticor.github.io/post/spark-yuan-ma-xue-xi-yi-sparkcontext/","stats":{"text":"9 min read","time":480000,"words":1650,"minutes":9},"isTop":false,"toc":"\n","date":"2022-05-31 21:53:05","dateFormat":"2022-05-31"},{"fileName":"spark-de-oom-fen-xi","abstract":"","description":"若作业提交时以yarn-client模式提交,Driver运行时的JVM参数是spark在spark-env.sh 配置文件中读取,因此在spark-env.sh中加入配置:SPARK_DRIVER_MEMORY=2G 作业在每个worke...","title":"Spark的OOM分析","tags":[],"feature":"","link":"https://staticor.github.io/post/spark-de-oom-fen-xi/","stats":{"text":"3 min read","time":125000,"words":369,"minutes":3},"isTop":false,"toc":"","date":"2022-05-31 15:41:51","dateFormat":"2022-05-31"},{"fileName":"paper-the-google-file-system-gfs","abstract":"","description":"知名的文件管理系统: GFS,HDFS,Facebook Haystack,FastDFS ... ... GFS是最著名的分布式文件系统,模仿者众多。 文件系统标准接口: (所有文件系统都必须提供) 创建, 删除, 打开, 关闭, 读取...","title":"[Paper] GFS, BigTable","tags":[{"name":"分布式系统","slug":"uPPtz-6Ny","used":true,"link":"https://staticor.github.io/tag/uPPtz-6Ny/"},{"name":"paper","slug":"BJ_Iwwr-R","used":true,"link":"https://staticor.github.io/tag/BJ_Iwwr-R/"}],"feature":"https://staticor.github.io/post-images/paper-the-google-file-system-gfs.jpg","link":"https://staticor.github.io/post/paper-the-google-file-system-gfs/","stats":{"text":"15 min read","time":893000,"words":4005,"minutes":15},"isTop":false,"toc":"\n","date":"2022-05-28 14:40:35","dateFormat":"2022-05-28"},{"fileName":"spark-shu-ju-lei-xing-xi-tong","abstract":"","description":"Spark (ScalaDoc) : https://spark.apache.org/docs/3.2.0/api/scala/org/apache/spark/sql/types/IntegerType$.html Spark -SQL...","title":"Spark数据类型系统","tags":[{"name":"spark","slug":"nj3HlfkXw","used":true,"link":"https://staticor.github.io/tag/nj3HlfkXw/"}],"feature":"https://staticor.github.io/post-images/spark-shu-ju-lei-xing-xi-tong.jpeg","link":"https://staticor.github.io/post/spark-shu-ju-lei-xing-xi-tong/","stats":{"text":"2 min read","time":73000,"words":260,"minutes":2},"isTop":false,"toc":"\n","date":"2022-05-27 09:34:37","dateFormat":"2022-05-27"},{"fileName":"du-shu-note-ddia-designing-data-intensive-applications","abstract":"","description":"DDIA 是 Designing Data-Intensive Applications 的简称,因为此书具有一定的普遍性和推广意义, 所以在数据计算领域和分布存储,有单独的称呼. 我对这本书基于之前几位社区译者的基础上进行了再次翻译和备注...","title":"读书Note-《DDIA》","tags":[{"name":"booknote","slug":"gIWbJvnhi","used":true,"link":"https://staticor.github.io/tag/gIWbJvnhi/"},{"name":"datasystem","slug":"fpMRWEw0UV","used":true,"link":"https://staticor.github.io/tag/fpMRWEw0UV/"}],"feature":"https://staticor.github.io/post-images/du-shu-note-ddia-designing-data-intensive-applications.jpeg","link":"https://staticor.github.io/post/du-shu-note-ddia-designing-data-intensive-applications/","stats":{"text":"14 min read","time":785000,"words":3730,"minutes":14},"isTop":false,"toc":"\n","date":"2022-05-26 08:52:28","dateFormat":"2022-05-26"},{"fileName":"apache-kafka-chang-jian-wen-ti","abstract":"","description":"Kafka, 分布式的消息引擎(中间件,消息队列), 提供点对点或发布-订阅,主要处理数据在系统间的流转。 也提供了弱持久化和弱流式计算,但据我了解,多数场景是只把Kafka作为消息传输工具。 为什么要用到消息队列, Kafka作为消息队列...","title":"Apache Kafka常见问题","tags":[],"feature":"","link":"https://staticor.github.io/post/apache-kafka-chang-jian-wen-ti/","stats":{"text":"3 min read","time":178000,"words":806,"minutes":3},"isTop":false,"toc":"","date":"2022-05-23 16:17:40","dateFormat":"2022-05-23"},{"fileName":"bu-tong-chang-jing-de-count1-vs-count","abstract":"","description":"Spark Spark 下 count(1) 与 count(*) 等价。 Spark下 count(*) 在AST Tree层面直接替换为了count(1). 源码: Astbuilder.scala 1830-1850行附近, (...","title":"不同场景的count(1) vs count(*)","tags":[],"feature":"","link":"https://staticor.github.io/post/bu-tong-chang-jing-de-count1-vs-count/","stats":{"text":"1 min read","time":17000,"words":66,"minutes":1},"isTop":false,"toc":"\n","date":"2022-05-22 14:36:24","dateFormat":"2022-05-22"},{"fileName":"segmenttree-and-ufset","abstract":"","description":"线段树 - SegmentTree 为什么要使用线段树? —— Range query 一类典型问题,染色问题。 每次在区间 「a,b」中挑选一个颜色进行粉刷。 求经过m次粉刷操作后,区间「i,j」上有多少种颜色? 线段树对区间查询有...","title":"数据结构(一)-线段树与并查集","tags":[{"name":"数据结构","slug":"BU80pNL4_","used":true,"link":"https://staticor.github.io/tag/BU80pNL4_/"}],"feature":"https://staticor.github.io/post-images/segmenttree-and-ufset.jpeg","link":"https://staticor.github.io/post/segmenttree-and-ufset/","stats":{"text":"3 min read","time":162000,"words":578,"minutes":3},"isTop":false,"toc":"\n","date":"2022-05-20 16:07:41","dateFormat":"2022-05-20"},{"fileName":"spark-de-zhi-xing-ji-hua","abstract":"","description":"A Deep Dive into Spark SQL's Catalyst Optimizer -- Yin Huai https://databricks.com/session/a-deep-dive-into-spark-sqls...","title":"Spark的执行计划","tags":[{"name":"spark","slug":"nj3HlfkXw","used":true,"link":"https://staticor.github.io/tag/nj3HlfkXw/"}],"feature":"https://staticor.github.io/post-images/spark-de-zhi-xing-ji-hua.jpeg","link":"https://staticor.github.io/post/spark-de-zhi-xing-ji-hua/","stats":{"text":"8 min read","time":477000,"words":1505,"minutes":8},"isTop":false,"toc":"\n","date":"2022-05-20 09:03:49","dateFormat":"2022-05-20"},{"fileName":"dui-bi-xue-xi-big-dataer-shu-ju-qing-xie","abstract":"","description":"什么是数据倾斜(Data Skewing) 数据倾斜的前提是分布处理,本质是Shuffle过程时不同Reducer节点的处理处理不均衡; 现象是在观看各Task运行耗时,发现绝大多数Task在很多时间内完成了,而只有部分任务一直在运行,典型...","title":"对比学习--Big Data(二)数据倾斜","tags":[{"name":"hive","slug":"MTigWmiSP","used":true,"link":"https://staticor.github.io/tag/MTigWmiSP/"},{"name":"spark","slug":"nj3HlfkXw","used":true,"link":"https://staticor.github.io/tag/nj3HlfkXw/"},{"name":"大数据","slug":"m5G8CnLvG","used":true,"link":"https://staticor.github.io/tag/m5G8CnLvG/"}],"feature":"https://staticor.github.io/post-images/dui-bi-xue-xi-big-dataer-shu-ju-qing-xie.jpeg","link":"https://staticor.github.io/post/dui-bi-xue-xi-big-dataer-shu-ju-qing-xie/","stats":{"text":"3 min read","time":121000,"words":533,"minutes":3},"isTop":false,"toc":"\n","date":"2022-05-19 11:51:06","dateFormat":"2022-05-19"},{"fileName":"java-zhong-de-hashmap","abstract":"","description":"Java1.8的优化关注点 怎么执行的散列化? Treeify 的时机和要求是什么? 数组中的元素数据至少为64个 链表元素数量至少为8个。 /** * The smallest table capacity for...","title":"数据结构进阶(一) Java中的HashMap","tags":[{"name":"数据结构","slug":"BU80pNL4_","used":true,"link":"https://staticor.github.io/tag/BU80pNL4_/"}],"feature":"https://staticor.github.io/post-images/java-zhong-de-hashmap.webp","link":"https://staticor.github.io/post/java-zhong-de-hashmap/","stats":{"text":"2 min read","time":112000,"words":385,"minutes":2},"isTop":false,"toc":"\n","date":"2022-05-17 21:16:00","dateFormat":"2022-05-17"},{"fileName":"hai-liang-shu-ju-de-jian-suo-he-pai-xu-wen-ti","abstract":"","description":"引子 有一个1G大小文件,每一行是一个词,词的大小不超过16byte, 内存限制是1M,返回频数最高的100个词。 读取每个词,计算hashCode 按hashCode 取模 5000, 将该值存到 5000个小文件,每个文件是200...","title":"海量数据的检索和排序问题","tags":[],"feature":"https://staticor.github.io/post-images/hai-liang-shu-ju-de-jian-suo-he-pai-xu-wen-ti.jpeg","link":"https://staticor.github.io/post/hai-liang-shu-ju-de-jian-suo-he-pai-xu-wen-ti/","stats":{"text":"2 min read","time":64000,"words":295,"minutes":2},"isTop":false,"toc":"\n","date":"2022-05-17 15:51:00","dateFormat":"2022-05-17"},{"fileName":"da-shu-ju-sheng-tai","abstract":"","description":" Hadoop Evolution 2002 Nutch project 2003 - 2004 Google Paper (GFS, MapReduce) 2008 Hadoop ——Apache Foundation , top-...","title":"大数据生态","tags":[{"name":"大数据","slug":"m5G8CnLvG","used":true,"link":"https://staticor.github.io/tag/m5G8CnLvG/"}],"feature":"https://staticor.github.io/post-images/da-shu-ju-sheng-tai.png","link":"https://staticor.github.io/post/da-shu-ju-sheng-tai/","stats":{"text":"4 min read","time":234000,"words":882,"minutes":4},"isTop":false,"toc":"\n","date":"2022-05-17 13:54:17","dateFormat":"2022-05-17"},{"fileName":"spark-chuang-kou-cha-xun","abstract":"","description":"相关代码: https://github.com/staticor/WindowQueryInSparkHive.git 什么是窗口查询 示例1 PARTITION BY country ORDER BY date ROWS BETWEE...","title":"Spark 窗口查询","tags":[{"name":"数据仓库","slug":"6ykOqkgld","used":true,"link":"https://staticor.github.io/tag/6ykOqkgld/"},{"name":"spark","slug":"nj3HlfkXw","used":true,"link":"https://staticor.github.io/tag/nj3HlfkXw/"}],"feature":"https://staticor.github.io/post-images/spark-chuang-kou-cha-xun.png","link":"https://staticor.github.io/post/spark-chuang-kou-cha-xun/","stats":{"text":"6 min read","time":306000,"words":945,"minutes":6},"isTop":false,"toc":"\n","date":"2022-05-16 08:46:32","dateFormat":"2022-05-16"},{"fileName":"compare-study","abstract":"","description":"💚 数据仓库 vs (关系型)数据库 关系型数据库, 面向事务 OLTP 数据库, 信息的持久化,提供了库表字段设计的规范和约束,同时有索引机制,大大加速了查询的性能。 解决的是事务操作,当业务发生时,需要把这一业务事件永久的记录下来,...","title":"对比学习--Big Data(一)","tags":[{"name":"Knowledge","slug":"0gdiih3gE","used":true,"link":"https://staticor.github.io/tag/0gdiih3gE/"},{"name":"大数据","slug":"m5G8CnLvG","used":true,"link":"https://staticor.github.io/tag/m5G8CnLvG/"}],"feature":"https://staticor.github.io/post-images/compare-study.jpeg","link":"https://staticor.github.io/post/compare-study/","stats":{"text":"5 min read","time":254000,"words":1155,"minutes":5},"isTop":true,"toc":"\n","date":"2022-02-01 11:09:08","dateFormat":"2022-02-01"},{"fileName":"hive-de-zhi-xing-ji-hua","abstract":"","description":"Hive的SQL是怎么转变成的MapReduce Driver端, 将HQL语句转换为AST; 借助于 ParseDriver: 将HQL语句转为Token , 对Token解析生成AST; 将AST转换为TaskTree; S...","title":"Hive的执行计划","tags":[{"name":"tuning","slug":"mHEqq0620","used":true,"link":"https://staticor.github.io/tag/mHEqq0620/"},{"name":"hive","slug":"MTigWmiSP","used":true,"link":"https://staticor.github.io/tag/MTigWmiSP/"}],"feature":"https://staticor.github.io/post-images/hive-de-zhi-xing-ji-hua.jpeg","link":"https://staticor.github.io/post/hive-de-zhi-xing-ji-hua/","stats":{"text":"8 min read","time":430000,"words":1370,"minutes":8},"isTop":false,"toc":"\n","date":"2022-01-05 10:23:26","dateFormat":"2022-01-05"},{"fileName":"lin-qun-yuan-shi-guan-yu-wei-ji-fen-de-yan-jiang-nei-rong","abstract":"","description":"内容来自于一个视频: 林群院士|教科书讲得太复杂,学微积分只需要一个案例|格致 林群院士简介: 林群:1935年出生,福建连江人。中国科学院数学与系统科学研究院研究员、中国科学院院士。 在计算数学,特别是微分方程的高性能解法方面,进行了...","title":"林群院士-关于微积分的演讲内容","tags":[{"name":"学习方法","slug":"46uXED5aG","used":true,"link":"https://staticor.github.io/tag/46uXED5aG/"},{"name":"微积分","slug":"c7awgdqP96","used":true,"link":"https://staticor.github.io/tag/c7awgdqP96/"}],"feature":"","link":"https://staticor.github.io/post/lin-qun-yuan-shi-guan-yu-wei-ji-fen-de-yan-jiang-nei-rong/","stats":{"text":"3 min read","time":169000,"words":841,"minutes":3},"isTop":false,"toc":"","date":"2021-07-08 10:02:53","dateFormat":"2021-07-08"},{"fileName":"hivetuning0520","abstract":"","description":"what and why Tuning: 逻辑正确,但实现消耗的资源超出预期。 资源: 任务运行时间, 占用集群的计算资源(Memory&Cores) 解决问题,促使Tuning的契机 —— 对现行任务,虽然能顺利执行成功,但对效果...","title":"「数据仓库技能树」--Hive Tuning","tags":[{"name":"hive","slug":"MTigWmiSP","used":true,"link":"https://staticor.github.io/tag/MTigWmiSP/"},{"name":"数据仓库","slug":"6ykOqkgld","used":true,"link":"https://staticor.github.io/tag/6ykOqkgld/"}],"feature":"https://staticor.github.io/post-images/hivetuning0520.jpeg","link":"https://staticor.github.io/post/hivetuning0520/","stats":{"text":"5 min read","time":259000,"words":1036,"minutes":5},"isTop":false,"toc":"\n","date":"2021-05-05 11:22:37","dateFormat":"2021-05-05"},{"fileName":"sparktuning","abstract":"","description":"文章主题, Spark调优。 Spark 任务要避免成为集群的运转负担,包括不合理的占用CPU、带宽和内存资源。 官方的Tuning文档 Tuning Spark 文档介绍了调优的三个思路: Data Serialization 数据序...","title":"Spark Tuning","tags":[{"name":"jvm","slug":"mA96HGnuT","used":true,"link":"https://staticor.github.io/tag/mA96HGnuT/"},{"name":"spark","slug":"nj3HlfkXw","used":true,"link":"https://staticor.github.io/tag/nj3HlfkXw/"}],"feature":"https://staticor.github.io/post-images/sparktuning.jpeg","link":"https://staticor.github.io/post/sparktuning/","stats":{"text":"5 min read","time":282000,"words":1205,"minutes":5},"isTop":false,"toc":"\n","date":"2021-05-05 11:22:37","dateFormat":"2021-05-05"},{"fileName":"data-modeling","abstract":"","description":"文章主题, Data Modeling Data Modeling (数据建模)的阶段 概念模型 逻辑模型 物理模型 这三个阶段与Hive/Spark 对于SQL语句的Explain过程如出一辙。 概念模型 ( Conceptual d...","title":"0521-Data Modeling (一)数据建模的三个阶段","tags":[{"name":"modeling","slug":"_DeA0FdqU","used":true,"link":"https://staticor.github.io/tag/_DeA0FdqU/"},{"name":"数据仓库","slug":"6ykOqkgld","used":true,"link":"https://staticor.github.io/tag/6ykOqkgld/"}],"feature":"https://staticor.github.io/post-images/data-modeling.jpg","link":"https://staticor.github.io/post/data-modeling/","stats":{"text":"2 min read","time":75000,"words":346,"minutes":2},"isTop":false,"toc":"\n","date":"2021-05-04 11:22:37","dateFormat":"2021-05-04"},{"fileName":"MySQL-stuff","abstract":"

提交语句到执行查询的过程(用户侧)

\n\n

一切要从连接开始, MySQL使用TCP连接。

\n
mysql -h$ip -P$port -u$user -p"password"\n
\n

服务端经TCP握手,建立连接,鉴定用户的身份:密码+权限都在这个过程完成校验。
\n(多久处理idle的连接? 在MySQL对应参数是 wait_timeout, 默认值 28800 秒, 8小时)

\n

提交查询命令, 之前总觉得MySQL有个cache机制,但后来了解到这个查询缓存功能有些鸡肋。
\n命中率低,失效情况普遍(前后两个查询,变动一个字符都会视作不同的查询)。

\n

在新版8.0已经删除了cache。

\n\n

接下来MySQL服务端要将客户端的提交查询进行解析,是否合法的查询。 词法分析

\n\n

( Hive将query转换成MapReduce的过程,也会经parser & analyzer & optimze 的过程)

\n

redo log , binlog

\n

数据持久化,一般有两个思路。

\n\n

我们自然希望edit log 越小越好(恢复操作快), image离目前越近越好(恢复操作快)。 所以editLog和image需要经常合并, 在Hadoop NameNode中,它有个辅助节点称为 Secondary NameNode就是完成的这项工作。

\n

而在MySQL中,采用的是WAL(Write-Ahead Logging),先写日志,再写磁盘。

\n
\"\"
\n

redo log 的 容量和设计: 几块文件(环形队列), 每个文件写到末尾从到再写。

\n

参考: https://dev.mysql.com/blog-archive/mysql-8-0-new-lock-free-scalable-wal-design/

\n

事务隔离

\n

ACID(我的记忆口决: 毛利兰的朋友—— 园子 一直要抹隔离霜, 持久防晒)

\n

SQL的事务隔离级别:

\n\n

transaction_isolation 查看当前的隔离级别。

\n

MVCC 与 undo log

\n

每行数据是有多个版本的,每次事务更新,修改了这行数据,都会生成一个新的数据版本。
\n每个数据版本有一列 transaction id 就对应着修改它的事务id, 标记为 row trx_id 。
\n如果这行数据被改了多次,就会有多个版本,对应着不同的 row trx_id。

\n

MySQL中的另一个日志: undo log

\n

事务的一致性视图(read-view)

\n

InnoDB为每个事务构造了一个数组,用来保存事务启动时,当前正在活跃的所有事务ID,活跃(启动了但未提交) 数组里事务ID最小值记为 低水位,当前系统已经创建的事务ID最大值+1 记为高水位。

\n

这个视图数组和高水位,组成了当前这个事务的一致性视图。 用之来判定数据的可见性:

\n\n

索引 B+树

\n

关于索引,描述得再多也不过份。
\n关于索引的生活举例,我个人更喜欢用的例子,并非是书中目录和页的关系,而是图书馆中的书架和书的关系。 有过借阅经历的我们能回忆起自己借书的步骤:

\n\n

以前在工作时,和索引接触最多的场景,就是接到DBA的要求,对线上慢查进行优化。

\n

当时内心第一反应就是“糟了,是不是索引没有用对(没走理想索引)“。

\n

系统来看,索引就是帮助寻地址定位数据的信息量,也是存放在数据页中的。

\n

在数据结构中,哈希表也是一种索引模型 —— 将key通过hash函数,使用hash结果可以快速回答底部元素是否存在的问题。 hash的局限性也非常明显,无法解决范围和带顺序的检索需求。

\n

第二种想法是使用连续数组,使用二分查找,也能快速查找一个或者一片连续的数据。
\n但数组的问题就是添加和删除数据时的维护成本,远没有链表灵活。所以,有序数组只适用于存储静态数据。

\n

第三种想法是引入非线性数据结构,树。

\n

BST,AVL,RB树。
\nMySQL使用的是多叉树(N叉的N,一般可达到1000-2000),数据都存放在叶子节点,中间节点只放索引。
\n如果是4层的树高, 第三层所有叶子节点的数量是1500的三次方,超过30亿数据了。
\n称为B-树, 或B+树。

\n

根节点常驻内存,中间节点的data page 是在磁盘的。
\n磁盘的IO 寻址是毫秒级,所以一次定位数据一般要2-3次的树中向下遍历工作。

\n

普通索引和唯一索引

\n

聚簇索引和非聚簇索引

\n

聚簇索引的叶子节点内容是整行的数据。 非主键索引的叶子节点是存放的主键的值, 在InnoDB 非主键索引也称为二级索引。

\n

回表: 二级索引,如果查询的数据是不在索引中的, 还要将查到的主键值再到聚簇索引中再查一次。
\n所以查询要尽量使用主键查询。

\n

覆盖索引, 查主键树就能返回查询结果,不用回表。

\n

索引建立

\n

索引维护

\n

页分裂: 新插入数据,会占掉原来的链表位置,即data page的空间。 那么如果这个页已经满了,B+树算法的设计是要申请一个新页,挪动部分数据。 就像动态数组的扩容, 页分裂显然是对性能有影响的。

\n

有分裂也有对应的逆过程——合并页。

\n

怎样减少分裂的操作呢。
\n如果说数据每次插入都是递增的,不会插入到之前的历史数据中,不就好了么?

\n

这恰恰响应了自增主键的表设计规范。

\n

索引下推

\n

Index condition pushdown

\n

在联合索引中,申请回表前增加判断, 减少回表次数。

\n

\n

锁的划分有多种方式,比较直观的分类是按锁的影响范围,分为

\n\n

全局锁, 对整个数据库加锁。 flush tables with read lock 场景,全库备份。
\n表级锁, lock tables ... read / write 分为表读锁和表写锁。
\n另一类表级锁是 MDL(Metadata lcok)。

\n

行锁,不是所有的引擎都支持的。 InNoDB支持行锁,在需要的时候加上行锁,事务结束之后释放,即两阶段锁协议。

\n

死锁

\n

以两个事务为例,死锁满足的条件是:

\n\n

死锁检测的成本要在服务端。

\n

附录:相关命令

\n
show processlist;  -- show current connections, include users, ip address and port, state\n\n\nshow variables  like '%timeout%' --  find  wait_timeout   28800 seconds\n\n
\n","description":"提交语句到执行查询的过程(用户侧) 连接器 一切要从连接开始, MySQL使用TCP连接。 mysql -h$ip -P$port -u$user -p"password" 服务端经TCP握手,建立连接,鉴定用户...","title":"MySQL要知道的知识","tags":[{"name":"MySQL","slug":"7bucEeeDnv","used":true,"link":"https://staticor.github.io/tag/7bucEeeDnv/"}],"feature":"https://staticor.github.io/post-images/MySQL-stuff.jpeg","link":"https://staticor.github.io/post/MySQL-stuff/","stats":{"text":"8 min read","time":444000,"words":2060,"minutes":8},"isTop":false,"toc":"\n","date":"2021-03-17 16:55:49","dateFormat":"2021-03-17"},{"fileName":"JItOkbzRI","abstract":"","description":"本文章,这本书的书摘。 书评 这本书在世界局势非常不确定的时候面世。他所详述的大议题,中东、中国、美国和欧洲,无论在今天还是在他或者我的有生之年,都充满着困难和挑战。 —— 卡林顿爵士, 北约组织秘书长 多年来,我有幸和他圣诞,并且...","title":"booknote,《李光耀观天下》","tags":[{"name":"新加坡","slug":"xin-jia-po","used":true,"link":"https://staticor.github.io/tag/xin-jia-po/"},{"name":"booknote","slug":"gIWbJvnhi","used":true,"link":"https://staticor.github.io/tag/gIWbJvnhi/"}],"feature":"https://staticor.github.io/post-images/JItOkbzRI.png","link":"https://staticor.github.io/post/JItOkbzRI/","stats":{"text":"6 min read","time":303000,"words":1507,"minutes":6},"isTop":false,"toc":"\n","date":"2021-03-04 15:08:42","dateFormat":"2021-03-04"},{"fileName":"learnnote-suan-fa-dao-lun-2020mit-yi","abstract":"","description":"2020年之后的新版课程,由 Eric Demaine 主讲。 (2010年之前老视频中的二讲教授) 主讲:Prof. Eric Demaine, Dr. Jason Ku, Prof. Justin Solomon 课程主页:htt...","title":"MIT 6.006--LearnNote-算法导论(2020.MIT)","tags":[{"name":"公开课","slug":"hAXiKNsM-","used":true,"link":"https://staticor.github.io/tag/hAXiKNsM-/"},{"name":"algorithm","slug":"1QR-i3r4F","used":true,"link":"https://staticor.github.io/tag/1QR-i3r4F/"}],"feature":"","link":"https://staticor.github.io/post/learnnote-suan-fa-dao-lun-2020mit-yi/","stats":{"text":"4 min read","time":194000,"words":800,"minutes":4},"isTop":false,"toc":"\n","date":"2020-09-25 10:45:04","dateFormat":"2020-09-25"},{"fileName":"NLZWE3893","abstract":"","description":"土匪(侠者)。 师爷,骗子。 黄四爷,恶霸地主。 曾经的革命者。 经典台词 来者不善啊。。。 “你才是来者!” 反正呢,我就是想当县长夫人。谁当 你是想站着,还是想挣钱? 我是想站着,还把钱挣了! 小六子...","title":"让子弹飞一会儿","tags":[{"name":"movie","slug":"movie","used":true,"link":"https://staticor.github.io/tag/movie/"}],"feature":"","link":"https://staticor.github.io/post/NLZWE3893/","stats":{"text":"3 min read","time":133000,"words":666,"minutes":3},"isTop":false,"toc":"\n","date":"2020-06-02 17:35:18","dateFormat":"2020-06-02"},{"fileName":"GC-in-java","abstract":"","description":"不断更新, 预计还需要1个月完成。 Java GC概念 什么是垃圾? 在程序运行中,没有任何指针(引用)指向的对象。 An object is considered garbage when it can no longer be r...","title":"Java的GC","tags":[{"name":"jvm","slug":"mA96HGnuT","used":true,"link":"https://staticor.github.io/tag/mA96HGnuT/"},{"name":"java","slug":"uVyPsHAfy_","used":true,"link":"https://staticor.github.io/tag/uVyPsHAfy_/"}],"feature":"https://staticor.github.io/post-images/GC-in-java.jpeg","link":"https://staticor.github.io/post/GC-in-java/","stats":{"text":"7 min read","time":416000,"words":1833,"minutes":7},"isTop":false,"toc":"\n","date":"2020-05-29 15:12:34","dateFormat":"2020-05-29"},{"fileName":"resilient-distributed-datasets-a-fault-tolerant-abstraction-for-in-memory-cluster-computing","abstract":"","description":"Abstract 我们提出了RDD, 弹性分布式数据集 ---- 一种分布式内存的抽象结构, 允许程序员以容错方式在大型集群上执行内存计算. 在当前计算框架下,有两种类型的计算应用表现性能较低: 迭代式算法和交互式数据挖掘, RDD也是受...","title":"[Paper]Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing","tags":[{"name":"paper","slug":"BJ_Iwwr-R","used":true,"link":"https://staticor.github.io/tag/BJ_Iwwr-R/"},{"name":"spark","slug":"nj3HlfkXw","used":true,"link":"https://staticor.github.io/tag/nj3HlfkXw/"}],"feature":"https://staticor.github.io/post-images/resilient-distributed-datasets-a-fault-tolerant-abstraction-for-in-memory-cluster-computing.jpg","link":"https://staticor.github.io/post/resilient-distributed-datasets-a-fault-tolerant-abstraction-for-in-memory-cluster-computing/","stats":{"text":"7 min read","time":384000,"words":1727,"minutes":7},"isTop":false,"toc":"\n","date":"2020-05-01 22:13:48","dateFormat":"2020-05-01"},{"fileName":"sparkkrdd1","abstract":"","description":"本篇文章结合 https://spark.apache.org/docs/latest/rdd-programming-guide.html#overview Paper 每个Spark应用的driver program都是由用户侧的mai...","title":"Spark中的RDD(一)+ Paper Reading","tags":[{"name":"spark","slug":"nj3HlfkXw","used":true,"link":"https://staticor.github.io/tag/nj3HlfkXw/"},{"name":"大数据","slug":"m5G8CnLvG","used":true,"link":"https://staticor.github.io/tag/m5G8CnLvG/"}],"feature":"https://staticor.github.io/post-images/sparkkrdd1.jpg","link":"https://staticor.github.io/post/sparkkrdd1/","stats":{"text":"8 min read","time":469000,"words":1595,"minutes":8},"isTop":false,"toc":"\n","date":"2019-12-02 20:45:41","dateFormat":"2019-12-02"},{"fileName":"hello-gridea","abstract":"

👏 欢迎使用 Gridea
\n✍️ Gridea 一个静态博客写作客户端。你可以用它来记录你的生活、心情、知识、笔记、创意... ...

\n","description":"👏 欢迎使用 Gridea ! ✍️ Gridea 一个静态博客写作客户端。你可以用它来记录你的生活、心情、知识、笔记、创意... ... Github Gridea 主页 示例网站 特性👇 📝 你可以使用最酷的 Markdo...","title":"Hello Gridea","tags":[],"feature":"https://staticor.github.io/post-images/hello-gridea.png","link":"https://staticor.github.io/post/hello-gridea/","stats":{"text":"1 min read","time":53000,"words":259,"minutes":1},"isTop":false,"toc":"\n","date":"2018-12-12 00:00:00","dateFormat":"2018-12-12"},{"fileName":"booknote-c-primer-plus","abstract":"","description":"速成回顾, learnxinyminutes-c lang C语言经典书, 通过关键插图或代码块记录这本书的精华。 第一个C语言程序, #include<stdio.h> 表示引入标准IO库的头文件。 接下来定义了返回值为in...","title":"Booknote,","tags":[{"name":"C lang","slug":"yRD3c8faX","used":true,"link":"https://staticor.github.io/tag/yRD3c8faX/"},{"name":"booknote","slug":"gIWbJvnhi","used":true,"link":"https://staticor.github.io/tag/gIWbJvnhi/"}],"feature":"https://staticor.github.io/post-images/booknote-c-primer-plus.jpg","link":"https://staticor.github.io/post/booknote-c-primer-plus/","stats":{"text":"4 min read","time":214000,"words":942,"minutes":4},"isTop":false,"toc":"","date":"2016-06-09 13:11:37","dateFormat":"2016-06-09"}],"tags":[{"name":"onedata","slug":"onedata","used":true,"link":"https://staticor.github.io/tag/onedata/","count":1},{"name":"数据中台","slug":"Wc3ajsb70","used":true,"link":"https://staticor.github.io/tag/Wc3ajsb70/","count":2},{"name":"bigdata","slug":"D6pPBGMAh","used":true,"link":"https://staticor.github.io/tag/D6pPBGMAh/","count":1},{"name":"booknote","slug":"gIWbJvnhi","used":true,"link":"https://staticor.github.io/tag/gIWbJvnhi/","count":5},{"name":"datasystem","slug":"fpMRWEw0UV","used":true,"link":"https://staticor.github.io/tag/fpMRWEw0UV/","count":3},{"name":"数据仓库","slug":"6ykOqkgld","used":true,"link":"https://staticor.github.io/tag/6ykOqkgld/","count":6},{"name":"大数据","slug":"m5G8CnLvG","used":true,"link":"https://staticor.github.io/tag/m5G8CnLvG/","count":6},{"name":"sourcecode","slug":"gpFCBLzVp","used":true,"link":"https://staticor.github.io/tag/gpFCBLzVp/","count":3},{"name":"spark","slug":"nj3HlfkXw","used":true,"link":"https://staticor.github.io/tag/nj3HlfkXw/","count":11},{"name":"puzzle","slug":"W00hF4OBW","used":true,"link":"https://staticor.github.io/tag/W00hF4OBW/","count":1},{"name":"linux","slug":"Qyd-OEPcb","used":true,"link":"https://staticor.github.io/tag/Qyd-OEPcb/","count":1},{"name":"data marketplace","slug":"sYbTBNDF0","used":true,"link":"https://staticor.github.io/tag/sYbTBNDF0/","count":1},{"name":"数据架构","slug":"kdsz4v2vc","used":true,"link":"https://staticor.github.io/tag/kdsz4v2vc/","count":1},{"name":"分布式系统","slug":"uPPtz-6Ny","used":true,"link":"https://staticor.github.io/tag/uPPtz-6Ny/","count":1},{"name":"paper","slug":"BJ_Iwwr-R","used":true,"link":"https://staticor.github.io/tag/BJ_Iwwr-R/","count":2},{"name":"数据结构","slug":"BU80pNL4_","used":true,"link":"https://staticor.github.io/tag/BU80pNL4_/","count":2},{"name":"hive","slug":"MTigWmiSP","used":true,"link":"https://staticor.github.io/tag/MTigWmiSP/","count":3},{"name":"Knowledge","slug":"0gdiih3gE","used":true,"link":"https://staticor.github.io/tag/0gdiih3gE/","count":1},{"name":"tuning","slug":"mHEqq0620","used":true,"link":"https://staticor.github.io/tag/mHEqq0620/","count":1},{"name":"学习方法","slug":"46uXED5aG","used":true,"link":"https://staticor.github.io/tag/46uXED5aG/","count":1},{"name":"微积分","slug":"c7awgdqP96","used":true,"link":"https://staticor.github.io/tag/c7awgdqP96/","count":1},{"name":"jvm","slug":"mA96HGnuT","used":true,"link":"https://staticor.github.io/tag/mA96HGnuT/","count":2},{"name":"modeling","slug":"_DeA0FdqU","used":true,"link":"https://staticor.github.io/tag/_DeA0FdqU/","count":1},{"name":"MySQL","slug":"7bucEeeDnv","used":true,"link":"https://staticor.github.io/tag/7bucEeeDnv/","count":1},{"name":"新加坡","slug":"xin-jia-po","used":true,"link":"https://staticor.github.io/tag/xin-jia-po/","count":1},{"name":"公开课","slug":"hAXiKNsM-","used":true,"link":"https://staticor.github.io/tag/hAXiKNsM-/","count":1},{"name":"algorithm","slug":"1QR-i3r4F","used":true,"link":"https://staticor.github.io/tag/1QR-i3r4F/","count":1},{"name":"movie","slug":"movie","used":true,"link":"https://staticor.github.io/tag/movie/","count":1},{"name":"java","slug":"uVyPsHAfy_","used":true,"link":"https://staticor.github.io/tag/uVyPsHAfy_/","count":1},{"name":"C lang","slug":"yRD3c8faX","used":true,"link":"https://staticor.github.io/tag/yRD3c8faX/","count":1}],"menus":[{"link":"/","name":"首页","openType":"Internal"},{"link":"/archives","name":"归档","openType":"Internal"},{"link":"/tags","name":"标签","openType":"Internal"},{"link":"/post/about","name":"关于","openType":"Internal"}],"themeConfig":{"themeName":"pure","postPageSize":15,"archivesPageSize":50,"siteName":"staticor in data","siteDescription":"To Think You Have to Write","footerInfo":"Powered by staticor @ github ","showFeatureImage":true,"domain":"https://staticor.github.io/","postUrlFormat":"SHORT_ID","tagUrlFormat":"SLUG","dateFormat":"YYYY-MM-DD","feedFullText":false,"feedCount":10,"archivesPath":"archives","postPath":"post","tagPath":"tag"},"customConfig":{"APP_ID":"","APP_KEY":"","about":"","avatar":"","caf":"#84fab0","ccf":"#5f6169","ccs":"#999fa7","ctf":"#ffffff","cts":"#dddddd","customCss":"","descfriend":"","dribbble":"","facebook":"","friends":[],"ga":"","github":"github.com/staticor","isEnabledCustomColor":false,"mermaid":true,"pageSize":"5","placeholder":"Just Go Go","recordIp":false,"skin":"green","twitter":"","vMaxWidth":"1000","vPadding":"2.5%","vPercentWidth":"100","valine":false,"visitor":false,"weibo":"","zhihu":""},"utils":{"now":1655881153291}} diff --git a/archives/index.html b/archives/index.html index f740ee01..ce733be8 100644 --- a/archives/index.html +++ b/archives/index.html @@ -1,107 +1,1097 @@ - + + -Gridea - - + +归档 | staticor in data + + + + - - + + + + + + + + + + - - - -
-
-