基于LUA+Redis集群 模式的分布式锁

package com.ppdai.merchant.service.configuration;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;


@Component
@Slf4j
public class RedisManager {

    private static Long lockExpirseTimeout = 10*1000L;

    @Autowired
    private RedisTemplate redisTemplate;

    public String get(String key){
        Object obj = redisTemplate.boundValueOps(key).get();
        if(obj == null){
            return  null;
        }else{
            return String.valueOf(obj);
        }
    }

    public void set(String key,String value){
        redisTemplate.boundValueOps(key).set(value);
    }



    public void set(String key, String value, Long time, TimeUnit timeUnit){
        redisTemplate.boundValueOps(key).set(value,time,timeUnit);
    }

    public Boolean exist(String key){
        return redisTemplate.hasKey(key);
    }

    public Boolean setNx(String key,Object value){
        return redisTemplate.boundValueOps(key).setIfAbsent(value);
    }

    public String getAndSet(String key,Object value){
        Object obj = redisTemplate.boundValueOps(key).getAndSet(value);
        if(obj == null){
            return  null;
        }else{
            return  String.valueOf(obj);
        }
    }


    public <T> T exeLuaScript(String luaScript, List<String> keys, List<String> args,Class<T> clz){
          T t = (T)redisTemplate.execute(new RedisCallback<T>(){
            @Override
            public T doInRedis(RedisConnection redisConnection) throws DataAccessException {
                Object nativeConnection = redisConnection.getNativeConnection();
                if (nativeConnection instanceof JedisCluster) {
                    return (T)((JedisCluster) nativeConnection).eval(luaScript.toString(), keys, args);
                }   // 单机模式
                else if (nativeConnection instanceof Jedis) {
                    return (T)  ((Jedis) nativeConnection).eval(luaScript.toString(), keys, args);
                }
                return null;
            }
        });

          if(t == null){
              throw new RuntimeException("redis model doesn't support luascript");
          }
          return  t;
    }

    public boolean trylock(String lockKey,String lockValue,Long lockWaitTimeout,Long lockExpirseTimeout){
        int timeout = lockWaitTimeout.intValue();
        while (timeout >= 0){
            String expireTimeout  = String.valueOf(lockExpirseTimeout/1000);
            List<String> keys = new ArrayList<String>();
            keys.add(lockKey);

            List<String> args = new ArrayList<String>();
            args.add(lockValue);
            args.add(expireTimeout);

            String lockLuaScript = setNxLuaScript();
            Long  exeResult = exeLuaScript(lockLuaScript,keys,args,Long.class);
            if (exeResult!=null && exeResult.intValue() == 1){
                return true;
            }

            String lockTimeStr = get(lockKey);
            if (lockTimeStr != null && Long.parseLong(lockTimeStr) < System.currentTimeMillis()){
                String oldLockTimeStr = getAndSet(lockKey,lockValue);
                if (oldLockTimeStr != null && oldLockTimeStr.equals(lockTimeStr)){
                    set(lockKey,lockValue,Long.valueOf(expireTimeout),TimeUnit.SECONDS);
                    return true;
                }
            }
            int sleepTime=new Random().nextInt(10)*100;
            timeout -= sleepTime;
            try {
                log.info("获取redis分布式锁失败,sleep:{}ms后重新获取",sleepTime);
                Thread.sleep(sleepTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return false;
    }


    public boolean lock(String lockKey){
        Long expires = System.currentTimeMillis() + lockExpirseTimeout + 1;
        String expiresStr = String.valueOf(expires);
        String expireTimeout  = String.valueOf(lockExpirseTimeout/1000);
        List<String> keys = new ArrayList<String>();
        keys.add(lockKey);

        List<String> args = new ArrayList<String>();
        args.add(expiresStr);
        args.add(expireTimeout);

        String lockLuaScript = setNxLuaScript();
        if (exeLuaScript(lockLuaScript,keys,args,Long.class) == 1){
            return true;
        }

        String lockTimeStr = get(lockKey);
        if (lockTimeStr != null && Long.parseLong(lockTimeStr) < System.currentTimeMillis()){
            String oldLockTimeStr = getAndSet(lockKey,expiresStr);
            if (oldLockTimeStr != null && oldLockTimeStr.equals(lockTimeStr)){
                set(lockKey,expiresStr,lockExpirseTimeout/1000,TimeUnit.SECONDS);
                return true;
            }
        }
        return false;
    }




    public void unlock(String lockKey,String oldValue){
        String luascript = delLuaScript();
        List<String> keys = new ArrayList<String>();
        keys.add(lockKey);
        List<String> args = new ArrayList<String>();
        args.add(oldValue);
        exeLuaScript(luascript,keys,args,Long.class);
    }


    private String delLuaScript(){
        StringBuffer luascript = new StringBuffer();
        luascript.append(" if redis.call('exists',KEYS[1]) == 1 and redis.call('get',KEYS[1]) == ARGV[1] then");
        luascript.append(" redis.call('del',KEYS[1])  return 1");
        luascript.append(" else return 0 end");
        return luascript.toString();
    }

    private String setNxLuaScript(){
        StringBuffer luascript = new StringBuffer();
        luascript.append(" if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then ");
        luascript.append(" redis.call('expire',KEYS[1],ARGV[2]) return 1");
        luascript.append(" else return 0 end");
        return luascript.toString();
    }


public static void main(String args[]){
    RedisManager redisManager = new RedisManager();
    //伪代码
    Long lockWaitTimeout = 5*1000L;
    Long lockExpirseTimeout = 10*1000L;
    String lockKey = "LOCK_KEY";
    String lockValue = String.valueOf(System.currentTimeMillis() + lockExpirseTimeout + 1);

    try{
        if(redisManager.trylock(lockKey,lockValue,lockWaitTimeout,lockExpirseTimeout)){
            //业务逻辑
        }
    }catch (Exception  e){

    }finally {
        redisManager.unlock(lockKey,lockValue);
    }

}




}

 

posted on 2020-05-11 18:45  柠檬糖大人你尽然盗号  阅读(682)  评论(0编辑  收藏  举报

导航