欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

redis实现分布式锁解决本地事务

程序员文章站 2024-01-05 10:40:40
...

1.springRedis实现分布式锁


import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisCommands;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * @author superchen
 * @since 2018/5/16
 **/
@Component
public class RedisDistributedLock {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    public static final String UNLOCK_LUA;

    //静态代码块初始化lua脚本 作用:key和value匹配则删除
    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"del\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }


    /*
    *  获取锁
    * */
    public boolean setLock(String key, long expire) {
        try {
            //lambda表达式 jdk8及以上支持  替换原类的匿名内部类
            RedisCallback<String> callback = (connection) -> {
                JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                String uuid = UUID.randomUUID().toString();
                return commands.set(key, uuid, "NX", "PX", expire);
            };
            String result = stringRedisTemplate.execute(callback);
            return !StringUtils.isEmpty(result);
        } catch (Exception e) {
            System.out.println("set redis occured an exception");
        }
        return false;
    }

    /*
    *   根据key获取对应的值
    * */
    public String get(String key) {
        try {
            RedisCallback<String> callback = (connection) -> {
                JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                return commands.get(key);
            };
            String result = stringRedisTemplate.execute(callback);
            return result;
        } catch (Exception e) {
            System.out.println("get redis occured an exception");
        }
        return "";
    }

    /*
    *   释放锁
    *   释放锁的时候,有可能因为持锁之后方法执行时间大于锁的有效期,
    *   此时有可能已经被另外一个线程持有锁,所以不能直接删除(这种情况通常是由于过期时间设置不合理导致)
    * */
    public boolean releaseLock(String key, String requestId) {
        try {
            List<String> keys = new ArrayList<>();
            keys.add(key);
            List<String> args = new ArrayList<>();
            args.add(requestId);

            // 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
            // spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本
            RedisCallback<Long> callback = (connection) -> {
                Object nativeConnection = connection.getNativeConnection();
                // 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
                // 集群模式
                if (nativeConnection instanceof JedisCluster) {
                    return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
                }

                // 单机模式
                else if (nativeConnection instanceof Jedis) {
                    return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
                }
                return 0L;
            };
            Long result = stringRedisTemplate.execute(callback);

            return result != null && result > 0;
        } catch (Exception e) {
            System.out.println("release lock occured an exception");
        } finally {
            // 清除掉ThreadLocal中的数据,避免内存溢出
            //lockFlag.remove();
        }
        return false;
    }
}

2.业务代码中使用分布式锁


import com.sinosoft.xplatform.core.sysmng.sysuser.dao.CgpConfigDao;
import com.sinosoft.xplatform.core.sysmng.sysuser.entity.CgpConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class TestService {

    private String requestId;
    private static final String key = "testKey";

    @Autowired
    private RedisDistributedLock redisDistributedLock;
    @Autowired
    private CgpConfigDao cgpConfigDao;

    public void getMoney(){
        //获得锁 1.键   2.过期时间(根据具体业务设置)
        boolean lockOk = redisDistributedLock.setLock(key, 3600000);
        try {
            if (lockOk) {
                requestId = redisDistributedLock.get(key);  //记录当前锁对应的value(UUID)
                String valueStr = cgpConfigDao.findSyncUserSystems("test", "test");//查询剩余额度
                int value = Integer.parseInt(valueStr);
                System.out.println("初始化查询结果value = "+value);

                //延时模拟业务逻辑处理  开始
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //延时模拟业务逻辑处理  结束

                if(value >= 100){
                    System.out.println("余额充足-----{}");
                    value -= 20;
                    //将改变后的结果保存至数据库
                    CgpConfig cgpConfig = new CgpConfig();
                    cgpConfig.setCconPid(225);
                    cgpConfig.setConfigCode("test");
                    cgpConfig.setConfigType("test");
                    cgpConfig.setValidity("A");
                    cgpConfig.setConfigValue(value+"");
                    cgpConfigDao.save(cgpConfig);
                }else{
                    System.out.println("余额不足----{}");
                }
            }else{
                //此时锁已被占用,可以直接返回 也可以自己实现重试机制(根据业务需求处理即可)
                System.out.println("get no lock----{}");
            }
        } catch (Exception ex) {
            System.out.println("test error");
        } finally {
            //无论处理结果如何一定要释放锁 虽然有过期时间不会导致死锁,但如果有重试机制则会极大影响执行效率。
            if (requestId != null)
                redisDistributedLock.releaseLock(key, requestId);
        }
    }
}

3.参考:

https://blog.csdn.net/qq_28397259/article/details/80839072

上一篇:

下一篇: