Redis高级篇(分布式锁)

目录

1、什么是分布式锁? 

2、分布式锁能够解决什么问题?

3、写好一把锁需要的条件和刚需是什么?

4、分布式锁出现超买超卖的经典案例

5、Redisson分布式锁引入


1、什么是分布式锁? 

        分布式锁是一种用于在分布式系统中同步访问共享资源的机制。在分布式系统中,多个节点同时访问共享资源可能会导致数据不一致或冲突。分布式锁通过在多个节点之间协调和管理共享资源的访问,确保同一时间只有一个节点能够访问该资源,从而保证数据的一致性和可靠性。

2、分布式锁能够解决什么问题?

  1. 并发控制:多个客户端同时访问共享资源时,分布式锁保证只有一个客户端能够对资源进行操作,避免了并发访问导致的数据冲突和竞争条件。

  2. 避免资源浪费:分布式锁可以防止多个客户端同时执行一段代码,从而避免了资源浪费和重复计算。

  3. 数据一致性:当多个客户端需要对共享数据进行更新时,分布式锁可以保证同一时间只能有一个客户端对数据进行修改,从而保持数据的一致性。

  4. 防止死锁:分布式锁可以帮助避免分布式系统中的死锁问题,确保共享资源的正常释放和使用。

  5. 防止数据竞争:分布式锁可以避免多个线程对共享资源的无序访问,保证数据的正确性和完整性。

3、写好一把锁需要的条件和刚需是什么?

1.独占性:任何时刻只能有且仅有一个线程持有

2.高可用:在Redis集群环境下,不能因为某一个节点挂了而出现获取锁失败

3.防死锁:杜绝死锁,必须要有超时控制或者撤销操作

4.不乱抢:不能私下unclock别人的锁,只能自己加锁自己解

5.可重入:同一个节点的同一个线程如果获得锁之后,它可以再次获取该锁

4、分布式锁出现超买超卖的经典案例

  • 单机场景下的代码示例(有两个微服务,此时未添加分布式锁)
package com.toonyoo.redis.CloudLock;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Service
public class StockService {

    private String _MSG = "";

    private String STOCK_KEY = "stock";

    // 定义lock锁
    private static final Lock lock = new ReentrantLock();


    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 模拟单机、高并发场景下,库存售卖的基本业务逻辑
     * 调用库存售卖接口,库存减一
     * @return
     */
    public String sale() {

        lock.lock();
        try {
            // 1.查询Redis库存
            String result = redisTemplate.opsForValue().get(STOCK_KEY);
            // 2.判断库存是否足够
            if (Integer.parseInt(result) > 0){
                // 3.更新库存
                redisTemplate.opsForValue().set(STOCK_KEY,  Integer.toString(Integer.parseInt(result) - 1));
                _MSG = "库存充足,下单成功!";
            }else {
                return _MSG = "库存不充足,下单失败!";
            }
        }finally {
            lock.unlock();
        }
        return _MSG;
    }
}

