场景描述:
小型电商网站,下单,生产有一定业务含义的唯一订单编号。
思路分析:
如果单台服务器已无法撑起并发量,怎么办?集群?

分布式锁的用途:

在分布式环境下协同共享资源的使用。
分布式锁的特点:
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锁实现(临时顺序结点)
* @author skymr
*
*/
public class ZookeeperLock1 implements Lock, Watcher{
public ZookeeperLock1(String url, int sessionTimeOut, String path){
this.parrentPath = path;
try {
//url是zookepper服务器的地址
zk = new ZooKeeper(url, sessionTimeOut, this);
latch.await();
} catch (Exception e) {
e.printStackTrace();
}
}
//zk客户端
private ZooKeeper zk;
//结点路径
private String parrentPath;
//用于初始化zk的,zk连接是异步的,但连接成功后才能进行调用
private CountDownLatch latch = new CountDownLatch(1);
private static ThreadLocal<String> currentNodePath = new ThreadLocal<String>();
public void lock() {
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() throws InterruptedException {
}
public boolean tryLock() {
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<String> 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, new Watcher() {
public void process(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) throws InterruptedException {
return false;
}
public void unlock() {
try {
//释放锁,删除节点
String mypath = currentNodePath.get();
if(mypath != null){
System.out.println(Thread.currentThread().getName() +" 释放锁");
zk.delete(mypath, -1);
currentNodePath.remove();
}
} catch (Exception e) {
}
}
public Condition newCondition() {
return null;
}
public void process(WatchedEvent event) {
System.out.println(event);
if(event.getType() == EventType.None){
//当连接上了服务器后,初始化完成
latch.countDown();
}
}
}
来源:https://www.cnblogs.com/yangyouwang/p/10057063.html