基础认知

  • 基础认知
    • 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 示例