分布式场景下生成订单ID
产生问题
在分布式(集群)环境下,每台JVM不能实现同步,在分布式场景下使用时间戳生成订单号可能会重复
分布式情况下,怎么解决订单号生成不重复
1、使用分布式锁
2、提前生成好,订单号,存放在redis取。获取订单号,直接从redis中取。【互联网公司常规做法】
使用分布式锁生成订单号技术
1.使用数据库实现分布式锁
缺点:性能差、线程出现异常时,容易出现死锁
2.使用redis实现分布式锁
缺点:锁的失效时间难控制、容易产生死锁、非阻塞式、不可重入
3.使用zookeeper实现分布式锁
实现相对简单、可靠性强、使用临时节点,失效时间容易控制
什么是分布式锁
分布式锁一般用在分布式系统或者多个应用中,用来控制同一任务是否执行或者任务的执行顺序。在项目中,部署了多个tomcat应用,在执行定时任务时就会遇到同一任务可能执行多次的情况,我们可以借助分布式锁,保证在同一时间只有一个tomcat应用执行了定时任务。
使用Zookeeper实现分布式锁
Zookeeper实现分布式锁原理
使用zookeeper创建临时序列节点来实现分布式锁,适用于顺序执行的程序,大体思路就是创建临时序列节点,找出最小的序列节点,获取分布式锁,程序执行完成之后此序列节点消失,通过watch来监控节点的变化,从剩下的节点的找到最小的序列节点,获取分布式锁,执行相应处理,依次类推……
代码实现zookeeper分布式锁
1、Maven依赖pom.xml
<dependencies>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
</dependencies>
2、创建ZookeeperLock接口
//锁 定义分布式锁
public interface ZookeeperLock {
//获取锁
public void getLock();
//释放锁
public void unLock();
}
3、创建抽象类ZookeeperAbstractLock
//重构重复代码,将重复代码交给子类执行
public abstract class ZookeeperAbstractLock implements ZookeeperLock{
//zk连接地址
private static final String ADDRESS = "127.0.0.1:2181";
//创建zk连接
protected ZkClient zkClient = new ZkClient(ADDRESS);
//创建节点PATH
protected static final String PATH = "/lock";
protected CountDownLatch countDownLatch = null;
public void getLock() {
if(tryLock()){
System.out.println("###成功获取锁###");
}else{
waitLock();
getLock();
}
}
//尝试获取锁
abstract Boolean tryLock();
//等待
abstract void waitLock();
public void unLock() {
if(zkClient != null){
zkClient.close();
System.out.println("###成功释放锁资源###");
System.out.println();
}
}
}
4、创建实现类ZookeeperDistributeLock
public class ZookeeperDistributeLock extends ZookeeperAbstractLock {
// 创建临时节点成功,返回true,创建失败,返回false
Boolean tryLock() {
try {
zkClient.createEphemeral(PATH);
return true;
} catch (RuntimeException e) {
//e.printStackTrace();
return false;
}
}
void waitLock() {
IZkDataListener iZkDataListener = new IZkDataListener() {
// 当节点发生删除时
public void handleDataDeleted(String dataPath) throws Exception {
if (countDownLatch != null) {
// 唤醒await()
countDownLatch.countDown();
//System.out.println("节点发生删除");
}
}
// 当节点发生改变时
public void handleDataChange(String dataPath, Object data) throws Exception {
System.out.println("节点发生改变");
}
};
// 注册节点信息
zkClient.subscribeDataChanges(PATH, iZkDataListener);
if (zkClient.exists(PATH)) {
countDownLatch = new CountDownLatch(1);
try {
// 等待,直到监听到节点被删除
countDownLatch.await();
} catch (InterruptedException e) {
//e.printStackTrace();
}
}
//删除事件通知
zkClient.unsubscribeDataChanges(PATH, iZkDataListener);
}
}
测试zookeeper分布式锁
//生成订单号规则 时间戳+业务id
public class CreateID {
//全局订单id
public static int count = 0;
public String getNumber() {
try {
Thread.sleep(200);
} catch (Exception e) {
}
SimpleDateFormat simpt = new SimpleDateFormat("yyyyMMddHHmmss");
return simpt.format(new Date()) + ++count;
}
}
//使用多线程模拟多用户下单
public class OrderService implements Runnable{
private CreateID cid = new CreateID();
private ZookeeperLock lock = new ZookeeperDistributeLock();
public void run() {
try {
lock.getLock();
getNumber();
} catch (Exception e) {
//e.printStackTrace();
}finally {
lock.unLock();
}
}
public void getNumber() {
String number = cid.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();
}
}
}