Redis锁保障线上订单不重复(redis锁防止重复下单)
操作
Redis是支持多种数据类型的高性能NoSQL内存数据库,其中也包括支持分布式锁功能,可以大大提高系统的可用性。网站应用在做电子商务或者是支付类应用时,常见的经典场景是,多个用户同时下单争抢同一个库存商品,此时需要设计一套可靠的方案来保障订单不重复操作,就可以用Redis锁来解决。
下面通过实例来介绍Redis的锁功能,建议读者使用Java实现,这样可以更好地结合原生redis命令行。
1.可重入锁:
用户请求下单时首先使用的是可重入锁,也就是说,同一个线程/进程调用同一把锁,调用次数可以超过1次,会累积计算,而不会发生死锁,代码如下:
jedis = new Jedis(“localhost”);
String key = “order_” + orderId;
String requestId = UUID.randomUUID().toString();
// SET NX PX 10: 这个命令是Set if Not Exists,带上10毫秒的超时值以保证获取锁的及时性
String result = jedis.set(key, requestId, “NX”, “PX”, 10);
if (“OK”.equals(result)) {
// 获取锁成功,执行自己的逻辑
}
// 解锁:判断当前锁是否属于自己,如果是,再次使用set操作释放锁,如果不是,怎不做任何操作
String script = “if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘del’, KEYS[1]) else return 0 end”;
Object obj = jedis.eval(script, Collections.singletonList(key), Collections.singletonList(requestId));
2.自旋锁:
当可重入锁(非阻塞)获取锁失败,返回错误时,用户可以选择使用自旋锁(阻塞)来重新获取锁,如下所示:
boolean locked = false;
String key = “order_” + orderId;
String requestId = UUID.randomUUID().toString();
while (!locked) {
// 自旋锁:一旦获取锁失败,立刻重试
locked = jedis.set(key, requestId, “NX”, “PX”, 10) == “OK”;
}
// 获取锁成功,执行自己的逻辑
// 解锁:判断当前锁是否属于自己,如果是,再次使用set操作释放锁,如果不是,怎不做任何操作,此时不允许多线程对同一把锁进行删除,以免出现脏读
String script = “if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘del’, KEYS[1]) else return 0 end”;
Object obj = jedis.eval(script, Collections.singletonList(key), Collections.singletonList(requestId));
以上便是使用Redis锁来保证系统订单不重复操作的一种常用方案,基本上也是目前比较好的解决方案。当然,由于Redis不适合所有的场景,当多结点分布式部署时,建议使用更加稳定并且支持分布式集群的ZooKeeper,来进行订单锁定。