<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>分布式系统 on 安橙的博客</title><link>https://blog.ans20xx.com/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/</link><description>Recent content in 分布式系统 on 安橙的博客</description><generator>Hugo -- 0.161.1</generator><language>zh</language><lastBuildDate>Thu, 19 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.ans20xx.com/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/index.xml" rel="self" type="application/rss+xml"/><item><title>HBase 复习</title><link>https://blog.ans20xx.com/posts/database/hbase-%E5%AD%A6%E4%B9%A0/</link><pubDate>Thu, 19 Mar 2026 00:00:00 +0000</pubDate><guid>https://blog.ans20xx.com/posts/database/hbase-%E5%AD%A6%E4%B9%A0/</guid><description>&lt;h1 id="基础认知"&gt;基础认知&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-71452863"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-71452863" style="display:none;"&gt;
- 基础认知
- HBase 和 MySQL、Redis、Elasticsearch、Hive 的区别
- HBase
- 一个建立在 Hadoop/HDFS 上的、支持联机、实时读写的分布式数据库，适合承载超大表
- 面向宽表/稀疏表
- 以 RowKey 为核心访问路径
- 强项是海量数据下的主键访问和范围扫描
- MySQL
- 关系型数据库
- 强 SQL 能力
- 强事务
- 适合 OLTP 业务系统
- Redis
- 内存型数据存储
- 超低延迟
- 丰富数据结构
- 更像高性能数据结构引擎/缓存平台
- ElasticSerach
- 分布式搜索引擎
- 文档模型
- 擅长全文检索、相关性排序、聚合分析
- 不是传统事务数据库
- Hive
- 数据仓库
- 面向离线分析
- 适合大规模批处理、报表、数仓
- 不适合在线高并发事务场景
- 列式/列族模型是什么
- 列族模型
- 看起来像一张表
- 但是每一行并不是必须拥有同样的列
- HBase 把列分成若干个 Column Family (列族)
- 同一个列族里面的数据会被一起管理和物理存储
- 每一条具体的数据单元是一个 Cell，由行键、列族、列限定符、事件戳和值共同确定
- HBase 是一个按 RowKey 组织、按列族管理、按单元格存值、支持多版本的稀疏宽表结构
- 模型示例
- 层次结构
- Table -&amp;gt; Row -&amp;gt; Column Family -&amp;gt; Column Qualifier -&amp;gt; Cell(Value &amp;#43; TimeStamp)
- 一张用户表 user_profile，定义了两个列族 info &amp;#43; stat
- 一行数据
- RowKey = user_1001
- info:name = &amp;#34;Alice&amp;#34;
- info:city = &amp;#34;Tokyo&amp;#34;
- 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 &amp;#43; family &amp;#43; qualifier，可以在不同 timestamp 下保存多个值
- Get/Scan API 也支持按照版本范围取数据
- HBase 的默认思维是：一个单元格可以有历史版本
- 为什么要有列族
- HBase 不只是逻辑上分组，还会影响物理管理和存储策略
- 列族可以把访问模式相近的数据放在一起
- 列族可以让不同数据有不同的存储策略
- 列族可以让稀疏表更自然
- 稀疏宽表
- 稀疏
- 不是每一行都有同样的列
- 举例
- 商品 A：颜色、尺寸、品牌
- 商品 B：品牌、功率、电压
- 商品 C：材质、长度、颜色、重量
- 如果用 MySQL，固定列会导致很多字段为空
- HBase 只保存实际存在的 family:qualifier-&amp;gt;value 即可
- HBase 在列族下使用动态 qualifier
- 宽表
- 列非常多，甚至 qualifier 的数量可以远大于传统关系表的字段数
- HBase 为什么是 Bigtable 风格数据库
- 什么是 Bigtable
- Bigtable 是 Google 提出的一个分布式存储系统，用来存放结构化数据，典型特点
- 面向超大规模数据
- 稀疏，可扩展的表
- 以 row key 为核心组织数据
- 支持列族
- 支持多版本
- 支持按键范围扫描
- 以 RowKey 为中心，而不是以 SQL 为中心
- Bigtable 风格数据库核心访问方式
- 按 RowKey 精准查
- 按 RowKey 前缀或范围扫描
- 设计 schema 时优先围绕查询路径设计 row key
- 设计数据库时，优先设计 RowKey 和访问路径，而不是优先设计复杂 SQL
&lt;/textarea&gt;
&lt;h1 id="数据模型与表设计"&gt;数据模型与表设计&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-84563271"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-84563271" style="display:none;"&gt;
- 数据模型与表设计
- 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
- 一行就是一个主键
- 点查为主
- 不需要时间维度
- 主维度 &amp;#43; 时间
- user_id#ts
- 主维度 &amp;#43; 倒序时间
- user_id#reverse_ts
- 桶 &amp;#43; 主维度 &amp;#43; 时间
- bucket(user_id)%16#user_id#reverse_ts
- 数据集前缀 &amp;#43; 业务键
- 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 区间，负载就不会均匀摊开，造成热点
&lt;/textarea&gt;
&lt;h1 id="架构原理"&gt;架构原理&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-45783621"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-45783621" style="display:none;"&gt;
- 架构原理
- 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-&amp;gt;Region-&amp;gt;Store-&amp;gt;MemStore/StoreFile-&amp;gt;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
&lt;/textarea&gt;
&lt;h1 id="实际操作"&gt;实际操作&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-25648137"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-25648137" style="display:none;"&gt;
- 实操
- 启动单机 HBase
- 单机模式是什么
- 单机模式是 HBase 最基础的步数形态
- 在 standalone 模式下，所有 HBase 守护进程都运行在一个 JVM 里
- Docker 启动
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/03/24/20260324134824394.png,252,116)
- 进入容器进行最小验证命令
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/03/24/20260324135239051.png,625,612)
- 用 Shell 建表、删表、put/get/scan
- 进入 shell
- hbase shell
- 命令基本结构
- 表名：test
- row key：row1
- 列：cf:a（列族:列限定符）
- create 建表
- create &amp;#39;test&amp;#39;,&amp;#39;cf&amp;#39;
- 创建表 test，在表中预定义列族 cf
- put 写入数据
- put &amp;#39;test&amp;#39;, &amp;#39;row1&amp;#39;, &amp;#39;cf:a&amp;#39;, &amp;#39;value1&amp;#39;
- &amp;#39;test&amp;#39;：表名
- &amp;#39;row1&amp;#39;：row key
- &amp;#39;cf:a&amp;#39;：列
- &amp;#39;value1&amp;#39;：值
- 本质：往某个表的某一行、不同列写值
- scan 扫描数据
- scan &amp;#39;test&amp;#39;
- 按 RowKey 顺序把表里面的一批数据扫出来
- get 读一行数据
- get &amp;#39;test&amp;#39;, &amp;#39;row1&amp;#39;
- 按 Rowkey 精确读取某一行
- get 是点查，scan 是范围扫
- disable 和 drop
- disable 的作用：先把表停用，把表从可服务状态切到不可服务状态
- drop 的作用：删除表
- 创建 namespace
- 什么是 namespace
- 如果创建表时未指定 namespace，则表存放在 default namespace 下
- create &amp;#39;test&amp;#39;,&amp;#39;cf&amp;#39; 本质等价于 &amp;#39;default:test&amp;#39;
- 为什么要有 namespace
- 表分组管理
- 避免表名冲突
- 便于做配额/约束
- namespace 是 HBase 里按业务域组织表的基本单位
- hbase 存在哪些 namespace
- default
- 用户表默认所在 namespace
- hbase
- 系统 namespace，保留给 hbase 内部表
- 创建 namespace
- create_namespace &amp;#39;demo&amp;#39;
- 查看 namespace
- list_namespace
- 在 namespace 下建表
- create &amp;#39;demo:test&amp;#39;, &amp;#39;cf&amp;#39;
- 写入和读取
- put &amp;#39;demo:test&amp;#39;, &amp;#39;row1&amp;#39;, &amp;#39;cf:a&amp;#39;, &amp;#39;value1&amp;#39;
- get &amp;#39;demo:test&amp;#39;, &amp;#39;row1&amp;#39;
- scan &amp;#39;demo:test&amp;#39;
- 删除 namespace
- drop_namespace &amp;#39;demo&amp;#39;
- 设置版本数、TTL、压缩
- 列族级配置
- family 是物理和策略边界
- VERSIONS、TTL、COMPRESSION 这类配置，默认思维都是这个 family 怎么存，不是这一列怎么存
- alter 是什么命令
- alter 用来修改已有表的 schema 或表/列族相关配置
- VERSIONS
- 控制什么
- 同一个 row&amp;#43;family&amp;#43;qualifier 最多保存多少个历史版本
- 怎么改
- alter &amp;#39;test&amp;#39;, NAME =&amp;gt; &amp;#39;cf&amp;#39;, VERSIONS =&amp;gt; 5
- TTL
- 控制什么
- 这个列族里面的数据默认能活多久
- 怎么改
- alter &amp;#39;test&amp;#39;, NAME =&amp;gt; &amp;#39;cf&amp;#39;, TTL =&amp;gt; 2592000
- COMPRESSION
- 控制什么
- 这个列族的 StoreFile/HFile 落盘时用什么压缩算法
- 怎么改
- alter &amp;#39;test&amp;#39;, NAME =&amp;gt; &amp;#39;cf&amp;#39;, COMPRESSION =&amp;gt; &amp;#39;SNAPPY&amp;#39;
- 验证修改
- describe &amp;#39;test&amp;#39;
- 查看 Region 分布
- web ui
- localhost:16010
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/03/24/20260324154801170.png,600,400)
- 简单 filter 查询
- 为什么要用 filter
- 不想整表扫出来自己筛
- 只看某些 row key 前缀/只看某些列名前缀/只取前 N 行/只保留某个列值满足条件的行
- 让 RegionServer 在服务端先过滤（server-side filtering）
- filter 的基本写法
- scan &amp;#39;表明&amp;#39;, { FILTER =&amp;gt; &amp;#34;过滤器表达式&amp;#34; }
- 准备练习数据
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/03/24/20260324155538852.png,475,206)
- 5 类简单 filter
- PrefixFilter：按 row key 前缀过滤
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/03/24/20260324155813044.png,801,131)
- PageFilter：限制返回多少行
- scan &amp;#39;demo:filter_test&amp;#39;, { FILTER =&amp;gt; &amp;#34;PageFilter(2)&amp;#34; }
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/03/24/20260324160025865.png,799,138)
- ColumnPrefixFilter：按列限定符前缀过滤
- scan &amp;#39;demo:filter_test&amp;#39;, { FILTER =&amp;gt; &amp;#34;ColumnPrefixFilter(&amp;#39;na&amp;#39;)&amp;#34; }
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/03/24/20260324160109778.png,801,134)
- SingleColumnValueFilter：按某一列的值筛行
- scan &amp;#39;demo:filter_test&amp;#39;, { FILTER =&amp;gt; &amp;#34;SingleColumnValueFilter(&amp;#39;cf&amp;#39;, &amp;#39;city&amp;#39;, =, &amp;#39;binary:Tokyo&amp;#39;)&amp;#34; }
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/03/24/20260324160214742.png,941,100)
- ValueFilter：按值过滤列
- 如果只是做简单的 family:qualifier:value 等值判断，推荐限制后再配 ValueFilter，这样能避免扫描无关的 family/column
- 组合使用 filter
- &amp;#34;PrefixFilter (&amp;#39;Row&amp;#39;) AND PageFilter (1) AND FirstKeyOnlyFilter ()&amp;#34;
&lt;/textarea&gt;
&lt;h1 id="java-客户端开发"&gt;Java 客户端开发&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-71642853"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-71642853" style="display:none;"&gt;
- Java 客户端开发
- Connection/Admin/Table
- 职责边界
- Connection：集群级入口
- 封装了到实际服务器和 ZooKeeper 的连接
- Admin：管理面接口
- Table：单表数据面接口
- 代码关系
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/03/24/20260324161657372.png,584,113)
- 最小 Java 示例
&lt;/textarea&gt;</description></item><item><title>nosql 复习.md</title><link>https://blog.ans20xx.com/posts/database/nosql-%E5%A4%8D%E4%B9%A0/</link><pubDate>Sat, 21 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.ans20xx.com/posts/database/nosql-%E5%A4%8D%E4%B9%A0/</guid><description>&lt;h1 id="cap-与一致性模型"&gt;CAP 与一致性模型&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-81634527"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-81634527" style="display:none;"&gt;
- CAP 与一致性模型
- CAP 定理
- 组成
- C(Consistency，一致性)：所有客户端在同一时刻读到的数据一致
- A(Availability，可用性)：每个请求都能在有限时间内得到非错误响应
- P(Partition tolerance，分区容错)：系统在网络分区时仍然能提供服务
- 关键结论
- 发生网络分区时，无法同时满足 C 和 A
- 分布式系统里 P 几乎是必选项（网络一定会分区/超时/抖动）
- 一致性
- 线性一致性
- 定义：所有操作看起来像某个全局时间顺序瞬间发生，读一定能看到最近完成的读
- 工程含义：通常需要 leader&amp;#43;共识&amp;#43;或严格 quorum&amp;#43;顺序约束
- 代价：跨节点协调-&amp;gt;延迟变高；分区时更可能不可用
- 顺序一致：比线性一致弱
- 只要存在一个全局顺序能解释所有的操作，但不要去符合真实时间，不要去最近完成的写立即可见
- 因果一致：社交消息系统常见
- 保证有因果关系的操作顺序一致；无因果关系的并发操作允许不同节点看到不同顺序
- 先发帖再评论，所有人必须看到帖再评论，但两个人同时发帖，顺序可能不一样
- 最终一致：AP 常见的一致性目标
- 定义：如果不再有新的更新，所有副本都最终收敛到一个值
- 允许：短时间读到旧值，不同客户端看到不同值
- 收敛：复制、反熵、读修复、后台合并、冲突解决策略
- Quorum(N/R/W)
- 数据库用 quorum 思路来调一致性/可用性
- 定义
- N：副本数
- W：写成功需要确认的副本数
- R：读成功需要读取的副本数
- 关键结论
- 若 R&amp;#43;W&amp;gt;N，读写集合必然有交集-&amp;gt;通常能读到最新写（接近强一致，但还要考虑版本选择/时钟/并发写）
- 若 R&amp;#43;W&amp;lt;=N，可能读不到最新写（更偏最终一致/读旧值概率更高）
- 举例
- W=2,R=2-&amp;gt;R&amp;#43;W=4&amp;gt;3：读写交集至少一个副本，读更可能拿到最新
- W=1,R=1-&amp;gt;2&amp;lt;=3：读很可能读到旧副本
- 分区时怎么选 C/A
- 更一致：提高 W 或 R，分区更容易失败
- 更可用：降低 W 或 R，分区时能继续响应，可能读旧/冲突
- 读修复与 hinted handoff
- Read Repair（读修复）：读时发现副本版本不一致，把新值回写到旧副本，加速收敛
- Hinted Handoff：某副本暂时不可达，协调者先把代写的 hint 记录下来，等它恢复再补写
- 冲突从哪里来？怎么解决？
- AP 系统分区时允许两边都写 -&amp;gt; 一定会出现冲突，关键是冲突语义
- LWW(Last Write Wins)
- 用事件戳/版本号选最新覆盖旧值
- 风险：依赖时钟
- 优点：实现简单，很多常见足够
- 向量时钟/版本向量
- 判断两个更新是否并发（不可比较）还是有先后关系（可比较）
- 并发写需要应用层合并
- CRDT
- 通过数学结构保证并发合并可收敛
- 一致性 != 事务 ACID
- CAP 的 C：指副本之间对外可见的读写一致语义
- ACID 里的 C：Consistency（约束一致性）是事务前后满足约束
- 事务强一致可能跨分区不可用；最终一致系统也可以在单分区内做局部事务
- 典型场景
- 必须强一致
- 余额扣款、库存扣减不超卖、唯一性约束、权限变更立即生效
- 宁可失败重试/排队，也不能错
- 可以最终一致
- 点赞数、播放量、推荐特征、日志埋点、监控指标
- 短时间误差可接受，最终对齐即可
- 折中：读写分离 &amp;#43; 关键路径强一致
- 下单扣库存走强一致存储：报表/BI 走 ClickHouse 最终一致导入
- 缓存允许短暂不一致，通过失效策略、异步刷新、补偿修正
&lt;/textarea&gt;
&lt;h1 id="分布式存储底层"&gt;分布式存储底层&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-48256371"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-48256371" style="display:none;"&gt;
- 分布式存储底层
- 分布式存储底层总览
- Client/SDK
- 路由层：根据 Key 找到 Shard/Leader
- 分片层：每个分片负责一段 key 空间
- 复制层：每个分片有 N 个副本
- 一致层/共识层：决定写要等谁确认，谁是 Leader，如何提交日志
- 存储引擎层：WAL &amp;#43; Memtable &amp;#43; SST/BTREE 等
- 后台维护：compaction、rebalance、修复、反熵、GC
- 数据分片
- 为什么必须分片
- 目标：水平扩展，吞吐=节点数线性增长
- 分片策略对比
- Hash 分片
- 优点：数据分布均匀，天然抗热点
- 缺点：范围查询弱，扩容时大量搬迁
- 适用：KV，用户 ID 随机读写，计数器，缓存
- Range 分片
- 优点：范围查询强
- 缺点：容易热点，需要 split/merge
- 适用：按事件排序的日志、时序、OLAP 分区表
- 一致性哈希 &amp;#43; 虚拟节点
- 目标：扩容/缩容时减少迁移量
- 一致性哈希结论：加/减一个节点只影响环上相邻的一段 key
- 虚拟节点：每台机器对应多个 vnode，负载更均衡，迁移更细粒度
- 路由：请求如何找到分片
- Client-side routing（客户端算）
- Client 拿到分片拓扑，自己算目标节点
- 优点：少一跳、性能好
- 缺点：拓扑变更要更新客户端（gossip/配置中心）
- Proxy/Router（中间代理）
- 由 Proxy 接请求，负责路由与重试
- 优点：客户端简单，拓扑变更透明
- 缺点：proxy 可能成为瓶颈/单点
- Coordinator （协调节点）
- 写入/查询可能由协调者拆分到多个分片再聚合
- 常用于分析系统/分布式 SQL
- Rebalance（扩缩容/再均衡）
- 扩容
- 数据迁移：搬迁期间读写怎么保证正确性
- 双写/转发：迁移期间某些 key 可能在 old/new 两处
- 限流：迁移会抢光 IO/CPU，影响线上延迟
- 策略
- 迁移期间双写，再切流
- 迁移期间转发（写 old，old 转发到 new）
- 分批搬迁&amp;#43;限速&amp;#43;热点优点处理
- 稳定窗口切换元数据
- 热点问题
- 热点是什么
- Hot key：单个 key QPS 极高
- Hot partition：单个分片承担大量 key 或大量访问
- 热点为什么致命
- 分布式扩展依赖均匀分布，热点让整体吞吐被最热分片锁死
- 延迟抖动：最热节点 CPU/网卡/锁竞争导致 p99 爆炸
- 治理手段
- Key 打散（加盐/分桶）
- counter:global 拆分成 counter:global:{0..99}，读时聚合，写时随机桶
- 代价：读放大/需要聚合
- 读写分离 &amp;#43; 多副本读
- 热读 key：允许从 follower/replica 读，或者使用 cache 层
- 本地缓存 &amp;#43; 请求合并
- 在网关/服务端合并通同 key 并发请求，防止缓存击穿引起雪崩
- 分区拆分
- range 分片：把热区间再拆细
- 配合动态 split
- 异步化/预计算
- 把热点聚合改成写日志，异步聚合出结果
- 副本复制
- Leader-Follower（主从/Primary-Replica）
- 写入路径：Client-&amp;gt;Leader 写入-&amp;gt;复制到 followers-&amp;gt;达到确认策略后返回
- 关键问题
- 同步复制 vs 异步复制
- 同步：延迟高但丢数据风险低
- 异步：延迟低但主挂可能丢最后一段数据
- 读策略
- 读 leader：更一致
- 读 follower：延迟小/抗压，但可能读旧
- 适用：大多数传统分布式 DB/缓存系统
- Multi-Leader（多主）
- 多个 Leader 都可写，各自复制
- 冲突不可避免，需要冲突解决（LWW/向量时钟/CRDT/业务合并）
- 适用：跨地域多活、离线写入，复杂度高
- Leaderless（无主，Dynamo 风格）
- 任意节点可接写，写到 N 个副本
- 用 Quorum(N/R/W)控制一致性
- 配套：读修复、hinted handoff、反熵
- 适用：高可用、可水平扩展、允许最终一致的系统
- 一致写入确认：Quorum
- 定义
- N：副本数
- W：写成功需要确认的副本数
- R：读成功需要读取的副本数
- 关键结论
- 若 R&amp;#43;W&amp;gt;N，读写集合必然有交集-&amp;gt;通常能读到最新写（接近强一致，但还要考虑版本选择/时钟/并发写）
- 若 R&amp;#43;W&amp;lt;=N，可能读不到最新写（更偏最终一致/读旧值概率更高）
- 举例
- W=2,R=2-&amp;gt;R&amp;#43;W=4&amp;gt;3：读写交集至少一个副本，读更可能拿到最新
- W=1,R=1-&amp;gt;2&amp;lt;=3：读很可能读到旧副本
- 共识与选主
- 共识
- 只有一个 Leader（避免脑裂乱写）
- 日志顺序一致（写入顺序全局一致）
- 多数派提交（保证故障后不回滚）
- Raft 最小正确集
- Leader 选主
- 节点有 term(任期)，超时发起选举
- 多数派投票选出 Leader
- 心跳维持 leader 身份
- 日志复制
- 客户端写入先到 leader
- leader 追加日志并复制给 follower
- follower 按 index/term 对齐（不一致就回滚到匹配点再补）
- 提交规则
- leader 只有在日志被多数派复制后才能提交
- 提交后对外可见
&lt;/textarea&gt;
&lt;h1 id="cassandrahbase"&gt;Cassandra/HBase&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-62543178"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-62543178" style="display:none;"&gt;
- Cassandra/HBase
- LSM-Tree &amp;#43; WAL
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/22/20260222101553862.png,604,382)
- LSM-Tree 要解决什么问题
- 传统 OLTP 存储引擎常见是 B&amp;#43;Tree
- B&amp;#43;Tree 更新/插入会改动树上的多个节点页
- 页可能在磁盘不同位置-&amp;gt;随机写很多
- 随机在 HDD 上很慢，在 SSD 上也不便宜
- LSM 的核心目标
- 把随机写变成顺序写/批量写
- 把整理数据到成本挪到后台异步做（Compaction）
- LSM 通过先写日志&amp;#43;写内存&amp;#43;顺序刷盘成不可变文件，再后台合并文件，大幅提高写吞吐，但是读取会更复杂，且 compaction 会带来放大和抖动
- 写入流程
- 先写 WAL（顺序追加）
- 把这条写操作追加到 WAL 文件末尾
- 顺序写，非常快
- 目的：宕机可恢复
- 写入 Memtable（内存有序结构）
- MemTable 一般用 SkipList/红黑树等保证有序
- 写入是内存操作，快
- MemTable 满了就 Freeze（变成 Immutable）
- 当前 Memetable 变成不可变，新写进入新的 MemTable
- immutable 的存在时为了让刷盘不阻塞写入
- 刷盘生成 SSTable（顺序写文件）
- 后台线程把 immutbale MemTable 以排序后的顺序写成 SSTable，不可变有序文件
- SSTable 通常附带
- 稀疏索引
- Bloom Filter
- 数据块
- 读流程
- 步骤
- 先查 MemTable
- 再查 Immutable MemTable
- 再查磁盘上的多个 SSTable
- 找到 Key 后还要做版本合并
- 同一个 Key 可能在很多层都有
- SSTable 不可变，更新不是原地修改，而是写一个新版本
- 旧版本会在后台 compaction 时被清理
- 读放大：SSTable 越多，读需要查的地方越多
- Bloom Filter 的作用
- 对于某个 SSTable，Bloom Filter 判断：这个 key 不可能在里面-&amp;gt;直接跳过，不读磁盘
- 如果 Bloom 说在，才去查索引/数据块
- Delete 怎么做
- LSM 不会立刻物理删除数据，而是写入一个 Tombstone（删除标记）
- Delete(key) 本质是写入 key-&amp;gt;tombstone
- 读到 tombstone 说明该 key 已经被删除
- 真正清理旧值与 tombstone，依靠 Compaction 完成
- Compaction
- 目标
- 合并多个 SSTable
- 丢弃被覆盖的旧版本
- 清理 tombstone
- 降低读放大、回收空间
- Compaction 为什么会导致 p99 抖动
- 需要大量读旧文件 &amp;#43; 写新文件
- CPU 做合并、校验、压缩
- 可能把 SSD/HDD 打满，影响在线请求
- compaction 策略
- Sized-Tiered Compaction
- 当 L0/L1 有若干大小相近的 SSTable，就合并成更大的
- 优点：写放大相对小，吞吐高
- 缺点：读放大可能更大
- Leveled Compaction
- 各层有大小上限，保证 L1&amp;#43; 之间范围重叠更可控
- 优点：读放大更小
- 缺点：写放大更高
&lt;/textarea&gt;
&lt;h1 id="数据建模与查询建模"&gt;数据建模与查询建模&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-68375241"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-68375241" style="display:none;"&gt;
- 数据建模与查询建模
- NoSQL 建模的通用原则
- Query-driven modeling(按查询建模)
- RDB 常按照范式建模，再用 SQL 去适配查询
- 在 NoSQL 里更像：先确定查询，再设计主键/分区/排序/冗余表
- Denormalization
- 目的：消灭 join，降低在线查询复杂度
- 代价：写入多处更新、需要处理一致性补偿
- 同一份实体多份查询视图表
- 把维度字段复制进事实表
- 预计算/物化视图
- 把聚合从读时搬到写时
- 写路径：事件 -&amp;gt; 聚合状态更新
- 读路径：直接读聚合结果
- 适合：排行榜、计数器、报表、指标面板
- 幂等与版本化
- NoSQL &amp;#43; 分布式写入经常至少一次交付，天然要支持幂等
- 热点治理
- 避免分区键过于集中
- 避免自增 RowKey
- 避免单 key 计数器
&lt;/textarea&gt;</description></item></channel></rss>