前言

分布式锁是由于集群的存在而产生的问题,常见的有三种解决方案

  • 基于数据库实现分布式锁
  • 基于redis
  • 基于zookeeper
    基于数据库的性能太差了,这里就简单实现下后两种

redis 实现分布式锁

redis 实现分布式锁的原理是setnx,设置值如果成功就返回1,否则就是0

具体实现:
先定义个接口:

public interface RLock {
    public String lock(Long acquireTimeout,Long timeout);
    public void unLock(String value);
}

实现类:

package com.redisLock.lock;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.UUID;

public class LockRedis implements RLock{
    // redis 线程池
    private JedisPool jedisPool;

    public LockRedis(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }
    // 相同的key 名称,对应的value 定义为锁的 Id

    // redis 实现分布式锁。有两个超时时间问题,
    /**
     * 两个时间:
     * 1. 获取锁的超时时间,规定时间内没有得到锁,直接放弃
     * 2 , 获取锁之后,key 有对应的有效期,会失效
     */
    private String redisLockKey = "redis_lock";

    /**
     *
     * @param acquireTimeout 获取锁的超时时间
     * @param timeout 锁失效的时间
     */
    public String lock(Long acquireTimeout,Long timeout) {
        // 1 建立redis 连接
        Jedis con = jedisPool.getResource();
        try{

            // 2 定义Key 的value ,我们生成订单号,释放锁的时候起作用
            String value = UUID.randomUUID().toString();
            // 3 定义锁失效时间,redis 认得式 秒,一般传过来的式毫秒
            int expireLock = (int) (timeout/1000);
            // 4 定义获取锁超时时间
            // 5 使用循环机制,不断的尝试获取锁
            Long endTime = System.currentTimeMillis()+acquireTimeout;
            while (System.currentTimeMillis()<endTime){
                // 获取锁,
                // 6 使用setNx 命令插入对应的redisLockKey,如果返回1 ,就得到了
                if(con.setnx(redisLockKey,value)==1){
                    con.expire(redisLockKey,expireLock);
                    return value;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (con!=null)
                con.close();
        }
        // 返回为空就是一直没有获取到,放弃了
        return null;
    }


    // 释放锁
    public void unLock(String value) {
        Jedis con = jedisPool.getResource();
        try{
            if (con.get(redisLockKey).equals(value)){
                con.del(redisLockKey);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (con!=null)
                con.close();
        }
    }

}

工具类模拟订单生成

package com.IdUtil;

import java.text.SimpleDateFormat;
import java.util.Date;

public class IdUtil {
    public static int count=0;
    public static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
    public String getOrderId(){
        return simpleDateFormat.format(new Date())+"-"+ count++;
    }

}

业务类

package com.redisLock.service;

import com.IdUtil.IdUtil;
import com.redisLock.lock.LockRedis;
import com.redisLock.lock.RLock;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class OrderService implements Runnable{
    private static JedisPool pool = null;
    static {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(200);
        config.setMaxIdle(2);
        config.setMaxWaitMillis(100*1000);
        pool = new JedisPool(config,"127.0.0.1",6379,3000);
    }
    IdUtil idUtil = new IdUtil();
    private RLock lockRedis = new LockRedis(pool);
    public void secKill(){
        String value = lockRedis.lock(30000L,30000L);
        if(value==null){
            System.out.println(Thread.currentThread().getName()+",获取锁失败");
            return;
        }
        // 获取订单号
        try {
            Thread.sleep(300);
            System.out.println(Thread.currentThread().getName()+"生产订单号"+idUtil.getOrderId());
        } catch (Exception e) {

        }
        // 释放锁
        lockRedis.unLock(value);
    }


    @Override
    public void run() {
        secKill();
    }
}

模拟jvm 测试

package com.redisLock;


import com.redisLock.service.OrderService;

public class Test {
    public static void main(String[] args) {

        for (int i = 0; i <100 ; i++) {
            OrderService thread = new OrderService();
            new Thread(thread).start();
        }
    }
}

zookeeper 实现分布式锁

它的原理是 临时节点,zookeeper 的节点是唯一的,创建成功就代表得到了锁,断开连接,临时节点删除,就释放了锁,同时,基于每个节点发生变化具有监听机制,会通知等待的线程。

先定义接口

package com.zk.lock;

/**
 * Created by Administrator on 2019/11/8.
 */
public interface ZLock {
    public void lock();
    public void unLock();
}

定义模板抽象类

package com.zk.lock;

import com.zk.lock.ZLock;
import org.I0Itec.zkclient.ZkClient;

/**
 * Created by Administrator on 2019/11/8.
 */
public abstract class ZAbstractLock implements ZLock {
    ZkClient zkClient = null;
    String path = "";

    public ZAbstractLock(ZkClient zkClient,String path) {
        this.zkClient = zkClient;
        this.path = path;
    }

    @Override
    public void lock() {
        if(!tryLock()){
            waitLock(); // util others unlock
            lock();
        }
    }
    @Override
    public void unLock() {
        if (zkClient!=null) zkClient.close();
    }
    protected abstract void waitLock();

    protected abstract boolean tryLock();
}

定义具体实现类

package com.zk.lock;

import com.zk.lock.ZAbstractLock;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;

import java.util.concurrent.CountDownLatch;

/**
 * Created by Administrator on 2019/11/8.
 */
public class ZLockClient extends ZAbstractLock {
    private  CountDownLatch countDownLatch  = null;

    public ZLockClient(ZkClient zkClient, String path) {
        super(zkClient, path);
    }

    @Override
    protected void waitLock() {
        IZkDataListener listener = new IZkDataListener() {
            @Override
            public void handleDataChange(String s, Object o) throws Exception {

            }
            @Override
            public void handleDataDeleted(String s) throws Exception {
                countDownLatch.countDown();
            }
        };
        zkClient.subscribeDataChanges(path,listener);
        if(zkClient.exists(path)){
            countDownLatch = new CountDownLatch(1);
            try {
                countDownLatch.await();// 如果不为零,会阻滞
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        zkClient.unsubscribeDataChanges(path,listener);
    }

    @Override
    protected boolean tryLock() {
        try {
            zkClient.createEphemeral(path);
            return true;
        }catch (Exception e){
            return false;
        }
    }
}

id 工具类还用上面的

package com.IdUtil;

import java.text.SimpleDateFormat;
import java.util.Date;

public class IdUtil {
    public static int count=0;
    public static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
    public String getOrderId(){
        return simpleDateFormat.format(new Date())+"-"+ count++;
    }

}

服务类

package com.zk.service;

import com.IdUtil.IdUtil;

import com.zk.lock.ZLock;
import com.zk.lock.ZLockClient;
import org.I0Itec.zkclient.ZkClient;

/**
 * Created by Administrator on 2019/11/8.
 */

public class OrderService implements Runnable{
    IdUtil idUtil = new IdUtil();
    ZkClient zkClient = new ZkClient("127.0.0.1");
    ZLock zkeeperLock = new ZLockClient(zkClient,"/zlock");
    // 生成订单
    public  void createOrder(){
        zkeeperLock.lock();
        // 获取订单号
        String orderId = idUtil.getOrderId();
        try {
            Thread.sleep(100);
            System.out.println(Thread.currentThread().getName()+"生成订单编号:"+orderId);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
          zkeeperLock.unLock();
        }
    }

    @Override
    public void run() {
        createOrder();
    }
}

测试

package com.zk;

import com.zk.service.OrderService;

/**
 * Created by Administrator on 2019/11/8.
 */
public class Test {
    public static void main(String[] args) {

        for (int i = 0; i < 100; i++) {
            OrderService service = new OrderService();
            new Thread(service,"thread"+i).start();
        }
    }
}

以上都输出:

Thread-99生产订单号2019-11-09-17-55-28-0
 Thread-63生产订单号2019-11-09-17-55-29-1
 Thread-5生产订单号2019-11-09-17-55-29-2
 Thread-70生产订单号2019-11-09-17-55-29-3
 Thread-66生产订单号2019-11-09-17-55-30-4
 Thread-15生产订单号2019-11-09-17-55-30-5
 Thread-41生产订单号2019-11-09-17-55-30-6
 Thread-53生产订单号2019-11-09-17-55-30-7
 Thread-30生产订单号2019-11-09-17-55-31-8
 Thread-85生产订单号2019-11-09-17-55-31-9
 Thread-68生产订单号2019-11-09-17-55-31-10
 Thread-1生产订单号2019-11-09-17-55-32-11
 Thread-38生产订单号2019-11-09-17-55-32-12
 Thread-19生产订单号2019-11-09-17-55-32-13
 Thread-57生产订单号2019-11-09-17-55-33-14
 。。。。。。。。。。。。。。。。。。。。

redis 和zookeeper 实现的不同

  • 使用redis实现分布式锁

redis中的set nx 命令,当key不存在时,才能在redis中将key添加成功,利用该属性可以实现分布式锁,并且redis对于key有失效时间,可以控制当某个客户端加锁成功之后挂掉,导致阻塞的问题。

  • 使用Zookeeper实现分布式锁

多个客户端在Zookeeper上创建一个相同的临时节点,因为临时节点只能允许一个客户端创建成功,那么只要任意一个客户端创建节点成功,谁就成功的获取到锁,当释放锁后,其他客户端同样道理在Zookeeper节点。