redisson实现分布式锁

本文档介绍了如何在Spring Boot应用中使用Redisson实现分布式锁,包括pom.xml配置、application.properties配置、Redisson的Java配置类以及具体使用方法。同时,通过JMeter进行并发测试,展示了在高并发场景下分布式锁的性能表现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

目录

一、pom.xml配置 

二、application.properties配置

三、redisson的java配置类

四、具体使用 

五、jemeter并发测试

其他使用 



1、加锁实现关键:
Redis的setNX命令
 
2、分布式加锁实现方式:
1)先加锁,加所有执行程序,程序执行完毕通过finally模块关闭锁。弊端:如果程序在执行finally之前程序异常崩溃锁将不会被释放

2)先加锁,加锁成功后进行过时操作,当并发量达到一定程度那么程序很可能会变慢,如原设置加锁时长为30秒,如果程序运行超过30秒,那么锁将被提前释放, 下一个线程将可以和上个线程同时操作,并且并发量足够大这种情况将一直存在,此时相当于锁失效

3)同时加锁并设置加锁时间,当并发量达到一定程度那么程序很可能会变慢,如原设置加锁时长为30秒,如果程序运行超过30秒,那么锁将被提前释放,下一个线程将可以和上个线程同时操作,并且并发量足够大这种情况将一直存在,此时相当于锁失效

4)redisson加锁:加锁、锁时长配置为原子性同步执行,,并且如果遇见大并发量造成的程序运行超过指定加锁时长会对线程的锁进行弹性续时,如果程序正常运行时长没超过加锁时长将按照程序运行时长正常释放锁

一、pom.xml配置 

spring-data-redis:redis基本连接,redisTempldate操作等依赖

redisson:分布式锁依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> 
<dependency>
     <groupId>org.redisson</groupId>
     <artifactId>redisson</artifactId>
     <version>3.5.0</version>
</dependency>

二、application.properties配置

## redis setting
spring.redis.host=127.0.0.1
spring.redis.password=123456
spring.redis.port=6379
## redis lettuce pool setting 
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-idle=10
spring.redis.lettuce.pool.min-idle=5

三、redisson的java配置类

package com.leadpms.qianlistandard.web.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Redisson 配置类
 *
 * @author Shaoyu Liu
 * @date 2021/6/2 16:31
 **/
@Configuration
public class RedissonConfig {


    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private String port;

    @Value("${spring.redis.password}")
    private String password;


    @Bean
    public RedissonClient getRedisson() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
        //添加主从配置
//        config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
        return Redisson.create(config);
    }


}
package com.leadpms.qianlistandard.web.config;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;
 
/**
 * Redis 配置类
 *
 * @author Shaoyu Liu
 * @date 2021/5/28 15:50
 **/
@Configuration
public class RedisConfig {
 
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;
 
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setDefaultSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
 
//    @Bean
//    public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig jedisPool,
//                                                         RedisStandaloneConfiguration jedisConfig) {
//        JedisConnectionFactory connectionFactory = new JedisConnectionFactory(jedisConfig);
//        connectionFactory.setPoolConfig(jedisPool);
//        return connectionFactory;
//    }
//
//    @Configuration
//    public static class JedisPoolConf {
//        @Value("${spring.redis.host:127.0.0.1}")
//        private String host;
//        @Value("${spring.redis.port:6379}")
//        private Integer port;
//        @Value("${spring.redis.password:}")
//        private String password;
//        @Value("${spring.redis.database:0}")
//        private Integer database;
//
//        @Value("${spring.redis.jedis.pool.max-active:8}")
//        private Integer maxActive;
//        @Value("${spring.redis.jedis.pool.max-idle:8}")
//        private Integer maxIdle;
//        @Value("${spring.redis.jedis.pool.max-wait:-1}")
//        private Long maxWait;
//        @Value("${spring.redis.jedis.pool.min-idle:0}")
//        private Integer minIdle;
//
//        @Bean
//        public JedisPoolConfig jedisPool() {
//            JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//            jedisPoolConfig.setMaxIdle(maxIdle);
//            jedisPoolConfig.setMaxWaitMillis(maxWait);
//            jedisPoolConfig.setMaxTotal(maxActive);
//            jedisPoolConfig.setMinIdle(minIdle);
//            return jedisPoolConfig;
//        }
//
//        @Bean
//        public RedisStandaloneConfiguration jedisConfig() {
//            RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
//            config.setHostName(host);
//            config.setPort(port);
//            config.setDatabase(database);
//            config.setPassword(RedisPassword.of(password));
//            return config;
//        }
//    }
 
}

四、具体使用 

1.redis定义商品个数为200件

set stock "200"

2.Java接口

@RestController
@RequestMapping("/address")
public class AddressController {
    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 商品总量
     */
    public static int TICKET_PRODUCT = 100;

    /**
     * 锁超时时间(秒)
     */
    public static long LOCK_TIMEOUT = 30;

    /**
     * 分布式锁前缀
     */
    public static String LOCK_KEY_PREFIX = "lock_product";

