分布式锁机制

分布式锁机制

在分布式项目中我们在处理线程安全问题时,通过加锁是行不通的,因为我们程序跑在多台机器上,每台机器拿到的是不同的锁对象,因此加锁无法避免线程安全问题,因此需要引入分布式锁的解决方案

引入分布式锁案例

// 库存扣减
package com.zhj.bean;

/**
 * @author zhj
 */
public class Stock {
    // 库存
    private static int num = 1;

    // 减少库存
    public boolean reduceStock() {
        if (num > 0) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num--;
            return true;
        } else {
            return false;
        }
    }
}

测试方法

package com.zhj.test;

import com.zhj.bean.Stock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author zhj
 */
public class stockMain {

    private static Lock lock = new ReentrantLock();

    static class StockTask implements Runnable {

        @Override
        public void run() {

            lock.lock();

            try {
                boolean b = new Stock().reduceStock();
                if (b) {
                    System.out.println(Thread.currentThread().getName() + " 减少库存成功");
                } else {
                    System.out.println(Thread.currentThread().getName() + " 减少库存失败");
                }
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            new Thread(new StockTask(),"线程" + i).start();
        }
    }
}

1. 使用数据库解决分布式锁问题

创建数据表

CREATE TABLE `lock_db`.`lock_record`  (
  `id` int NOT NULL,
  `lock_name` varchar(255) NOT NULL COMMENT '唯一锁名称',
  PRIMARY KEY (`id`),
  UNIQUE INDEX `unique_lock_name`(`lock_name`) COMMENT '唯一索引'
);

通过数据库添加一条唯一数据进行加锁操作,解锁是删除该条数据

数据库解决分布式锁

package com.zhj.lock;

import com.zhj.bean.LockRecord;
import com.zhj.mapper.LockRecordMapper;
import org.springframework.stereotype.Component;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @author zhj
 */
@Component
public class DBLock implements Lock {

    private static final String LOCK_NAME = "db_lock_stock";
    @Resource
    private LockRecordMapper lockRecordMapper;

    @Override
    public void lock() {
        while (true) {
            boolean flag = tryLock();
            if (flag) {
                try {
                    LockRecord lockRecord = new LockRecord();
                    lockRecord.setLockName(LOCK_NAME);
                    lockRecordMapper.insert(lockRecord);
                    return;
                } catch (Exception e) {
                    System.out.println("-------");
                }
            } else {
                System.out.println("等待中...");
            }
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    /**
     * 尝试获取锁,根据指定的名称在数据表发起一次查询
     * @return
     */
    @Override
    public boolean tryLock() {
        Example example = new Example(LockRecord.class);
        example.createCriteria().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.createCriteria().andEqualTo("lockName", LOCK_NAME);
        lockRecordMapper.deleteByExample(example);
    }

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

2. 使用Redis解决分布式锁问题

通过设置过期时间避免死锁出现

package com.zhj.lock;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @author zhj
 */
@Component
public class RedisLock implements Lock {

    private static final String LOCK_NAME = "redis_lock_stock";

    @Resource
    private RedisTemplate redisTemplate;

    @Override
    public void lock() {
        while (true) {

            // unlock 未执行会出现死锁,我们可以通过设置过期时间防止死锁
            // Boolean flag = redisTemplate.opsForValue().setIfAbsent("lockName", LOCK_NAME);
            Boolean flag = redisTemplate.opsForValue().setIfAbsent("lockName", LOCK_NAME,15,TimeUnit.SECONDS);
            if (flag) {
                return;
            } else {
                System.out.println("等待获取redis锁");
            }
        }
    }

    @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 void unlock() {
        redisTemplate.delete("lockName");
    }

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

使用官方推荐redisson实现

为解决单点问题,我们常使用redis集群,那么使用redis时可能出现主节点突然挂掉的事件,没来得及同步数据,这样就会两个客户端同时拥有,出现超卖现象。redis官方推迟Redlock算法解决分布式问题的redisson

1.导入对应依赖

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

2.使用

package com.zhj.test;

import com.zhj.bean.Stock;
import com.zhj.lock.RedisLock;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.config.Config;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.annotation.Resource;

/**
 * @author zhj
 */
public class stockMain {

    // private static Lock lock = new ReentrantLock();
    // 分布式锁
    @Resource
    private static RedisLock redisLock;
    // private static DBLock dbLock;
    private static RLock rLock;

    static {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        // dbLock = classPathXmlApplicationContext.getBean(DBLock.class);
        redisLock = classPathXmlApplicationContext.getBean(RedisLock.class);
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(1);
        Redisson redisson = (Redisson) Redisson.create(config);
        rLock = redisson.getLock("redis_lock_stock");
    }

    static class StockTask implements Runnable {

        @Override
        public void run() {

            // dbLock.lock();
            // redisLock.lock();
            rLock.lock();

            boolean b = new Stock().reduceStock();

            // dbLock.unlock();
            // redisLock.unlock();
            rLock.unlock();
            if (b) {
                System.out.println(Thread.currentThread().getName() + " 减少库存成功");
            } else {
                System.out.println(Thread.currentThread().getName() + " 减少库存失败");
            }
        }
    }

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            new Thread(new StockTask(),"线程" + i).start();
        }
    }
}

3. 使用zookeeper实现分布式锁

在这里插入图片描述

为每一个线程创建一个有序的临时节点,为了确保有序性,在创建完节点,会再回去全部节点,再重新进行一次排序,排序过程中,每个线程要判断自己剩下的临时节点的序号是否是最小。如果是最新,将会获得锁,执行相关操作,释放锁。如果不是最小的,会监听它前的一个节点,当前一个节点被删除时,它会获得锁,依次类推

package com.zhj.lock;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

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;

/**
 * @author zhj
 */
public class ZKLock implements Lock {
    // 客户端
    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 lockInterruptibly() throws InterruptedException {

    }

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

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

    @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 Condition newCondition() {
        return null;
    }
}

分布式队列

队列特性:先进先出

在这里插入图片描述

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值