redis分布式锁

一.redis相关命令

setnx()命令:

setnx的含义就是SET if Not Exists,其主要有两个参数 setnx(key, value)。该方法是原子的,如果key不存在,则设置当前key成功,返回1;如果当前key已经存在,则设置当前key失败,返回0。
get()命令:

get(key) 获取key的值,如果存在,则返回;如果不存在,则返回nil;
getset()命令:
这个命令主要有两个参数 getset(key, newValue)。该方法是原子的,对key设置newValue这个值,并且返回key原来的旧值。
假设key原来是不存在的,那么多次执行这个命令,会出现下边的效果:

  1. getset(key, “value1”) 返回nil,此时key的值会被设置为value1

  2. getset(key, “value2”) 返回value1 此时key的值会被设置为value2

  3. 依次类推!

二.具体步骤

  1. setnx(lockkey, 当前时间+过期超时时间) ,如果返回1,则获取锁成功;如果返回0则没有获取到锁,转向2。
    2. get(lockkey)获取值oldExpireTime ,并将这个value值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转向3。

  2. 计算newExpireTime=当前时间+过期超时时间,然后getset(lockkey, newExpireTime) 会返回当前lockkey的值currentExpireTime。

  3. 判断currentExpireTime与oldExpireTime 是否相等,如果相等,说明当前getset设置成功,获取到了锁。如果不相等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试。

  4. 在获取到锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,如果小于锁设置的超时时间,则直接执行delete释放锁;如果大于锁设置的超时时间,则不需要对锁进行处理。

分布式锁DistributedLockHandler类
package tk.mybatis.springboot.distributedLock;
 
import org.springframework.stereotype.Service;
 
import redis.clients.jedis.Jedis;
 
@Service("distributedLockHandler")
public class DistributedLockHandler {
 
	private static final Integer Lock_Timeout = 3;
 
	private Jedis jedis;
 
	/**
	 * 外部调用加锁的方法
	 * @param lockKey 锁的名字
	 * @param timeout 超时时间(放置时间长度,如:5L)
	 * @return
	 */
	public boolean tryLock(String lockKey, Long timeout) {
		try {
			Long currentTime = System.currentTimeMillis();//开始加锁的时间
			boolean result = false;
			
			while (true) {
				if ((System.currentTimeMillis() - currentTime) / 1000 > timeout) {//当前时间超过了设定的超时时间
					System.out.println("Execute DistributedLockHandler.tryLock method, Time out.");
					break;
				} else {
					result = innerTryLock(lockKey);
					if (result) {
						break;
					} else {
						System.out.println("Try to get the Lock,and wait 100 millisecond....");
						Thread.sleep(100);
					}
				}
			}
			return result;
		} catch (Exception e) {
			System.out.println("Failed to run DistributedLockHandler.getLock method."+ e);
			return false;
		}
	}
	
	/**
	 * 释放锁
	 * @param lockKey 锁的名字
	 */
	public void realseLock(String lockKey) {
		if(!checkIfLockTimeout(System.currentTimeMillis(), lockKey)){
			jedis.del(lockKey);
		}
	}
	
	/**
	 * 内部获取锁的实现方法
	 * @param lockKey 锁的名字
	 * @return
	 */
	private boolean innerTryLock(String lockKey) {
		
		long currentTime = System.currentTimeMillis();//当前时间
		String lockTimeDuration = String.valueOf(currentTime + Lock_Timeout + 1);//锁的持续时间
		Long result = jedis.setnx(lockKey, lockTimeDuration);
		
		if (result == 1) {
			return true;
		} else {
			if (checkIfLockTimeout(currentTime, lockKey)) {
				String preLockTimeDuration = jedis.getSet(lockKey, lockTimeDuration);
				if (currentTime > Long.valueOf(preLockTimeDuration)) {
					return true;
				}
			}
			return false;
		}
		
	}
 
	/**
	 * 判断加锁是否超时
	 * @param currentTime 当前时间
	 * @param lockKey 锁的名字
	 * @return
	 */
	private boolean checkIfLockTimeout(Long currentTime, String lockKey) {
		if (currentTime > Long.valueOf(jedis.get(lockKey))) {//当前时间超过锁的持续时间
			return true;
		} else {
			return false;
		}
	}
 
	public DistributedLockHandler setJedis(Jedis jedis) {
		this.jedis = jedis;
		return this;
	}
 
}

调用Demo:

package tk.mybatis.springboot.distributedLock;
 
import redis.clients.jedis.Jedis;
 
/**
 * 基于redis的setnx()、get()、getset()方法 分布式锁
 * @author KF01
 *
 */
public class Demo {
	private static final String lockKey = "Lock.TecentIm_Interface_Counter";
 
	public static void main(String[] args) {
		Jedis jedis = new Jedis("127.0.0.1", 6379);
		
		DistributedLockHandler distributedLockHandler = new DistributedLockHandler().setJedis(jedis);
		try{
            boolean getLock = distributedLockHandler.tryLock(lockKey, Long.valueOf(5));
 
            if(getLock){
                // Do your job
            	System.out.println("Do your job........");
            }
 
        }catch(Exception e){
            System.out.println(e);
        }finally {
            distributedLockHandler.realseLock(lockKey);
        }
	}
}

三.实际使用

/**
     * 获取锁
     *
     * @param key
     * @param expireMillis  过期时间 单位毫秒
     * @param timeoutMillis 获取锁超时时间 单位毫秒
     * @return : boolean
     */
    public boolean lock(String key, long expireMillis, long timeoutMillis) {
        String lockKey = generateLockKey(key);
        long start = System.currentTimeMillis();
        try {
            while ((System.currentTimeMillis() - start) < timeoutMillis) {
                if(jimDBClient.set(lockKey, "0", expireMillis, TimeUnit.MILLISECONDS, false)) {
                    return true;
                }
                //加暂停时间防止活锁
                TimeUnit.MILLISECONDS.sleep(redisLockSleepTime + RandomUtils.nextInt(0,100));
            }
        } catch (Exception e) {
            logger.error("RedisLock 获取锁失败,KEY:"+key, e);
        }
        return false;
    }
    
    /**
     * 组装锁的key
     *
     * @param key
     * @return : java.lang.String
     */
    private String generateLockKey(String key) {
        return LOCK_KEY_PRE +key;
    }
    /**
     * 释放锁
     *
     * @param key
     * @return : void
     */
    public void unlock(String key) {
        String lockKey = generateLockKey(key);
        try {
            jimDBClient.del(key);
        } catch (Exception e) {
            logger.error("Redis锁解锁失败,key:{" + lockKey + "};", e);
        }
    }

调用demo:

public void demo() {
	boolean isLocked = redisLock.lock(lockKey, redisLockExpireTime, redisLockTimeOut);
	        if(isLocked) {
				try{
					//业务逻辑
				} catch (Exception e) {
	                throw e;
	            } finally {
	                redisLock.unlock(lockKey);
	            }
	  		}else{
	            //如果获取锁失败则返回重试
	            logger.error("获取Redis锁失败,返回重试,Key:"+lockKey);
	            throw new RuntimeException("获取锁失败,返回重试,Key:"+lockKey) ;
	        }
}

四.结论

1.实际使用的是一个简易的分布式锁,主要目的是为了防止高并发导致的DB唯一索引频繁抛出异常导致死锁,(已经加乐观锁,但实际过程中依旧会出现频繁唯一索引冲突)
2.简易的分布式锁也并不是一个公平锁,但已经能解决上述死锁问题,所以未进行优化.
3.若实际生产中依旧出现问题,估计还会继续更新...
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值