zookeeper学习四:分布式锁

首先介绍以下锁,以下面减少库存案例讲解

普通情况

代码:

public class Stock  {
    //库存数量
    private static int num=1;

    public boolean reduceStock(){
        if(num>0){
            num--;
            return true;
        }else{
            return false;
        }
    }

}
public class StockMain implements  Runnable {
    public void run() {
            boolean b=new Stock().reduceStock();
            if(b){
                System.out.println(Thread.currentThread().getName()+"减少库存成功!");
            }else{
                System.out.println(Thread.currentThread().getName()+"减少库存失败!");
            }
    }

    public static  void main(String []args){
        new Thread(new StockMain(),"线程1").start();
        new Thread(new StockMain(),"线程2").start();
    }
}

结果:谁先抢到谁成功,也有可能前一个抢到没执行完毕,后一个线程也进入判断

,,

 

 

线程安全问题

public class Stock  {
    //库存数量
    private static int num=1;

    public boolean reduceStock() throws InterruptedException {
        if(num>0){
            Thread.sleep(1000);
            num--;
            return true;
        }else{
            return false;
        }
    }

}


public class StockMain implements  Runnable {
    public void run() {
        boolean b= false;
        try {
            b = new Stock().reduceStock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(b){
                System.out.println(Thread.currentThread().getName()+"减少库存成功!");
            }else{
                System.out.println(Thread.currentThread().getName()+"减少库存失败!");
            }
    }

    public static  void main(String []args){
        new Thread(new StockMain(),"线程1").start();
        new Thread(new StockMain(),"线程2").start();
    }
}

结果:先抢到线程的进入方法后,沉睡一秒,足够后面的线程进入判断条件,所以全部成功,产生线程安全问题

加入锁机制

 

public class Stock  {
    //库存数量
    private static int num=1;

