在Java项目中,分布式锁的应用场景广泛,主要用于解决分布式系统中的资源争用问题,确保并发操作的安全性和数据一致性。
以下是一些具体的应用场景:
1. 分布式定时任务
- 场景描述:在分布式系统中,多个节点可能同时运行相同的定时任务。为了避免任务重复执行导致资源浪费或数据不一致,需要使用分布式锁确保同一时刻只有一个节点在执行任务。
2. 数据库事务
- 场景描述:在分布式系统中,多个节点可能同时访问同一个数据库。为了保证数据的一致性和避免冲突,可以使用分布式锁来控制对数据库事务的并发访问。
3. 分布式缓存
- 场景描述:在分布式缓存中,多个节点可能同时访问同一个缓存对象。为了避免缓存击穿、雪崩或数据不一致,可以使用分布式锁来同步对缓存对象的访问。
4. 分布式信号量
- 场景描述:在某些情况下,需要限制同时访问某个资源的节点数量。例如,在线游戏中限制同时登录的玩家数量。这时可以使用分布式锁来实现分布式信号量,控制并发访问量。
5. 分布式任务队列
- 场景描述:在分布式任务队列中,多个节点可能同时竞争执行任务队列中的任务。使用分布式锁可以保证只有一个节点获得执行权限,避免任务重复执行和并发问题。
6. 分布式集群的负载均衡
- 场景描述:在分布式集群中,多个节点可能同时处理同一个请求。为了避免多个节点同时访问同一资源导致数据不一致或冲突,可以使用分布式锁来确保资源的单一访问性。
7. 防止重复提交
- 场景描述:在分布式系统中,如果一个请求同时发往多个服务实例,可能会出现重复提交的现象。使用分布式锁可以确保同一个请求只被处理一次,避免重复提交导致的资源浪费或数据不一致。
8. 分布式限流
- 场景描述:在某些高并发场景下,为了保护系统稳定和资源安全,需要对访问频率进行限制。使用分布式锁可以实现分布式限流,确保系统的负载在可控范围内。
9. 集群缓存同步
- 场景描述:在分布式系统中,多个节点上的缓存需要保持同步以避免数据不一致。使用分布式锁可以确保在更新缓存时只有一个节点进行操作,其他节点在缓存更新完成后再进行同步。
10. 分布式资源分配
- 场景描述:在分布式系统中,可能需要分配共享资源给多个节点使用。使用分布式锁可以确保在分配资源时只有一个节点进行操作,避免资源冲突和数据不一致。
示例
场景描述
在一个电商网站的秒杀活动中,多个用户同时购买同一个商品,由于系统采用分布式部署,多个服务实例可能同时处理这些请求。
为了避免库存超卖的问题,可以使用分布式锁来确保同一时间只有一个订单可以下单。
分布式锁实现步骤
- 引入依赖:首先,在项目中引入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("未获得锁, 请求被跳过");
}
}
}
示例说明
- 初始化:
• 在应用启动时,初始化Jedis连接池,以便后续操作Redis。
- 下单逻辑:
• 当用户下单时,调用placeOrder
方法。
• 在placeOrder
方法中,首先尝试获取分布式锁。
• 如果获取锁成功,则进入临界区执行库存扣减逻辑。
• 库存扣减成功后,执行其他下单逻辑,如生成订单、更新用户余额等。
• 最后,在finally
块中释放锁,确保即使发生异常也能正确释放锁。
• 如果获取锁失败,则打印日志并跳过当前请求,避免重复扣减库存。
通过这种方式,可以确保在分布式环境中同一时间只有一个订单可以扣减库存,从而避免库存超卖的问题。