diff --git a/notes/pics/snowflake.png b/notes/pics/snowflake.png index 6ea1c76f..199c3590 100644 Binary files a/notes/pics/snowflake.png and b/notes/pics/snowflake.png differ diff --git a/notes/分布式.md b/notes/分布式.md index 01dfa3dd..900bc183 100644 --- a/notes/分布式.md +++ b/notes/分布式.md @@ -85,7 +85,11 @@ SELECT * FROM distributed_lock WHERE name = 'order:1024' FOR UPDATE; COMMIT; ``` -其它客户端执行同一条语句时阻塞,超过 innodb_lock_wait_timeout(默认 50 秒)返回错误;加上 NOWAIT 时拿不到锁立即返回错误,相当于非阻塞锁。持有锁的进程崩溃后连接断开,事务回滚,锁立即释放:kill -9 持有锁的进程后,等待中的客户端马上拿到了锁(MySQL 8.0 实测,见[附录 A7](#a7-mysql-行锁实现的分布式锁))。需要注意的是,持有锁的机器宕机或网络中断时,MySQL 收不到断开的通知,要等这个空闲连接超过 wait_timeout(默认 8 小时)才回滚。可以为持有锁的连接调小 wait_timeout,但要大于持锁期间连接空闲的最长时间(例如调用远程服务),否则业务还没执行完锁就被释放了。代价是持有锁期间一直占用一个数据库连接和一个未提交的事务,并发高时会耗尽连接池。 +其它客户端执行同一条语句时阻塞,超过 innodb_lock_wait_timeout(默认 50 秒)返回错误;加上 NOWAIT 时拿不到锁立即返回错误,相当于非阻塞锁。持有锁的进程崩溃后连接断开,事务回滚,锁立即释放:kill -9 持有锁的进程后,等待中的客户端马上拿到了锁(MySQL 8.0 实测,见[附录 A7](#a7-mysql-行锁实现的分布式锁))。 + +需要注意的是,持有锁的机器宕机或网络中断时,MySQL 收不到断开的通知,要等这个空闲连接超过 wait_timeout(默认 8 小时)才回滚。可以为持有锁的连接调小 wait_timeout,但要大于持锁期间连接空闲的最长时间(例如调用远程服务),否则业务还没执行完锁就被释放了。 + +代价是持有锁期间一直占用一个数据库连接和一个未提交的事务,并发高时会耗尽连接池。 ### Redis 的 SET NX 指令 @@ -157,7 +161,7 @@ RedLock 不要求各节点的时钟同步,但假设各节点的时钟走速大 UPDATE inventory SET stock = 97, fence = 8 WHERE sku_id = 10086 AND fence <= 8; ``` -RedLock 使用的随机值不是递增的,算法本身无法提供栅栏令牌;ZooKeeper 则可以使用加锁时创建的有序节点的序号或者事务 ID(zxid),它们都是单调递增的。 +RedLock 使用的随机值不是递增的,算法本身无法提供栅栏令牌;ZooKeeper 则可以使用加锁时创建的节点的事务 ID(czxid),它在整个集群中单调递增。有序节点的序号不适合作令牌:序号的计数器属于父节点,Curator 把锁的父节点建成容器节点,锁空闲后父节点会被删除,序号又从 0 开始。 Redisson 已把 RedLock 标为弃用,改为推荐 RLock 加上主从同步检查;需要令牌时用 RFencedLock,它在加锁时对一个计数键执行 INCR,返回值作为令牌。这个计数键和锁在同一个主节点上,主从复制是异步的,故障转移后计数可能回退,令牌不再严格递增,所以它同样不能完全排除旧持有者的写入。 @@ -636,7 +640,7 @@ gRPC 选择 HTTP/2 是因为它的 [多路复用](HTTP.md#二进制分帧层): - 每次调用都要设置超时时间,否则下游变慢时,调用方的线程会一直等待,最终被耗尽; - 调用方超时不代表服务方没有执行:请求可能在网络中延迟,或者在服务方的线程池中排队、遇到 GC 停顿,这些时间都计入了调用方的耗时,却不在服务方记录的处理时间里,于是调用方超时了,服务方却执行成功了; -- 因此只有幂等的接口才能自动重试,例如查询;扣库存这类写操作要带上请求 ID,由服务方去重(见 [幂等](#九幂等)),否则重试会扣减两次; +- 因此只有幂等的接口才能自动重试,例如查询;扣库存这类写操作要带上请求 ID,由服务方去重(见 [幂等](#九幂等)),否则重试会扣减两次。需要注意的是,有的框架默认就会重试,例如 Dubbo 默认的集群容错方式是 failover,失败或超时后换一台服务器重试 2 次,一次调用最多执行 3 次,写操作要改为 failfast 或者带上请求 ID; - 调用链较长时,下游的超时时间应该比上游短。gRPC 会把截止时间(deadline)随请求传给服务方,超时后调用方收到 DEADLINE_EXCEEDED,服务方的框架也会取消这次调用,但正在执行的业务代码不会被中断,耗时的处理要自己检查是否已被取消;服务方调用下游时沿用这次请求的上下文,Java 和 Go 的 gRPC 会自动把剩余的时间传下去。