Redis分布式锁 分段加锁思想实现 分段锁

Redis分布式锁 分段加锁思想实现 分段锁

一、前言

​ 这里我简单说一下,代码可能没那么完美。希望各位大佬可以多多指点!!!要是有测试能力的兄弟可以帮我测一下,我这个分段加锁我是全部都在Redis中实现主要用到的技术就是Lua脚本

二、建议

​ 建议搭配消息队列使用,防止一下进来太多请求。达到一个削峰的效果。

三、简述

​ 尝试加锁: 首先当一个请求发送过来了,尝试获取分段锁得时候会查出所有的分段仓库的key并尝试创建标识如果成功返回仓库的KEY不成功就进行下个仓库KEY的如果成功了会使用仓库的KEY创建一个标识来锁定这个库存并添加过期时间然后返回一个 仓库的KEY。

​ 解锁:根据之前那个 仓库的KEY 查出用户唯一标识然后和我们解锁传入的用户id进行比较看看是否相对再决定是否释放这个锁,释放锁的时候我们会检查库存如果为0那么顺便删除库存提高下次锁获取的效率。

在这里插入图片描述
在这里插入图片描述

四、流程图

在这里插入图片描述

五、核心代码

Lua脚本部分为了方便各位大神查看我单独写一个代码块


//TRY_LOCK
if redis.call("EXISTS",KEYS[1]) > 0 then
    local kus = redis.call("HKEYS",KEYS[1]);
    for i = 1, #kus do
        if redis.call('setnx',kus[i],ARGV[1]) > 0 then
            redis.call('expire', kus[i], ARGV[2])
            return kus[i];
        end
    end
else
    return nil;
end

//DECR
local kusName = KEYS[1]; local key = KEYS[2];
local uid =  redis.call('get',key);
local kuName = redis.call('HGET',kusName,key);
local kuNum = redis.call('get',kuName);
if redis.call("EXISTS",kusName) > 0 then
    if KEYS[3] == uid then
        if kuNum ~= nil and tonumber(kuNum) <= 1 then
            return redis.call('DECR',kuName);
        else
            return null;
        end
    end
else
    return -1;
end

//UN_LOCK
local kusName = KEYS[1];
local key = KEYS[2];
local uid =  redis.call('get',key);
local kuName = redis.call('HGET',kusName,key);
local kuNum = redis.call('get',kuName);
if uid == KEYS[3] then
    if kuNum ~=nil and tonumber(kuNum) <= 0 then
        redis.call('hdel',kusName,key);
        redis.call('del',kuName);
    end
    return redis.call('del',key);
end
return false;
package com.suoxin.core.lock;

import com.suoxin.utils.RedisTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;

/**
 * @author 所心
 * @create 2020-09-01 下午 5:11
 * @description Redis分段锁
 */
public class RedisDistributedLock {

    Logger log = LoggerFactory.getLogger(RedisDistributedLock.class);

    @Resource
    private RedisTools tools;
	//这里我使用得是lua得方式
    private static final String TRY_LOCK = "if redis.call('EXISTS',KEYS[1]) > 0 then local kus = redis.call('HKEYS',KEYS[1]); for i = 1, #kus do if redis.call('setnx',kus[i],ARGV[1]) > 0 then redis.call('expire', kus[i], ARGV[2]) return kus[i]; end end else return nil; end";
    private static final String DECR = "local kusName = KEYS[1]; local key = KEYS[2]; local uid =  redis.call('get',key); local kuName = redis.call('HGET',kusName,key); local kuNum = redis.call('get',kuName); if KEYS[3] == uid then if kuNum ~= nil and tonumber(kuNum) >= 1 then return redis.call('DECR',kuName); else return nil; end end";
    private static final String UN_LOCK = "local kusName = KEYS[1]; local key = KEYS[2]; local uid =  redis.call('get',key); local kuName = redis.call('HGET',kusName,key); local kuNum = redis.call('get',kuName); if uid == KEYS[3] then if kuNum ~=nil and tonumber(kuNum) <= 0 then redis.call('hdel',kusName,key); redis.call('del',kuName); end return redis.call('del',key); end return false;";

    private String KUS_NAME = null;
    private String UNIQUE_ID = null;
    private String EXPIRE = "60";
    private String KEY_ID = null;

    /**
     *
     * @param KUS_NAME 分段库名称
     * @param UNIQUE_ID 唯一标识
     * @param EXPIRE 过期时间 以秒为单位
     */
    public RedisDistributedLock(String KUS_NAME, String UNIQUE_ID, String EXPIRE) {
        this.KUS_NAME = KUS_NAME;
        this.UNIQUE_ID = UNIQUE_ID;
        this.EXPIRE = EXPIRE;
        //log.info("参数初始化:===>总仓库名称: {}, 唯一标识: {}, 过期时间: {}",KUS_NAME,UNIQUE_ID,EXPIRE);
    }

    /**
     *  初始化工具类
     * @param tools
     */
    public void setTools(RedisTools tools) {
        this.tools = tools;
    }

