1. 程式人生 > 其它 >redis 分散式鎖-簡易版與 redisson 實驗

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);

2RedisServiceImpl 實現

@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 有空再去看看原始碼。到此結束!