桥下の民 - 分布式
type: channel
link: https://wordplay.work/index.php/category/分布式
items: 分布式锁 Redission
items: 分布式锁概述
items: 分布式事务
items: 分布式系统基本概念 CAP&BASE

分布式锁 Redission
type: item
date: 2023-06-26T02:59:00+00:00
description: 一、关于 在某些场景中，多个进程必须以互斥的方式独占共享资源，这时用分布式锁是最直接有效的。 随着技术快速发展，数据规模增大，分布式系统越来越普及，一个应用往往会部署在多台机器上（多节点），在有些场景中，为了保证数据不重复，要求在同一时刻，同一任务只在一个节点上运行，即保证某一方法同一时刻只能被一个线程执行。在单机环境中，应用是在同一进程下的，只需要保证单进程多线程环境中的线程安全性，通过 JAVA 提供的 volatile、ReentrantLock、synchronized 以及 concurrent 并发包下一些线程安全的类等就可以做到。而在多机部署环境中，不同机器不同进程，就需要在多进程下保证线程的安全性了。因此就需要分布式锁。 以往的工作中看到或用到几种实现方案，有基于 zk 的，也有基于 redis 的。由于实现上逻辑不严谨，线上时不时会爆出几个死锁 case。那么，什么样的分布式锁实现，才算是比较好的方案？ 常见分布式锁方案对比： 表格中对比了几种常见的方案，redis+lua 基本可应付工作中分布式锁的需求。然而，当偶然看到 redisson 分布式锁实现方案，相比以上方案，redisson 保持了简单易用、支持锁重入、支持阻塞等待、Lua 脚本原子操作。 分布式锁需满足四个条件： 首先，为了确保分布式锁可用，我们至少要确保锁的实现同时满足以下四个条件： 1、互斥性。在任意时刻，只有一个客户端能持有锁。 2、不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁，也能保证后续其他客户端能加锁。 3、加锁和解锁必须是同一个客户端，客户端自己不能把别人加的锁给解了，即不能误解锁。 4、具有容错性。只要大多数 Redis 节点正常运行，客户端就能够获取和释放锁。 二、Redisson 分布式锁的实现 2.1、Redisson 分布式重入锁用法 Redisson 支持单点模式、主从模式、哨兵模式、集群模式，这里以单点模式为例： // 1、构造 redisson 实现分布式锁必要的 Config Config config = new Config (); config.useSingleServer ().setAddress ("redis://127.0.0.1:5379").setPassword ("123456").setDatabase (0); // 2、构造 RedissonClient RedissonClient redissonClient = Redisson.create (config); // 3、获取锁对象实例（无法保证是按线程的顺序获取到） RLock rLock = redissonClient.getLock (lockKey); try { /** * 4、尝试获取锁 * waitTimeout 尝试获取锁的最大等待时间，超过这个值，则认为获取锁失败 * leaseTime 锁的持有时间，超过这个时间锁会自动失效（值应设置为大于业务处理的时间，确保在锁有效期内业务能处理完） */ boolean res = rLock.tryLock ((long) waitTimeout, (long) leaseTime, TimeUnit.SECONDS); if (res) { // 成功获得锁，在这里处理业务 } } catch (Exception e) { throw new RuntimeException ("aquire lock fail"); } finally { // 无论如何，最后都要解锁 rLock.unlock (); } redisson 这个框架重度依赖了 Lua 脚本和 Netty，各种 Future 及 FutureListener 的异步、同步操作转换。 肯定要定义 2 个接口：加锁、解锁；大道至简，redisson 的作者就是在加锁和解锁的执行层面采用 Lua 脚本，逼格高，而且重要有原子性保证。加锁和解锁过程中还巧妙地利用了 redis 的发布订阅功能。 2.2、加锁 & 解锁 Lua 脚本 加锁、解锁 Lua 脚本是 redisson 分布式锁实现最重要的组成部分。 1、加锁Lua脚本 脚本入参： 脚本内容： -- 若锁不存在：则新增锁，并设置锁重入计数为1、设置锁过期时间 if (redis.call('exists', KEYS[1]) == 0) then redis.call('hset', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; -- 若锁存在，且唯一标识也匹配：则表明当前加锁请求为锁重入请求，故锁重入计数+1…

