From c79f2f416b38042ca386929d7f60e119edfd8b0b Mon Sep 17 00:00:00 2001 From: CyC2018 Date: Wed, 4 Nov 2026 22:39:58 +0800 Subject: [PATCH] =?UTF-8?q?Update=20=E5=88=86=E5=B8=83=E5=BC=8F.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补充分布式锁与 Redis 淘汰策略、UUIDv7,以及幂等中切面与事务的顺序、COMMIT 结果未知时的处理。 --- notes/分布式.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/notes/分布式.md b/notes/分布式.md index 65746f23..ca2b72ab 100644 --- a/notes/分布式.md +++ b/notes/分布式.md @@ -105,7 +105,8 @@ SET lock_key unique_value NX PX 30000 - 释放锁:value 设置为客户端的唯一标识,释放锁时先判断 value 是否为自己设置的再删除,避免误删其它客户端的锁。判断和删除要原子地完成,通常放在一个 [Lua 脚本](Redis.md#lua-脚本)中(Redis 8.4 起也可以用下文的 DELEX):分成 GET 和 DEL 两条命令时,GET 确认是自己的锁之后、DEL 执行之前,锁可能恰好过期并被其它客户端拿到,DEL 删掉的就是别人的锁; - 锁续期:如果业务执行时间超过了过期时间,锁会被提前释放;过期时间太长,持有者崩溃后其它客户端又要等很久。过期时间通常取业务正常耗时的几倍,耗时难以估计时使用后台线程定时延长过期时间,例如下文 Redisson 的看门狗; -- 主从切换:Redis 的主从复制是异步的,如果主节点加锁后还未同步到从节点就宕机,从节点经[故障转移](Redis.md#十二sentinel)晋升为主节点之后锁就丢失了。 +- 主从切换:Redis 的主从复制是异步的,如果主节点加锁后还未同步到从节点就宕机,从节点经[故障转移](Redis.md#十二sentinel)晋升为主节点之后锁就丢失了; +- 内存淘汰:锁的键带有过期时间,内存满时 allkeys-* 和 volatile-* 这类淘汰策略都可能在到期之前把它删掉,存放锁的 Redis 要用 noeviction,这时内存满了加锁会报错,要当作获取锁失败处理,见[数据淘汰策略](Redis.md#七数据淘汰策略)。

@@ -678,6 +679,8 @@ gRPC 选择 HTTP/2 是因为它的 [多路复用](HTTP.md#二进制分帧层): 需要注意的是,MySQL 的 UUID() 生成的是基于时间的 v1,时间戳的低位在最前面,短时间内连续生成的值几乎是递增的,接近顺序插入:同样插入 100 万行用时 5.1 秒、数据 164 MB,多出的部分主要来自 36 个字符的长度。 +RFC 9562(2024)定义的 UUIDv7 把 48 位的毫秒时间戳放在最前面,除去版本和变体位,其余的位可以是随机数,也可以放更细的时间或计数器,按字节比较大体随时间递增,存成 BINARY(16) 作主键时新值基本都插入到末尾,规范也建议能用 v7 时不用 v1。JDK 26 起可以用 UUID.ofEpochMillis(System.currentTimeMillis()) 生成,但同一毫秒内的多个值不保证递增,需要严格递增时可以用 uuid-creator 这类带计数器的库,由同一个生成器实例生成的值才保证递增。 + 号段模式要避免取下一段时卡住发号。美团 Leaf 为每个业务准备两个号段缓冲区,当前号段发出 10% 后就异步把下一段取到另一个缓冲区,当前号段用完时直接切换;下一段还没取回时短暂等待,仍然没有就返回错误。号段长度一般按高峰 QPS 的若干分钟设置,美团建议取 600 倍,即 10 分钟的用量,这样数据库宕机时还能继续发号一段时间。 ### 雪花算法 @@ -745,7 +748,9 @@ public synchronized long nextId() { - 处理中:期间到达的重复请求返回“处理中”(例如 HTTP 409),让客户端稍后再查;过期时间要长于处理的最长耗时,崩溃后等它过期就能重试; - 已完成:保存第一次的结果,保留到上游不再重试之后,例如微信支付的回调失败后会在约 24 小时内反复重发。记录中还可以保存请求内容的摘要,同一个幂等键带着不同的内容再次提交时直接报错(Idempotency-Key 首部的 IETF 草案建议返回 422),而不是回放第一次的结果。 -业务已经提交、记录还没改为“已完成”时崩溃,重试仍会再执行一次,所以业务写入本身仍要靠唯一约束或状态条件兜底,调用外部服务时让下游按同一个业务键去重。 +业务已经提交、记录还没改为“已完成”时崩溃,重试仍会再执行一次,所以业务写入本身仍要靠唯一约束或状态条件兜底,调用外部服务时让下游按同一个业务键去重。用注解加切面实现时,切面要包在事务的外面,事务提交之后再把记录改为“已完成”;切面在事务里面时,记录改为“已完成”之后事务仍可能回滚,重试就被挡掉了。 + +数据库的提交本身也可能结果未知:COMMIT 发出后连接断开,事务可能已经提交,也可能没有,MySQL Connector/J 这时抛出 SQLState 为 08007(Transaction resolution unknown)的异常。重连后要按业务键查询确认,或者让重试本身是幂等的。 实现幂等首先要定义什么算同一个请求,也就是幂等键怎么来。以用户狂点“提交订单”为例: