Redis分布式锁的实现(Jedis)

       在分布式系统的开发中,分布式锁的开发,一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁

       本文讲解如何通过Redis来实现分布式锁的开发。在数据库中,我们可以利用数据库自身的锁机制;在ZooKeeper中,我们可以利用ZooKeeper节点的有序性/唯一性来实现;那么在Redis中,分布式锁该如何实现呢?

       在Redis中,为我们提供了一个setnx指令。我们可以通过setnx指令,来完成分布式锁的开发。我们可以先来简单的了解一下setnx指令的用途,如下图:

在实现分布式锁的实现之前,我们先来了解一下锁的特性:

        ①能够获得锁,也能够释放锁;

        ②锁必须设置有超时时间,避免发生死锁现象

        ③释放锁的时候,需要判断当前释放锁的线程是不是已经获取到的锁的线程

Redis分布式锁的实现

  1.获取一个Redis连接

/**
 * 获取Redis连接
 */
public class JedisConnectionUtils {

    private static JedisPool pool = null;

    /**
     * 静态块初始化
     */
    static {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        //设置最大连接数(也可以说是一种限流机制)
        jedisPoolConfig.setMaxTotal(10);
        pool = new JedisPool(jedisPoolConfig,"192.168.204.201",6379);
    }

    /**
     * 通过getJedis()方法,从连接池中获取一个Redis连接
     * @return
     */
    public static Jedis getJedis(){
        return pool.getResource();
    }
}

  2.获得锁/释放锁方法   

    遇到的问题说明:

    ①设置过期时间:设置过期时间,是防止拿到锁后睡着了啥也不干,其他人都得等着的情况

    上锁的同时,设置过期时间:防止上锁后突然服务器断电,导致永远不释放锁的问题

public class JedisDistributedLock {
    /**
     * 获得锁
     *
     * @param lockName 锁的名称
     * @param acquireTimeout 获得锁的超时时间
     * @param lockTimeout 锁本身的过期时间
     * @return
     */
    public String acquireLock(String lockName,long acquireTimeout,long lockTimeout){
        String identifier = UUID.randomUUID().toString();//保证释放锁的时候,是同一个持有锁的人
        String lockKey = "lock:"+lockName;
        //超时时间
        int lockExpire = (int) (lockTimeout / 1000);
        Jedis jedis = null;
        try {
            //获取Redis连接
            jedis = JedisConnectionUtils.getJedis();
            long end = System.currentTimeMillis() + acquireTimeout;
            //获取锁的限定时间
            while(System.currentTimeMillis() < end){
                if(jedis.setnx(lockKey,identifier) == 1){//设置值成功
                    // 设置超时时间(最好在setnx上锁时同时设置超时时间,避免刚上锁还没设置草是时间就挂掉,毕竟不是一个原子操作)
                    jedis.expire(lockKey,lockExpire);
                    return identifier;//获得锁成功
                }

                try{
                    //等待片刻后进行获取锁的重试
                    Thread.sleep(100);
                } catch (InterruptedException e){
                    e.printStackTrace();
                }
            }

        } catch (Exception e){
            e.printStackTrace();
        } finally {
            jedis.close();
        }
        return null;
    }

    /**
     * 释放锁
     * @param lockName 锁的名称
     * @param identifier
     * @return
     */
    public boolean releaseLock(String lockName,String identifier){
        System.out.println(System.currentTimeMillis() + "--" + lockName + "开始释放锁:"+identifier);

        String lockKey = "lock:"+lockName;
        Jedis jedis = null;
        boolean isRelease = false;
        try{
            jedis = JedisConnectionUtils.getJedis();
            while(true){
                //通过监听,来保证当前释放锁的线程是不是已经获得锁的线程
                jedis.watch(lockKey);
                //判断是否为同一把锁
                if(identifier.equals(jedis.get(lockKey))){
                    //通过事务,来完成锁的释放
                    Transaction transaction = jedis.multi();
                    transaction.del(lockKey);
                    if(transaction.exec().isEmpty()){
                        continue;
                    }
                    isRelease = true;
                }
                jedis.unwatch();
                break;
            }
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            jedis.close();
        }
        return isRelease;
    }
}

  3.测试

