场景描述:
小型电商网站,下单,生产有一定业务含义的唯一订单编号。
思路分析:
如果单台服务器已无法撑起并发量,怎么办?集群?
分布式锁的用途:
在分布式环境下协同共享资源的使用。
分布式锁的特点:
1.排他性: 只有一个线程能获取到。
2.阻塞性: 其他未抢到的线程阻塞,直到锁释放出来,在抢。
3.可重入性:线程获得锁后,后续是否可重复获取该锁。
我们掌握的计算机技术中,有哪些能提供排他性?
1. 文件系统
2. 数据库: 主键 唯一约束 for update
3. 缓存redis: setnx
4. zookeeper: 类似文件系统
常用的分布式锁实现技术
1. 基于数据库实现分布式锁
性能较差。容易出现单点故障。
锁没有失效时间,容易死锁。
2. 基于缓存实现分布式锁
实现复杂
存在死锁
3. 基于zookeeper实现分布式锁
实现相对简单
可靠性高
性能较好
基于zookeeper实现分布式锁
方式一:
1.去获取锁创建节点
2.获取锁成功,执行业务并且释放锁,等待唤醒。
3. 获取锁失败,注册节点的watcher,阻塞等待,直到上一个成功获取锁释放到锁,才会取消watcher,尝试抢锁。
特性:
同父的子节点不可重名
假如部署在规模较大集群会发生'惊群效应'
1.巨大的服务器性能损耗
2.网络冲击
3.可能造成宕机
方式二: (解决方案)
1.创建一个锁目录lock
2.希望获得锁的线程A就在lock目录下,创建临时顺序节点
3.获取锁目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁
4.线程B获取所有节点,判断自己不是最小节点,设置监听(watcher)比自己次小的节点(只关注比自己次小的节点是为了防止发生“羊群效应”)
5.线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是最小的节点,获得锁。
特性:
临时顺序节点
代码实现
/*** zookeeper锁实现(临时顺序结点)
*@authorskymr
**/
public class ZookeeperLock1 implementsLock, Watcher{public ZookeeperLock1(String url, intsessionTimeOut, String path){this.parrentPath =path;try{//url是zookepper服务器的地址
zk = new ZooKeeper(url, sessionTimeOut, this);
latch.await();
}catch(Exception e) {
e.printStackTrace();
}
}//zk客户端
privateZooKeeper zk;//结点路径
privateString parrentPath;//用于初始化zk的,zk连接是异步的,但连接成功后才能进行调用
private CountDownLatch latch = new CountDownLatch(1);private static ThreadLocal currentNodePath = new ThreadLocal();public voidlock() {if(!tryLock()){
String mypath=currentNodePath.get();//如果尝试加锁失败,则进入等待
synchronized(mypath){
System.out.println(Thread.currentThread().getName()+" lock失败,进入等待");try{
mypath.wait();
}catch(Exception e) {
}
System.out.println(Thread.currentThread().getName()+" lock等待完成");
}//等待别人释放锁后,自己再去加锁
lock();
}else{
System.out.println(Thread.currentThread().getName()+" lock成功");
}
}public void lockInterruptibly() throwsInterruptedException {
}public booleantryLock() {try{//加锁代码是创建一个节点
String mypath =currentNodePath.get();if(mypath == null){
mypath= zk.create(parrentPath + "/", "111".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
currentNodePath.set(mypath);
}final String currentPath =mypath;
List allNodes = zk.getChildren(parrentPath, false);
Collections.sort(allNodes);//不抛异常就表示创建成功啦
String nodeName = mypath.substring((parrentPath + "/").length());if(allNodes.get(0).equals(nodeName)){//当前结点是最小的节点,获取锁成功
return true;
}else{//监听最小的结点
String targetNodeName = parrentPath + "/" + allNodes.get(0);
System.out.println(Thread.currentThread().getName()+" 需要等待节点删除" +targetNodeName);
zk.exists(targetNodeName,newWatcher() {public voidprocess(WatchedEvent event) {if(event.getType() ==EventType.NodeDeleted){synchronized(currentPath){
currentPath.notify();
}
System.out.println(Thread.currentThread().getName()+" 通知Lock等待中的线程重试加锁");
}
}
});
}return false;
}catch(Exception e) {return false;
}
}public boolean tryLock(long time, TimeUnit unit) throwsInterruptedException {return false;
}public voidunlock() {try{//释放锁,删除节点
String mypath =currentNodePath.get();if(mypath != null){
System.out.println(Thread.currentThread().getName()+" 释放锁");
zk.delete(mypath,-1);
currentNodePath.remove();
}
}catch(Exception e) {
}
}publicCondition newCondition() {return null;
}public voidprocess(WatchedEvent event) {
System.out.println(event);if(event.getType() ==EventType.None){//当连接上了服务器后,初始化完成
latch.countDown();
}
}
}