    /**
     * 获取锁 前面设置的时间就是锁得过期时间
     * @return
     */
    public boolean tryLock(){
        List<String> keys = Arrays.asList(KUS_NAME);
        List<String> args = Arrays.asList(UNIQUE_ID,EXPIRE);
        Object result = tools.luaScript(TRY_LOCK, keys, args);
        if (result != null && (result.toString().length() > 3)){
            this.KEY_ID = result.toString();
            //log.info("仓库ID: {} 返回结果: {} 获取锁成功",KEY_ID,result);
            return true;
        } else {
            //log.info("仓库ID: {} 返回结果: {} 获取锁失败",KEY_ID,result);
            return false;
        }

    }

    /**
     * 原子性扣减
     * @return
     */
    public Integer decr(){
        Object result = null;
        if (KEY_ID != null){
            List<String> keys = Arrays.asList(KUS_NAME,KEY_ID,UNIQUE_ID);
            List<String> args = Arrays.asList();
            result = tools.luaScript(DECR,keys,args);
            if (result != null && !result.toString().isEmpty()){
                log.info("消费成功 返回结果: {}",result);
                return 1;
            }else {
                //log.info("消费失败 返回结果: {}",result);
                return 0;
            }
        }
        log.info("已售空...{}"+ result);
        return -1;
    }

    /**
     * 释放锁
     */
    public void unLock(){
        if (KEY_ID != null){
            List<String> keys = Arrays.asList(KUS_NAME,KEY_ID,UNIQUE_ID);
            List<String> args = Arrays.asList();
            Object result = tools.luaScript(UN_LOCK, keys, args);
            //log.info("释放锁 返回结果: {}",result);
        }
    }
}

工具类代码:

package com.suoxin.utils;

import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author 所心
 * @create 2020-08-06 下午 11:01
 * @description RedisUtils 工具类
 */
public class RedisTools {

    private RedisTemplate<String, Object> redisTemplate;

    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    /**
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     * 普通缓存放入
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    
    /**
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     *
     *  HashSet
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
            

    public Map<String, Object> clear() {
        Map<String, Object> map = new HashMap<>();
        try {
            // 获取所有key
            Set<String> keys = redisTemplate.keys("*");
            assert keys != null;
            // 迭代
            Iterator<String> it1 = keys.iterator();
            while (it1.hasNext()) {
                // 循环删除
                redisTemplate.delete(it1.next());
            }
            map.put("code", 1);
            map.put("msg", "清理全局缓存成功");
            return map;
        } catch (Exception e) {
            map.put("code", -1);
            map.put("msg", "清理全局缓存失败");
            return map;
        }
    }

    public Object luaScript(String script, List<String> keys, List<String> args){
        return redisTemplate.execute((RedisCallback<Object>) connection -> {
            Object nativeConnection = connection.getNativeConnection();
            // 集群模式
            if (nativeConnection instanceof JedisCluster) {
                return ((JedisCluster) nativeConnection).eval(script, keys, args);
            }
            // 单机模式
            else if (nativeConnection instanceof Jedis) {
                return ((Jedis) nativeConnection).eval(script, keys, args);
            }
            return false;
        });
    }
}

六、测试Controller

package com.suoxin.controller;

import com.suoxin.core.lock.RedisDistributedLock;
import com.suoxin.utils.RedisTools;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author 所心
 * @create 2020-09-03 下午 1:22
 * @description 测试Controller
 */
@RestController
public class SeckillController {

    @Resource
    private RedisTools tools;

    private RedisDistributedLock lock = null;
	/**
     *
     * @param kuname Redis初始化得Redis库名称
     * @param allnum 库存总数
     * @param subnum 分段数
     * @return 仅是测试没有写太完美但是核心代码还是五脏俱全得
     */
    @RequestMapping("/init")
    public Map<String,Object> init(@RequestParam("kuname") String kuname,@RequestParam("allnum") Integer allnum,@RequestParam("subnum") Integer subnum){

        ConcurrentHashMap<String, Object> map = new ConcurrentHashMap();
        int max=100000,min=1000;
        for (int i = 0; i < (allnum/subnum); i++) {

            long nowtime = System.currentTimeMillis();
            int ran2 = (int) (Math.random()*(max-min)+min);

            map.put(UUID.randomUUID().toString(), Long.parseLong(nowtime+""+ran2));
            tools.set(nowtime+""+ran2, subnum);
        }
        tools.hmset(kuname, map);

        Map<String, Object> map1 = new HashMap<>();
        map1.put("msg","成功");
        return map1;
    }
	/**
     *
     * @param kuname 初始化时得Redis仓库名称
     * @param timeout 超时时间
     * @return
     */
    @RequestMapping("/seckill")
    public Map<String, Object> seckill(@RequestParam("kuname") String kuname,@RequestParam(value = "timeout",defaultValue = "5") String timeout){
        Map<String, Object> map1 = new HashMap<>();
        long run = System.currentTimeMillis();
        //初始化锁
        lock = new RedisDistributedLock(kuname, UUID.randomUUID().toString(), timeout);
        lock.setTools(tools);
        try {
            //获取锁
            if (lock.tryLock()) {
                map1.put("msg","恭喜您抢到了!!!:" + lock.decr());
            } else {
                map1.put("msg","很遗憾您没有抢到 ╥﹏╥...");
            }
        } finally {
            //计算时间
            long end  = System.currentTimeMillis();
            map1.put("elapsedTime","耗时:===>"+ (end-run) +"ms");
            //释放锁
            lock.unLock();
            return map1;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/G3187576034/article/details/108482317