分布式锁:
1,基于 Redis 的分布式锁
利用 SETNX 和 SETEX
基本命令主要有:
SETNX(SET If Not Exists):当且仅当 Key 不存在时,则可以设置,否则不做任何动作。
SETEX:可以设置超时时间
其原理为:通过 SETNX 设置 Key-Value 来获得锁,随即进入死循环,每次循环判断,如果存在 Key ,表示锁被别的线程使用,则继续循环,如果不存在 Key,则表示别的线程未在使用锁,则跳出循环,并开始执行任务,任务结束后删除 Key 以释放锁。
这种方式可能会导致死锁,为了避免这种情况,需要设置超时时间。
通过这种方式创建的分布式锁存在以下问题:
高并发的情况下,如果两个线程同时进入循环,可能导致加锁失败。
SETNX 是一个耗时操作,因为它需要判断 Key 是否存在,因为会存在性能问题。
**2,Redis 官方推荐的 Redlock **
添加依赖以下依赖后,将业务代码写入到获取锁的回调方法中
org.redisson
redisson
3,基于数据库的分布式锁
它的基本原理和 Redis 的 SETNX 类似,其实就是创建一个分布式锁表,加锁后,我们就在表增加一条记录,释放锁即把该数据删掉
它同样存在一些问题:
(1)没有失效时间,容易导致死锁;
乐观锁
基本原理为:乐观锁一般通过 version 来实现,也就是在数据库表创建一个 version 字段,每次更新成功,则 version+1,读取数据时,我们将 version 字段一并读出,每次更新时将会对版本号进行比较,如果一致则
悲观锁(排他锁)
4,基于 Zookeeper 的分布式锁
建立一个节点,假如名为 lock 。节点类型为持久节点(Persistent)
每当进程需要访问共享资源时,会调用分布式锁的 lock() 或 tryLock() 方法获得锁,这个时候会在第一步创建的 lock 节点下建立相应的顺序子节点,节点类型为临时顺序节点(EPHEMERAL_SEQUENTIAL),通过组成特定的名字 name+lock+顺序号。
在建立子节点后,watch lock节点, 在watch方法中对下面的所有以 name 开头的子节点进行排序,判断刚刚建立的子节点顺序号是否是最小的节点,假如是最小节点,则获得该锁对资源进行访问。否则继续等待,使用完锁的线程会删除自己所建立的临时节点,这时,触发其他线程的watch方法,判断是否获取到了锁。
5,基于 Redis Zset的乐观锁
向Redis的Zset中,插入一条记录,value为线程id,score为当前毫秒数。while循环获取score,如果当前线程对应的score为最小,则获得锁,业务逻辑执行完成后,删除对应的value值
分布式事务
数据库事务中的四大特性 ACID
A:原子性(Atomicity),一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。
事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
就像你买东西要么交钱收货一起都执行,要么发不出货,就退钱。
**C:一致性(Consistency),**事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。一致性与原子性是密切相关的。在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以操持完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。
**I:隔离性(Isolation),**指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。
由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
打个比方,你买东西这个事情,是不影响其他人的。
D:持久性(Durability),指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。
即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
打个比方,你买东西的时候需要记录在账本上,即使老板忘记了那也有据可查。
原子性与一致性的区别
一致性是基础,也是最终目的,其他三个特性(原子性、隔离性和持久性)都是为了保证一致性的
在比较简单的场景(没有高并发)下,可能会发生一些数据库崩溃等情况,这个时候,依赖于对日志的 REDO/UNDO 操作就可以保证一致性
而在比较复杂的场景(有高并发)下,可能会有很多事务并行的执行,这个时候,就很可能导致最终的结果无法保证一致性,比如(内容来自知乎链接):
即,这个时候,原子性不能保证一致性。因为从单个事务的角度看,不管是事务 1 还是事务 2,它们都保证的原子性(单个事务内的所有操作全部成功了),但最终,它们并没有保证数据库的一致性(因为从逻辑上说,账户 A 应该增加了 200 元,而不是 100 元)
所以,为了保证并发情况下的一致性,又引入了隔离性的概念
隔离性
即事务之间感知不到彼此的存在,就好像只存在本身一个事务一样,在看隔离性之前,需要了解并发事务带来的异常。
并发事务导致的问题
(1)第一类丢失更新(Lost Update):撤销一个事务时,把其他事务已提交的更新数据覆盖。
(2)脏读(Dirty Reads):脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
(3)幻读也叫虚读(Phantom Reads):一个事务执行两次查询,第二次结果集包含第一次中没有或某些行已经被删除的数据,造成两次结果不一致,只是另一个事务在这两次查询中间插入或删除了数据造成的。幻读是事务非独立执行时发生的一种现象。
(4)不可重复读(Non-Repeatable Reads):一个事务两次读取同一行的数据,结果得到不同状态的结果,中间正好另一个事务更新了该数据,两次结果相异,不可被信任。
(5)第二类丢失更新(Lost Update):是不可重复读的特殊情况。如果两个事物都读取同一行,然后两个都进行写操作,并提交,第一个事物所做的改变就会丢失。
事务隔离级别
(1)未提交读(Read Uncommitted):允许脏读,其他事务只要修改了数据,即使未提交,本事务也能看到修改后的数据值。也就是可能读取到其他会话中未提交事务修改的数据。并发事务导致的问题一个也避免不了
(2)提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)。可避免脏读的发生。
(3)可重复读(Repeated Read):可重复读。无论其他事务是否修改并提交了数据,在这个事务中看到的数据值始终不受其他事务影响。可避免脏读、不可重复读的发生。MySQL数据库(InnoDB引擎)默认使用可重复读( Repeatable read)
(4)串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。可避免脏读、不可重复读、幻读的发生。
SpringCloud认识五之分布式锁和分布式事务
收藏 | 第一次有人把“分布式事务”讲的这么简单明了
分布式事务中常见的三种解决方案