redis 分布式锁-简易版与 redisson 实验

1

在原来的redsi测试服务上,继续来做实验。点击这里

在原有的IRedisService接口上新增两个 分布式的获取锁与释放锁。

/**
     * 获取锁
     * @param lockKey
     * @param requestId
     * @param expireTimeSeconds
     * @return
     */
    boolean getLock(String lockKey, String requestId, long expireTimeSeconds);

    /**
     * 释放锁
     * @param lockKey
     * @param requestId
     * @return
     */
    boolean releaseLock(String lockKey, String requestId);

2 RedisServiceImpl 实现

@Override
    public boolean getLock(String lockKey, String requestId, long expireTimeSeconds) {
        return redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTimeSeconds, TimeUnit.SECONDS);
    }

    @Override
    public boolean releaseLock(String lockKey, String requestId) {
        String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
        return (Boolean) this.redisTemplate.execute(new DefaultRedisScript(script, Boolean.class), Collections.singletonList(lockKey), new Object[]{requestId});

    }

getLock

在获得锁的时候,opsForValue().setIfAbsent 用到了redis的 setnx特性,这是分布式锁的关键一步,

为了保证锁不被一直占用,要有时间的限制,获取锁同时给了失效时间,保证了原子性。

releaseLock

释放的同时,也要保证原子性,这里用了lua脚本。

3 redis 锁工具类

 因为锁是一个经常用到的东西,所以为了方便他人的使用,需要改造成工具类。

 我们的redis 是一个service     tools是一个class  这时不能直接注入。

需要用springUtil 


package com.zhouqiang.demo.tools;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @author :zhouqiang
 * @date :2021/8/11 17:32
 * @description:
 * @version: $
 */
@Component
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    public SpringUtil() {
    }

    @Override
    public void setApplicationContext(ApplicationContext arg0) throws BeansException {
        applicationContext = arg0;
    }

    public static <T> T getBean(String id, Class<T> type) {
        return applicationContext.getBean(id, type);
    }

    public static <T> T getBean(String id, Object params) {
        return (T) applicationContext.getBean(id, new Object[]{params});
    }

    public static <T> T getBean(Class<T> type) {
        return applicationContext.getBean(type);
    }

    public static <T> T getBeanByParams(Class<T> type, Object... objects) {
        return applicationContext.getBean(type, objects);
    }
}

这时候,就可以写出完整的  Locl 锁工具类,放在公共的地方,方便其他同事调用。


package com.zhouqiang.demo.tools;

import com.zhouqiang.demo.enmu.RedisEnum;
import com.zhouqiang.demo.service.redis.IRedisService;
import lombok.Getter;

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

/**
 * @author :zhouqiang
 * @date :2021/8/11 15:58
 * @description:redsi缓存锁
 * @version: $
 */
public class Lock {


    private String key;
    private String requestId;
    private long second;

    private Lock(String key, String requestId, long second) {

        if (second <= 0) {
            throw new RuntimeException("超时时间必须大于0");
        }

        this.key = key;
        this.requestId = requestId;
        this.second = second;
    }


    private IRedisService getRedisLockService() {
        return SpringUtil.getBean(IRedisService.class);
    }

    /**
     * 写一个单例
     *
     * @param redisEnum
     * @param keys
     * @return
     */
    public static Lock getInstance(RedisEnum redisEnum, Object... keys) {

        return new Lock(redisEnum.getKey(), UUID.randomUUID().toString(), redisEnum.getExpiredTime());
    }


    /**
     * 得到锁
     */
    public boolean getLock() {
        return getRedisLockService().getLock(key, requestId, second);
    }

    /**
     * 释放锁
     *
     * @return
     */
    public boolean releaseLock() {
        return getRedisLockService().releaseLock(key, requestId);
    }

}


既然写了工具类,方便调用。缓存的KEY,也需要规范使用。

还要redis枚举。


package com.zhouqiang.demo.enmu;

import lombok.Getter;

/**
 * @author :zhouqiang
 * @date :2021/8/11 16:27
 * @description:缓存枚举
 * @version: $
 */
