redis实现分布式锁释放锁和分布式锁实现可重入性

本文为上一篇redis使用setnx实现分布式锁的增加篇 重在体会思想 与开源的框架自然是无法比拟的

 

如果当前线程已经获取到锁的情况下,不需要重复获取锁,而是直接复用。
 
秒杀A里面同时调用秒杀B 需要实现锁的复用 不然会报该锁 获取失败 执行错误
 
package com.shanhe.lock;


public interface RedisLock {
    /**
     * 获取锁
     *
     * @return
     */
    boolean tryLock();

    /**
     * 释放锁
     *
     * @return
     */
    boolean releaseLock();
}

访问的接口地址

import com.shanhe.entity.CommodityDetails;
import com.shanhe.lock.impl.RedisLockImpl;
import com.shanhe.task.mapper.CommodityDetailsMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;


@Slf4j
@RestController
public class SeckillRedisService {
    @Autowired
    private CommodityDetailsMapper commodityDetailsMapper;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    private String redisLockKey = "lock";
    @Autowired
    private RedisLockImpl redisLockImpl;

    /**
     *   redis实现分布式锁
     * 
     */
    @RequestMapping("/seckillRedisLock")
    public String seckillRedisLock(Long commodityId) throws Exception {
        try {
            return seckillLockA(commodityId);
        } catch (Exception e) {
            log.error("<e:>", e);
            return "fail";
        } finally {
            // 释放锁
            redisLockImpl.releaseLock();
        }
    }

    private String seckilLockA(Long commodityId) {
        // 获取锁
        boolean getLock = redisLockImpl.tryLock();
        if (!getLock) {
            return "获取锁失败,请稍后重试!";
        }
        CommodityDetails commodityDetails = commodityDetailsMapper.getCommodityDetails(commodityId);
        return seckillLockB(commodityDetails);
    }

    private String seckillLockB(CommodityDetails commodityDetails) {
        boolean getLock = redisLockImpl.tryLock();
        if (!getLock) {
            return "获取锁失败,请稍后重试!";
        }

        Long stock = commodityDetails.getStock();
        if (stock > 0) {
            log.info("<开始执行扣库存..>");
            int result = commodityDetailsMapper.reduceInventory(1l);
            return result > 0 ? "扣库存成功" : "扣库存失败";
        }
        // 扣库存失败
        log.info("<扣库存失败>");
        return "fail";
    }


}

分布式锁具体实现

package com.shanhe.lock.impl;

import com.shanhe.entity.RedisLockInfo;
import com.shanhe.lock.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


@Component
@Slf4j
public class RedisLockImpl implements RedisLock {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    private String redisLockKey = "lock";
    /**
     * 缓存redis锁
     */
    private static Map<Thread, RedisLockInfo> lockCacheMap = new ConcurrentHashMap<>();
    /**
     * 重试时间
     */
    private Long timeout = 3000L;
    private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

    @Override
    public boolean tryLock() {
        // 1.多个jvm执行setnx 命令 最终只有一个jvm能够成功
        // setnx
        Thread cuThread = Thread.currentThread();
        RedisLockInfo redisLockInfo = lockCacheMap.get(cuThread);
        if (redisLockInfo != null && redisLockInfo.isState()) {
            // 这把锁可重入次数—+1
            log.info("<您在之前已经获取过锁,锁直接可重入>");
            return true;
        }

        // 重试机制  重试次数 或者 重试10s
        Long startTime = System.currentTimeMillis();
        Long lockExpire = 30000l;
        for (; ; ) {
            Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(redisLockKey, "1", lockExpire, TimeUnit.SECONDS);
            if (lock) {
                log.info("<获取锁成功>");
                lockCacheMap.put(cuThread, new RedisLockInfo(cuThread, lockExpire));
                // 开始续命监听
                return true;
            }
            // 控制一个超时的时间
            Long endTime = System.currentTimeMillis();
            if (endTime - startTime > timeout) {
                log.info("<重试的时间已经过了,不能够在继续重试啦>");
                return false;
            }
            // 继续循环
            try {
                Thread.sleep(10);
            } catch (Exception e) {

            }
        }

    }

    @Override
    public boolean releaseLock() {
        // A线程创建的锁 B线程执行释放锁 是谁创建的锁,就应该给谁删除。 lua脚本实现删除
        return stringRedisTemplate.delete(redisLockKey);
    }

    public RedisLockImpl() {
        //开始定时任务实现续命
        this.scheduledExecutorService.scheduleAtFixedRate(new LifeExtensionThread(), 0, 5, TimeUnit.SECONDS);
    }

    /**
     * 当我们的获取锁的jvm业务执行时间>过期key的超时时间 应该实现续命:
     * 当key过期的时候:走事件回调到客户端。---时间在延长
     * 延长过期key 应该是提前的。通过定时任务提前延长过期key
     * 算法实现:
     * 开启一个定时任务,每隔一段时间检测获取到锁的线程,延长该过期key的时间
     */
    class LifeExtensionThread implements Runnable {

        @Override
        public void run() {
            lockCacheMap.forEach((k, lockInfo) -> {
                try {
                    // 如何判断当前线程还没有执行完毕?
                    Thread lockServiceThread = lockInfo.getLockThread();
                    if (lockServiceThread.isInterrupted()) {
                        log.info(">>当前线程已经被停止,不需要实现续命<<");
                        lockCacheMap.remove(k);
                        return;
                    }
                    // 需要控制续命多次,如果获取到锁的jvm 续命多次还是没有将业务逻辑执行完毕的情况下处理: 主动释放锁 事务会回滚
                    Integer lifeCount = lockInfo.getLifeCount();
                    if (lifeCount > 3) {
                        log.info(">>您已经续命了多次当前线程还没有释放锁,现在主动将该锁释放 避免死锁的问题");
                        // 1.事务回滚
                        // 2.释放锁 dele
                        releaseLock();
                        // 3.将该线程主动停止
                        lockServiceThread.interrupt();
                        // 4.移除监听
                        lockCacheMap.remove(k);
                        return;
                    }
                    //提前实现续命 延长过期key的时间
                    stringRedisTemplate.expire(redisLockKey, lockInfo.getExpire(), TimeUnit.SECONDS);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }

}

 

posted @ 2023-02-07 21:35  山河永慕~  阅读(298)  评论(0编辑  收藏  举报