    @GetMapping("/testRedis")
    public void testRedis() {
        RLock rLock = redissonClient.getLock(LOCK_KEY_PREFIX);
        try {
            rLock.lock();
            int stock = (Integer) redisTemplate.opsForValue().get("stock");
            if (stock > 0) {
                TICKET_PRODUCT = stock - 1;
                redisTemplate.opsForValue().set("stock", TICKET_PRODUCT);
                System.out.println("卖出1件商品,库存剩余:" + TICKET_PRODUCT);
            } else {
                System.out.println("售卖失败!库存不足");
            }

        } finally {
            rLock.unlock();
        }
    }
}

五、jemeter并发测试

1.设置并发数,并发发起时间

2.设置接口地址端口号等

3.请求结果

 

 

其他使用 

package com.leadpms.qianlistandard.web.rest;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.ObjectUtils;

import java.util.concurrent.TimeUnit;

/**
 * @author Shaoyu Liu
 * @date 2021/6/2 16:07
 **/
public class Test {


    @Autowired
    private Redisson redisson;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 商品总量
     */
    public static int TICKET_PRODUCT = 100;
    /**
     * 锁超时时间(秒)
     */
    public static long LOCK_TIMEOUT = 30;
    /**
     * 商品锁前缀
     */
    public static String LOCK_KEY_PREFIX = "lock_product";

    public void RedisLock() {
        // 1.先加锁,加所有执行程序,程序执行完毕通过finally模块关闭锁。弊端:如果程序在执行finally之前程序异常崩溃锁将不会被释放
        Boolean lockResult = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY_PREFIX, "distributed_lock");
        try {
            if (!ObjectUtils.isEmpty(lockResult)) {
                if (lockResult) {
                    TICKET_PRODUCT--;
                    System.out.println("卖出1件商品,库存剩余:" + TICKET_PRODUCT);
                }
                if (!lockResult) {
                    System.out.println("售卖失败!库存不足");
                }
            }
        } finally {
            redisTemplate.delete(LOCK_KEY_PREFIX);
        }

     //2.先加锁,加锁成功后进行过时操作,当并发量达到一定程度那么程序很可能会变慢,如原设置加锁时长为30秒,如果程序运行超过30秒,那么锁将被提前释放,
//下一个线程将可以和上个线程同时操作,并且并发量足够大这种情况将一直存在,此时相当于锁失效
        Boolean lockResult = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY_PREFIX, "distributed_lock");
        try {
            if (!ObjectUtils.isEmpty(lockResult)) {
                if (lockResult) {
                    redisTemplate.expire(LOCK_KEY_PREFIX, LOCK_TIMEOUT, TimeUnit.SECONDS);
                    TICKET_PRODUCT--;
                    System.out.println("卖出1件商品,库存剩余:" + TICKET_PRODUCT);
                }
                if (!lockResult) {
                    System.out.println("售卖失败!库存不足");
                }
            }
        } finally {
            redisTemplate.delete(LOCK_KEY_PREFIX);
        }

        /*
         数据库双写不一致(写入缓存和写入数据库):加入uuid来确保在删除锁的时候不会删掉其他线程的锁。
         在线程执行时间轴上,并不一定是先到的线程先执完毕的,也有可能先到的线程在执行过程中被晚到的线程先一步执行完毕。
         可能第一个线程先到的线程拿到的库存为100,然后在30秒内没执行完毕锁被释放掉了,然后第二个后到线程拿到拿到的库存也是100卖出5件商品剩95件,
         然后此时线程一执行完毕,在他之前拿到的100件上也减去1变为99件,此时发生超卖
         */
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        // 原子执行 setnx lock_product uuid && expire lock_product 30 ,到期后lock_product自动删除
        Boolean result = redisTemplate.opsForValue().setIfAbsent("lock_product", uuid, 30, TimeUnit.SECONDS);
        if (result) {
            try {
                int stock = (Integer) redisTemplate.opsForValue().get("stock");
                if (stock > 0) {
                    PRODUCT_COUNT = stock - 1;
                    redisTemplate.opsForValue().set("stock", PRODUCT_COUNT);
                    System.out.println("卖出1件商品,商品还剩" + PRODUCT_COUNT);
                } else {
                    System.out.println("售卖失败,库存不足");
                }
            } finally {
                String lockValue = (String) redisTemplate.opsForValue().get("lock_product");
                // 判断只删除属于自己线程的锁
                if (StringUtils.equals(uuid, lockValue)) {
                    redisTemplate.delete("lock_product");
                }
            }
        }

        // 4.redisson加锁:加锁、锁时长配置为原子性同步执行,,并且如果遇见大并发量造成的程序运行超过指定加锁时长会对线程的锁进行弹性续时,
        // 如果程序正常运行时长没超过加锁时长将按照程序运行时长正常释放锁
        RLock redissonLock = redisson.getLock(LOCK_KEY_PREFIX);
        try {
            redissonLock.lock();
            int stock = (Integer) redisTemplate.opsForValue().get("stock"); // jedis.get("key");
            if (stock > 0) {
                TICKET_PRODUCT = stock - 1;
                redisTemplate.opsForValue().set("stock", TICKET_PRODUCT);  // jedis.set("key","value");
                System.out.println("卖出1件商品,库存剩余:" + TICKET_PRODUCT);
            } else {
                System.out.println("售卖失败!库存不足");
            }

        } finally {
            redissonLock.unlock();
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值