首页 > 数据库 >redis实现分布式锁释放锁和分布式锁实现可重入性

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

时间:2023-02-07 21:44:21浏览次数:47  
标签:return log 入性 redis private 线程 import public 分布式

本文为上一篇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();
                }
            });
        }
    }

}

 

标签:return,log,入性,redis,private,线程,import,public,分布式
From: https://www.cnblogs.com/shanheyongmu/p/17099909.html

相关文章

  • 《分布式技术原理与算法解析》学习笔记Day04
    分布式选举算法为什么需要分布式选举?分布式意味着我们的应用部署在一个集群中,集群包含多个节点或者服务器,对于一个集群来说,多个节点是怎么协同工作的呢?我们需要有一个主......
  • redis使用setnx实现分布式锁
     packagecom.shanhe.service;importcom.shanhe.entity.CommodityDetails;importcom.shanhe.lock.impl.RedisLockImpl;importcom.shanhe.mapper.CommodityDetail......
  • Redis在java中的使用
    1.添加价包<!--Redis依赖--><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-redis</artifactId><version>......
  • redis超卖(分布式锁) setnx排他独占 最终效果,既可以防止死锁,又可以设置过期时间
    热点key过期,加一个锁,抢占到锁才会去查询mysql数据库,然后查到数据后重新写入redis,几百台服务器,几十个热点数据,不能用jvm锁            ----......
  • Curator实现分布式锁代码
    Curator环境搭建Maven依赖<dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>4.2.0</version></......
  • 一文搞懂Redis
    作者:京东物流刘丽侠姚再毅康睿刘斌李振一、Redis的特性1.1Redis为什么快?基于内存操作,操作不需要跟磁盘交互,单次执行很快命令执行是单线程,因为是基于内存操作,单次执行......
  • 单线程架构的Redis如此之快的 4 个原因
    前言作为内存中数据存储,Redis以其速度和性能着称,通常被用作大多数后端服务的缓存解决方案。但是,在内部,Redis采用单线程架构。为什么单线程设计依然会有这么高的性能?如......
  • redisson分布式锁的应用——秒杀、超卖 简单例子(分布式锁相关)
    1、常见的分布式事务锁1、数据库级别的锁乐观锁,给予加入版本号实现悲观锁,基于数据库的forupdate实现2、Redis,基于SETNX、EXPIRE实现3、Zookeeper,基于InterProcess......
  • Redis 压缩包单节点安装
    下载Redis稳定版本直接下载或者去官网自行下载​​Download|Redis​​wgethttps://download.redis.io/redis-stable.tar.gz编译Redistar-xzvfredis-stable.tar.gzcdre......
  • 复习redis持久化的两种方式RDB和AOF
    redis持久化----两种方式1、redis提供了两种持久化的方式,分别是RDB(RedisDataBase)和AOF(AppendOnlyFile)。2、RDB,简而言之,就是在不同的时间点,将redis存储的数据生成快照并......