public class JedisDistributedLockTest implements Runnable {
    @Override
    public void run() {
        while(true){
            JedisDistributedLock distributedLock = new JedisDistributedLock();
            String rs = distributedLock.acquireLock("updateOrder", 2000, 5000);
            if(rs != null){
                System.out.println(System.currentTimeMillis() + "--" + Thread.currentThread().getName() + "-> 成功获得锁:"+rs);
                try {
                    Thread.sleep(1000);
                    distributedLock.releaseLock("updateOrder",rs);
                } catch (InterruptedException e){
                    e.printStackTrace();
                }
                break;
            }
        }
    }
    
    public static void main(String[] args) {
        JedisDistributedLockTest test = new JedisDistributedLockTest();
        //开启10个线程
        for (int i = 0; i < 10 ; i++) {
            new Thread(test,"tName:"+i).start();
        }
    }
}

  测试结果:

1566185177722--tName:2-> 成功获得锁:e5d88639-d266-4e45-acab-d2ef1c19f30f
1566185179222--updateOrder开始释放锁:e5d88639-d266-4e45-acab-d2ef1c19f30f
1566185179259--tName:7-> 成功获得锁:2e1a9b77-2e69-430d-8b15-ade320f911da
1566185180759--updateOrder开始释放锁:2e1a9b77-2e69-430d-8b15-ade320f911da
1566185180835--tName:6-> 成功获得锁:25bb2c0d-b64c-486f-9ebd-eb718c821953
1566185182336--updateOrder开始释放锁:25bb2c0d-b64c-486f-9ebd-eb718c821953
1566185182338--tName:8-> 成功获得锁:0c1c4f54-e2ca-4d98-ba8c-b0f8cddb8492
1566185183838--updateOrder开始释放锁:0c1c4f54-e2ca-4d98-ba8c-b0f8cddb8492
1566185183937--tName:9-> 成功获得锁:1318b8fd-dc09-454b-b44a-90f9c3999147
1566185185437--updateOrder开始释放锁:1318b8fd-dc09-454b-b44a-90f9c3999147
1566185185439--tName:5-> 成功获得锁:bdb8853b-c9ca-4138-8895-96eb08a4445f
1566185186939--updateOrder开始释放锁:bdb8853b-c9ca-4138-8895-96eb08a4445f
1566185186941--tName:3-> 成功获得锁:3b4ab6b6-7d6c-4821-a744-029f6b4c2a76
1566185188441--updateOrder开始释放锁:3b4ab6b6-7d6c-4821-a744-029f6b4c2a76
1566185188442--tName:4-> 成功获得锁:a6a14f8c-6b93-4b86-bfa4-40cd099a22ae
1566185189942--updateOrder开始释放锁:a6a14f8c-6b93-4b86-bfa4-40cd099a22ae
1566185190041--tName:1-> 成功获得锁:586e6de3-eb4f-4384-9010-357658cea5b0
1566185191541--updateOrder开始释放锁:586e6de3-eb4f-4384-9010-357658cea5b0
1566185191542--tName:0-> 成功获得锁:56d14bc0-ee90-47ba-836e-f27d8333e8b2
1566185193042--updateOrder开始释放锁:56d14bc0-ee90-47ba-836e-f27d8333e8b2

总结

       如果你的项目中Redis是多机部署的,那么你可以尝试使用Redission实现分布式锁。Redission不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务相对高级的功能。Redis官方推荐我们使用Redission来完成对Redis的相关操作。如需了解Redission的使用,请参考:Redission中文使用文档

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

扛麻袋的少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值