一、为什么需要分布式锁
1.1 单体项目同步实现
在单进程(启动一个jvm)的系统中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量。而同步的本质是通过锁来实现的。为了实现多个线程在一个时刻同一个代码块只能有一个线程可执行,那么需要在某个地方做个标记,这个标记必须每个线程都能看到,当标记不存在时可以设置该标记,其余后续线程发现已经有标记了则等待拥有标记的线程结束同步代码块取消标记后再去尝试设置标记。这个标记可以理解为锁。
并发问题:
解决方案:
在java中可以通过synchronize和lock等手段来实现。
1.2 分布式同步问题及解决方案
1.2.1.问题
很多时候我们需要保证一个方法在同一时间内只能被同一个线程执行。在单机环境中,通过 Java 提供的并发 API 我们可以解决,但是在分布式环境下,就没有那么简单啦。
- 分布式与单机情况下最大的不同在于其不是多线程而是多进程。
- 多线程由于可以共享堆内存,因此可以简单的采取内存作为标记存储位置。而进程之间甚至可能都不在同一台物理机上,因此需要将标记存储在一个所有进程都能看到的地方。
那么原来的方案就不行了
1.2.2 解决方案
分布式锁作用就是在分布式系统,保证某个方法只能在同一时间某个进程的某个线程执行。
在整个分布式环境下都只有一份。
锁是多个进程共享的,多个进程保证某段代码只有一个线程执行使用。
分布式环境中定时任务
分布式缓存-集群环境下:同步更新缓存
2.分布式锁概述
2.1.什么是分布式锁
就是在在分布式环境下,保证某个公共资源只能在同一时间被多进程应用的某个进程的某一个线程访问时使用锁。
2.2.几个使用场景分析-一段代码同一时间只能被同一个线程执行
- 库存超卖 (库存被减到 负数),上面案例就是库存超卖
- 定时任务
- 分布式缓存中缓存同步
- 转账(多个进程修改同一个账户)
-
2.3.需要什么样的分布式锁
- 可以保证在分布式部署的应用集群中同一个方法在同一时间只能被一台机器上的一个线程执行。
- 这把锁要是一把可重入锁(避免死锁)
- 这把锁最好是一把阻塞锁(自旋)(根据业务需求考虑要不要这条)
- 这把锁最好是一把公平锁(根据业务需求考虑要不要这条)
- 有高可用的获取锁和释放锁功能
- 获取锁和释放锁的性能要好
2.4.常见的分布式锁解决方案
2.4.1.思路
- 当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。
- 与单机模式下的锁不仅需要保证进程可见,还需要考虑进程与锁之间的网络问题。
- 分布式锁还是可以将标记存在内存),只是该内存不是某个进程分配的内存而是公共内存如
Redis、Memcache。至于利用数据库、文件,zk等做锁与单机的实现是一样的,只要保证标记能互斥就行。
2.4.2.分布式锁三种方式
- 基于数据库操作
- 基于redis缓存和超时
- 基于zookeeper 临时顺序节点+watch
从理解的难易程度角度(从低到高)数据库 > 缓存 > Zookeeper
从实现的复杂性角度(从低到高)Zookeeper >= 缓存 >
数据库 从性能角度(从高到低)缓存 > Zookeeper >= 数据库
从可靠性角度(从高到低)Zookeeper > 缓存 > 数据库
基于数据库基本不用,zk或redis要根据项目情况来决定,如果你项目本来就用到zk,就使用zk,否则redis。