redis——分布式锁

一、Jedis的简单创建

package com.app.redis;

import com.app.redis.lock.RedisWithLock;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.params.SetParams;

import java.util.Arrays;
import java.util.List;

/**
 * @author:wuqi
 * @date:2020/2/8
 * @description:com.app.redis
 * @version:1.0
 */
public class RedisUtils {

    /**
     * 创建单例
     */

    private RedisUtils() throws IllegalAccessException {
        throw new IllegalAccessException();
    }

//    private static Jedis JEDIS = null;
    private static JedisPool jedisPool = null;
    private static final String HOST = "192.168.0.114";
    static{
//        JEDIS = new Jedis(HOST,6379,1000);
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(100);
        config.setMaxIdle(100);
        config.setMaxWaitMillis(10000);
        config.setTestOnBorrow(true);
        jedisPool = new JedisPool(config,HOST,6379);
    }

    private static Jedis getJedis(){
//        return jedis;
        Jedis jedis = jedisPool.getResource();
        if (jedis != null){
            return jedis;
        }else {
            jedis = jedisPool.getResource();
            if(jedis != null){
                return jedis;
            }
            return new Jedis(HOST,6379,1000);
        }

    }

    /**
     * 测试连接
     */
    public static void main(String[] args){
        Jedis jedis = null;
        try {
            jedis = getJedis();
            jedis.set("linkTest2","hello World2");
            String back = jedis.set("linkTest","hello World");
            System.out.println(("OK").equals(back));
            Object response = RedisUtils.eval(RedisWithLock.UNLOCK_EVAL, Arrays.asList("linkTest","linkTest2"), Arrays.asList("hello World","hello World2"));
            System.out.println(response);
        }finally {
            if(jedis != null){
                //释放jedispool的一个连接
                jedis.close();
            }
            //关闭jedispool
            close();
        }
    }

    /**
     * 封装方法
     */

    interface CallWithRedis<T>{

        public T call(Jedis jedis);

    }

    private static <T> T execute(CallWithRedis<T> caller){
        try (Jedis jedis = getJedis()){
            return caller.call(jedis);
        }
    }

    public static String set(String key, String value, SetParams params){
        return RedisUtils.execute(new CallWithRedis<String>() {
            @Override
            public String call(Jedis jedis) {
                return jedis.set(key,value,params);
            }
        });
    }

    public static String get(String key){
        return RedisUtils.execute(new CallWithRedis<String>() {
            @Override
            public String call(Jedis jedis) {
                return jedis.get(key);
            }
        });
    }

    public static Long del(String key){
        return RedisUtils.execute(new CallWithRedis<Long>() {
            @Override
            public Long call(Jedis jedis) {
                return jedis.del(key);
            }
        });
    }

    public static Long expire(String key, int seconds){
        return RedisUtils.execute(new CallWithRedis<Long>() {
            @Override
            public Long call(Jedis jedis) {
                return jedis.expire(key,seconds);
            }
        });
    }

    public static Object eval(String script, List<String> keys, List<String> value){
        return RedisUtils.execute(new CallWithRedis<Object>() {
            @Override
            public Object call(Jedis jedis){
                return jedis.eval(script,keys,value);
            }
        });
    }

    public static void close(){
        if(jedisPool != null){
            jedisPool.close();
        }
    }
}

二、单机下分布式锁

 redis是单线程的,所以指令都是原子操作,可实现分布式锁(乐观锁,类似于CAS自旋锁)。

JVM锁synchronize和Lock是计数器实现,其中Lock用CAS自旋锁实现代码块的原子性来保证线程安全,

redis实现分布式锁类似于CAS自旋锁实现,用setnx原子操作自旋实现代码块原子性来保证线程安全,但有几点需要注意。这篇博客描述的很详细

自己实现的一个单机redis下分布式锁

public class RedisWithLock implements Lock {

    private String key;
    private ThreadLocal<String> valueLocal = new ThreadLocal();

