一、 分布式锁简介
在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。在单机环境中,Java中其实提供了很多并发处理相关的API,但是这些API在分布式场景中就无能为力了。也就是说单纯的Java Api并不能提供分布式锁的能力。
所以针对分布式锁的实现目前有多种方案:
分布式锁一般有三种实现方式:
- 数据库锁;
- 基于Redis的分布式锁;
- 基于ZooKeeper的分布式锁。
分布式锁应该是怎么样的?
互斥性 可以保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。
- 这把锁要是一把可重入锁(避免死锁)
- 不会发生死锁:有一个客户端在持有锁的过程中崩溃而没有解锁,也能保证其他客户端能够加锁
- 这把锁最好是一把阻塞锁(根据业务需求考虑要不要这条)
- 有高可用的获取锁和释放锁功能
- 获取锁和释放锁的性能要好
二、CAP理论
1998年,加州大学的计算机科学家 Eric Brewer 提出,分布式系统有三个指标。
Consistency 一致性
Availability 可用性
Partition tolerance 分区容错性
它们的第一个字母分别是 C、A、P。
Eric Brewer 说,这三个指标不可能同时做到。这个结论就叫做 CAP 定理。
CAP理论告诉我们:一个分布式系统不可能同时满足一致性(C:Consistency)、可用性(A:Availability)和分区容错性(P:Partition tolerance)这三个基本需求,最多只能同时满足其中两项。
选 择 | 说 明 |
---|---|
CA | 放弃分区容错性,加强一致性和可用性,其实就是传统的单机数据库的选择 |
AP | 放弃一致性(这里说的一致性是强一致性),追求分区容错性和可用性,这是很多分布式系统设计时的选择,例如很多NoSQL系统就是如此 |
CP | 放弃可用性,追求一致性和分区容错性,基本不会选择,网络问题会直接让整个系统不可用 |
(一)分区容忍性
分布式系统应该一直持续运行,即使在不同节点间同步数据的时候,出现了大量的数据丢失或者数据同步延迟。 换句话说,分区容忍性是站在分布式系统的角度,对访问本系统的客户端的再一种承诺:我会一直运行,不管我的内部出现何种数据同步问题,强调的是不挂掉。
(二)一致性
对于任何从客户端发达到分布式系统的数据读取请求,要么读到最新的数据要么失败。换句话说,一致性是站在分布式系统的角度,对访问本系统的客户端的一种承诺:要么我给您返回一个错误,要么我给你返回绝对一致的最新数据,不难看出,其强调的是数据正确。
(三)可用性
对于任何求从客户端发达到分布式系统的数据读取请求,都一定会收到数据,不会收到错误,但不保证客户端收到的数据一定是最新的数据。换句话说,可用性是站在分布式系统的角度,对访问本系统的客户的另一种承诺:我一定会给您返回数据,不会给你返回错误,但不保证数据最新,强调的是不出错。
关系型数据库同时保证CA时,指的并非是分布式环境下,简单地说就是单机环境下。
如果在Master-Slave类复制式分布式环境下,默认保证的是CP。
(四)CAP理论验证
网络中有两个节点N1和N2,可以简单的理解N1和N2分别是两台计算机,他们之间网络可以连通,N1中有一个应用程序A,和一个数据库V0,N2也有一个应用程序B和一个数据库V0。现在,A和B是分布式系统的两个部分,V0是分布式系统的数据存储的两个子数据库。
在满足一致性的时候,N1和N2中的数据是一样的,V0=V0。
在满足可用性的时候,用户不管是请求N1或者N2,都会得到立即响应。
在满足分区容错性的情况下,N1和N2有任何一方宕机,或者网络不通的时候,都不会影响N1和N2彼此之间的正常运作。
- 必须保证分区容错性
对于一个分布式系统而言,P是前提,必须保证,因为只要有网络交互就一定会有延迟和数据丢失,这种状况我们必须接受,必须保证系统不能挂掉。
分布式系统正常运转的流程,用户向N1机器请求数据更新,程序A更新数据库V0为V1,分布式系统将数据进行同步操作M,将V1同步的N2中V0,使得N2中的数据V0也更新为V1,N2中的数据再响应N2的请求。
这里,可以定义N1和N2的数据库V之间的数据是否一样为一致性;
外部对N1和N2的请求响应为可用性;
N1和N2之间的网络环境为分区容错性。
这是正常运作的场景,也是理想的场景,然而现实是残酷的,当错误发生的时候,一致性和可用性还有分区容错性,是否能同时满足,还是说要进行取舍呢?
作为一个分布式系统,它和单机系统的最大区别,就在于网络,现在假设一种极端情况,N1和N2之间的网络断开了,我们要支持这种网络异常,相当于要满足分区容错性,能不能同时满足一致性和响应性呢?还是说要对他们进行取舍?
- 一致性和可用性二者择其一
假设在N1和N2之间网络断开的时候,有用户向N1发送数据更新请求,那N1中的数据V0将被更新为V1,由于网络是断开的,所以分布式系统同步操作M,所以N2中的数据依旧是V0;这个时候,有用户向N2发送数据读取请求,由于数据还没有进行同步,应用程序没办法立即给用户返回最新的数据V1,怎么办呢?
有二种选择,第一,牺牲数据一致性,保证可用性。响应旧的数据V0给用户;
第二,牺牲可用性,保证数据一致性。阻塞等待,直到网络连接恢复,数据更新操作M完成之后,再给用户响应最新的数据V1。
这个过程,证明了要满足分区容错性的分布式系统,只能在一致性和可用性两者中,选择其中一个。
三、BASE理论
BASE:全称:Basically Available(基本可用),Soft state(软状态),和 Eventually consistent(最终一致性)三个短语的缩写,来自 ebay 的架构师提出。
Base 理论是对 CAP 中一致性和可用性权衡的结果,其来源于对大型互联网分布式实践的总结,是基于 CAP 定理逐步演化而来的。其核心思想是:
既是无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。
1. 什么是基本可用呢?
1)假设系统,出现了不可预知的故障,但还是能用,相比较正常的系统而言。
2)响应时间上的损失:正常情况下的搜索引擎 0.5 秒即返回给用户结果,而基本可用的搜索引擎可以在 1 秒作用返回结果。
3)功能上的损失:在一个电商网站上,正常情况下,用户可以顺利完成每一笔订单,但是到了大促期间,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。
2.什么是软状态呢?
相对于原子性而言,要求多个节点的数据副本都是一致的,这是一种 “硬状态”。
软状态指的是:允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。
软状态,然后不可能一直是软状态,必须有个时间期限。在期限过后,应当保证所有副本保持数据一致性。从而达到数据的最终一致性。这个时间期限取决于网络延时,系统负载,数据复制方案设计等等因素。
系统能够保证在没有其他新的更新操作的情况下,数据最终一定能够达到一致的状态,因此所有客户端对系统的数据访问最终都能够获取到最新的值。
3. 最终一致性分为 5 种:
1)因果一致性(Causal consistency)
指的是:如果节点 A 在更新完某个数据后通知了节点 B,那么节点 B 之后对该数据的访问和修改都是基于 A 更新后的值。于此同时,和节点 A 无因果关系的节点 C 的数据访问则没有这样的限制。
2)读己之所写(Read your writes)
这种就很简单了,节点 A 更新一个数据后,它自身总是能访问到自身更新过的最新值,而不会看到旧值。其实也算一种因果一致性。
3)会话一致性(Session consistency)
会话一致性将对系统数据的访问过程框定在了一个会话当中:系统能保证在同一个有效的会话中实现 “读己之所写” 的一致性,也就是说,执行更新操作之后,客户端能够在同一个会话中始终读取到该数据项的最新值。
4)单调读一致性(Monotonic read consistency)
单调读一致性是指如果一个节点从系统中读取出一个数据项的某个值后,那么系统对于该节点后续的任何数据访问都不应该返回更旧的值。
5) 单调写一致性(Monotonic write consistency)
指一个系统要能够保证来自同一个节点的写操作被顺序的执行。
然而,在实际的实践中,这 5 种系统往往会结合使用,以构建一个具有最终一致性的分布式系统。实际上,不只是分布式系统使用最终一致性,关系型数据库在某个功能上,也是使用最终一致性的,比如备份,数据库的复制过程是需要时间的,这个复制过程中,业务读取到的值就是旧的。当然,最终还是达成了数据一致性。
总的来说,BASE 理论面向的是大型高可用可扩展的分布式系统,和传统事务的 ACID 是相反的,它完全不同于 ACID 的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间是不一致的。
四、服务降级
服务降级,当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。
(一)服务降级方式:
1.服务接口拒绝服务:
无用户特定信息,页面能访问,但是添加删除提示服务器繁忙。页面内容也可在Varnish或CDN内获取。
2.页面拒绝服务:
页面提示由于服务繁忙此服务暂停。跳转到varnish或nginx的一个静态页面。
3.延迟持久化:
页面访问照常,但是涉及记录变更,会提示稍晚能看到结果,将数据记录到异步队列或log,服务恢复后执行。
4.随机拒绝服务:
服务接口随机拒绝服务,让用户重试,目前较少有人采用。因为用户体验不佳。
(二)持久层降级方式
数据操作动作 | 通过Cache工作 | 通过异步数据队列 |
---|---|---|
增insert | 禁止 | 允许但不能有重复问题 |
删delete | 禁止 | 允许但不能有复合操作 |
改update | 禁止 | 允许只留最后结果 |
查query | 允许,若未命中问询mysql或其他持久层 | 走cache |
(三)降级方式
1. 直觉管理方式:运维人员可以指定哪些模块降级。
当服务器检测到压力增大,服务器监测自动发送通知给运维人员
运维人员根据自己或相关人员判断后通过配置平台设置当前运行等级来降级
降级首先可以对非核心业务进行接口降级。
如果效果不显著,开始对一些页面进行降级,以此保证核心功能的正常运行。
2.分级管理方式:运维人员无需关心业务细节,直接按级别降低即可。
当服务器检测到压力增大,服务检测自动发送通知给运维人员。
运维人员根据情况选择运行等级1~10.
各个应用根据自己的级别自动判断是否工作,如何拒绝
3. 服务降级埋点的地方:
消息中间件:所有API调用可以使用消息中间件进行控制
前端页面:指定网址不可访问(NGINX+LUA)
底层数据驱动:拒绝所有增删改动作,只允许查询
五、乐观锁
乐观锁假设认为数据一般情况下不会造成冲突,只有在进行数据的提交更新时,才会检测数据的冲突情况,如果发现冲突了,则返回错误信息实现方式:时间戳(timestamp)记录机制实现:给数据库表增加一个时间戳字段类型的字段,当读取数据时,将timestamp字段的值一同读出,数据每更新一次,timestamp也同步更新。当对数据做提交更新操作时,检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,若相等,则更新,否则认为是失效数据。若出现更新冲突,则需要上层逻辑修改,启动重试机制同样也可以使用version的方式。
性能对比:
(1) 悲观锁实现方式是独占数据,其它线程需要等待,不会出现修改的冲突,能够保证数据的一致性,但是依赖数据库的实现,且在线程较多时出现等待造成效率降低的问题。一般情况下,对于数据很敏感且读取频率较低的场景,可以采用悲观锁的方式
(2) 乐观锁可以多线程同时读取数据,若出现冲突,也可以依赖上层逻辑修改,能够保证高并发下的读取,适用于读取频率很高而修改频率较少的场景
(3) 由于库存回写数据属于敏感数据且读取频率适中,所以建议使用悲观锁优化