缓存一致性问题

概述 应用读多写少时,常在 PostgreSQL 与 Redis 之间加一层缓存:热数据放 Redis,持久化仍由 PostgreSQL 负责。难点不在「能不能缓存」,而在写路径与读路径如何协调——数据库已更新,缓存仍是旧值;或缓存已失效,并发读把脏数据又写回缓存。 下文以 Redis 作缓存、PostgreSQL 作权威数据源,梳理几种主流缓存模式、各自的一致性特征与适用场景。示例以伪代码和常见实践为主,便于对照落地。 模式总览 模式 谁加载缓存 谁写缓存 一致性 复杂度 典型场景 Cache-Aside 应用 应用 最终一致,需自行处理竞态 低 通用业务,最常用 Read-Through 缓存层 缓存层 同 Cache-Aside 中 统一缓存 SDK Write-Through 应用 缓存层同步写 DB 较强一致 中 读多、可接受写延迟 Write-Back 应用 缓存层异步刷盘 弱一致,有丢数据风险 高 写密集、可容忍短暂不一致 Binlog 同步 中间件 CDC 消费 binlog 更新缓存 最终一致,解耦业务 高 多服务共享缓存、不想侵入业务 分布式事务 应用 + 协调者 2PC / TCC / Saga 强一致或业务级一致 很高 资金、库存等强一致域 graph LR App[应用] Redis[(Redis)] PG[(PostgreSQL)] App -->|Cache-Aside / Read-Through / Write-Through| Redis App -->|直接写或经缓存层| PG Redis -->|miss 时回源| PG PG -->|Binlog / WAL| CDC[CDC 消费者] CDC -->|更新/失效| Redis Cache-Aside(旁路缓存) 也叫 Lazy Loading。应用自己管缓存:读先查 Redis,miss 再查 PostgreSQL 并回填;写先更新 PostgreSQL,再删除(推荐)或更新 Redis。 ...

Created 19196-06-19 · Updated 19196-06-19 · 6 min · 1080 words · -

Committer 服务分布式锁设计(2023)

背景 Committer 服务负责消费 Kafka 消息,将网络策略通过 NSO 提交到物理网络设备。原始设计使用单线程顺序消费:每次从 Kafka 拉取一条消息后提交,等待 NSO 返回结果再处理下一条。 单线程设计的瓶颈在于 NSO 提交设备的耗时差异很大,从几秒到几分钟不等。一条消息若因设备响应慢而阻塞,整个消费循环只能等待,整体吞吐严重受限。 改造目标是将 Committer 改为多线程并发消费,让不同设备的策略可以同时提交。 设备级并发约束 多线程并发带来一个新问题:物理网络设备在同一时刻只能处理一个配置提交。若并发向同一台设备提交多条策略,NSO 会返回冲突错误,设备状态也可能不一致。 因此,并发的粒度需要控制在设备维度:不同设备的提交可以同时进行,但同一台设备上的提交必须串行。 实现方案 多线程消费 Kafka 消费线程不再直接执行提交逻辑,而是将每条消息交给 ThreadPoolExecutor(默认 10 个 worker)异步处理。消费线程本身快速返回,继续 poll 下一批消息。 设备锁设计 引入一张设备锁表,记录当前正在进行中的提交和对应的设备,以及锁变更的时间戳。 提交任务在执行前先获取设备锁: SELECT ... FROM device_lock WHERE device_id = %s FOR UPDATE NOWAIT 加锁使用 NOWAIT:竞争失败时立即抛出异常而非阻塞等待,消费线程可以快速感知锁状态,不会因为等锁而阻塞整个提交流程。 提交完成后,在 finally 块中释放锁: SELECT ... FROM device_lock WHERE device_id = %s FOR UPDATE 解锁不使用 NOWAIT:此时当前任务本身就是锁的持有者,不存在竞争,阻塞等待语义更安全,确保成功取到行后再执行删除,不会因为瞬时异常而意外跳过清理步骤。无论 NSO 超时、解析异常还是其他未知错误,设备锁都会被清理,不会出现因为异常路径遗漏而导致的死锁。 锁的粒度是设备,不是请求。同一台设备的多条策略竞争同一把锁,不同设备的提交互不影响。 锁竞争与 Kafka 重投 竞争失败的消息不会被丢弃。Kafka 本身保证至少一次投递,消息会在下一个消费周期重新投递,再次参与锁竞争。NSO 侧做了幂等处理,重复提交不会导致设备状态错误。 ...

Created 3036-06-03 · Updated 5056-06-05 · 1 min · 124 words · -

PostgreSQL 锁机制

