分布式锁肯定是用在分布式环境下。在分布式环境下,使用分布式锁的目的也是保证同一时刻只有一个线程来修改共享变量,修改共享缓存……。
前景:
jdk提供的锁只能保证线程间的安全性,但分布式环境下,各节点之间的线程同步执行却得不到保障,分布式锁由此诞生。
实现方式有以下几种:
基于数据库实现分布式锁;
基于缓存(Redis等)实现分布式锁;
基于Zookeeper实现分布式锁;
生成订单号会出现多线程安全问题:
分布式锁解决办法
传统方式生成订单号ID
业务场景
在分布式情况,生成全局订单号ID
生成订单号方案
- 使用时间戳
- 使用UUID
- 推特 (Twitter) 的 Snowflake 算法——用于生成唯一 ID
//使用多线程模拟生成订单号
public class OrderService implements Runnable {
private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
public void run() {
getNumber();
}
public void getNumber() {
String number = orderNumGenerator.getNumber();
System.out.println(Thread.currentThread().getName() + ",生成订单ID:" + number);
}
public static void main(String[] args) {
System.out.println("####生成唯一订单号###");
for (int i = 0; i < 100; i++) {
new Thread(new OrderService()).start();
}
}
}
多线程生成订单号,线程安全问题解决
使用synchronized或者loca锁
Synchronized同步代码块方式
//使用多线程模拟生成订单号
public class OrderService implements Runnable {
private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
public void run() {
getNumber();
}
public void getNumber() {
synchronized (this) {
String number = orderNumGenerator.getNumber();
System.out.println(Thread.currentThread().getName() + ",生成订单ID:" + number);
}
}
public static void main(String[] args) {
System.out.println("####生成唯一订单号###");
OrderService orderService = new OrderService();
for (int i = 0; i < 100; i++) {
new Thread(orderService).start();
}
}
}
Lock锁方式
public class OrderService implements Runnable {
private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
// 使用lock锁
private java.util.concurrent.locks.Lock lock = new ReentrantLock();
public void run() {
getNumber();
}
public void getNumber() {
try {
// synchronized (this) {
lock.lock();
String number = orderNumGenerator.getNumber();
System.out.println(Thread.currentThread().getName() + ",生成订单ID:" + number);
// }
} catch (Exception e) {
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
System.out.println("####生成唯一订单号###");
OrderService orderService = new OrderService();
for (int i = 0; i < 100; i++) {
new Thread(orderService).start();
}
}
}
分布式场景下生成订单ID
生成的唯一id需要具备哪些条件
-
全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。
-
趋势递增:在MySQL InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。
-
单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。
-
信息安全:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要ID无规则、不规则。
UUID
关于分布式id,很多人会想到使用UUID,UUID在唯一性上确实可以达到这个目的,但它也存在很大的缺陷
优点:
-
性能非常高:本地生成,没有网络消耗。
缺点:
-
不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。
-
信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病xxxx者位置。
-
ID作为主键时在特定的环境会存在一些问题,比如做DB主键的场景下,UUID就非常不适用。(MySQL官方有明确的建议主键要尽量越短越好,36个字符长度的UUID不符合要求。对MySQL索引不利:如果作为数据库主键,在InnoDB引擎下,UUID的无序性可能会引起数据位置频繁变动,严重影响性能。)
https://github.com/downgoon/snowflake
业务场景
在分布式情况,生成全局订单号ID
产生问题
在分布式(集群)环境下,每台JVM不能实现同步,在分布式场景下使用时间戳生成订单号可能会重复
分布式情况下,怎么解决订单号生成不重复
- 使用分布式锁
- 提前生成好,订单号,存放在redis取。获取订单号,直接从redis中取。
使用分布式锁生成订单号技术
1.使用数据库实现分布式锁
缺点:性能差、线程出现异常时,容易出现死锁
2.使用redis实现分布式锁
缺点:锁的失效时间难控制、容易产生死锁、非阻塞式、不可重入
3.使用zookeeper实现分布式锁
实现相对简单、可靠性强、使用临时节点,失效时间容易控制
什么是分布式锁
分布式锁一般用在分布式系统或者多个应用中,用来控制同一任务是否执行或者任务的执行顺序。在项目中,部署了多个tomcat应用,在执行定时任务时就会遇到同一任务可能执行多次的情况,我们可以借助分布式锁,保证在同一时间只有一个tomcat应用执行了定时任务
使用Zookeeper实现分布式锁
Zookeeper实现分布式锁原理
使用zookeeper创建临时序列节点来实现分布式锁,适用于顺序执行的程序,大体思路就是创建临时序列节点,找出最小的序列节点,获取分布式锁,程序执行完成之后此序列节点消失,通过watch来监控节点的变化,从剩下的节点的找到最小的序列节点,获取分布式锁,执行相应处理,依次类推……
Maven依赖
<dependencies>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
</dependencies>
代码不贴了,有点多