分布式锁概述
type: item
date: 2023-06-25T17:52:00+00:00
description: 一、需具备的条件 1、获取 / 释放锁性能要好。 2、原子性。 3、网络中断或宕机无法释放锁时，锁必须被清除，不然会发生死锁。 4、可重入一个线程中可以多次获取同一把锁，比如一个线程在执行一个带锁的方法，该方法中又调用了另一个需要相同锁的方法，则该线程可以直接执行调用的方法，而无需重新获得锁。 5、阻塞锁 / 非阻塞锁，阻塞锁即没有获取到锁，则继续等待获取锁；非阻塞锁即没有获取到锁后，不继续等待，直接返回锁失败。 二、实现方式 2.1、数据库锁 一般很少使用数据库锁，性能不好并且容易产生死锁。 2.1.1、基于 MySQL 锁表 该实现方式完全依靠数据库唯一索引来实现，当想要获得锁时，即向数据库中插入一条记录，释放锁时就删除这条记录。这种方式存在以下几个问题： 1、锁没有失效时间，解锁失败会导致死锁，其他线程无法再获取到锁，因为唯一索引 insert 都会返回失败。 2、只能是非阻塞锁，insert 失败直接就报错了，无法进入队列进行重试。 3、不可重入，同一线程在没有释放锁之前无法再获取到锁。 2.1.2、 采用乐观锁增加版本号 根据版本号来判断更新之前有没有其他线程更新过，如果被更新过，则获取锁失败。 2.2、缓存锁 2.2.1、基于 setnx、expire 两个命令来实现 基于 setnx（set if not exist）的特点，当缓存里 key 不存在时，才会去 set，否则直接返回 false。如果返回 true 则获取到锁，否则获取锁失败，为了防止死锁，我们再用 expire 命令对这个 key 设置一个超时时间来避免。但是这里看似完美，实则有缺陷，当我们 setnx 成功后，线程发生异常中断，expire 还没来的及设置，那么就会产生死锁。 解决上述问题有两种方案： 第一种是采用 redis2.6.12 版本以后的 set，它提供了一系列选项： EX seconds – 设置键 key 的过期时间，单位时秒 PX milliseconds – 设置键 key 的过期时间，单位时毫秒 NX – 只有键 key 不存在的时候才会设置 key 的值 XX – 只有键 key 存在的时候才会设置 key 的值 第二种采用 setnx ()，get ()，getset () 实现，大体的实现过程如下： 1、线程 Asetnx，值为超时的时间戳 (t1)，如果返回 true，获得锁。 2、线程 B 用 get 命令获取 t1，与当前时间戳比较，判断是否超时，没超时 false，如果已超时执行步骤 3。 3、计算新的超时时间 t2，使用 getset 命令返回 t3 (这个值可能其他线程已经修改过)，如果 t1==t3, 获得锁，如果 t1!=t3 说明锁被其他线程获取了。 4、获取锁后，处理完业务逻辑，再去判断锁是否超时，如果没超时删除锁，如果已超时，不用处理（防止删除其他线程的锁）。 2、RedLock 算法 redlock 算法是 redis 作者推荐的一种分布式锁实现方式，算法的内容如下： 1、获取当前时间； 2、尝试从 5 个相互独立 redis 客户端获取锁； 3、计算获取所有锁消耗的时间，当且仅当客户端从多数节点获取锁，并且获取锁的时间小于锁的有效时间，认为获得锁； 4、重新计算有效期时间，原有效时间减去获取锁消耗的时间； 5、删除所有实例的锁。 redlock 算法相对于单节点 redis 锁可靠性要更高，但是实现起来条件也较为苛刻。 1、必须部署 5 个节点才能让 Redlock 的可靠性更强。 2、需要请求 5 个节点才能获取到锁，通过 Future 的方式，先并发向 5 个节点请求，再一起获得响应结果，能缩短响应时间，不过还是比单节点 redis 锁要耗费更多时间。 然后由于必须获取到 5 个节点中的 3 个以上，所以可能出现获取锁冲突，即大家都获得了 1-2 把锁，结果谁也不能获取到锁，这个问题，redis 作者借鉴了 raft 算法的精髓，通过冲突后在随机时间开始，可以大大降低冲突时间，但是这问题并不能很好的避免，特别是在第一次获取锁的时候，所以获取锁的时间成本增加了。 如果 5 个节点有 2 个宕机，此时锁的可用性会极大降低，首先必须等待这两个宕机节点的结果超时才能返回，另外只有 3 个节点，客户端必须获取到这全部 3 个节点的锁才能拥有锁，难度也加大了。 如果出现网络分区，那么可能出现客户端永远也无法获取锁的情况，介于这种情况，下面我们来看一种更可靠的分布式锁 zookeeper 锁。 2.3、zookeeper 分布式锁 zookeeper 是一个为分布式应用提供一致性服务的软件，它内部是一个分层的文件系统目录树结构，规定统一个目录下只能有一个唯一文件名。 数据模型： 永久节点：节点创建后，…