PostgreSQL 的锁机制分为多个层级,从应用层的 Advisory Lock 到行级、表级,再到内核内部的 LWLock,每一层都有其适用场景。 锁的层级总览 层级 类型 由谁控制 可见性 表级锁 8 种模式 SQL 语句或 LOCK TABLE pg_locks 行级锁 4 种模式 SELECT FOR ... pg_locks (元组锁) 页级锁 共享/排他 Buffer Manager 内部 不可见 Advisory Lock 会话级/事务级 应用程序 pg_locks Predicate Lock 谓词锁 串行化隔离级别 (SSI) pg_locks LWLock / Spinlock 内核共享内存保护 内核内部 不可见 表级锁 PostgreSQL 表级锁有 8 种模式,从弱到强排列。冲突意味着两个锁不能被同时持有。 8 种模式 模式 缩写 典型操作 说明 ACCESS SHARE AS SELECT 最弱,只与 ACCESS EXCLUSIVE 冲突 ROW SHARE RS SELECT FOR UPDATE/SHARE 与 EXCLUSIVE、ACCESS EXCLUSIVE 冲突 ROW EXCLUSIVE RE INSERT、UPDATE、DELETE 与 SHARE、SHARE ROW EXCLUSIVE、EXCLUSIVE、ACCESS EXCLUSIVE 冲突 SHARE UPDATE EXCLUSIVE SUE VACUUM、ANALYZE、CREATE INDEX CONCURRENTLY 与自身及更强模式冲突 SHARE S CREATE INDEX(非 CONCURRENTLY) 阻止并发写 SHARE ROW EXCLUSIVE SRE CREATE TRIGGER、部分 ALTER TABLE 比 SHARE 更强,与自身冲突 EXCLUSIVE E REFRESH MATERIALIZED VIEW CONCURRENTLY 只允许并发 ACCESS SHARE(即普通 SELECT) ACCESS EXCLUSIVE AE DROP TABLE、TRUNCATE、ALTER TABLE、LOCK TABLE 最强,与所有模式冲突,阻塞所有操作包括 SELECT 冲突矩阵 AS RS RE SUE S SRE E AE ACCESS SHARE - - - - - - - ✗ ROW SHARE - - - - - - ✗ ✗ ROW EXCLUSIVE - - - - ✗ ✗ ✗ ✗ SHARE UPD EXC - - - ✗ ✗ ✗ ✗ ✗ SHARE - - ✗ ✗ - ✗ ✗ ✗ SHARE ROW EXC - - ✗ ✗ ✗ ✗ ✗ ✗ EXCLUSIVE - ✗ ✗ ✗ ✗ ✗ ✗ ✗ ACCESS EXCL ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ 显式表锁 -- 显式加表锁,默认 ACCESS EXCLUSIVE LOCK TABLE orders; -- 指定模式 LOCK TABLE orders IN SHARE MODE; LOCK TABLE orders IN ROW EXCLUSIVE MODE; -- 非阻塞,获取不到立即失败 LOCK TABLE orders IN ACCESS EXCLUSIVE MODE NOWAIT; 常见场景:批量数据迁移时先 LOCK TABLE ... IN EXCLUSIVE MODE,防止迁移过程中有新写入。 ...

Created 3036-06-03 · Updated 4046-06-04 · 8 min · 1554 words · -

Spring Boot + PostgreSQL 持久层框架

主流持久层框架 Spring Data JPA + Hibernate Spring Boot 默认推荐,依赖 spring-boot-starter-data-jpa ORM 框架,用注解映射对象和表 自动生成 SQL,支持 JPQL / Criteria API 适合领域模型复杂的场景 MyBatis / MyBatis-Plus 半自动 ORM,SQL 写在 XML 或注解中 对 SQL 控制力强,适合复杂查询 MyBatis-Plus 提供代码生成、分页等增强功能 JOOQ 类型安全的 SQL DSL,SQL 风格写法 编译期检查 SQL,适合喜欢写 SQL 的团队 商业数据库需付费版 Spring Data JDBC 比 JPA 更轻量,无懒加载、缓存等复杂特性 SQL 更透明,适合简单 CRUD R2DBC(响应式) 非阻塞响应式驱动,配合 WebFlux 使用 依赖 spring-boot-starter-data-r2dbc 选型建议 场景 推荐 快速开发,标准 CRUD Spring Data JPA 复杂 SQL,精细控制 MyBatis-Plus 强类型 SQL,重查询业务 JOOQ 响应式架构 R2DBC 连接池 Spring Boot 默认使用 HikariCP 作为连接池。 ...

Created 25256-04-25 · Updated 11116-05-11 · 3 min · 467 words · -