分布式(distributed)是为了解决单个物理服务器容量和性能瓶颈问题而采用的优化手段,将一个业务拆分成不同的子业务,分布在不同的机器上执行。服务之间通过远程调用协同工作,对外提供服务。
一、分布式理论
1、CAP原则
在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性)这 3 个基本需求,最多只能同时满足其中的 2 个。
- Consistency(一致性): 指数据在多个副本之间能够保持一致的特性(严格的一致性)
- Availability(可用性):指系统提供的服务必须一直处于可用的状态,每次请求都能获取到非错的响应(不保证获取的数据为最新数据)
- Partition tolerance(分区容错性):分布式系统即便是系统出现网络分区,整个系统也要持续对外提供服务。
什么是网络分区?
分布式系统中,多个节点之间的网络本来是连通的,但是因为某些故障(比如部分节点网络出了问题)导致节点之间无法通信的情况, 整个网络就分成了几块区域,这就叫 网络分区。
2、为什么CAP不能同时满足
首先,在分布式系统中,分区是必然存在的,因此P就必须要满足,否则就违背了分布式系统的初衷。
在满足P的前提下,系统中的某个节点在进行写操作。为了保证 C, 必须要禁止其他节点的读写操作,这就和 A 发生冲突了。如果为了保证 A,其他节点的读写操作正常的话,那就和 C 发生冲突了。
因此,分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。
另外,需要补充说明的一点是:如果网络分区正常的话(系统在绝大部分时候所处的状态),也就说不需要保证 P 的时候,C 和 A 能够同时保证。
Spring Cloud在CAP法则上主要满足的是AP法则,Dubbo和Zookeeper在CAP法则主要满足的是CP法则 ,Nacos 不仅支持 CP 也支持 AP
3、BASE理论
BASE 是 Basically Available(基本可用)、Soft-state(软状态) 和 Eventually Consistent(最终一致性) 三个短语的缩写。
其核心思想时:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。 也就是牺牲数据的一致性来满足系统的高可用性,系统中一部分数据不可用或者不一致时,仍需要保持系统整体“主要可用”。
Basically Available(基本可用):什么是基本可用呢?假设系统出现了不可预知的故障,但还是能用,只是相比较正常的系统而言,可能会有响应时间上的损失,或者功能上的降级(正常情况下,用户可以使用系统的全部功能,但是由于系统访问量突然剧增,系统的部分非核心功能无法使用)。
Soft-state(软状态):什么是硬状态呢?要求多个节点的数据副本都是一致的,这是一种“硬状态”。
软状态也称为弱状态,相比较硬状态而言,允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。
Eventually Consistent(最终一致性):最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
二、分布式锁的实现
一个最基本的分布式锁需要满足:
- 互斥:任意一个时刻,锁只能被一个线程持有。
- 高可用:锁服务是高可用的,当一个锁服务出现问题,能够自动切换到另外一个锁服务。并且,即使客户端的释放锁的代码逻辑出现问题,锁最终一定还是会被释放,不会影响其他线程对共享资源的访问。这一般是通过超时机制实现的。
- 可重入:一个节点获取了锁之后,还可以再次获取锁。
除了上面这三个基本条件之外,一个好的分布式锁还需要满足下面这些条件:
- 高性能:获取和释放锁的操作应该快速完成,并且不应该对整个系统的性能造成过大影响。
- 非阻塞:如果获取不到锁,不能无限期等待,避免对系统正常运行造成影响。
1、MYSQL实现
用数据库实现分布式锁比较简单,就是创建一张锁表,数据库对字段作唯一性约束。
加锁的时候,在锁表中增加一条记录即可;释放锁的时候删除记录就行。
如果有并发请求同时提交到数据库,数据库会保证只有一个请求能够得到锁。
这种属于数据库 IO 操作,效率不高,而且频繁操作会增大数据库的开销,因此这种方式在高并发、高性能的场景中用的不多。
2.Redis实现
set nx ex
,这样可以设置一个简单的分布式锁,set nx
是如果key不存在,则创建key,否则失败;而ex
则给这个key设置了过期时间,避免锁无法释放。而且Redis的命令都是原子性的,这样就保持了获得锁和设置过期时间是一起操作的
对于自己设计的分布式锁,如果要保证判断该锁是否为自己的和释放锁这两个操作为原子操作,就需要用Lua脚本。释放锁的时候判断是否与获得锁的线程id一样
Redisson 是一个开源的 Java 语言 Redis 客户端,提供了很多开箱即用的功能,不仅仅包括多种分布式锁的实现。
而redisson不仅实现了上述功能,还更为强大,有以下功能:
- 自动续期
- 可重入
- 读写锁
- 公平锁
下面介绍redisson原理
key是锁的名称,value是个map,map的key是线程id,value是锁的重入次数,然后设置锁过期时间
- 如果加锁成功,锁的重入次数加一,这就实现了重入锁的功能;
- 加锁成功后,就会执行一个看门狗的机制。看门狗机制是为了防止业务还没执行完,但锁到期了的问题。看门狗就是一个定时任务,只要当前线程任务没有挂掉,且没有主动释放锁,就会隔一段时间给锁续期。默认情况下,每过 10 秒,看门狗就会执行续期操作,将锁的超时时间设置为 30 秒。
- 如果失败,返回锁的过期时间
- 加锁失败后,会进入一个循环中,此线程会被semaphore阻塞,当之前的线程释放锁后,会通过semaphore来唤醒此线程,然后获得锁后跳出此循环
- 释放锁时,如果该锁的线程id不是自己的,就无权释放;如果是,就将重入次数减一,如果减后的重入次数还是>0,就不能释放,更新锁到期时间,否则就释放锁,然后发送锁释放消息,唤醒被阻塞的线程
无论加锁成功或失败,都会有一个future结果器来接收加锁结果
3、Zookeeper实现
三、分布式事务
分布式事务,就是指不是在单个服务或单个数据库架构下,产生的事务,例如:
- 跨数据源的分布式事务
- 跨服务的分布式事务
- 综合情况
我们之前解决分布式事务问题是直接使用Seata框架的AT模式,但是解决分布式事务问题的方案远不止这一种。