Redis实现分布式锁方案

分布式架构


windows配置nginx


  • 配置nginx.conf
    由于在本地模拟测试,所以我们做相同ip,不同端口的负载均衡。
worker_processes  1;
 
events {
    worker_connections  1024;
}
 
 
http {
    include       mime.types;
    default_type  application/octet-stream;
 
    sendfile        on;
    keepalive_timeout  65;
 
    upstream sunpy {
        server 127.0.0.1:8090 weight=1;
        server 127.0.0.1:8091 weight=1;
    }
    
    server {
        listen       8888;
        server_name  localhost;
 
        location / {
            proxy_pass http://sunpy;
        }
    }
}
  • 启动nginx
    nginx -c conf/nginx.conf
  • 测试


不加分布式锁会出现的情况


public void testRedisLock(String shopCode) {
    int amount = Integer.parseInt(stringRedisTemplate.opsForValue().get(shopCode));
 
    if (amount <= 0) {
        log.info("+++++++++++++++++++++++++++> 已经没有库存!");
    } else {
        int result = amount - 1;
        stringRedisTemplate.opsForValue().set(shopCode, String.valueOf(amount - 1));
        log.info("**************************> 当前库存为 = " + result);
    }
}

压测结果:

  • 问题:没有使用分布式锁,库存问题出现了超卖,出现了脏数据。

利用redis的setnx实现分布式锁


导包:

<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
 
<!--spring2.x集成redis所需common-pool2-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
 
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>${jackson-databind.version}</version>
</dependency>

程序:

@Slf4j
@Service
public class RedisService {
 
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
 
    public void testRedisLock(String shopCode) {
        String redisLock = "SPY-LOCK";
 
        Boolean absent = stringRedisTemplate.opsForValue().setIfAbsent(redisLock, "程序加锁测试", 10, TimeUnit.SECONDS);
 
        if (!absent) {
            log.info("===========================> 加锁操作失败!");
            return;
        }
 
        try {
            int amount = Integer.parseInt(stringRedisTemplate.opsForValue().get(shopCode));
 
            if (amount <= 0) {
                log.info("+++++++++++++++++++++++++++> 已经没有库存!");
            } else {
                int result = amount - 1;
                stringRedisTemplate.opsForValue().set(shopCode, String.valueOf(result));
                log.info("**************************> 当前库存为 = " + result);
            }
        } finally {
            stringRedisTemplate.delete(redisLock);
        }
    }
}

压测结果:

Redis分布式锁会出现哪些问题


  • 锁被其他人释放
  • 死锁
  • 锁提前过期
  • 单点Redis出现故障

出现解其他人锁的问题


问题描述:
当前线程删除了其他线程的锁,主要还是在于锁获取的token令牌是相同的。
解决方法:
每次设置锁的时候,保持不一样,只有锁令牌相同允许通过,才可以删除锁。

public void testRedisLock(String shopCode) {
    String redisLock = "SPY-LOCK";
    String token = UUID.randomUUID().toString();
    Boolean absent = stringRedisTemplate.opsForValue().setIfAbsent(redisLock, token, 10, TimeUnit.SECONDS);
 
    if (!absent) {
        return;
    }
 
    try {
        int amount = Integer.parseInt(stringRedisTemplate.opsForValue().get(shopCode));
 
        if (amount <= 0) {
        } else {
            int result = amount - 1;
            stringRedisTemplate.opsForValue().set(shopCode, String.valueOf(result));
            log.info("**************************> 当前库存为 = " + result);
        }
    } finally {
        String value = stringRedisTemplate.opsForValue().get(redisLock);
 
        if (token.equals(value)) {
            stringRedisTemplate.delete(redisLock);
        }
    }
}

死锁问题


  • 问题描述:
    当前线程在锁内,如果没有自己主动释放锁,出现异常等其他情况中断程序,没有运行释放锁的程序,会造成其他线程无法获取锁,而自己一直独占这种死锁情况。
  • 解决方法:
    提供一个redis锁过期时间,但是也会有问题,就是锁如果设置时间过短,程序未运行完就释放锁了,主要原因在于程序运行时间无法准确估计。
Boolean absent = stringRedisTemplate.opsForValue().setIfAbsent(redisLock, "程序加锁测试", 10, TimeUnit.SECONDS);

Redisson框架


为什么使用Redisson框架

  • Redisson框架本身提供了锁续期方案,优化了死锁问题;
  • Redisson提供了重入锁机制;
  • Redisson还提供了读写锁机制;
  • Redisson提供了计数器和信号量(类似jdk中CountDownLatch和Semphore);
  • 针对redis单点问题,Redisson提供了对Redis哨兵模式、集群模式支持的分布式锁;

springboot整合Redisson

导包:

<!--使用redisson作为分布式锁-->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.17.6</version>
</dependency>

获取RedissonClient的bean:

@Configuration
public class InitRedisConfig {
 
    @Value("${spring.redis.host}")
    private String redisHost;
    @Value("${spring.redis.port}")
    private Integer redisPort;
    @Value("${spring.redis.database}")
    private Integer redisDatabase;
 
    /**
     * 所有对Redisson的使用都是通过RedissonClient对象
     * @return
     */
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient(){
        // 创建配置 指定redis地址及节点信息
        Config config = new Config();
        String redisAddr = "redis://" + redisHost + ":" + redisPort;
        config.useSingleServer()
                .setAddress(redisAddr)
                .setPassword(null)
                .setDatabase(redisDatabase);
 
        // 根据config创建出RedissonClient实例
        return Redisson.create(config);
    }
}

分布式锁减库存:

public void testRedission(String shopCode) {
    String redisLock = "SPY-LOCK";
    RLock rLock = redissonClient.getLock(redisLock);
 
    try {
        rLock.lock(10, TimeUnit.SECONDS);
        int amount = Integer.parseInt(stringRedisTemplate.opsForValue().get(shopCode));
 
        if (amount <= 0) {
            log.info("**************************> 没有库存了");
        } else {
            int result = amount - 1;
            stringRedisTemplate.opsForValue().set(shopCode, String.valueOf(result));
            log.info("**************************> 当前库存为 = " + result);
        }
    } finally {
        rLock.unlock();
    }
}

结果:

  • redisson的非阻塞锁
    利用redission中的tryLock方法,实现非阻塞锁。
public void testRedission(String shopCode) {
    String redisLock = "SPY-LOCK";
    RLock rLock = redissonClient.getLock(redisLock);
 
    try {
        boolean flag = rLock.tryLock(10, TimeUnit.SECONDS);
 
        if (!flag) {
            log.info("++++++++++++++++++++++++++> 没有获取到锁");
            return;
        }
 
        int amount = Integer.parseInt(stringRedisTemplate.opsForValue().get(shopCode));
 
        if (amount <= 0) {
            log.info("**************************> 没有库存了");
        } else {
            int result = amount - 1;
            stringRedisTemplate.opsForValue().set(shopCode, String.valueOf(result));
            log.info("**************************> 当前库存为 = " + result);
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        rLock.unlock();
    }
}
  • redisson自动续期
    实现:
boolean flag = rLock.tryLock(10, 30, TimeUnit.SECONDS);

10:waitTime,等待锁的时间,在这个指定时间内无法获取锁返回失败。
30:leaseTime,锁持有时间,到达指定时间了,锁未持有了,那么锁会释放。
TimeUnit.SECONDS:时间单位

参考


怎样实现redis分布式锁? - Kaito的回答 - 知乎
https://www.zhihu.com/question/300767410/answer/1931519430

https://blog.csdn.net/cz_00001/article/details/127867215

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容