    public boolean reduceStock() throws InterruptedException {
        if(num>0){
            Thread.sleep(1000);
            num--;
            return true;
        }else{
            return false;
        }
    }

}

public class StockMain implements  Runnable {
    private static Lock lock=new ReentrantLock();
    public void run() {
        boolean b= false;
        try {
            //上锁
            lock.lock();;
            b = new Stock().reduceStock();
            //解锁
            lock.unlock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(b){
                System.out.println(Thread.currentThread().getName()+"减少库存成功!");
            }else{
                System.out.println(Thread.currentThread().getName()+"减少库存失败!");
            }
    }

    public static  void main(String []args){
        new Thread(new StockMain(),"线程1").start();
        new Thread(new StockMain(),"线程2").start();
    }
}

结果:只有前一个线程执行完毕后,后一个才会执行

 

引出问题:分布式系统集群环境下,负载均衡之后,服务不可能只发给一台机器,锁机制已经不满足现状,分布式锁出现。

分布式锁

1.数据库实现分布式锁的思路分析(操作同一数据库)

数据库分布式锁实现

创建表

CREATE TABLE `lock_record` (
	`id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT COMMENT '主键',
	`lock_name` VARCHAR ( 50 ) DEFAULT NULL COMMENT '锁名称',
	PRIMARY KEY ( `id` ),
UNIQUE KEY `lock_name` ( `lock_name` ) 
) ENGINE = INNODB AUTO_INCREMENT = 38 DEFAULT CHARSET = utf8

 

定义锁

实现Lock接口,tryLock()尝试获取锁,从锁表中查询指定的锁记 录,如果查询到记录,说明 已经上锁,不能再上锁

 

上锁

lock方法获取锁之前先调用tryLock()方法尝试获取锁,如果未加锁则向锁表中插入一条锁记录来获取 锁,这里我们通过循环,如果上锁我们一致等待锁的释放

释放锁

即是将数据库中对应的锁表记录删除

注意在尝试获取锁的方法tryLock中,存在多个线程同时获取锁的情况,可以简单通过synchronized解决
import com.itheima.demo.bean.LockRecord;
import com.itheima.demo.mapper.LockRecordMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import tk.mybatis.mapper.entity.Example;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

@Component
public class DbLock implements Lock {

    private static final String LOCK_NAME = "db_lock_stock";


    @Autowired
    private LockRecordMapper lockRecordMapper;

    //上锁
    @Override
    public void lock() {
        while(true){
            if(tryLock()){
                //向锁表中插入一条记录
                LockRecord lockRecord = new LockRecord();
                lockRecord.setLockName(LOCK_NAME);
                lockRecordMapper.insertSelective(lockRecord);
                return;
            }else{
                System.out.println("等待锁.......");
            }
        }
    }

    //尝试获取锁
    @Override
    public boolean tryLock() {
        //查询lockRecord的记录
        Example example = new Example(LockRecord.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("lockName",LOCK_NAME);
        LockRecord lockRecord = lockRecordMapper.selectOneByExample(example);
        if(lockRecord==null){
            return true;
        }
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    //释放锁的操作
    @Override
    public void unlock() {
        Example example = new Example(LockRecord.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("lockName",LOCK_NAME);
        lockRecordMapper.deleteByExample(example);
    }

    @Override
    public Condition newCondition() {
        return null;
    }


    @Override
    public void lockInterruptibly() throws InterruptedException {

    }
}

2.redis实现分布式锁

 

redis分布式锁的实现基于setnxset if not exists),设置成功,返回1;设置失败,返回0,

释放锁的操作通过del指令来完成 如果设置锁后在执行中间过程时,程序抛出异常,导致del指令没有调用,锁永远无法释放,这样就会 陷入死锁。所以我们拿到锁之后会给锁加上一个过期时间,这样即使中间出现异常,过期时间到后会自动释放锁。同时在setnx expire 如果进程挂掉,expire不能执行也会死锁。所以要保证setnxexpire是一个原子性操作即可。

redis 2.8之后推出了setnxexpire的组合指令


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

@Component
public class RedisLock implements Lock {

    private static final String LOCK_NAME = "redis_stock_lock";

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public void lock() {
        while(true){
            //上锁 setnx
          //  Boolean isLock = redisTemplate.opsForValue().setIfAbsent("lockName", LOCK_NAME);
            Boolean isLock =  redisTemplate.opsForValue().setIfAbsent("lockName",LOCK_NAME,10,TimeUnit.SECONDS);
            //思考 锁过期怎么办?如何保证锁不过期 锁的自动续期 20 18 20 20
            if(isLock){
                return;
            }else{
                System.out.println("等待锁........");
            }
        }

    }


    @Override
    public void unlock() {
        // 删除指定的锁的key
        redisTemplate.delete("lockName");
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }



    @Override
    public Condition newCondition() {
        return null;
    }
}
redis 实现分布式锁存在的问题,为了解决 redis 单点问题,我们会部署 redis 集群,在 Sentinel 集群中, 主节点突然挂掉了。同时主节点中有把锁还没有来得及同步到从节点。这样就会导致系统中同样一把锁 被两个客户端同时持有,不安全性由此产生。 redis 官方为了解决这个问题,推出了 Redlock 算法解决这 个问题。但是带来的网络消耗较大。

 

分布式锁的redisson实现:

<dependency> 
    <groupId>org.redisson</groupId> 
    <artifactId>redisson</artifactId> 
    <version>3.6.5</version>
</dependency>

获取锁,释放锁

Config config = new Config();config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDataba se(0);
Redisson redisson = (Redisson) Redisson.create(config);
RLock mylock = redisson.getLock(key); 
//获取锁
mylock.lock();
//释放锁
mylock.unlock();

3.zookeeper实现分布式锁

原理:

zookeeper通过创建临时序列节点来实现分布式锁,适用于顺序执行的程序,大体思路就是创建 临时序列节点,找出最小的序列节点,获取分布式锁,程序执行完成之后此序列节点消失,通过watch 来监控节点的变化,从剩下的节点的找到最小的序列节点,获取分布式锁,执行相应处理,依次类推

原生实现
首先在 ZkLock 的构造方法中,连接 zk, 创建 lock 根节点
添加 watch 监听临时顺序节点的删除
获取锁操作
释放锁
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;


public class ZkLock implements Lock {


    //zk客户端
    private ZooKeeper zk;
    //zk是一个目录结构,locks
    private String root = "/locks";
    //锁的名称
    private String lockName;
    //当前线程创建的序列node
    private ThreadLocal<String> nodeId = new ThreadLocal<>();
    //用来同步等待zkclient链接到了服务端
    private CountDownLatch connectedSignal = new CountDownLatch(1);
    private final static int sessionTimeout = 3000;
    private final static byte[] data= new byte[0];


    public ZkLock(String config, String lockName) {
        this.lockName = lockName;

        try {
            zk = new ZooKeeper(config, sessionTimeout, new Watcher() {

                @Override
                public void process(WatchedEvent event) {
                    // 建立连接
                    if (event.getState() == Event.KeeperState.SyncConnected) {
                        connectedSignal.countDown();
                    }
                }

            });

            connectedSignal.await();
            Stat stat = zk.exists(root, false);
            if (null == stat) {
                // 创建根节点
                zk.create(root, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    class LockWatcher implements Watcher {
        private CountDownLatch latch = null;

        public LockWatcher(CountDownLatch latch) {
            this.latch = latch;
        }

        @Override
        public void process(WatchedEvent event) {

            if (event.getType() == Event.EventType.NodeDeleted)
                latch.countDown();
        }
    }

    @Override
    public void lock() {
        try {
            // 创建临时子节点
            String myNode = zk.create(root + "/" + lockName , data, ZooDefs.Ids.OPEN_ACL_UNSAFE,
                    CreateMode.EPHEMERAL_SEQUENTIAL);

            System.out.println(Thread.currentThread().getName()+myNode+ "created");

            // 取出所有子节点
            List<String> subNodes = zk.getChildren(root, false);
            TreeSet<String> sortedNodes = new TreeSet<>();
            for(String node :subNodes) {
                sortedNodes.add(root +"/" +node);
            }

            String smallNode = sortedNodes.first();


            if (myNode.equals( smallNode)) {
                // 如果是最小的节点,则表示取得锁
                System.out.println(Thread.currentThread().getName()+ myNode+"get lock");
                this.nodeId.set(myNode);
                return;
            }

            String preNode = sortedNodes.lower(myNode);

            CountDownLatch latch = new CountDownLatch(1);
            Stat stat = zk.exists(preNode, new LockWatcher(latch));// 同时注册监听。
            // 判断比自己小一个数的节点是否存在,如果不存在则无需等待锁,同时注册监听
            if (stat != null) {
                System.out.println(Thread.currentThread().getName()+myNode+
                        " waiting for " + root + "/" + preNode + " released lock");

                latch.await();// 等待,这里应该一直等待其他线程释放锁
                nodeId.set(myNode);
                latch = null;
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }


    @Override
    public void unlock() {
        try {
            System.out.println(Thread.currentThread().getName()+ "unlock ");
            if (null != nodeId) {
                zk.delete(nodeId.get(), -1);
            }
            nodeId.remove();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }



    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }



    @Override
    public Condition newCondition() {
        return null;
    }
}
分布式队列
队列特性: FIFO (先入先出), zookeeper 实现分布式队列的步骤:
  • 在队列节点下创建临时顺序节点 例如/queue_info/192.168.1.1-0000001
  • 调用getChildren()接口来获取/queue_info节点下所有子节点,获取队列中所有元素
  • 比较自己节点是否是序号最小的节点,如果不是,则等待其他节点出队列,在序号最小的节点注册 watcher
  • 获取watcher通知后,重复步骤

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值