首先目前基本用的Redis 分布式锁有两种:
1、Redis 锁通常通过 SETNX(SET if Not eXists)命令实现,该命令只在键不存在时设置值。结合 TTL(Time To Live)可以防止锁永远不释放的问题。
2、Redisson 是一个在 Redis 基础上实现的 Java 分布式锁库,提供了更多高级功能,如可重入锁、公平锁等。
先说一下第一种:
使用 SETNX 锁实现的分布式锁通常比较简单,适用于一些简单的场景和对实时性要求不高的场景。对于一些对实时性要求较高、对锁的粒度要求较严格的场景,可能需要使用更为复杂的分布式锁实现方式。
假设我们有一个在线商城系统,用户下单时需要扣减商品库存。在高并发情况下,多个用户可能同时下单购买同一件商品,为了避免超卖(即卖出超过库存数量)的情况发生,我们可以使用 SETNX 锁来控制对商品库存的访问。
import redis.clients.jedis.Jedis;
public class StockService {
// Redis 连接地址
private static final String REDIS_HOST = "127.0.0.1";
private static final int REDIS_PORT = 6379;
// 商品库存键名
private static final String STOCK_KEY = "product_stock";
public static void main(String[] args) {
// 模拟多个用户同时下单
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
// 创建 Redis 连接
Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);
// 尝试设置库存锁,设置成功表示获取到了锁
Long result = jedis.setnx(STOCK_KEY + "_lock", "1");
if (result == 1) {
// 获取到了锁,继续执行业务逻辑
int stock = Integer.parseInt(jedis.get(STOCK_KEY));
if (stock > 0) {
// 如果库存大于 0,则扣减库存,并模拟下单操作
jedis.decr(STOCK_KEY);
System.out.println("用户下单成功,剩余库存:" + (stock - 1));
} else {
System.out.println("库存不足,无法下单");
}
} else {
// 没有获取到锁,说明有其他线程正在操作库存,无法下单
System.out.println("库存操作繁忙,无法下单,请稍后再试");
}
// 释放库存锁
jedis.del(STOCK_KEY + "_lock");
// 关闭 Redis 连接
jedis.close();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
第二种:
常见的场景是在分布式系统中使用分布式锁来确保只有一个服务实例执行某个特定的任务或操作,避免出现并发问题或资源竞争。
例如,考虑一个电商网站的订单库存管理系统。假设有多个服务实例同时监听用户的订单提交请求,并且在用户下单时需要从库存中减去相应的数量。在这种情况下,就需要确保同一时间只有一个服务实例能够操作库存,以避免库存数量被错误地减少超过实际库存量。
在这个场景下,可以使用分布式锁来保护对库存的操作。当某个服务实例要修改库存时,先尝试获取一个分布式锁,如果获取成功,则执行库存操作;如果获取失败,则表示有其他服务实例正在执行库存操作,此时等待或放弃操作。
通过使用分布式锁,可以确保同一时间只有一个服务实例能够修改库存,从而避免了因并发操作而导致的库存不一致或超卖的问题。
public class InventoryManagement {
private static final String LOCK_KEY = "inventory_lock";
private static final String INVENTORY_KEY = "inventory";
public static void main(String[] args) {
// 创建 Redisson 客户端连接
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redissonClient = Redisson.create(config);
// 获取可重入锁对象
RLock lock = redissonClient.getLock(LOCK_KEY);
try {
// 尝试获取锁,最多等待 10 秒
boolean isLocked = lock.tryLock(10, 10, java.util.concurrent.TimeUnit.SECONDS);
if (isLocked) {
// 获取锁成功,执行库存操作
int currentInventory = getCurrentInventoryFromDatabase(); // 从数据库中获取当前库存
**if (currentInventory > 0) {**
// 减少库存数量
decreaseInventoryInDatabase();
System.out.println("库存减少成功,当前库存为: " + (currentInventory - 1));
} else {
System.out.println("库存不足,无法减少库存");
}
} else {
// 获取锁失败,处理失败逻辑
System.out.println("获取锁失败,处理失败逻辑...");
}
} catch (InterruptedException e) {
// 处理异常
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
// 关闭 Redisson 客户端连接
redissonClient.shutdown();
}
}
private static int getCurrentInventoryFromDatabase() {
// 从数据库中获取当前库存数量
// 这里省略实际代码,用假数据代替
return 10;
}
private static void decreaseInventoryInDatabase() {
// 减少数据库中的库存数量
// 这里省略实际代码
}
}
这里我一开始有疑惑就是为啥正常if…else也可以呀,但是又想到是分布式,他要求每次只有一个线程在存取数据,所以这里就是要用到锁。
说到这个Resioon里面有个RedLock算法常见:
public class RateLimiterService {
private static final String REDIS_CLUSTER_NODES = "redis://127.0.0.1:6379"; // Redis集群节点地址
private static final String LOCK_KEY = "rate_limiter_lock"; // 锁的名称
public static void main(String[] args) {
// 创建 Redisson 客户端连接
Config config = new Config();
config.useClusterServers().addNodeAddress(REDIS_CLUSTER_NODES);
RedissonClient redissonClient = Redisson.create(config);
// 获取 RedLock 对象
RLock redLock = redissonClient.getRedLock(LOCK_KEY);
try {
// 尝试获取 RedLock,最多等待 10 秒
boolean isLocked = redLock.tryLock(10, TimeUnit.SECONDS);
if (isLocked) {
// 获取锁成功,执行限流器逻辑
System.out.println("限流器逻辑:允许通过");
} else {
// 获取锁失败,处理失败逻辑
System.out.println("获取锁失败,无法通过限流器");
}
} catch (InterruptedException e) {
// 处理异常
e.printStackTrace();
} finally {
// 释放锁
redLock.unlock();
// 关闭 Redisson 客户端连接
redissonClient.shutdown();
}
}
}
这就是我的总结,惭愧惭愧。