分布式锁在项目中的应用场景

13 阅读5分钟

image.png 在Java项目中,分布式锁的应用场景广泛,主要用于解决分布式系统中的资源争用问题,确保并发操作的安全性和数据一致性。

以下是一些具体的应用场景:

1. 分布式定时任务

  • 场景描述:在分布式系统中,多个节点可能同时运行相同的定时任务。为了避免任务重复执行导致资源浪费或数据不一致,需要使用分布式锁确保同一时刻只有一个节点在执行任务。

2. 数据库事务

  • 场景描述:在分布式系统中,多个节点可能同时访问同一个数据库。为了保证数据的一致性和避免冲突,可以使用分布式锁来控制对数据库事务的并发访问。

3. 分布式缓存

  • 场景描述:在分布式缓存中,多个节点可能同时访问同一个缓存对象。为了避免缓存击穿、雪崩或数据不一致,可以使用分布式锁来同步对缓存对象的访问。

4. 分布式信号量

  • 场景描述:在某些情况下,需要限制同时访问某个资源的节点数量。例如,在线游戏中限制同时登录的玩家数量。这时可以使用分布式锁来实现分布式信号量,控制并发访问量。

5. 分布式任务队列

  • 场景描述:在分布式任务队列中,多个节点可能同时竞争执行任务队列中的任务。使用分布式锁可以保证只有一个节点获得执行权限,避免任务重复执行和并发问题。

6. 分布式集群的负载均衡

  • 场景描述:在分布式集群中,多个节点可能同时处理同一个请求。为了避免多个节点同时访问同一资源导致数据不一致或冲突,可以使用分布式锁来确保资源的单一访问性。

7. 防止重复提交

  • 场景描述:在分布式系统中,如果一个请求同时发往多个服务实例,可能会出现重复提交的现象。使用分布式锁可以确保同一个请求只被处理一次,避免重复提交导致的资源浪费或数据不一致。

8. 分布式限流

  • 场景描述:在某些高并发场景下,为了保护系统稳定和资源安全,需要对访问频率进行限制。使用分布式锁可以实现分布式限流,确保系统的负载在可控范围内。

9. 集群缓存同步

  • 场景描述:在分布式系统中,多个节点上的缓存需要保持同步以避免数据不一致。使用分布式锁可以确保在更新缓存时只有一个节点进行操作,其他节点在缓存更新完成后再进行同步。

10. 分布式资源分配

  • 场景描述:在分布式系统中,可能需要分配共享资源给多个节点使用。使用分布式锁可以确保在分配资源时只有一个节点进行操作,避免资源冲突和数据不一致。

示例

场景描述

在一个电商网站的秒杀活动中,多个用户同时购买同一个商品,由于系统采用分布式部署,多个服务实例可能同时处理这些请求。

为了避免库存超卖的问题,可以使用分布式锁来确保同一时间只有一个订单可以下单。

分布式锁实现步骤

  1. 引入依赖:首先,在项目中引入Jedis库来操作Redis。在Maven项目的pom.xml中添加以下依赖:
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.0.1</version>
</dependency>

2. 创建分布式锁工具类:实现一个基于Redis的分布式锁工具类,该类包含获取锁、释放锁等方法。

import redis.clients.jedis.Jedis;

public class RedisDistributedLock {
    private Jedis jedis;
    private String lockKey;
    private int expireTime;

    public RedisDistributedLock(Jedis jedis, String lockKey, int expireTime) {
        this.jedis = jedis;
        this.lockKey = lockKey;
        this.expireTime = expireTime;
    }

    public boolean lock() {
        long result = jedis.setnx(lockKey, String.valueOf(System.currentTimeMillis() + expireTime));
        if (result == 1) {
            jedis.expire(lockKey, expireTime);
            return true;
        } else {
            String currentValue = jedis.get(lockKey);
            if (currentValue != null && Long.parseLong(currentValue) < System.currentTimeMillis()) {
                String oldValue = jedis.getSet(lockKey, String.valueOf(System.currentTimeMillis() + expireTime));
                if (oldValue != null && oldValue.equals(currentValue)) {
                    jedis.expire(lockKey, expireTime);
                    return true;
                }
            }
        }
        return false;
    }

    public void unlock() {
        String currentValue = jedis.get(lockKey);
        if (currentValue != null && Long.parseLong(currentValue) > System.currentTimeMillis()) {
            jedis.del(lockKey);
        }
    }
}

3. 使用分布式锁控制库存扣减:在商品下单的逻辑中,使用分布式锁来确保同一时间只有一个订单可以扣减库存。

import redis.clients.jedis.Jedis;

public class OrderService {
    private Jedis jedis;
    private String lockKey = "product_stock_lock";
    private int expireTime = 5000; // 锁的有效期,单位毫秒

    public OrderService(Jedis jedis) {
        this.jedis = jedis;
    }

    public void placeOrder(int productId, int quantity) {
        RedisDistributedLock lock = new RedisDistributedLock(jedis, lockKey, expireTime);
        if (lock.lock()) {
            try {
                int stock = Integer.parseInt(jedis.get("stock:" + productId));
                if (stock >= quantity) {
                    jedis.set("stock:" + productId, String.valueOf(stock - quantity));
                    System.out.println("扣减成功, 剩余库存: " + (stock - quantity));
                    // 执行其他下单逻辑,如生成订单、更新用户余额等
                } else {
                    System.out.println("扣减失败, 库存不足");
                }
            } finally {
                lock.unlock();
            }
        } else {
            System.out.println("未获得锁, 请求被跳过");
        }
    }
}

示例说明

  1. 初始化

• 在应用启动时,初始化Jedis连接池,以便后续操作Redis。

  1. 下单逻辑

• 当用户下单时,调用placeOrder方法。

• 在placeOrder方法中,首先尝试获取分布式锁。

• 如果获取锁成功,则进入临界区执行库存扣减逻辑。

• 库存扣减成功后,执行其他下单逻辑,如生成订单、更新用户余额等。

• 最后,在finally块中释放锁,确保即使发生异常也能正确释放锁。

• 如果获取锁失败,则打印日志并跳过当前请求,避免重复扣减库存。

通过这种方式,可以确保在分布式环境中同一时间只有一个订单可以扣减库存,从而避免库存超卖的问题。