目录

1 分布式锁的实现方式

2 redis分布式锁的原理

2.1 分布式锁的基本实现

2.2 如何避免死锁

2.3 解锁

3 redis分布式锁实现示例

4 验证

4.1 创建redis连接池

4.2 单线程验证

4.3 多线程验证


1 分布式锁的实现方式

分布式锁常用的有三种实现方式:

1)基于zookeeper的分布式锁;

2)基于数据库的分布式锁;

3)基于redis的分布式锁。

每一种分布式锁都有自己的优缺点,今天我们来介绍一下基于redis的分布式锁。

2 redis分布式锁的原理

2.1 分布式锁的基本实现

redis的存储结构是key-value形式的,并且redis的操作都是单线程执行的。基于这两点我们可以设置一个key来做多线程的竞争资源,线程拿到key就继续业务流程,拿不到就等待。使用redis加锁,实际上就是给key设置一个值:

set lock_key random_value NX

NX键不存在时,才对键进行操作。这样一个线程操作lock_key时,其他线程就需要等待,达到了加锁的目的。

但是如果一个线程一直持有锁,就会造成死锁的情况。那么如何规避死锁呢?

2.2 如何避免死锁

可以设置过期时间,比如设置过期时间为500毫秒:

set lock_key random_value NX PX 500

这样即使线程没有释放锁,到了过期时间lock_key也会自动被释放。其他线程就可以拿到锁了。

2.3 解锁

redis的解锁就是把key删除即可,但是删除的时候不能随便删,比如线程A不能删除线程B的key,这个时候value就起到作用了,random_value我们设置为随机值,每一个线程都生成一个随机值作为random_value,删除key的时候先判断随机值是否和本线程的一致,一致的才可以删除。

3 redis分布式锁实现示例

package rediscat;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;
import java.util.Collections;

/**
 * @ClassName RedisUtils
 * @Description redis分布式锁实现
 * @Author boy
 * @Date 2019/11/8 2:24 PM
 */
public class RedisUtils {

    private static final String LOCK_SUCCESS = "OK";//加锁成功
    private static final Long UNLOCK_SUCCESS = 1L;//解锁成功

    /*
     * @Author boy
     * @Description 加锁
     * @Date 2019/11/8 8:20 PM
     * @Param [jedis, key, value, expireTime]
     * @return boolean
     */
    public static boolean lockKey(Jedis jedis,String key,String value,int expireTime){
        SetParams params = SetParams.setParams();
        params.px(expireTime); //过期时间,单位是ms
        params.nx();//意思是SET IF NOT EXIST,即当key不存在时,进行set操作;若key已经存在,则不做任何操作
        String result = jedis.set(key,value, params);
        if (LOCK_SUCCESS.equals(result)){
            return true;
        }
        return false;
    }

    /*
     * @Author boy
     * @Description 解锁
     * @Date 2019/11/8 8:21 PM
     * @Param [jedis, lockKey, value]
     * @return boolean
     */
    public static boolean unLockKey(Jedis jedis, String lockKey, String value) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey),Collections.singletonList(value));
        if (UNLOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }


}

4 验证

4.1 创建redis连接池

package rediscat;


import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
 * @ClassName RedisClientService
 * @Description TODO
 * @Author boy
 * @Date 2019/11/8 4:05 PM
 */


public class RedisClientService {
    public static JedisPool pool = null;

    static {
        JedisPoolConfig config = new JedisPoolConfig();
        // 设置最大连接数
        config.setMaxTotal(500);
        // 设置最大空闲数
        config.setMaxIdle(100);
        // 设置最大等待时间
        config.setMaxWaitMillis(1000 * 100);
        // 在borrow一个jedis实例时,是否需要验证,若为true,则所有jedis实例均是可用的
        config.setTestOnBorrow(true);
        pool = new JedisPool(config, "121.36.25.118", 6379, 300000);
    }

}

4.2 单线程验证

package rediscat;

import redis.clients.jedis.Jedis;

import java.util.UUID;

/**
 * @ClassName RedisTest
 * @Description TODO
 * @Author boy
 * @Date 2019/11/8 4:24 PM
 */
public class RedisTest {

    public void testLock(){
        Jedis jedis = RedisClientService.pool.getResource();
        String key = "lockKey";
        String value = UUID.randomUUID().toString();
        int expireTime = 100;
        boolean b = RedisUtils.lockKey(jedis,key,value,expireTime);
        System.out.println("加锁 " + value + " " + b);
        jedis.close();


        Jedis jedis1 = RedisClientService.pool.getResource();
        boolean bb = RedisUtils.unLockKey(jedis,key,value);
        System.out.println("解锁 " + value + " " + bb);
        jedis1.close();
    }

    public static void main(String[] args){
        for(int i=0;i<400;i++){
            RedisTest redisTest =  new RedisTest();
            redisTest.testLock();
        }
    }
}

4.3 多线程验证

package rediscat;

/**
 * @ClassName TaskThread
 * @Description TODO
 * @Author boy
 * @Date 2019/11/8 8:27 PM
 */


public class TaskThread extends Thread {
    private RedisTest redisTest;

    public TaskThread(RedisTest redisTest) {
        this.redisTest = redisTest;
    }

    @Override
    public void run() {
        try {
            synchronized (this) {
                redisTest.testLock();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        RedisTest service = new RedisTest();
        for (int i = 0; i < 400; i++) {
            TaskThread thread = new TaskThread(service);
            thread.start();
        }
    }

}

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