Redis实现分布式锁(spring定时任务集群应用Redis分布式锁)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/QiaoRui_/article/details/83141060

         之前2片文章介绍了

                spring利用注解实现定时任务:https://blog.csdn.net/QiaoRui_/article/details/82999655

                spring定时任务的动态修改:https://blog.csdn.net/QiaoRui_/article/details/83110794

描述:

             不管用不用动态执行,单机服务都是没有问题的,但是如果服务是集群模式下,那么一个任务在每台机器都会执行一次,这肯定不是我们需要的,我们要实现的是整个集群每次只有一个任务执行成功,但是spring对此并没有很好的支持,所以我们需要有一个统一的数据获取处,参考网上决定利用rides的一致性来实现,开始就参考网上利用setnx命令实现的,但是还是感觉不太完善,就去抛开定时任务,直接实现Redis分布式锁应该是最合适的,在定时任务业务处加锁,业务执行完解锁即可。

             此事例经过多个网站参考并自己实际操作是不存在任何问题,完全可以正常使用。

实现:


import redis.clients.jedis.JedisCluster;

import java.util.Collections;

/**
 *
 * redis实现分布式锁,并释放锁
 */
public class RedisTool {

    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;

    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间,毫秒
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(JedisCluster jedis, String lockKey, String requestId, int expireTime) {
        /*
        *设置锁并设置超时时间,lockKey表示Redis key,requestId表示Redis value,SET_IF_NOT_EXIST表示有值不进行设置(NX),
        * SET_WITH_EXPIRE_TIME表示是否设置超时时间(PX)设置,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;
    }

    /**
     * 释放分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseDistributedLock(JedisCluster jedis, String lockKey, String requestId) {

        /*
        * 利用Lua脚本代码,首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)
        * eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令,这样就不会出现上一个代码执行完挂了后边的出现问题,还是一致性的解决
        * */
        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)) {
            return true;
        }
        return false;

    }

}

根据上篇博客的业务进行引用

package com.rails.travel.conf.task.myschedule;

import com.rails.travel.common.FrameSpringBeanUtil;
import com.rails.travel.common.RedisTool;
import redis.clients.jedis.JedisCluster;

import java.util.Date;


public class MyRunnable3 implements Runnable {

    //因为是线程所以无法注入bean对象,只能通过这种方法去获取bean对象
    //FrameSpringBeanUtil工具类在另一篇博客:https://blog.csdn.net/QiaoRui_/article/details/83094960
    private JedisCluster jedisCluster = FrameSpringBeanUtil.getBean(JedisCluster.class);

    @Override
    public void run() {
        //加锁,调用上边的工具类,参数具体看工具类说明
        boolean lock = RedisTool.tryGetDistributedLock(jedisCluster, MyRunnable3.class.toString(), MyRunnable3.class.toString(), 3000);
        //加锁成功则执行业务,不成功则不执行业务
        if (lock){
            //此处业务代码
            System.out.print("业务执行了3" + new Date());
            //业务执行完成解锁,如果在执行业务中或者是在解锁出现了异常,宕机等,锁会根据加锁时的key过期时间自己消除
            RedisTool.releaseDistributedLock(jedisCluster,MyRunnable3.class.toString(), MyRunnable3.class.toString());
        }
    }
}

说明:

            加锁解锁工具类直接是查看博客和官网等获取用的,博客必须看,可以完全明白实现原理及现在网上的错误做法的问题所在,我开始用的就是博客中提到的第一种错误方法

            博客:https://wudashan.cn/2017/10/23/Redis-Distributed-Lock-Implement/#%E5%8F%82%E8%80%83%E9%98%85%E8%AF%BB

           Redis中文命令参考中在set命令最后也给出了同样的解决方式:http://doc.redisfans.com/string/set.html

           Redis官网关于分布式锁中也是这种方式包括推荐的GitHub实现也是这种方式:https://redis.io/topics/distlock

猜你喜欢

转载自blog.csdn.net/QiaoRui_/article/details/83141060