redis分布式锁的简单实现

1.什么是分布式锁

        系统部署在不同的机器上就是分布式(运行在不同的jvm中),在分布式环境中,对不同的应用程序共享资源加锁就是分布式锁

2.为什么会有分布式锁

        传统的锁机制,如lock锁,其底层原理是通过jdk的一些指令进行加锁操作,而分布式环境中,应用运行在不用的jvm中,因此传统的同步锁机制已经不能够满足

3.什么时候分布式锁,什么时候分布式事务

      应用之间的共享资源发生竞争时,需要使用分布式锁,而数据库中需要保持数据一致性的时候,则需要使用分布式事务

4.常用的分布式锁有哪些

      数据库,乐观锁;redis分布式锁;zookeeper分布式锁

开始实战

1.创建一个maven项目,引入依赖

2.创建jedispoll的配置类

private static JedisPool pool;//jedis连接池
private static int maxLink = 20;//最大连接数
private static int minDleLink = 10;//最小空闲连接数
private static int maxDleLink = 20;//最大空闲连接数
private static boolean testOnBorrow = true;//测试连接可用性

static {
    //初始化连接池
    initPool();
}

public static Jedis getJedis() {
    return pool.getResource();
}

public static void close(Jedis jedis) {
    jedis.close();
}


private static void initPool() {
    JedisPoolConfig config = new JedisPoolConfig();
    config.setMaxTotal(maxLink);
    config.setMaxIdle(maxDleLink);
    config.setMinIdle(minDleLink);
    config.setTestOnBorrow(testOnBorrow);
    config.setBlockWhenExhausted(true);
    pool = new JedisPool(config, "10.10.10.10", 6379, 5000, null);
}

3.创建锁的工具类,方便锁的操作(注意,jedis依赖需要 3.1版本以下,set方法支持多参数传入)

 

public class DistributedLockUtil {


    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    private static final Long RELEASE_SUCCESS = 1L;


    /**
     * 1. 通过setnx()方法尝试加锁,如果当前锁不存在,返回加锁成功。
     * 2. 如果锁已经存在则获取锁的过期时间,和当前时间比较,如果锁已经过期,则设置新的过期时间,返回加锁成功
     *
     * @param lockName
     * @return 存在问题
     * 1. 由于是客户端自己生成过期时间,所以需要强制要求分布式下每个客户端的时间必须同步。
     * 2. 当锁过期的时候,如果多个客户端同时执行jedis.getSet()方法,那么虽然最终只有一个客户端可以加锁,但是这个客户端的锁的过期时间可能被其他客户端覆盖。
     * 3. 锁不具备拥有者标识,即任何客户端都可以解锁
     */
    public static boolean errorLock(String lockName) {
        System.out.println(Thread.currentThread() + "开始尝试加锁");
        //设置key获取锁
        Long result = RedisPoolUtil.setnx(lockName, String.valueOf(System.currentTimeMillis() + 5000));
        //判断加锁是否成功
        if (result != null && result.intValue() == 1) {
            System.out.println("线程" + Thread.currentThread() + "加锁成功");
            //给锁一个过期时间,避免死锁
            RedisPoolUtil.expire(lockName, 5);
            //释放锁
            RedisPoolUtil.del(lockName);
            return true;
        } else {
            //取锁失败()
            String lockValueA = RedisPoolUtil.get(lockName);
            //比较是否过期
            if (lockValueA != null && Long.parseLong(lockValueA) >= System.currentTimeMillis()) {
                String lockValueB = RedisPoolUtil.getSet(lockName, String.valueOf(System.currentTimeMillis() + 5000));
                if (lockValueB == null || lockValueB.equals(lockValueA)) {
                    System.out.println(Thread.currentThread() + "加锁成功!");
                    RedisPoolUtil.expire(lockName, 5);
                    RedisPoolUtil.del(lockName);
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        }
    }

    /**
     * 1.set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性
     * 2.对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁
     * 3.value赋值为requestId,代表加锁的客户端请求标识,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端
     *
     * @return
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

    /**
     * 释放锁
     *
     * @return
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
        //使用lua命令
        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(requestId));
        if (RELEASE_SUCCESS.equals(result)) {
            System.out.println(Thread.currentThread() + "释放锁成功");
            return true;
        }
        return false;

    }

    /**
     * 不能区分锁的拥有者
     *
     * @param jedis
     * @param lockKey
     */
    public static void errorReleaseLock(Jedis jedis, String lockKey) {
        jedis.del(lockKey);
    }
}

4.编写业务测试类,模拟火车票购票业务

public class TrainTicketService {
    @Autowired
    private RedisPool redisPool;

    private final String LOCK_KEY = "LOCK_KEY";

    public void bugTicket() {
        //获取锁
        Jedis jedis = RedisPool.getJedis();
        String requestId = UUID.randomUUID().toString();
        System.out.println(Thread.currentThread() + "尝试加锁");
        boolean result = DistributedLockUtil.tryGetDistributedLock(jedis, LOCK_KEY, requestId, 30);
        if (!result) {
            System.out.println(Thread.currentThread() + "获取锁失败");
            try {
                Thread.sleep(1);
                bugTicket();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread() + "获取锁成功,开始业务代码");
        //释放锁
        DistributedLockUtil.releaseDistributedLock(jedis, LOCK_KEY, requestId);
    }
public class TrainThread extends Thread {
    @Autowired
    private TrainTicketService service;

    public TrainThread(TrainTicketService service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.bugTicket();
    }
}

6.测试

TrainTicketService service = new TrainTicketService();
for (int i = 0; i < 20; i++) {
    TrainThread thread = new TrainThread(service);
    thread.start();
}

结果显示

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值