    /**
     * KEYS[1] == Arrays.asList(key).get(0)
     * ARGV[1] == Arrays.asList(value).get(0)
     */
    public static final String UNLOCK_EVAL =
            "if redis.call('get',KEYS[1]) == ARGV[1] then
" +
                    "    return redis.call('del',KEYS[1])
" +
                    "else
" +
                    "    return 0
" +
                    "    end";

    public RedisWithLock(String key){
        this.key = key;
    }

    @Override
    public void lock() {
        String value = new Random().nextLong() + "";
        valueLocal.set(value);
        for (;;){
            if ("OK".equals(RedisUtils.set(key,valueLocal.get(),SetParams.setParams().nx().ex(5)))){
                String val = valueLocal.get();
                //创建守护线程 unlock前刷新expire时间
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            String lock = null;
                            for(;;){
                                lock = RedisUtils.get(key);
                                if(lock != null && lock.equals(val)){
                                    RedisUtils.expire(key,5);
                                    Thread.sleep(4000);
                                }else{
                                    break;
                                }
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
                thread.setDaemon(true);
                thread.start();
                break;
            }
        }
    }

    @Override
    public void unlock() {
        Object response = RedisUtils.eval(UNLOCK_EVAL, Arrays.asList(key), Arrays.asList(valueLocal.get()));
        if(Integer.valueOf(response.toString()) == 0){
            System.out.println("解锁失败");
        }
        valueLocal.remove();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean tryLock() {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        throw new UnsupportedOperationException();

    }

    @Override
    public Condition newCondition() {
        throw new UnsupportedOperationException();
    }
}

//模拟服务A
public class AppA {
    public static void main(String[] args){
        RedisWithLock lock = new RedisWithLock("lock");
        ExecutorService pool = Executors.newFixedThreadPool(2);
        pool.submit(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    System.out.println("appA thread1 lock :"+DateUtils.format(System.currentTimeMillis()));
                    Thread.sleep(10000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                    System.out.println("appA thread1 unlock :"+DateUtils.format(System.currentTimeMillis()));
                }
            }
        });
        pool.submit(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    System.out.println("appA thread2 lock :"+DateUtils.format(System.currentTimeMillis()));
                    Thread.sleep(3000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                    System.out.println("appA thread2 unlock :"+DateUtils.format(System.currentTimeMillis()));
                }
            }
        });
        pool.shutdown();
        while (!pool.isTerminated()){

        }
        RedisUtils.close();
    }
}

//模拟服务B
public class AppB {
    public static void main(String[] args){
        RedisWithLock lock = new RedisWithLock("lock");
        ExecutorService pool = Executors.newFixedThreadPool(2);
        pool.submit(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    System.out.println("appB thread1 lock :"+DateUtils.format(System.currentTimeMillis()));
                    Thread.sleep(3000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                    System.out.println("appB thread1 unlock :"+DateUtils.format(System.currentTimeMillis()));
                }
            }
        });
        pool.submit(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    System.out.println("appB thread2 lock :"+DateUtils.format(System.currentTimeMillis()));
                    Thread.sleep(3000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                    System.out.println("appB thread2 unlock :"+DateUtils.format(System.currentTimeMillis()));
                }
            }
        });
        pool.shutdown();
        while (!pool.isTerminated()){

        }
        RedisUtils.close();
    }
}

 三、集群下分布式锁——redlock(红锁)

单机下实现的分布式锁,在集群下不是绝对线程安全的,例如一个客户端在主节点中申请成功一把锁,主节点还未来得及同步到从节点,主节点突然挂掉了(基本不会发生),从节点变成主节点,此时新主节点是没有锁的,当另一个客户端申请锁会成功。未解决这个问题Antirez发明了redlock算法:

redlock算法:提供多个redis实例,实例之间相互独立,没有主从关系,加锁时过半redis实例set成功就认为加锁成功,释放锁时一样。

redlock相比于单机的分布式锁,由于需要向多个节点进行读写,所以性能会低一些,使用哪种锁需要慎重考虑,redlock的不安全性 https://www.cnblogs.com/baichunyu/p/11631777.html

参考《Redis深度历险》

原文地址:https://www.cnblogs.com/wqff-biubiu/p/12275728.html