OpenSpec 实验
实验元数据 (Meta Data) 用于日后检索和归档,建立知识索引。 实验编号/标题:OpenSpec 实验 ...
CSS 复习
基础核心 - 基础核心 - CSS 基础与选择器 - CSS 的三种引入方式 - ```html <!-- 1. 行内样式:直接写在标签上,优先级最高但不推荐大量使用 --> <p style="color: red; font-size: 16px;">这是行内样式</p> <!-- 2. 内嵌样式:写在 <head> 的 <style> 标签里 --> <head> <style> p { color: blue; } </style> </head> <!-- 3. 外链样式(推荐!):单独的 .css 文件 --> <link rel="stylesheet" href="style.css"> ``` - 基础选择器 - ```css /* 元素选择器 —— 选中所有该标签 */ p { color: #333; } h1 { font-size: 24px; } /* 类选择器 —— 最常用,可复用 */ .card { background: #fff; padding: 16px; } .highlight { color: orange; } /* ID 选择器 —— 唯一的,一个页面同一个 ID 只用一次 */ #header { height: 60px; } /* 通配符选择器 —— 选中所有元素,常用于重置 */ * { margin: 0; padding: 0; box-sizing: border-box; /* 第 3 天会详细讲 */ } ``` 布局体系 响应式与进阶 工程化与实战
思维导图新功能测试
- 项目规划 - 前端 - 示例代码 ```js let a = 3 console.log(a) ``` - <h1 style="color: blue"> 你好 </h1> -  - java 代码 ```java public class TimestampWatermarkDemo { public static class Event { public String user; public long eventTime; public int value; public Event(){} public Event(String user, long eventTime, int value) { this.user = user; this.eventTime = eventTime; this.value = value; } public long getEventTime() { return eventTime; } @Override public String toString() { return "Event{" + "user='" + user + '\'' + ", eventTime=" + eventTime + ", value=" + value + '}'; } } public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); DataStream<Event> source = env.fromElements( new Event("alice", 1000L, 1), new Event("alice", 4000L, 1), new Event("alice", 3000L, 1), new Event("bob", 8000L, 1), new Event("alice", 12000L, 1) ); WatermarkStrategy<Event> wmStrategy = WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ofSeconds(5)) .withTimestampAssigner((event, recordTimestamp) -> event.getEventTime()); DataStream<Event> withWaterMark = source.assignTimestampsAndWatermarks(wmStrategy); withWaterMark.print(); env.execute("timestamp-water-mark-demo"); } } ``` 1 let a = 3
不同寻常
贴上“我的“标签, 带着强烈的执着, 一切都会显得不同寻常。 ...
Flink 复习
Flink 整体认知 - Flink 整体认知 - 认识 Flink - Flink 是什么 - Flink 是一个对于有界和无界数据流进行有状态计算的框架和分布式处理引擎 - 框架:Flink 不是一个单独的软件命令,而是一套开发 + 运行的系统 - Flink 和 Hadoop/Spark/Storm 的区别 - 基本定义 - Hadoop 是什么 - 大数据基础设施体系 - HDFS:分布式存储 - MapReduce:离线批处理计算 - YARN:资源管理 - 早期大数据时代的“存储+资源管理+离线计算“基础平台 - Spark 是什么 - 通用的大数据计算引擎 - 流处理主要通过 Structured Streaming - 更偏通用分析平台,批处理很强,流处理也能做 - Storm 是什么 - 是一个分布式实时计算系统 - 较早一代的纯流式处理框架,比 Flink 早 - 比较维度 - 主要定位 - Hadoop - 海量数据存储 - 离线批处理 - 高吞吐,不强调低延迟 - Spark - 通用大数据引擎 - 强批处理 - 可扩展到 SQL、ML、Streaming - Flink - 有状态流处理 - 事件时间 - 流批统一 - 更强实时计算语义 - 处理模型 - Hadoop: Batch - 读一批数据->map->shuffle->reduce->输出结果 - Spark: Batch + Micro-batch - Flink: Native Streaming,批流统一 - 延迟特征 - Hadoop - 延迟高,通常分钟级甚至更长 - Spark - micor-batch 延迟比原生流高一点 - Flink - 低延迟实时计算 - 状态能力 - Hadoop - 不擅长生命周期有状态流计算 - Spark - 能做状态处理,但不是以 stateful streaming 为核心 - Flink - stateful computations over data streams - exactly-once state consistency - 什么是批处理、流处理、实时计算 - 批处理 - 定义:先把一段时间内的数据收集起来,等数据攒够一批后,再统一进行计算 - 特点:先到齐,再处理、延迟高、吞吐高、程序有结束点 - 流处理 - 定义:把数据看成一个持续到来的事件流,数据一到,系统就持续处理 - 特点:连续不断、持续进行、低延迟、要处理历史记忆 - 实时计算 - 定义:从数据产生,到计算结果可用,时间尽可能短 - 实时计算不是来了就必须零延迟,而是在业务可接受的时间范围内尽快给出结果 - Flink 的应用场景 - 事件驱动应用 - 系统不是定时查数据库,而是收到事件才触发计算、状态更新或外部动作 - 典型业务 - 欺诈检测 - 异常检测 - 规则告警 - 业务流程监控 - 数据分析应用 - 从原始数据中提取信息和洞察 - 典型业务 - 每分钟订单量 - 热门榜单 - 实时数仓 - 离线 + 实时一体分析 - 数据管道与 ETF - 从上游系统读数据,做清洗、转换、补全、标准化、过滤、路由,再写到下游系统 - 典型业务 - Kafka 到 Kafka - Kafka 到数据库/搜索引擎/湖仓 - CDC + 实时同步 - Flink 基本架构 - JobManager、TaskManager - 最小 Flink 集群 -  - 在本地或某台机器上提交作业 - JobManager 接收这个作业,分析要怎么跑 - 把真正要执行的子任务分发给多个 TaskManager - TaskManager 在各自机器上并行处理数据 - 什么是 JobManager - 是 Flink 集群的控制中心 - 负责接收/解析/调度/监控/作业、协调故障恢复、管理元信息 - 什么是 TaskManager - 是 Flink 集群里真正干活的 worker - 主要负责执行子任务/占用和提供计算资源/与其他 TaskManger 传输数据/向 JobManager 汇报状态 - Slot 是什么 - 定义 - Flink 用 Slot 告诉调度器:这台 TaskManager 最多能接多少份执行任务 - 为什么需要 Slot - 控制一个 TaskManager 能接多少任务 - 做资源切分 - 在隔离与利用率之间做平衡 - Slot 和 TaskManager 的关系 - 一个 TaskManager 可以有多个 Slot - Slot 是 TaskManager 提供给集群调度的执行容量 - Slot 是资源申请和任务放置的基本单位之一 - Slot 和并行度的关系 - 并行度指的是一个算子会拆成多少个并行子任务 - 执行这些子任务需要 Slot - 在默认 slot sharing 开启时,作业大致需要的 slot 数 = 该作业的最高并行度 - JobGraph、ExecutionGraph - 什么是 JobGraph - Flink 作业的逻辑图/逻辑执行计划 - JobGraph 里面的点,叫做 JobVertex,它是一个逻辑阶段/一个逻辑算子节点 - JobGraph 不强调每个并行子任务 - 什么是 ExecutionGraph - 是 JobGraph 经过并行展开后的实际执行拓扑 - ExecutionGraph 的核心点是 ExecutionVertex - 并行度是什么 - 本质 - Flink 算子可以想象成一个类似读 Kafka/做 map 转换/做窗口聚合的工作 - 并行度决定一个算子会被拆分成几个 subtask 一起做 - 为什么需要并行度 - 一份工作只让一个实例做,处理能力有限 - 提高并行度,本质就是把工作拆开,让多个 subtask 同时处理 - 一个 flink 作业如何运行起来 - 总流程 - 写 Flink 程序,调用 execute() - Client 负责提交作业 - 程序先形成逻辑数据流图 - JobManager 接收作业 - JobManager 把 JobGraph 转成 ExecutionGraph - 调度器根据并行度和可用 slot 做任务部署 - TaskManager 真正执行 subtask - 运行中持续监控、处理状态、必要时恢复故障 - 编写代码时,发生了什么 - 写的是逻辑处理链路,不是真正的分布式执行指令 - 调用 execute() 后,作业才真正进入提交流程 - Client 做了什么 - Client 是提交入口,负责将应用提交到集群 - Client 不是主要计算节点 - JobGraph 在什么时候出现 - 代码先被整理为数据流图,也就是 JobGraph - JobGraph 偏逻辑层,还没有真正展开成每个 subtask,不是最终的并发执行形态 - JobManager 接手后做什么 - 接收作业 - 管理作业生命周期 - 把逻辑图变成执行图 - 协调资源和任务部署 - Execution Graph 怎么来的 - ExecutionGraph 是 JobGraph 的并行化版本 - 并行度在这里真正展开 - 运行状态也在这里变得具体 - 调度阶段发生什么 - 要跑多少个 subtask - 这些 subtask 放到哪些地方跑 - 数据怎么在它们之间流动 - slot 发挥什么作用 - TaskManager 提供 slots - 调度器找空闲 slot - 默认 slot sharing 会影响所需 slot 数 - TaskManager 真正干活时发生了什么 - TaskManager 执行 subtask - 每个 subtask 处理自己那部分的输入数据 - 上下游 subtask 之间可能发生网络传输 - 状态、窗口、定时器等能力都在这里真正运作 - 作业运行中,JobManager 还在做什么 - 跟踪任务状态 - 监控资源与任务健康情况 - 失败时触发恢复 - 作业失败了会怎样 - JobManager 感知失败 - 调度器决定如何恢复 - TaskManager 上的任务可能被重新部署 - 搭建开发环境 - Maven 项目骨架 - POM 文件 -  - fink-streaming-java - 是 DataStream API 入门最核心的依赖之一 - 编写 StreamExecutionEnvironment、DataStream 这类代码会用到它 - flink-clients - 用于本地执行、提交作业相关能力 - DataStream API 的基本项目结构 - 最小程序骨架 -  -  - 代码详解 - StreamExecutionEnvironment - 这是 DataStream 程序的入口 - StreamExectionEnviroment 是所有 Flink 程序的基础,通过 getExecutionEnvironment() 获取 - 是整个 Flink 程序的运行上下文或总入口 - 负责创建 source、串起数据流、设置并行度/checkpoint 等运行参数、触发执行 - Source - Source 就是数据从哪里来 - DataStrema 最初是从各种 source 创建出来的 - Transformation - 就是对数据流做处理 - DataStream 程序是对数据流做 transformation 的程序 - 基础类别 - map:一条变一条 - filter:按条件过滤 - flatMap:一条变多条 - keyBy:按 key 分组 - sum/reduce:聚合 - Sink - Sink 就是结果往哪里去 - 结果通过 sink 返回,可以写入文件、标准输出等 - print() 可以直接在控制台打印输出 - sinkTo(xxx) 可以写入自定义的目的 - execute() - Flink Job 是在调用 execute() 时创建并提交的 - DataStream 程序最后一步就是触发程序执行 - 跑通 WordCount - 最小 WordCount 代码 -  -  - 代码解析 - 创建执行环境 - StreamExecutionEnvironment 是每个 DataStream 程序的起点,表示开始描述一条 Flink 数据处理流水线了 - 创建输入流 - flatMap 把每一行拆分成单词 - flatMap 可以对一个输入产生任意多输出 - Tuple2<String, Integer> - 是 Flink 常见的二元组类型 - keyBy(value -> value.f0):按单词分组 - keyBy 会把流按照 key 逻辑分区,所有相同 key 会进入同一个分区 - sum(1):对第二个字段求和 - DataStreamAPI 基础 - Source、Transformation、Sink - Source - Flink 程序接收数据的地方 - 可以来自内存集合、文件、消息队列、socket 等 - 集合 Source - DataStream<String> stream = env.fromElements("a","b","c"); - DataStream<Integer> nums = env.fromElements(1,2,3); - 真实生产的 Source - Kafka - 文件 - Socket - 自定义 source - 其他外部系统 connector - Transformation - Operators 会把一个或多个 DataStream 转换成新的 DataStream - 程序可以把这些变换组合成复杂的数据流拓扑 - map - 一条输入变成一条输出 - DataStream<Integer> result = nums.map(x -> x*2) - map 不会改变条数 - 适合做字段转换、格式转换、简单加工 - flatMap - 一条输入可以变为多条输出,也可以一条都不输出 -  - flat 适合切词、拆分、展开 - WordCount 通常用它来把一行拆分成多个词 - 比 map 更灵活 - filter - 按条件保留或丢弃数据 - DataStream<Integer> evenNums = nums.filter(x -> x % 2 == 0); - filter 不改数据结构,主要做筛选 - keyBy - 按 Key 对数据流做分组 - 所有具有相同 key 的记录会被分到同一个分区上,通常内部是哈希分区 -  - keyBy 不是聚合 - 只是按 key 把数据归组 - 后面的 sum、reduce、状态、窗口通常都要建立在 keyBy 后面 - reduce - 对同一个 key 下的数据做增量操作 -  - reduce 必须通常接在 keyBy 后面 - 是增量聚合 - 比一次性收集所有数据再算更高效 - Sink - result.print() - print() 是最小可用 Sink - 主要用于学习和调试 - 后续接 Kafka、文件、数据库,思路还是一样,只是 Sink 换了 - union/connect - union - 定义 - 会把两条或多条数据流合并为一条新流,新流包含所有输入流的元素 - 所有输入流必须是相同类型 - 示例 -  - 关键点 - 类型必须一直 - union 后得到的还是同一种类型的普通 DataStream - union 适合本来就是同类数据,只是来自不同来源 - connect - 连接两条流,但是保留各自的类型信息 - 示例 -  - connect 更像先把两条流并排放在一起,然后你自己决定怎么分别处理左边和右边 - connect 后怎么用 - connect 一般不会单独停在那,通常会继续接处理函数 -  - RichFunction - 为什么要有 RichFunction - map/filter/flatMap 拿不到 Flink 运行时信息,也不方便做初始化和清理 - 需求:程序开始时初始化资源/程序结束时做清理/知道当前子任务编号/知道并行度/拿到运行时上下文 - RichFunction 定义了生命周期方法,以及访问函数执行上下文方法 - RichFunction 是什么 - RichFunction 是一个基础接口,很多常见的 rich 版本函数都建立在它之上 - 平时真正会写的,不是 RichFunction 本身,而是具体子类 - RichMapFunction - RichFilterFunction - RichFlatMapFunction - 可以理解为普通 Function 的增强版 - 和普通函数的区别 - 除了处理逻辑,还多了 open()、close()、getRuntimeContext() - open 用于初始化,close 用于清理,getRuntimeContext 用于获取运行时上下文 - RichFunction 最核心的三个能力 - open(OpenContext openContext) - 这是初始化方法 - 会在真正工作方法例如 map、join 前调用 - 适合做一次性初始化工作 - 就像开工前准备 - close() - 这是清理方法 - 会在主工作方法最后一次调用之后执行 - 适合做清理工作 - 就像收工后打扫现场 - getRuntimeContext() - 拿运行时上下文的方法 - 包含函数运行并行度、当前 subtask 的索引、执行该 task 的名称 - 例子 -  - 基本调试方法 - 先用最小输入跑通 - 先用 fromElements() 构造很小的输入集 - 最基础的调试方式: print() - 中间的关键步骤也可以加上 print() -  - 按 source/transformation/sink 分段排查 - 先怀疑 source - source 有没有真的产生数据 - 输入是不是空的 - 输入格式是不是和我想的不一样 - 可以先 lines.print("source") 确认下 - 再怀疑 transformation - 每做一步变化,就临时加一个 print() - 最后怀疑 sink 时间与窗口 - 时间与窗口 - 时间语义 - processing time - 什么是 processing time - processing time 就是某个算子在处理这条数据时,所在机器的系统时间 - 不看事件真正什么时候发生,而是看 Flink 现在处理到它时是几点 - processing time 的本质 - 用机器时钟当做时间标准 - 不需要从数据取时间戳 - 不需要 watermark 来推动时间前进 - event time - 什么是 event time - 不看 flink 什么时候处理到这条数据,而是看这条数据业务上到底是什么时候发生的 - 这个时间通常在进入 flink 前就嵌入到记录了 - 为什么 event time 重要 - 如果想得到可复现、符合业务真实时间的分析结果,就应该用 event time - 把统计口径从系统什么时间看到它,切回什么时候真正发生 - 本质 - 用事件自带的时间戳,作为所有时间相关操作的基准 - 必须能从数据集提取时间戳 - 系统需要某种机制来判断某个时间点之前的数据大致到齐了 - ingestion time - 事件进入 Flink 时被赋予的时间 - WaterMark - WaterMark 是什么 - WaterMark 是 Flink 用于衡量 EventTime 进度的机制 - Watermark 在数据流中携带一个时间戳 t,WaterMark(t) 表示事件已经推进到 t - 为什么需要 WaterMark - 真实世界里的数据经常会乱序到达 - 问题:应该等到什么时候,才能认为某个时间点之前的数据差不多到齐了 - Watermark 表示什么 - 到目前为止,我认为时间戳小于等于 t 的事件应该都到了 - Watermark 和窗口的关系 - Watermark 最常见的作用,就是触发 EventTime 窗口计算 - Watermark 决定什么时间可以开始算 - Watermark 和迟到数据有什么关系 - 如果某条事件的时间戳小于等于当前已经到达的 watermark,但现在才到,这条数据就是迟到数据 - Watermark 怎么来的 - 使用 Event Time 时,Flink 需要知道每个元素的时间戳 - 时间戳可以通过 TimestampAssigner 提取 - 需要 WatermarkGenerator 生成 watermark,两者一起通过 WatermarkStrategy 配置 - Watermark 来自于定义的时间策略 - Watermark 理解 - Watermark 就像 Event Time 世界里的逻辑时钟 - 系统告诉下游:可以把时间推进到这里了 - 有序和乱序流的 Watermark 生成方式 - 为什么分有序流和乱序流 - Watermark 的生成方式,本质取决于你的数据时间戳分布特征 - 有序流的 Watermark 生成方式 - 核心假设 - 后面到来的事件,时间戳不会比前面的小 - 生成逻辑 - 在有序流里,当前看到的最大时间戳就代表时间已经推进到那里了 - 乱序流的 Watermark 生成方式 - 核心假设 - 虽然事件会乱序到达,但是可以给出一个最大乱序范围 B - 生成逻辑 - 当前 watermark = 已观察到的最大事件时间戳 - 最大乱序容忍时间 - 具体例子 -  - 事件对象里面要有事件时间字段 - Source 只是构造测试数据 - WatermarkStrategy 是核心 - forBoundedOutOfOrderness(Duration.ofSeconds(5)) - 允许这条流最多乱序 5s - Watermark 会根据当前看到的最大时间戳-5 秒来推进 - withTimestampAssigner()告诉 Flink 事件时间从那一列取 - assignTImestampsAnsWatermarks - 给流里的每个元素附上事件时间戳 - 同时让 Flink 根据策略找 Watermark - 窗口基础 - 为什么需要窗口 - 流数据是无界的,理论上不会自然结束 - 对这种数据不能简单说等全部到期再统计 - Flink 的窗口机制就是把连续到来的流按规则划分为一个个有序集合 - Flink 里窗口的总体分类 - Tumbling Window (滚动窗口) - 定义:是固定大小、连续、互不重叠的窗口 - 适合场景 - 每 5 分钟订单数 - 每小时 GMV - 每日 UV - 每分钟接口调用量 - Sliding Window (滑动窗口) - 定义:也是固定大小的窗口,会按照一个更小的步长持续滑动 - 示例:窗口大小 10 分钟,每 5 分钟滑动一次 - 适合场景 - 最近 10 分钟 PV,每分钟更新一次 - 最近 30 分钟订单量,每 5 分钟更新一次 - Session Window (会话窗口) - 定义 - Session Window 没有固定窗口,它的边界由活动间隔决定 - session window 的边界由 inactivity gap 定义 - 指定 gap 时间内没有新事件到来,当前 session 就关闭 - 如果中间一直没有很长时间断开,这些行为属于同一个会话 - 适合场景 - 用户会话分析 - App 连续使用时长 - 连续操作链路 - Count Window (计数窗口) - 定义 - Count Window 按元素个数切 - 每 10 条数据统计一次/每 100 条点击做一次聚合 - 适合常见 - 每 100 条日志做一次分析 - 每 50 条事件批量聚合 - 窗口最重要的两个维度 - 切分维度 - 按时间切 - 按数量切 - 是否重叠 - 不重叠 - 可能重叠 - 不固定边界 - 窗口对齐 - 时间窗口是对齐到 epoch 的 - 时间窗口默认是按时间对齐的,不是按程序启动时刻随意切 - 窗口聚合 - 窗口聚合解决什么问题 - 窗口把无界流切成有限片段后,通常还要对每个片段做计算 - 不同业务对怎么算有不同要求 - 有的业务只要一个简单结果,例如总数 - 有的要复杂中间结构,比如平均值要维护 sunm 和 count - 有的除了结果,还要输出窗口边界、key、触发时间等元信息 - 增量聚合 - 增量聚合,就是数据一到,就立即把它并入当前结果 - 不是把整个窗口所有元素都存起来,等窗口结束后再统一计算 - 全量窗口处理 - 先把窗口里所有元素都留着,等窗口触发时,再把完整集合交给函数处理 - reduce:最轻量的窗口聚合 - 本质 - ReduceFunction 的核心思想是:把两个同类型元素合并成一个同类型元素 - 为什么高效 - 窗口里不需要保留全部元素,只需要持续维护一个当前归并结果 - 最大的限制 - 输入类型和输出类型必须一致 - aggregate:更通用的增量聚合 - 本质 - 把聚合拆成了几个阶段 - 创建累加器 createAccumulator() - 把新元素加进累加器 add() - 从累加器得到最终输出 getResult() - 为什么比 reduce 更灵活 - 把三种类型分开 - 输入类型 IN - 累加器类型 ACC - 输出类型 OUT - ProcessWindowFunction:最灵活的窗口处理 - 本质 - 核心价值不是更快,而是信息更多、能力更强 - 适合什么场景 - 需要窗口开始时间/结束时间 - 需要访问 key - 需要输出带窗口元信息的结果 - 需要看完整窗口数据 - 需要复杂后处理逻辑 - 最大的优势 - 能够拿到窗口元信息 - 迟到数据处理 - 什么叫迟到数据 - 这条数据的事件时间本来属于某个旧窗口 - 但它到达系统时,这个窗口已经因为 watermark 推进而触发过了 - 它来晚了 - allow lateness - 是 Flink 为窗口保留的额外容忍时间 - allowedLateness(...) 指定窗口在 watermark 超过结束时间后还能保存多久,以接收迟到数据 - side output - side output 是把某些特殊数据从主流旁路输出的机制 - 把太晚了、已经不能再进主窗口的数据单独收集出来 - 结果修正 - 配置了 allow lateness 后,窗口第一次输出的结果可能不是最终结果 - 后面如果又来了还能接受的迟到数据,窗口结果会更新并再次触发输出 状态与容错 - 状态与容错 - State 基础 - 什么是状态 - 状态=程序对过去信息的记忆 - 没有状态:程序只看当前一条数据 - 有状态:程序处理当前数据时,还能参考历史数据 - 为什么流处理需要状态 - 要做累计 - 用户累计消费金额 - 某设备累计告警次数 - 某单词当前总出现次数 - 要做对比 - 当前温度和上一次温度相比是否突增 - 当前订单状态和之前状态是否矛盾 - 要做时序逻辑 - 10 s 内连续 3 次登录失败 - 下单后 15 分钟未支付 - 一段会话里的连续点击行为 - 要在故障后恢复业务上下文 - Key State - 定义 - Keyed State 是和 Key 绑定的状态 - Keyed State 只能用于 Keyed Stream 上 - 先对 DataStream 做 keyBy(...),然后才能使用 keyed state - 为什么重要 - 很多业务天然都是按照实体分开记忆的 - 底层直觉 - 一个分布式哈希表,按 key 切开,每个并行实例只负责自己那部分 key - Operate State - 定义 - Operator State 是和算子并行实例绑定到状态,而不是和业务 key 绑定 - 算子级记忆,而不是业务实体记忆 - 适合什么场景 - source/sink 级别的处理位点 - 算子实例自己的工作上下文 - Keyed State - ValueState:最常用的 Keyed State - 是什么 - ValueState<T> 表示:每个 key 只有一个值 - 典型场景 - 每个用户累计点击次数 - 每个订单当前状态 - 每个设备最近一次温度 - 每个用户最近一次登录时间 - 每个 key 当前告警开关状态 - ListState:每个 Key 存一串值 - 是什么 - ListState<T> 表示:每个 Key 下面维护一个列表 - 典型场景 - 缓存最近若干条事件 - 暂存等待拼接到多条消息 - 收集一个 key 下的一批原始记录 - 先攒起啦,后面统一处理 - 风险 - 容易无限增长 - MapState:每个 Key 下再套一个 map - 是什么 - MapState<UK,UV> 表示:每个 key 对应一个 map - 典型场景 - 每个用户下,不同商品类别的点击次数 - 每个设备下,不同传感器字段的最新值 - ReducingState:自动做同类型聚合 - 是什么 - ReducingState<T> 表示:每个 key 不直接存明细,而是存一个经过 reduce 的聚合结果 - AggregatingState:更灵活的聚合状态 - 是什么 - AggregatingState<IN, OUT> 和 Reducing State 很像,但更通用 - 包含输入类型 - ProcessFunction - 为什么需要 ProcessFunction - 传统的算子 - map/filter/flatMap/keyBy/window 聚合 - 更偏声明式处理,而不是自己掌控每条事件+时间回调+状态细节 - 无法解决的问题 - 订单创建后 15 分钟未支付就报警 - 用户 10s 内连续登录失败 3 次 - 某 key 1 分钟没有更新时输出当前统计值 - 无法解决问题共同点 - 要逐条处理事件 - 要记住状态 - 要在未来某个时间点触发逻辑 - ProcessFunction 是什么 - 当高级 API 不够表达你的业务规则时,就下到 ProcessFunction 这一层自己控制 - 更像一个加强版的 flatMap - 对每条输入都能处理 - 能产生 0、1 或多条输出 - 能访问上下文 - 能注册 timer - 在 timer 到点时再执行逻辑 - KeyedProcessFunction 的核心方法 - processElement(...) - 每来一条输入事件,Flink 就会调用它 - 事件到了,现在立即处理它 - onTimer(...) - 注册的 timer 到点时,Flink 会调用它 - 之前约好的时间到了,现在执行补充逻辑 - Context 提供什么 - 在 processElement(...),Flink 会提供一个 Context,这个 Context 能做几件事 - timestamp():拿当前元素时间戳 - timeService():访问定时器服务 - getCurrentKey():拿当前 Key - output(...):输出到 side output - 什么是 Timer - 在当前处理某条事件时,告诉 Flink:“到未来某个时间点,请再回头执行一次逻辑“ - Timer 为什么 和状态一起用 - Timer 只负责未来提醒你一次,但是提醒你时应该做什么,要靠状态来判断 - Checkpoint 与容错 - Checkpoint 是什么 - Checkpoint 是 Flink 对“作业状态+输入流位置“的一致性快照 - Checkpoint 至少包含两类东西 - 各个 Stateful operator 的状态快照 - 各个 source 当前读到哪儿的位点,例如 Kafka offset 这类输入位置 - 为什么流处理必须依赖 Checkpoint - 状态负责记忆,checkpoint 负责让记忆在失败后不丢 - Checkpoint 保存的是什么 - Operator/Keyed State - Source 位置 - Timers 连接外部系统 - 连接外部系统 - Flink 读写文件与接 - Kafka 集成 - Flink 与数据库 - 序列化与数据格式 Flink SQL 与 Table API - Flink SQL 与 Table API - 认识 Table API 与 Flink SQL - 为什么要有 Table API 与 SQL - DataStream 更像手写处理逻辑,Table API/SQL 更像声明我要算什么 - Table API 是什么 - Table API 是一种 language-integrated query API - 是 SQL 的超集,专为 Flink 设计 - Flink SQL 是什么 - 用 SQL 语法来描述 Flink 上的流批处理逻辑 - 可以理解为 - 输入表先注册好 - 直接写 SQL - Flink 帮你把 SQL 变为实际可执行程序 - Table API 和 SQL 的关系是什么 - 不是两套互斥系统,而是同一层抽象的两种表达方式 - SQL - 更适合分析型开发 - 数仓开发 - DDL/DML 风格任务 - 让更多懂 SQL 的人参与开发 - Table SQL - 想保留代码层面的类型和组合能力 - 需要和 Java/Scala/Python 代码更紧密集成 - 想在 API 层做更多程序化的表运算 - 什么是 Dynamic Table (动态表) - 动态表就是把“不断到来的流“逻辑上看成一张会持续变化的表 - 什么是 Continuous Query (连续查询) - Flink SQL 不是查一下就结束都传统 SQL,而是持续运行、持续更新结果的 SQL - append/retract/upsert 结果是什么意思 - 问题:结果表变化,怎么把这个变化告诉下游? - 动态表包含三种编码方式 - Append-only - 结果表只会新增,不会修改旧结果,只发追加记录就行 - Retract - 如果结果表中的旧值会被更新或删除,就不能只 append - retract stream 用 add/retract message 来表示 - 对于 update 会先发撤回旧值,再发一条新增新值 - Upsert - upsert stream 需要唯一键 - INSERT 和 UPDATE 都可以用 upsert message 表示 - DELETE 用 delete message 表示 - 相比 retract,upsert 只需一条消息 - Flink SQL 基础语法 - 整体心智模型 - 典型 Flink SQL 作业 - CREATE TABLE source ... - CREATE TABLE sink ... - INSERT INTO sink - SELECT ... FROM source WHERE ... GROUP BY ... - 四步流程 - 定义源表 - 定义结果表 - 写查询逻辑 - 把查询逻辑结果写到目标表 - CREATE TABLE 是什么 - 本质 - CREATE TABLE 是在当前 Catalog 里注册一个表定义,让这个表可以被 SQL 使用 - 告诉 Flink:有一张表,它的字段是什么,连接那个外部系统,怎么读,怎么写 - 最小骨架 -  - 列定义:字段名和类型 - WITH (...):连接器和外部系统参数 - WITH(...) - 通常是连接外部系统所需的 table options - connector - 指定背后连接那个系统(Kafka/filesystem/upsert-kafka/print) - format 相关参数 - 告诉 Flink 数据是 JSON、CSV、Avro 等什么格式 - 外部系统连接参数 - 例如 Kafka topic、broker 地址、分区键等 - Flink SQL 最基础的查询语法 - SELECT - SELECT user_id, amount FROM orders; - WHERE - SELECT user_id, amount FROM orders WHERE amount > 100; - GROUP BY - SELECT user_id, COUNT(*) FROM orders GROUP BY user_id; - INSERT TO - INSERT INTO result SELECT user_id, COUNT(*) FROM orders GROUP BY user_id; - sqlQuery() 和 executeSql() 怎么理解 - sqlQuery() 用于 SELECT/VALUES 这类查询,返回一个 Table - executeSql() 用于执行 DDL、DML 等语句,例如 CREATE TABLE、INSERT INTO - 时间属性与窗口 SQL - 什么是时间属性 - 在流式 SQL 中,窗口函数要求引用的是有效的时间属性,也就是 processing time attribuet 或 event time attribute - 被 Flink 运行时承认,并能参与时间推进和窗口计算的特殊时间列 - 时间属性分类 - 处理时间属性 (Processing Time) - 这条记录被 Flink 当前机器处理到的时间 - 事件时间属性 (Event Time/Rowtime) - 事件时间是业务事件真正发生的时间 - 在 SQL 中定义处理时间属性 - 在 CREATE TABLE 时,可以定义一个处理时间列 -  - 在 SQL 中定义事件时间属性 - 事件时间通常来自源数据中的某个时间段 - 仅仅有这个字段还不够,通常要搭配 WATERMARK -  - ts 是事件时间类 - WATERMARK FOR ts AS ... 告诉 Flink 如何基于 ts 推进事件时间 - 窗口 SQL 推荐怎么写 - Windowing TVF (窗口表值函数) - 窗口聚合支持 TUMBLE/HOP/CUMULATE/SESSION 这些都通过 Windowing TVF 表达的 - 什么是 TUMBLE 窗口 SQL - TUMBLE 对应滚动窗口 - 按固定窗口大小分配数据 - 窗口不重叠 - 在结果中增加 window_start、window_end、window_time 三列 -  - DESCRIPTOR(ts):告诉 Flink 用 ts 这列作为时间属性 - INTERVAL '5' MINUTES:窗口大小 5 分钟 - 什么是 HOP 窗口 SQL - HOP 对应滑动窗口 - HOP 有 slide 和 size - 它会创建可能重叠的窗口 - 同一条记录可能会进入多个窗口 -  - 每 1 分钟滑动一次 - 每个窗口覆盖近 10 分钟 - 结果表同样会带窗口边界列 - 什么是 CUMULATE 窗口 SQL - CUMULATE 是累积窗口 - 适合总窗口范围较大,但想看逐步累积结果 - 每小时一个大窗口,每 5 分钟输出一次当前已累计到哪里的聚合值 -  - step 是 5 分钟 - max size 是 1 小时 - 窗口会逐步扩张输出 - 什么是 SESSION 窗口 SQL - SESSION 对应会话窗口 -  - 30 分钟内持续有事件,属于同一会话 - 维表 JOIN 与实时数仓 - 什么是事实流,什么是维表 - 实时流 - 不断到来的业务事件流 - 通常来自于 Kafka、CDC、日志流等 source table - 维表 - 维表通常是描述型、补充型信息 - 为什么实时数仓一定遇到维表 JOIN - 事实流提供发生了什么,维表提供这件事属于谁、是什么、怎么解释 - 什么是实时宽表 - 把事实流和一个或多个维表关联后,得到的一张字段更完整、可直接分析或落库的结果表 - 什么是 Lookup Join - 以事实流为驱动,对外部维表做按键查询 - 什么是 Temporal Join/Versioned Table Join - 按某个时间点,去取维表在那个时刻的版本,再和事实表做关联 - 什么是 Versioned Table - 一张会变化、并且保留变化历史的表 原理深入 - 原理深入 - 背压与性能调优 - 什么是背压 - 上游发太快,下游吃不动
价值
有人随着年龄增值, 有人随着年龄贬值, 这就说明了追求生命内涵的重要性。 ...
HBase 复习
基础认知 - 基础认知 - HBase 和 MySQL、Redis、Elasticsearch、Hive 的区别 - HBase - 一个建立在 Hadoop/HDFS 上的、支持联机、实时读写的分布式数据库,适合承载超大表 - 面向宽表/稀疏表 - 以 RowKey 为核心访问路径 - 强项是海量数据下的主键访问和范围扫描 - MySQL - 关系型数据库 - 强 SQL 能力 - 强事务 - 适合 OLTP 业务系统 - Redis - 内存型数据存储 - 超低延迟 - 丰富数据结构 - 更像高性能数据结构引擎/缓存平台 - ElasticSerach - 分布式搜索引擎 - 文档模型 - 擅长全文检索、相关性排序、聚合分析 - 不是传统事务数据库 - Hive - 数据仓库 - 面向离线分析 - 适合大规模批处理、报表、数仓 - 不适合在线高并发事务场景 - 列式/列族模型是什么 - 列族模型 - 看起来像一张表 - 但是每一行并不是必须拥有同样的列 - HBase 把列分成若干个 Column Family (列族) - 同一个列族里面的数据会被一起管理和物理存储 - 每一条具体的数据单元是一个 Cell,由行键、列族、列限定符、事件戳和值共同确定 - HBase 是一个按 RowKey 组织、按列族管理、按单元格存值、支持多版本的稀疏宽表结构 - 模型示例 - 层次结构 - Table -> Row -> Column Family -> Column Qualifier -> Cell(Value + TimeStamp) - 一张用户表 user_profile,定义了两个列族 info + stat - 一行数据 - RowKey = user_1001 - info:name = "Alice" - info:city = "Tokyo" - stat:login_cnt = 25 - info、stat 是列族 - name、city、login_cnt 是列限定符 - 完整列名是 family:qualifier - 每个值都带 timestamp,可以保留多个版本 - 列族、列、Cell - RowKey - 是一行数据的主键,也是 HBase 最核心的访问入口 - 查 HBase,本质是在查某个 RowKey,或者扫描一段 RowKey 的范围 - 决定这一行是谁 - 决定数据在字典序上的排序方式 - 决定大部分查询的性能上限 - Column Family - 是 HBase 中必须定义的逻辑分组 - 列族在创建表时就确定好 - HBase shell 和 API 文档都以先创建表,定义 family,再在 family 下写具体列的方式使用 HBase - 列族是一组经常一起出现,一起管理,一起存储策略配置的数据 - Column Qualifier(列限定符) - 列限定符就是列族下面具体的列名 - 列限定符不需要像 MySQL 字段在建表时全部固定列出来 - 列族通常固定,但是 qualifier 可以按需动态增加。 - HBase 适合列很多,而且不同记录拥有的列不一样的场景 - Cell - Cell 是最小存储单元 - 一个 Cell 由这些部分唯一定位 - row - family - qualifier - timestamp - value - TimeStamp/Version - HBase 天然支持多版本 - 同一个 row + family + qualifier,可以在不同 timestamp 下保存多个值 - Get/Scan API 也支持按照版本范围取数据 - HBase 的默认思维是:一个单元格可以有历史版本 - 为什么要有列族 - HBase 不只是逻辑上分组,还会影响物理管理和存储策略 - 列族可以把访问模式相近的数据放在一起 - 列族可以让不同数据有不同的存储策略 - 列族可以让稀疏表更自然 - 稀疏宽表 - 稀疏 - 不是每一行都有同样的列 - 举例 - 商品 A:颜色、尺寸、品牌 - 商品 B:品牌、功率、电压 - 商品 C:材质、长度、颜色、重量 - 如果用 MySQL,固定列会导致很多字段为空 - HBase 只保存实际存在的 family:qualifier->value 即可 - HBase 在列族下使用动态 qualifier - 宽表 - 列非常多,甚至 qualifier 的数量可以远大于传统关系表的字段数 - HBase 为什么是 Bigtable 风格数据库 - 什么是 Bigtable - Bigtable 是 Google 提出的一个分布式存储系统,用来存放结构化数据,典型特点 - 面向超大规模数据 - 稀疏,可扩展的表 - 以 row key 为核心组织数据 - 支持列族 - 支持多版本 - 支持按键范围扫描 - 以 RowKey 为中心,而不是以 SQL 为中心 - Bigtable 风格数据库核心访问方式 - 按 RowKey 精准查 - 按 RowKey 前缀或范围扫描 - 设计 schema 时优先围绕查询路径设计 row key - 设计数据库时,优先设计 RowKey 和访问路径,而不是优先设计复杂 SQL 数据模型与表设计 - 数据模型与表设计 - RowKey 设计原则 - 总纲 - RowKey 不是随便选一个主键,而是 HBase 里面最重要的访问索引、排序依据和分布依据。 - 每行只有一个被索引的值,就是 row key - 本质 - 数据如何定位 - 数据怎么按字典序排序 - 数据怎么在集群中分布,会不会热点 - RowKey 为什么重要 - 最快、最强、最自然的访问路径就是按照 RowKey 去查和按范围扫 - RowKey 设计不好 - 查询路径不顺 - scan 范围过大 - 热点集中 - 读写吞吐不均衡 - 很难靠 SQL 或者二级索引补救 - 核心原则 - 先按查询方式设计,不要按照字段含义设计 - 最常见的查询是什么 - 查询是点查还是范围查 - 查询维度谁排第一 - 结果取最近一条、最近 N 条还是一个时间段 - 让最常用的过滤维度出现在 RowKey 前部 - Row Key 前缀直接决定 - 哪些数据会排在一起 - 哪些数据可以用 prefix/range 高效扫描 - Row Key 要支持高频查询的自然范围扫描 - HBase 擅长的不是随意条件查询,而是连续键空间扫描 - 建议围绕 row key prefix 设计 schema - 例如查询设备时序数据 - 按设备查最近数据 - 按设备查某段时间数据 - device_id#reverse_ts - 避免单 key 递增直接写入,防止热点 - 不断递增的 rowKey 最新写入通常会不断打到键空间尾部,把写压力集中到少数 region - 在可扫描性和均匀分布之间做平衡 - 分桶:bucket(user_id)#user_id#reverse_ts - 时间类场景通常需要显式设计时间顺序 - rowKey 要短、稳定、可解析 - 常见设计模式 - 单主键模型 - user_id - 一行就是一个主键 - 点查为主 - 不需要时间维度 - 主维度 + 时间 - user_id#ts - 主维度 + 倒序时间 - user_id#reverse_ts - 桶 + 主维度 + 时间 - bucket(user_id)%16#user_id#reverse_ts - 数据集前缀 + 业务键 - U#1001 - 列族应该怎么拆 - 为什么列族拆分重要 - 列族会把同一族的一组列物理共置 - 列族带有独立的存储属性 - 每一行都拥有相同的列族集合 - 未写入的单元格不占用实际空间 - 列族拆分总原则 - 同查:经常一起读的字段,放在一起 - 同配:需要相同存储策略的字段,适合放在一起 - 同寿命:生命周期相近的字段,适合放在一起 - 同频率:更新频率相近的字段,适合放在一起 - 拆分口诀 - 基础资料一族、频繁指标一族、短期日志一族、打对象单独一族 - 版本机制 - 什么是版本 - 同一个字段可能保存多份历史值 - 每一份历史值靠 timestamp 区分 - HBase 官方文档把 Cell 展示为包含 row、column、timestamp 和 value - timestamp 和 version 的关系 - timestamp 是版本标识 - 在 HBase 里,每写入一个 Cell,都要有对应的 timestamp - timestamp 由系统自动生成,也可以由自己指定 - version 是字段保留的历史份数 - 按列族配置,说明最多留多少份历史数据 - TTL、压缩、Bloom Filter - TTL 是什么 - Time To Live,生存时间 - 是 cell contents 的生存时间,单位是 s - 是列族级的数据过期策略 - TTL 解决的问题 - 控制历史数据无限膨胀 - 降低存储成本 - 让冷热数据边界更为清晰 - 压缩是什么 - 压缩就是把 HBase 存到 StoreFile/HFile 里面的数据,用某种压缩算法压小 - 可以在 ColumnFamily 上启用压缩,且不需要重建表 - 用 CPU 换磁盘空间、换 I/O 带宽 - Bloom Filter 是什么 - 是一种概率型存在性判断结构 - 帮助你快速判断这个 StoreFile 里面大概率没有你想要的数据,就不去做无效读取了 - 热点问题 - 什么是热点 - HBase/Bigtable 这类系统的数据是按照 rowKey 的字段序排列的 - 相邻的 Key 也会落在相邻的 key range 上,这些 key range 被分配给特定的 Region/RegionServer 来服务 - 如果请求集中在某一个 RowKey/某一小段连续 RowKey/某个不断增长的最新 key 区间,负载就不会均匀摊开,造成热点 架构原理 - 架构原理 - HMaster 做什么 - HMaster 的定位 - HMaster 负责协调和管理,真正承担读写请求的是 RegionServer - HMaster 类似调度中心和元数据/运维控制器 - HMaster 的核心职责 - 管理整个集群的 Region 分配 - HBase 的表会被切分成很多 Region - 集群启动时给 Region 找归属的 RegionServer - RegionServer 宕机后重新分配它原来负责 Region - 负载均衡主动迁移 Region - 监控 RegionServer 的存活状态 - 处理 RegionServer 故障后的恢复 - 做负载均衡 - 处理 DDL 和 schema 管理 - 参与主备切换和集群高可用 - RegionServer 做什么 - RegionServer 的定位 - 是数据面 - 真正保存并服务 Region - 真正执行 Put/Get/Scan/Delete - 真正把数据从内存刷到 File - 真正做 compaction 和 split - RegionServer 的核心职责 - 持有并管理 Region - HMaster 决定 Region 给谁 - RegionServer 真正把 Region 跑起来 - 处理客户端读请求 - 处理客户端写请求 - 管理每个 Region 内部的 Store/MemStore/StoreFile - 运行后台维护线程:flush、compaction、WAL 滚动、split - 管理 BlockCache - 执行部分 Region 级维护操作 - Put 流程 - 客户端根据元数据找到 Region 所在的 Region Server - 请求发到该 Region Server - RegionServer 找到这个 Region 对应 family 的 Store - 先写 WAL - 在把数据写到 Store 里面的 MemStore - 后台达到条件后,MemStoreFlusher 把数据 flush 到 StoreFile - 之后 CompactSplitThread/MajorCompactionChecker 再持续维护这些文件 - Get/Scan 流程 - 客户端根据元数据找到 Region 所在的 Region Server - RegionServer 在对应 Region 的 Store 中查找 - 优先利用 BlockCache - 必要时再读 StoreFile/HFile - 利用索引和 BloomFilter 缩小无效读取 - 返回结果给客户端 - Region 是什么 - Region 定义 - Region 是一段连续的、按 RowKey 排序的行范围 - 表的层级结构 Table->Region->Store->MemStore/StoreFile->Block - 为什么要有 Region - 做分布式切片 - 支持扩展 - 支持负载均衡 - Region 结构 - 一个 Region 内部,每个 ColumFile 对应一个 Store,每个 Store 有自己的 MemStore 和若干 StoreFile - Region 如何分配 - 同一时刻,一个 Region 只由一个 RegionServer 提供服务 - 一个 RegionServer 可以持有很多 Region - HMaster 负责 Region 给谁、是否迁移、是否均衡 - RegionServer 负责真正持有 Region 并处理这个 Region 的读写 - Region 为什么需要切分 - 单个 Region 太大,迁移和恢复成本高 - 该 Region 所在 RegionServer 负载可能过重 - 并行度不够,一大段数据只能由一个 Region 服务 - 热点更集中,难以分散 - split 的目的,是把一个过大的 key range 拆分成两个更小的 key range,来提升可扩展性和分布性 - 什么时候会 split - 由 RegionSplitPolicy 决定 - 当 Region 增长到某个阈值/满足当前 split policy 的条件 - WAL 是什么 - 为什么需要 WAL - HBase 不是收到请求就立即写成 HFile - 写请求先进入内存里面的 MemStore - MemStore 是内存结构,掉电或进程崩溃会丢 - WAL 为还没落成 HFile 的持久化阶段兜底 - 写入请求里的 cells 会一直保留,直到成功持久化到 WAL 和 MemStore - MemStore 负责写入速度,WAL 负责稳定性 - 为什么叫 Write-Ahead - 先把将要发生的修改记录到日志中,再依赖后续流程把它整理进正式数据文件 - 这条写操作在正式长期存储结构整理完成之前 - 已经被写入到一个可恢复的日志里 - WAL 处在的位置/Put 操作链路 - 客户端把 Put 请求发送到目标 RegionServer - RegionServer 把这次修改追加到 WAL - 同时把数据写入目标 Store 的 MemStore - 之后后台线程再把 MemStore Flush 成 StoreFile/HFile - 最后通过 compaction 整理文件 - WAL 记录的内容 - WAL 记录的是这次写操作的增量编辑记录 - WAL 写入的内容加做 WALEdit,有 WAL.Entry、WALKey 类型来表示一条日志条目及其键 - MemStore、StoreFile/HFile、BlockCache - 概述 - MemStore:写入时的内存缓冲区 - StoreFile/HFile:Flush 之后落盘的正式数据文件 - BlockCache:读路径上的数据块缓存 - 在一个 Region 里,每个列族对应一个 Store,每个 Store 有自己的 MemStore 和若干 StoreFile - MemStore 是什么 - MemStore 是 Store 在内存中的缓冲区 - 客户端写入数据, RegionServer 会先把修改写入 WAL,并把数据存放在对应的 Store 的 MemStore - 后续达到条件后,再 flush 到磁盘形成 StoreFile - 主要解决写的快的问题,如果每次 Put/Delete 都直接改磁盘主文件,开销会很大 - 先写入内存,可以把随机小写聚合起来,再批量 flush 到磁盘 - MemStore 只是内存态,不是最终查询文件,内容会在后续 flush 后变为 StoreFile/HFile - StoreFile 是什么 - 是 Store 在磁盘上的数据文件 - MemStore 不会只 flush 一次,每次 flush 会生成新的 StoreFile - 当 BlockCache miss 且 MemStore 没有目标数据,RegionStore 会去 HFile/StoreFile 中查 - HFile 是什么 - 是底层文件格式,StoreFile 是 HBase 在 RegionServer/Store 这一层使用的数据文件抽象 - 一个 StoreFile 对应一个 HFile - BlockCache 是什么 - 是 HBase 读路径上的数据库缓存 - 缓存的是从 HFile 里读出来的 block - 解决的是读的快的问题 - Flush、Compaction、Split 的流程 - Flush 是什么 - 定义 - 把某个 Store 的 MemStore 内容写到磁盘,生成新的 StoreFile - 为什么必须 Flush - MemStore 在内存中,不能无限增长 - flush 后发生什么 - flush 完成后,这批数据会变成新的 StoreFile,被纳入该 Store 的文件集合。 - Flush 触发条件 - 内存压力触发 - 时间触发 - 运维/内部流程触发 - Compaction 是什么 - Compaction 是把一个 Store 多个 StoreFile 文件合并整理成更少的新文件 - Minor Compaction 和 Major Compaction 的区别 - Minor Compaction - 把若干个较小的 StoreFile 合并成更少的较大文件,但通常不是把该 Store 的所有文件一次性全部重写 - Major Compaction - 更彻底地重写该 Store 的文件集合 - Split 是什么 - Split 是把一个 Region 沿 RowKey 边界切成两个子 Region - Zookeeper 的作用 - 核心作用 - 主节点选举:决定谁是 active HMaster - 服务发现:让客户端知道该连谁 - 节点状态感知:知道哪些服务还活着 - 协调关键元数据入口 - 为什么 HBase 需要 ZooKeeper - HBase 把协调问题外包给 ZooKeeper,把自己更多精力放在存储和读写路径上 - Zookeeper 不做什么 - 不存储业务明细数据 - 不承担高吞吐读写 - 不等于 HMaster 实际操作 - 实操 - 启动单机 HBase - 单机模式是什么 - 单机模式是 HBase 最基础的步数形态 - 在 standalone 模式下,所有 HBase 守护进程都运行在一个 JVM 里 - Docker 启动 -  - 进入容器进行最小验证命令 -  - 用 Shell 建表、删表、put/get/scan - 进入 shell - hbase shell - 命令基本结构 - 表名:test - row key:row1 - 列:cf:a(列族:列限定符) - create 建表 - create 'test','cf' - 创建表 test,在表中预定义列族 cf - put 写入数据 - put 'test', 'row1', 'cf:a', 'value1' - 'test':表名 - 'row1':row key - 'cf:a':列 - 'value1':值 - 本质:往某个表的某一行、不同列写值 - scan 扫描数据 - scan 'test' - 按 RowKey 顺序把表里面的一批数据扫出来 - get 读一行数据 - get 'test', 'row1' - 按 Rowkey 精确读取某一行 - get 是点查,scan 是范围扫 - disable 和 drop - disable 的作用:先把表停用,把表从可服务状态切到不可服务状态 - drop 的作用:删除表 - 创建 namespace - 什么是 namespace - 如果创建表时未指定 namespace,则表存放在 default namespace 下 - create 'test','cf' 本质等价于 'default:test' - 为什么要有 namespace - 表分组管理 - 避免表名冲突 - 便于做配额/约束 - namespace 是 HBase 里按业务域组织表的基本单位 - hbase 存在哪些 namespace - default - 用户表默认所在 namespace - hbase - 系统 namespace,保留给 hbase 内部表 - 创建 namespace - create_namespace 'demo' - 查看 namespace - list_namespace - 在 namespace 下建表 - create 'demo:test', 'cf' - 写入和读取 - put 'demo:test', 'row1', 'cf:a', 'value1' - get 'demo:test', 'row1' - scan 'demo:test' - 删除 namespace - drop_namespace 'demo' - 设置版本数、TTL、压缩 - 列族级配置 - family 是物理和策略边界 - VERSIONS、TTL、COMPRESSION 这类配置,默认思维都是这个 family 怎么存,不是这一列怎么存 - alter 是什么命令 - alter 用来修改已有表的 schema 或表/列族相关配置 - VERSIONS - 控制什么 - 同一个 row+family+qualifier 最多保存多少个历史版本 - 怎么改 - alter 'test', NAME => 'cf', VERSIONS => 5 - TTL - 控制什么 - 这个列族里面的数据默认能活多久 - 怎么改 - alter 'test', NAME => 'cf', TTL => 2592000 - COMPRESSION - 控制什么 - 这个列族的 StoreFile/HFile 落盘时用什么压缩算法 - 怎么改 - alter 'test', NAME => 'cf', COMPRESSION => 'SNAPPY' - 验证修改 - describe 'test' - 查看 Region 分布 - web ui - localhost:16010 -  - 简单 filter 查询 - 为什么要用 filter - 不想整表扫出来自己筛 - 只看某些 row key 前缀/只看某些列名前缀/只取前 N 行/只保留某个列值满足条件的行 - 让 RegionServer 在服务端先过滤(server-side filtering) - filter 的基本写法 - scan '表明', { FILTER => "过滤器表达式" } - 准备练习数据 -  - 5 类简单 filter - PrefixFilter:按 row key 前缀过滤 -  - PageFilter:限制返回多少行 - scan 'demo:filter_test', { FILTER => "PageFilter(2)" } -  - ColumnPrefixFilter:按列限定符前缀过滤 - scan 'demo:filter_test', { FILTER => "ColumnPrefixFilter('na')" } -  - SingleColumnValueFilter:按某一列的值筛行 - scan 'demo:filter_test', { FILTER => "SingleColumnValueFilter('cf', 'city', =, 'binary:Tokyo')" } -  - ValueFilter:按值过滤列 - 如果只是做简单的 family:qualifier:value 等值判断,推荐限制后再配 ValueFilter,这样能避免扫描无关的 family/column - 组合使用 filter - "PrefixFilter ('Row') AND PageFilter (1) AND FirstKeyOnlyFilter ()" Java 客户端开发 - Java 客户端开发 - Connection/Admin/Table - 职责边界 - Connection:集群级入口 - 封装了到实际服务器和 ZooKeeper 的连接 - Admin:管理面接口 - Table:单表数据面接口 - 代码关系 -  - 最小 Java 示例
OpenClaw 原理
1. 学习主题 我要学习的内容: OpenClaw 原理 我为什么要学它: (写清楚用途、场景、目标,比如面试、项目、论文、考试) ...
查看 OpenClaw 的 memory 系统
实验元数据 (Meta Data) 用于日后检索和归档,建立知识索引。 实验编号/标题:例如:查看 OpenClaw 的 Memory 系统 ...
钉钉天元 AI 实践
客户情报 + 销售助理