高并发场景下进行压测,明显发现库存超卖,可知上述单机锁失效,原因在于Lock锁只能作用于单个JVM实例,在分布式场景下(即多个JVM)操作同一共享资源时,单机锁失效。


  •  微服务场景下引入分布式锁

    /**
     * 模拟微服务、高并发场景下,库存售卖的基本业务逻辑,使用Redis分布式锁
     * 调用库存售卖接口,库存减一
     * version 2.0
     *
     * @return
     */
    public String saleV2() {

        String REDIS_KEY = "redis_lock";

        // 使用uuid+线程id当做全局唯一id
        String REDIS_KEY_VALUE = IdUtil.simpleUUID().toString() + Thread.currentThread().getId();

        // 模拟CAS自旋操作,使用setnx命令,尝试对Redis加锁,并设置过期时间
        while (!redisTemplate.opsForValue().setIfAbsent(REDIS_KEY, REDIS_KEY_VALUE, 30L, TimeUnit.SECONDS)){
            // 暂停20ms,尝试再次加锁
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        // 加锁不成功,则会一直尝试加锁,若加锁成功,则会进入下方代码块
        try {
            // 1.查询Redis库存
            String result = redisTemplate.opsForValue().get(STOCK_KEY);
            // 2.判断库存是否足够
            if (Integer.parseInt(result) > 0) {
                // 3.更新库存
                Integer stock = Integer.parseInt(result);
                redisTemplate.opsForValue().set(STOCK_KEY, Integer.toString(--stock));
                _MSG = "库存充足,下单成功!剩余库存" + stock;
            } else {
                return _MSG = "库存不充足,下单失败!剩余库存" + redisTemplate.opsForValue().get(STOCK_KEY);
            }
        } finally {
            // 解锁操作,同时也要防止误删除 (此处代码依然会因为非原子操作而引发问题,要想真正解决得使用Lua脚本)
            if (redisTemplate.opsForValue().get(REDIS_KEY).equalsIgnoreCase(REDIS_KEY_VALUE)){
                redisTemplate.delete(REDIS_KEY);
            }
        }
        return _MSG;
    }

5、Redisson分布式锁引入

        红锁算法(Redlock Algorithm)是一种用于分布式系统中实现分布式锁的算法。它由Redis的作者提出,用于解决分布式环境下的并发访问问题。

红锁算法的基本原理如下:

  1. 获取锁:当一个客户端需要获取锁时,它会向多个Redis节点(通常是3个或5个)发送SET命令,这3或5个Redis节点均是master,它们之间没有任何关系,每个命令都设置一个相同的键和值,并附带一个超时时间。

  2. 大部分节点都成功设置锁:如果大多数节点(例如大于等于N/2+1,其中N是Redis节点的总数)都能成功设置锁,那么该客户端就会认为自己成功获取到了锁。

  3. 获取锁失败:如果大多数节点设置锁失败,那么客户端认为自己没有获取到锁,并放弃当前的操作。

  4. 锁的释放:当客户端释放锁时,它会向所有节点发送DEL命令进行解锁。

Java代码实现Redisson分布式锁

1、引入pom依赖 

<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.24.3</version>
</dependency>

2、RedisConfig

package com.toonyoo.config;


import org.redisson.Redisson;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


/**
 * Redis相关配置
 */
@Configuration
public class RedisConfig {

    /**
     * Redisson配置方法
     * @return
     */
    @Bean
    public Redisson getRedisson() {

        // 创建config对象
        Config config = new Config();
        // Redis单节点,配置数据库地址,选择几号库,数据库连接密码等
        config.useSingleServer().setAddress("redis://192.168.10.128:6379").setDatabase(0).setPassword("11111");

        // Redis多节点配置
        // config.useClusterServers().addNodeAddress("redis://192.168.10.126:6379","redis://192.168.10.127:6379","redis://192.168.10.128:6379");
        return (Redisson) Redisson.create(config);
    }
}

 3、业务代码之Redisson分布式锁的运用

    @Autowired
    private Redisson redisson;

    public String saleByRedisson() {

        String REDIS_KEY = "redis_lock";

        RLock redissonLock = redisson.getLock(REDIS_KEY);
        redissonLock.lock(20,TimeUnit.MILLISECONDS);
        try {
            // 1.查询Redis库存
            String result = redisTemplate.opsForValue().get(STOCK_KEY);
            // 2.判断库存是否足够
            if (Integer.parseInt(result) > 0) {
                // 3.更新库存
                Integer stock = Integer.parseInt(result);
                redisTemplate.opsForValue().set(STOCK_KEY, Integer.toString(--stock));
                _MSG = "库存充足,下单成功!剩余库存" + stock;
            } else {
                return _MSG = "库存不充足,下单失败!剩余库存" + redisTemplate.opsForValue().get(STOCK_KEY);
            }
        } finally {
            // 解锁操作,同时也要防止误删除(只有当 当前锁被占有并且是当前所属线程占有才能够解锁,此处方法底层使用了Lua脚本封装,可保证原子性操作)
            if (redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()){
                redissonLock.unlock();
            }
        }
        return _MSG;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值