分布式事务
type: item
date: 2022-07-09T14:39:00+00:00
description: 分布式事务的解决方案有如下几种： 全局事务 基于可靠消息服务的分布式事务 TCC 最大努力通知 一、方案 1：全局事务（DTP 模型） 全局事务基于 DTP 模型实现。DTP 是由 X/Open 组织提出的一种分布式事务模型 ——X/Open Distributed Transaction Processing Reference Model。它规定了要实现分布式事务，需要三种角色： AP：Application 应用系统它就是我们开发的业务系统，在我们开发的过程中，可以使用资源管理器提供的事务接口来实现分布式事务。 TM：Transaction Manager 事务管理器 分布式事务的实现由事务管理器来完成，它会提供分布式事务的操作接口供我们的业务系统调用。这些接口称为 TX 接口。 事务管理器还管理着所有的资源管理器，通过它们提供的 XA 接口来同一调度这些资源管理器，以实现分布式事务。 DTP 只是一套实现分布式事务的规范，并没有定义具体如何实现分布式事务，TM 可以采用 2PC、3PC、Paxos 等协议实现分布式事务。 RM：Resource Manager 资源管理器 能够提供数据服务的对象都可以是资源管理器，比如：数据库、消息中间件、缓存等。大部分场景下，数据库即为分布式事务中的资源管理器。 资源管理器能够提供单数据库的事务能力，它们通过 XA 接口，将本数据库的提交、回滚等能力提供给事务管理器调用，以帮助事务管理器实现分布式的事务管理。 XA 是 DTP 模型定义的接口，用于向事务管理器提供该资源管理器 (该数据库) 的提交、回滚等能力。 DTP 只是一套实现分布式事务的规范，RM 具体的实现是由数据库厂商来完成的。 1.1、实际方案：基于 XA 协议的两阶段提交 XA 是一个分布式事务协议，由 Tuxedo 提出。XA 中大致分为两部分：事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现，比如 Oracle、DB2 这些商业数据库都实现了 XA 接口，而事务管理器作为全局的调度者，负责各个本地资源的提交和回滚。XA 实现分布式事务的原理如下： 总的来说，XA 协议比较简单，而且一旦商业数据库实现了 XA 协议，使用分布式事务的成本也比较低。但是，XA 也有致命的缺点，那就是性能不理想，特别是在交易下单链路，往往并发量很高，XA 无法满足高并发场景。XA 目前在商业数据库支持的比较理想，在 mysql 数据库中支持的不太理想，mysql 的 XA 实现，没有记录 prepare 阶段日志，主备切换回导致主库与备库数据不一致。许多 nosql 也没有支持 XA，这让 XA 的应用场景变得非常狭隘。 二、方案 2：基于可靠消息服务的分布式事务（事务消息中间件） 这种实现分布式事务的方式需要通过消息中间件来实现。假设有 A 和 B 两个系统，分别可以处理任务 A 和任务 B。此时系统 A 中存在一个业务流程，需要将任务 A 和任务 B 在同一个事务中处理。下面来介绍基于消息中间件来实现这种分布式事务。 在系统 A 处理任务 A 前，首先向消息中间件发送一条消息。 消息中间件收到后将该条消息持久化，但并不投递。此时下游系统 B 仍然不知道该条消息的存在。 消息中间件持久化成功后，便向系统 A 返回一个确认应答。 系统 A 收到确认应答后，则可以开始处理任务 A。 任务 A 处理完成后，向消息中间件发送 Commit 请求。该请求发送完成后，对系统 A 而言，该事务的处理过程就结束了，此时它可以处理别的任务了。但 commit 消息可能会在传输途中丢失，从而消息中间件并不会向系统 B 投递这条消息，从而系统就会出现不一致性。这个问题由消息中间件的事务回查机制完成。 消息中间件收到 Commit 指令后，便向系统 B 投递该消息，从而触发任务 B 的执行。 当任务 B 执行完成后，系统 B 向消息中间件返回一个确认应答，告诉消息中间件该消息已经成功消费，此时，这个分布式事务完成。 上述过程可以得出如下几个结论： 消息中间件扮演者分布式事务协调者的角色。 系统 A 完成任务 A 后，到任务 B 执行完成之间，会存在一定的时间差。在这个时间差内，整个系统处于数据不一致的状态，但这短暂的不一致性是可以接受的，因为经过短暂的时间后，系统又可以保持数据一致性，满足 BASE 理论。 上述过程中，如果任务 A 处理失败，那么需要进入回滚流程，如下图所示： 若系统 A 在处理任务 A 时失败，那么就会向消息中间件发送 Rollback 请求。和发送 Commit 请求一样，系统 A 发完之后便可以认为回滚已经完成，它便可以去做其他的事情。 消息中间件收到回滚请求后，直接将该消息丢弃，而不投递给系统 B，从而不会触发系统 B …

