一、常见方式
1.1 synchronized 加锁【只能解决单个jvm线程安全问题】
1.2 Set化后的MQ
我们可以按用户ID做Set(用户ID % Set数)进而分成多个组,为不同的组创建不同的MQ队列,这样一个用户同一时间只在一个队列中,一个队列的处理是串行化的,实现了锁的功能,同时又有多个Set来完成并行化,在性能上会好于分布式锁,并且代码上没有太多改动
1.3 乐观锁
传入版本号,每次更新时,版本号加1 ,更新的条件是版本号要等于传入的版本号
1.4 关系型数据库
1.5 redis锁
- 原理
使用redis实现分布式锁时,加锁操作必须是原子操作,否则多客户端并发操作时会导致各种各样的问题。由于我们实现的是可重入锁,加锁过程中需要判断客户端ID的正确与否。而redis原生的简单接口没法保证一系列逻辑的原子性执行,因此采用了lua脚本来实现加锁操作。lua脚本可以让redis在执行时将一连串的操作以原子化的方式执行。 - 应用
基于注解的redis锁
1.6 Zookeeper 锁
- 原理
使用zookeeper创建临时序列节点来实现分布式锁,适用于顺序执行的程序,大体思路就是创建临时序列节点,找出最小的序列节点,获取分布式锁,程序执行完成之后此序列节点消失,通过watch来监控节点的变化,从剩下的节点的找到最小的序列节点,获取分布式锁,执行相应处理,依次类推…… - 步骤
1)多个Jvm同时在Zookeeper上创建同一个相同的节点( /Lock)
2)zk节点唯一的! 不能重复!节点类型为临时节点, jvm1创建成功时候,jvm2和jvm3创建节点时候会报错,该节点已经存在。这时候 jvm2和jvm3进行等待。
3)
- PS
如果程序一直不处理完,可能导致死锁(其他的一直等待)。设置有效期~ 直接close()掉 其实连接也是有有效期设置的
二、细节
2.1 锁释放与超时
我们必须要加上超时时间,如(tryLock(<等待锁的时长>,<锁占用的最大时长>)),超时时间设置过长会导致服务异常后无法及时获取新的锁,过短又有可能在业务没有执行完锁提前释放了。更优雅但偏复杂的方法是使用心跳超时设置,即与占有锁的服务保持心跳,在心跳超时后再释放锁
2.2 性能及高可用
出于性能考虑,一般分布式锁都是非公平锁,如果要保证加锁顺序二选用公平锁时要注意对性能的影响。加解锁操作本身要保证性能及可用性,避免单点,锁信息要持久化,慎用自旋避免浪费CPU
2.3 数据一致性
数据一致性 分布式锁要合理设置锁标记以区分是哪个实例、哪个线程的操作,可重入锁要做好计数及相应的unlock次数,同时必须保证数据的一致,这要求我们只能选择CP特性(见下文)的服务作为分布式锁的中间件
三、要点
3.1 如何模拟分布式锁场景
package com.toov5.Lock;
public class OrderService implements Runnable {
private OrderNumGenerator orderNumGenerator = new OrderNumGenerator(); // 定义成全局的
private ExtLock lock = new ZookeeperDistrbuteLock();
public void run() {
getNumber();
}
public synchronized void getNumber() { // 加锁 保证线程安全问题 让一个线程操作
try {
lock.getLock();
String number = orderNumGenerator.getNumber();
System.out.println(Thread.currentThread().getName() + ",number" + number);
} catch (Exception e) {
} finally {
lock.unLock();
}
}
public static void main(String[] args) {
// OrderService orderService = new OrderService();
for (int i = 0; i < 100; i++) { // 开启100个线程
//模拟分布式锁的场景
new Thread(new OrderService()).start();
}
}
}
3.2 可靠性分布式系统具备特性
- 互斥性
作为锁,需要保证任何时刻只能有一个客户端(用户)持有锁 - 可重入
同一个客户端在获得锁后,可以再次进行加锁 - 高可用
获取锁和释放锁的效率较高,不会出现单点故障 - 自动重试机制
当客户端加锁失败时,能够提供一种机制让客户端自动重试
3.3 redis 基本操作
添加key:
SET foo bar EX 20 NX
GET foo
四、常见问题
4.1 获取redis 锁失败