redis的分布式锁

1.前言

1.现在互联网发展的越来的越迅速,架构也有传统的单体架构,向分布式架构发展,涉及到分布式,就难免会遇到使用分布式锁的场景,分布式锁的实现目前使用比较多的是依赖于redis,或zk的实现。

2.redis分布式锁需要注意的点。

1.这里只讨论独占锁的情况,即同一个时间段,同一个=把锁只能被一个实例获取。
2.锁用完后需要释放
3.锁需要设置超时时间,防止实例中途挂了,却没有释放锁,导致死锁的情况
4.锁只能由加锁者释放,不能释放他人的锁
5.如果锁已经超时,但是程序还未执行完,应该能给锁提前修改锁的过期时间,避免出现锁失效的情况。

3.实现

借助redis的set命令,的NX表示不存在就设置,同时set的时候设置其过期时间,操作需要保持原子性, value可以用订单号,或者uuid,一个唯一标识符,和线程绑定在一块。
释放锁,即删除key,判断当前是否是锁的拥有者,是则删除,否则删除失败。操作使用lua脚本,保证操作的原子性。

package com.redis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.io.*;

public class RedisLockUtil {

    private final static Logger LOGGER = LoggerFactory.getLogger(RedisLockUtil.class);
    private JedisPool jedisPool = null;
    /**
     * 不存在才设置
     */
    public static final String NX = "NX";
    /**
     * 存在才设置
     */
    private final String XX = "XX";
    //秒
    private final String EX = "EX";
    //毫秒
    private final String PX = "PX";

    private final String SUCCESS_REPLY="OK";

    public static final String LUA_DEL_KEY_IFXX="if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";


    public RedisLockUtil(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    public JedisPool getJedisPool() {
        return jedisPool;
    }

    public void setJedisPool(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    private Jedis getJedis(){
        return jedisPool.getResource();
    }

    public boolean lock(byte[] key, byte[] value,long time){
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();

            String resultStr =jedis.set(key,value,NX.getBytes("utf-8"),EX.getBytes("utf-8"),time);
            if(resultStr != null && resultStr.equals(SUCCESS_REPLY)){
                return true;
            }else{
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            returnObject(jedis);
        }
        return false;
    }

    public boolean releaseLock(byte[] key,byte[] value){
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            Object result = jedis.eval(LUA_DEL_KEY_IFXX.getBytes("utf-8"),1,key,value);
            if(result.toString().equals("1")){
                return true;
            }else{
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            returnObject(jedis);
        }
        return false;
    }

}

测试

import com.redis.JedisUtil;
import com.redis.RedisLockUtil;
import org.junit.Test;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

public class LockTest {



	@Test
	public void distributeLockTest() throws IOException {

		JedisPoolConfig config = new JedisPoolConfig();

		config.setMaxTotal(1000);
		config.setMaxIdle(10);
		config.setMaxWaitMillis(10000);
		config.setTestOnBorrow(true);
		config.setTestOnReturn(true);
		JedisPool jedisPool = new JedisPool(config,"127.0.0.1",6379);

		RedisLockUtil redisLockUtil = new RedisLockUtil(jedisPool);
		redisLockUtil.setJedisPool(jedisPool);
		String distributeTestKey = "dis_test_key";

		Thread t = new Thread(new ConsumerLockTest(redisLockUtil,distributeTestKey));
		t.setName("thread 1");
		t.start();
		while (true){

		}
	}

	@Test
	public void distributeLockTest2() throws IOException {

		JedisPoolConfig config = new JedisPoolConfig();

		config.setMaxTotal(1000);
		config.setMaxIdle(10);
		config.setMaxWaitMillis(10000);
		config.setTestOnBorrow(true);
		config.setTestOnReturn(true);
		JedisPool jedisPool = new JedisPool(config,"127.0.0.1",6379);

		RedisLockUtil redisLockUtil = new RedisLockUtil(jedisPool);
		redisLockUtil.setJedisPool(jedisPool);
		String distributeTestKey = "dis_test_key";

		Thread t = new Thread(new ConsumerLockTest(redisLockUtil,distributeTestKey));
		t.setName("thread 2");
		t.start();
		while (true){

		}

	}

	@Test
	public void distributeLockTest3() throws IOException {

		JedisPoolConfig config = new JedisPoolConfig();

		config.setMaxTotal(1000);
		config.setMaxIdle(10);
		config.setMaxWaitMillis(10000);
		config.setTestOnBorrow(true);
		config.setTestOnReturn(true);
		JedisPool jedisPool = new JedisPool(config,"127.0.0.1",6379);

		RedisLockUtil redisLockUtil = new RedisLockUtil(jedisPool);
		redisLockUtil.setJedisPool(jedisPool);
		String distributeTestKey = "dis_test_key";

		Thread t = new Thread(new ConsumerLockTest(redisLockUtil,distributeTestKey));
		t.setName("thread 3");
		t.start();
		while (true){

		}

	}

	public class ConsumerLockTest implements Runnable{
		private RedisLockUtil redisLockUtil;
		private String distributeTestKey;

		public ConsumerLockTest(RedisLockUtil redisLockUtil,String distributeTestKey) {
			this.redisLockUtil = redisLockUtil;
			this.distributeTestKey = distributeTestKey;
		}
		@Override
		public void run() {

			SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			try {

				while(!redisLockUtil.lock(distributeTestKey.getBytes(),
						String.valueOf(Thread.currentThread().getName()).getBytes(),28000)){
				}
				System.out.println(format.format(new Date())+","+Thread.currentThread().getName()+":获取了锁");
				Thread.sleep(15000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}finally {
				boolean result =redisLockUtil.releaseLock(distributeTestKey.getBytes(),
						String.valueOf(Thread.currentThread().getName()).getBytes());
				if(result){
					System.out.println(format.format(new Date())+","+Thread.currentThread().getName()+":成功释放了锁");
				}else{
					System.out.println(format.format(new Date())+","+Thread.currentThread().getName()+":释放锁失败");
				}
			}
		}
	}


}

同时运行3个junit方法即可,如上并未考虑到锁超时,但线程并未执行完毕的情况,可以开启一个线程,检测锁快要超时了,为其重新设置过期时间,类似的已有框架redisson实现了高性能的分布式锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值