分布式系统基本概念 CAP&BASE
type: item
date: 2022-07-08T15:51:00+00:00
description: 一、分布式 将不同的业务分部在不同的地方，就构成了一个分布式的系统，系统 A 是整个分布式系统的入口，用户访问量大的时候要么是速度巨慢，可能会引起单点失败。 二、集群（Cluster） 将三个服务器（系统 A）的系统就组成了一个集群。 三、负载均衡（Load Balancer） 负载均衡的工作独立出来，放到独立的服务器上（例如 nginx）： 负载均衡的服务器虽然工作内容简单，就是拿到请求，分发请求，但是 Nginx 还是有可能挂掉，单点失败还是会出现。 可以将负载均衡也组成一个集群，但是和系统 A 的集群有两点不同： 1、这个新的集群中虽然有两个机器，但是可以用某种办法，让这个机器对外只提供一个 IP 地址，也就是用户看到的好像只有一个机器。 2、同一时刻，只让一个负载均衡的机器工作，另外一个原地待命，如果工作的那个挂掉了，待命的那个就顶上去。 四、分布式架构 4.1.集中式系统 所谓的集中式系统就是指由一台或多台主计算机组成中心节点，数据集中存储于这个中心节点中，并且整个系统的所有业务单元都集中部署在这个中心节点上，系统的所有功能均由其集中处理。 集中式系统的最大的特点就是部署结构非常简单。因此无需考虑如何对服务进行多节点的部署，也就不用考虑各节点之间的分布式协作问题。但是，由于采用单机部署，很可能带来系统大而复杂、难于维护、发生单点故障（单个点发生故障的时候会波及到整个系统或者网络，从而导致整个系统或者网络的瘫痪）、扩展性差等问题。 4.2、分布式系统 分布式系统是一个硬件或软件组件分布在不同的网络计算机上，彼此之间仅仅通过消息传递进行通信和协调的系统。简单来说就是一群独立计算机集合共同对外提供服务，但是对于系统的用户来说，就像是一台计算机在提供服务一样。分布式意味着可以采用更多的普通计算机（相对于昂贵的大型机）组成分布式集群对外提供服务。计算机越多，CPU、内存、存储资源等也就越多，能够处理的并发访问量也就越大。 各个主机之间通信和协调主要通过网络进行，所以分布式系统中的计算机在空间上几乎没有任何限制，这些计算机可能被放在不同的机柜上，也可能被部署在不同的机房中。但是，无论空间上如何分布，一个标准的分布式系统应该具有以下几个主要特征： 分布性 分布式系统中的多台计算机之间在空间位置上可以随意分布，同时，机器的分布情况也会随时变动。 对等性 分布式系统中的计算机没有主／从之分，即没有控制整个系统的主机，也没有被控制的从机，组成分布式系统的所有计算机节点都是对等的。副本（Replica）是分布式系统最常见的概念之一，指的是分布式系统对数据和服务提供的一种冗余方式。在常见的分布式系统中，为了对外提供高可用的服务，我们往往会对数据和服务进行副本处理。数据副本是指在不同节点上持久化同一份数据，当某一个节点上存储的数据丢失时，可以从副本上读取该数据，这是解决分布式系统数据丢失问题最为有效的手段。另一类副本是服务副本，指多个节点提供同样的服务，每个节点都有能力接收来自外部的请求并进行相应的处理。 并发性 在一个计算机网络中，程序运行过程的并发性操作是非常常见的行为。例如同一个分布式系统中的多个节点，可能会并发地操作一些共享的资源，如何准确并高效地协调分布式并发操作也成为了分布式系统架构与设计中最大的挑战之一。 缺乏全局时钟 在分布式系统中，很难定义两个事件究竟谁先谁后，原因就是因为分布式系统缺乏一个全局的时钟序列控制。 故障总是会发生 组成分布式系统的所有计算机，都有可能发生任何形式的故障。除非需求指标允许，在系统设计时不能放过任何异常情况。 五、分布式系统面临的问题 5.1、通信异常 分布式系统需要在各个节点之间进行网络通信，因此网络通信都会伴随着网络不可用的风险或是系统不可用都会导致最终分布式系统无法顺利完成一次网络通信。另外，即使分布式系统各节点之间的网络通信能够正常进行，其延时也会远大于单机操作，会影响消息的收发的过程，因此消息丢失和消息延迟变得非常普遍。 5.2、网络分区 当网络由于发生异常情况，导致分布式系统中部分节点之间的网络延时不断增大，最终导致组成分布式系统的所有节点中，只有部分节点之间能够进行正常通信，而另一些节点则不能，这个现象为网络分区，就是俗称的 “脑裂”。当网络分区出现时，分布式系统会出现局部小集群，在极端情况下，这些局部小集群会独立完成原本需要整个分布式才能完成的功能，这就对分布式一致性提出类非常大的挑战。 5.3、三态 分布式系统的每一次请求与响应，存在特有的三态概念，即成功、失败与超时。当出现超时现象时，网络通信的发起方是无法确定当前请求是否被成功处理的。 5.4、节点故障 节点故障则是分布式环境下另一个比较常见的问题，指的是组成分布式系统的服务器节点出现的宕机或僵死现象。 六、分布式理论 (一) …