谈谈我对redis分布式锁的理解以及设计

1.设计原理:在分布式环境下,会存在多个线程竞争系统资源,最典型的是秒杀场景,现在我专门为秒杀场景设计了一个分布式锁,以及使用Jmeter模拟秒杀场景对分布式锁进行秒杀测试。

2.分布式锁基本逻辑:

1)判断当前线程获取的锁是否存在,如果存在就直接返回true,不做任何操作。

2)如果不存在锁,就直接创建锁,然后获取当前锁的值,用当前时间+超时时间作为锁的值,以UUID作为键。遇到锁存在,但是锁过期的情况下,就自动为锁进行续命,重新加锁。

3)并且判断锁旧的锁值是否为空,为空就说明被重新续命了,只能保证一个线程拿到锁,所以使用

if (StringUtils.isEmpty(oldValue) && oldValue.equals(currValue)){
    return true;
}
给锁续命

3.添加依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>distributelock</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>distributelock</name>
    <description>distributelock</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <image>
                        <builder>paketobuildpacks/builder-jammy-base:latest</builder>
                    </image>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

4.配置文件

server:
  port: 8636


spring:
  redis:
    port: 6379
    password:
    host: localhost

5.分布式锁组件

package com.example.distributelock.utils;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;


/**
 * @className:@DistributeRock
 * @Description:分布式锁组件
 * @Author:
 * @Version:1.0
 **/

@Component
public class DistributeRock {
    //创建日志打印对象
    private Logger logger = LoggerFactory.getLogger(DistributeRock.class);

    @Autowired
    private RedisTemplate redisTemplate;

    public boolean locked(String key,String value){

         //判断所加的锁是否存在,存在返回true,不存在就加锁
        if (redisTemplate.opsForValue().setIfAbsent(key,value)){
            return true;
        }

         String currValue = redisTemplate.opsForValue().get(key).toString();

         logger.info("long =={}",Long.parseLong(currValue));
        //判断锁是否为空且锁是不是过期了,因为是以当前时间+锁的超时时间定义值的
        if (!StringUtils.isBlank(currValue)&&Long.parseLong(currValue)<System.currentTimeMillis()){
            //获取并且重新设置值 ,相当于锁的续命,
            String oldValue = redisTemplate.opsForValue().getAndSet(key,value).toString();
            //只让一个线程拿到锁,另外的线程进来的时候currValue值已经被修改
            if (StringUtils.isEmpty(oldValue) && oldValue.equals(currValue)){
                return true;
            }
        }

        return false;

    }
    public void unlock(String key,String value){
        try {
            String currValue = redisTemplate.opsForValue().get(key).toString();
            logger.info("currValue============={}",currValue);
            logger.info("value============={}",value);
            if (!StringUtils.isEmpty(currValue)  && currValue.equals(value)){
                redisTemplate.opsForValue().getOperations().delete(key);
            }
        }catch (Exception e){
            e.printStackTrace();
            logger.info("解锁异常!");
        }

    }

    /**
     * 提前预设库存
     */
    @PostConstruct
    public void   initStock(){

         Object  stockObj =redisTemplate.opsForValue().get("stock");
        if (null !=stockObj){
            logger.info("存在key");
            redisTemplate.delete("stock");
        }
        redisTemplate.opsForValue().setIfAbsent("stock","10000",99999999999L, TimeUnit.HOURS);

        logger.info("库存初始化成功!");
   }



}

6.redisTemplate配置

package com.example.distributelock.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @className:@RedisConfig
 * @Description:TODO
 * @Author:
 * @Version:1.0
 **/
@Configuration
public class RedisConfig {

    /**
     * 创建一个redis操作模板bean
     * @param factory
     * @return
     */
     @Bean
    public RedisTemplate<String,Object>redisTemplate(RedisConnectionFactory factory){

        //创建一个实体
        RedisTemplate<String,Object> template = new RedisTemplate<String,Object>();
        template.setConnectionFactory(factory);
        GenericToStringSerializer genericToStringSerializer = new GenericToStringSerializer(Object.class);
        template.setValueSerializer(genericToStringSerializer);
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;


    }

}

7.controller相关代码

package com.example.distributelock.controller;

/**
 * @className:@StockController
 * @Description:TODO
 * @Author:
 * @Version:1.0
 **/

import com.example.distributelock.utils.DistributeRock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * 库存压力测试
 */
@RestController
@RequestMapping("/testlock")
public class StockController {

    //设置过期时间10s
    private static final Integer EXPIRE_TIME =10 ;

    //日志
    private Logger logger = LoggerFactory.getLogger(StockController.class);
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Autowired
    private DistributeRock distributeRock;
    @GetMapping("/pressTest")
    public Map<String,Object>pressTest(){
        Map<String,Object> map = new HashMap<>();
        long lockTime = System.currentTimeMillis()  +10*1000;
        String uuid = UUID.randomUUID().toString();

        boolean isLock = distributeRock.locked(uuid,String.valueOf(lockTime));
        if (!isLock){
            map.put("data","");
            map.put("code",500);
            map.put("msg","系统正忙....");
            return map;
        }
        try {
            if (Integer.parseInt(redisTemplate.opsForValue().get("stock").toString())>0){
                redisTemplate.opsForValue().decrement("stock");
                map.put("data","库存剩余:"+Integer.parseInt(redisTemplate.opsForValue().get("stock").toString()));
                logger.info("uid==={}",uuid);
                map.put("code",200);
                map.put("msg","库存扣减成功");
                logger.info("库存扣减成功,剩余"+Integer.parseInt(redisTemplate.opsForValue().get("stock").toString()));
                return map;
            }else {
                map.put("data","库存剩余:"+Integer.parseInt(redisTemplate.opsForValue().get("stock").toString()));
                logger.info("uid==={}",uuid);
                map.put("code",500);
                map.put("msg","库存扣失败!");
                logger.error("库存扣减失败!!!!,剩余"+Integer.parseInt(redisTemplate.opsForValue().get("stock").toString()));
            }


        }catch (Exception e){
            logger.info("库存扣减失败!");
        }finally {
            distributeRock.unlock(uuid,String.valueOf(lockTime));
            logger.info("释放锁!!");
        }
        return map;
    }


}

8.我在本地的IDEA启动了4个不同端口的SpringBoot项目进行测试。

9.下面附上nginx的配置。

10.Jemeter的配置也一起放上来吧,希给大家借鉴一下。

总结:分布式锁三部曲,

1.创建唯一标识的id去识别锁。

2.redis的原子性操作,即  redisTemplate.opsForValue().setIfAbsent 和redisTemplate.opsForValue().getAndSet方法的应用。

3.在锁未被释放的时候,进行锁的续命,保证当前获取锁的线程执行完之后,才能让其他线程获取锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心之所想,行则将至

创作不易,希望大家多多鼓励支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值