redisTemplate 实现分布式锁 (windows版redis)

一 安装windows版本redis

https://github.com/microsoftarchive/redis/releases

二 代码

package com.hang.redis.controller;

import com.hang.redis.business.QueryBusiness;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Controller
 *
 * @author Hang W
 */
@RestController
@RequestMapping("query")
@SuppressWarnings("all")
public class QueryController {

    @Autowired
    private QueryBusiness queryBusiness;

    /**
     * 根据key查询value
     *
     * @param key
     * @return
     */
    @GetMapping("name/{key}")
    public String query(@PathVariable String key) {

        return queryBusiness.query(key);

    }

    /**
     * redis分布式锁
     *
     * @param key
     * @return
     */
    @GetMapping("stock/{key}")
    public int getStock(@PathVariable String key) {

        return queryBusiness.getStock(key);

    }

}
package com.hang.redis.business.impl;

import com.hang.redis.business.QueryBusiness;
import com.hang.redis.utils.RedisUtils;
import com.hang.redis.utils.impl.RedisUtilsImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/**
 * Business
 *
 * @author Hang W
 */
@Component
@SuppressWarnings("all")
public class QueryBusinessImpl implements QueryBusiness {

    private final static Logger logger = LoggerFactory.getLogger(RedisUtilsImpl.class);

    private final static String STOCK = "noodles";

    @Autowired
    private RedisUtils redisUtils;

    /**
     * 根据key查询value
     *
     * @param key
     * @return
     */
    @Override
    public String query(String key) {
        return redisUtils.get(key);
    }

    /**
     * 业务场景
     * 从数据库查询库存,如果存在则减少库存
     *
     * @return
     */
    @Override
    public int getStock(String key) {

        // lock
        boolean flag = redisUtils.lock(STOCK, com.hang.redis.utils.StringUtils.getRequestId(), 120000);

        int count = 0;
        try {
            if(flag) {
                logger.info("QueryBusinessImpl.getStock Key: {}", key);
                String value = redisUtils.get(key);
                logger.info("QueryBusinessImpl.getStock Value: {}", value);
                if(!StringUtils.isEmpty(value)) {
                    count = Integer.parseInt(value);
                    if(0 < count) {
                        count--;
                        redisUtils.set(key, "" + count);
                    } else {
                        return count;
                    }
                } else {
                    return count;
                }

                // nulock
                redisUtils.unlock(STOCK);
            }
        } catch (Exception e) {
            logger.info("QueryBusinessImpl.getStock Exception: {}", e);
            redisUtils.unlock(STOCK);
        } finally {
            redisUtils.unlock(STOCK);
        }
        return count;
    }

}

redis

package com.hang.redis.utils.impl;

import com.hang.redis.utils.RedisUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * redis 实现类
 *
 * @author Hang W 
 */
@Component
@SuppressWarnings("all")
public class RedisUtilsImpl implements RedisUtils {

    private final static Logger logger = LoggerFactory.getLogger(RedisUtilsImpl.class);

    private ThreadLocal<Map<String, Integer>> threadLocal = new ThreadLocal<Map<String, Integer>>();

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public void set(String key, String value) {
        logger.info("RedisUtilsImpl.set Key: {}, Value: {}", key, value);
        redisTemplate.boundValueOps(key).set(value);

    }

    @Override
    public void setnx(String key, String value) {
        logger.info("RedisUtilsImpl.set Key: {}, Value: {}", key, value);
        redisTemplate.boundValueOps(key).setIfAbsent(value, 10000, TimeUnit.MILLISECONDS);

    }

    @Override
    public String get(String key) {
        String value;
        try{
            logger.info("RedisUtilsImpl.get Key: {}", key);
            value = redisTemplate.boundValueOps(key).get();
            logger.info("RedisUtilsImpl.get Value: {}", value);
        } catch (Exception e) {
            logger.info("RedisUtilsImpl.get Exception: {}", e);
            return "";
        }
        return value;
    }