@Getter
public enum  RedisEnum {
    /**
     * 商品锁
     */
    SHOP_CLOCK("SHOP_CLOCK", 10L,"商品锁");


    /**
     * redis的key
     */
    private final String key;

    /**
     * 键的过期时间,单位为秒,有效期默认为20秒
     */
    private final Long expiredTime;

    /**
     * key的描述
     */
    private final String desc;

    RedisEnum(String key, Long expiredTime, String desc) {
        this.key = key;
        this.desc = desc;
        this.expiredTime = expiredTime;
    }


    public static RedisEnum getByKey(String key) {
        for (RedisEnum value : values()) {
            if (value.getKey() == key) {
                return value;
            }
        }
        return null;
    }
}


这样一套下来,基本上redis锁是完成了。

4 业务


 随便写一个业务接口。

然后写实现类。

package com.zhouqiang.demo.service.redisDemo.impl;

import com.zhouqiang.demo.enmu.RedisEnum;
import com.zhouqiang.demo.enmu.ResultCodeEnum;
import com.zhouqiang.demo.entity.ResultCode;
import com.zhouqiang.demo.service.redis.IRedisService;
import com.zhouqiang.demo.service.redisDemo.RedisDemoService;
import com.zhouqiang.demo.tools.Lock;
import org.redisson.Redisson;

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

/**
 * @author :zhouqiang
 * @date :2021/8/12 10:51
 * @description:
 * @version: $
 */
public class RedisDemoServiceImpl implements RedisDemoService {

    @Resource
    private IRedisService iRedisService;

    @Resource
    private Redisson redisson;

    @Override
    public String redisDemoTest() {

        String id = UUID.randomUUID().toString();
        //防止操作的事务发生异常造成堵塞  最后都会释放锁 不影响别人操作
        Object redis = null;
        try {
            //得到锁 5秒失效期
            boolean lock = Lock.getInstance(RedisEnum.SHOP_CLOCK, id, 5).getLock();
            if (lock == false) {
                return ResultCodeEnum.LOCK_FAIL.getMsg();
            }
            //要操作的事务(举例)
            iRedisService.setValue("redis", id);
            redis = iRedisService.getValue("redis");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //释放锁  万一超时 锁自己没了  不能误删别人的正在进行的锁
            Lock.getInstance(RedisEnum.SHOP_CLOCK, id).releaseLock();
        }

        if(redis==null){
            return ResultCodeEnum.MESSAGE_FAIL.getMsg();
        }
        return redis.toString();
    }
}

这里对分布式锁的考虑是比较多的,需要注意很多事项。

1 异常的捕捉

2 缓存的时间

3 锁的释放

这里我还加了返回的枚举,ResultCodeEnum 也做了标准化。

package com.zhouqiang.demo.enmu;

import com.zhouqiang.demo.entity.Result;
import lombok.Getter;

import javax.ws.rs.GET;

/**
 * @author :zhouqiang
 * @date :2021/8/12 10:34
 * @description:
 * @version: $
 */
@Getter
public enum ResultCodeEnum  {
    LOCK_SUCCESS(0, "获取锁成功!"),
    LOCK_FAIL(-1, "获取锁失败!"),
    MESSAGE_FAIL(-1, "消息失败!"),
    ;
    private int code;
    private String msg;

     ResultCodeEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

5  测试

 最后写一个接口测试。这里的返回体用了result封装。

result 封装在这里。

封装了这些,是为了标准化一点。也为了以后好维护。


到此,Redis 分布式锁的就这样了。

BUT redsi 自己封装的一个redisson,更加的好用。

6   redission分布式锁

Redisson 支持单点模式、主从模式、哨兵模式、集群模式 非常的强大啊。

redisson这个框架重度依赖了Lua脚本和Netty,代码很牛逼,各种Future及FutureListener的异步、同步操作转换

实现起来也是非常的容易。

引依赖

这里还需要配置,选择模式等等,下次在做展示。

 <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.4.3</version>
        </dependency>

在业务层只需要几步,

 这样就省略了我们1-4 所有的操作。简直就是泪目。

至于redison 有空再去看看源码。到此结束!

原文地址:https://www.cnblogs.com/zq1003/p/15132127.html