    /**
     * 分布式锁
     * 1.全局key
     * 2.唯一请求id —— 请求次数(可重入)
     * 3.过期时间
     * 4.非阻塞
     * 5.看门狗
     *
     * @param lock
     * @param requestId
     * @param time
     * @return
     */
    @Override
    public boolean lock(String lock, String requestId, long time) {
        Boolean flag = false;
        try{
            Map<String, Integer> map = threadLocal.get();
            logger.info("RedisUtilsImpl.lock ThreadLocal: {}", map);
            if(CollectionUtils.isEmpty(map)) {
                map = new HashMap<>();
                map.put(requestId, 1);
                threadLocal.set(map);

                logger.info("RedisUtilsImpl.lock Key: {}, Value: {}, Time: {}", lock, requestId, time);
                flag = redisTemplate.boundValueOps(lock).setIfAbsent(requestId, time, TimeUnit.SECONDS);
                logger.info("RedisUtilsImpl.lock Return: {}", flag);

                // 非阻塞
                if(!flag) {
                    new Thread(() -> {
                        while(true) {
                            Boolean f = redisTemplate.boundValueOps(lock).setIfAbsent(requestId, time, TimeUnit.SECONDS);
                            logger.info("RedisUtilsImpl.lock new Thread: {}", f);
                            if(f)
                                break;
                        }
                    }).start();
                }

                /**
                 * 看门狗
                 * 锁默认失效时间为30秒
                 *
                 * 1.先查看requestId是否被替换.
                 * 2.如果没有被替换,查看ttl,还是多少时间,20s <= time 临界值时,就再继续延迟30s.
                 * 3.如果已经被替换,表示逻辑已经执行完毕.
                 */
                new Thread(() -> {
                    while(true) {
                        String getRequestId = redisTemplate.boundValueOps(lock).get();
                        logger.info("RedisUtilsImpl.lock GetRequestId: {}", getRequestId);
                        if(StringUtils.isEmpty(getRequestId)) {
                            break;
                        } else if(getRequestId.equals(requestId)) {
                            Long expireTime = redisTemplate.getExpire(lock);
                            logger.info("RedisUtilsImpl.lock ExpireTime: {}", expireTime);
                            if(20 <= expireTime) {
                                redisTemplate.expire(lock, time, TimeUnit.MILLISECONDS);
                            }
                        } else {
                            break;
                        }
                    }
                }).start();

            } else {
                Integer count = map.get(requestId);
                map.put(requestId, count++);
                threadLocal.set(map);
            }
        } catch (Exception e) {
            logger.info("RedisUtilsImpl.lock Exception: {}", e);
            redisTemplate.delete(lock);
        }
        return flag;
    }

    /**
     * 业务代码执行完毕删除key,后续请求可以获取key
     *
     * @param lock
     */
    @Override
    public void unlock(String lock) {
        try {
            Map<String, Integer> map = threadLocal.get();
            logger.info("RedisUtilsImpl.unlock ThreadLocal: {}", map);
            if(!CollectionUtils.isEmpty(map)) {
                String requestId = (String) map.keySet().toArray()[0];
                Integer count = (Integer) map.values().toArray()[0];
                String getRequestId = redisTemplate.boundValueOps(lock).get();
                if(requestId.equals(getRequestId) && 1 < count) {
                    logger.info("RedisUtilsImpl.unlock Key: {}, Count(?): {}", lock, count);
                    map.put(requestId, count--);
                    threadLocal.set(map);
                } else if (requestId.equals(getRequestId) && 1 == count) {
                    logger.info("RedisUtilsImpl.unlock Key: {}, Count(=1): {}", lock, count);
                    redisTemplate.delete(lock);
                    threadLocal.remove();
                }
            }
        } catch (Exception e) {
            logger.info("RedisUtilsImpl.unlock Exception: {}", e);
            redisTemplate.delete(lock);
        } finally {
            redisTemplate.delete(lock);
        }
    }

}

三 git地址

已经上传到git,自测没问题

https://github.com/XUEZHIQIAN/springboot-redis.git

说明:

1.使用 redisTemplate 

redisTemplate.boundValueOps(lock).setIfAbsent(requestId, time, TimeUnit.SECONDS)

2.全局key(分布式下使用同一个唯一的key)

3.唯一请求id(每个请求申请一个唯一的id,用于锁定key,并且实现可重入 count 数量 + 1)

4.过期时间和 set 使用同一个方法,保证原子性

5.非阻塞,当多个请求对同一个key进行加锁时,当其中一个用户已经获取到 key,那么其他请求就进行自旋 直到获取到 key

6.看门狗,当业务逻辑没有处理完,但过期时间快到,则自动续时间

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值