java设计的订单锁怎么解决_Java分布式锁的三种实现方案

本文介绍了Java中三种常见的分布式锁实现方案:数据库乐观锁、基于Redis的分布式锁及基于Zookeeper的分布式锁。乐观锁通过版本号避免并发更新导致的数据错误,Redis方案利用SETNX、GETSET等命令确保锁的原子性和防止死锁,而Zookeeper方案通过临时顺序节点实现锁的公平性和可扩展性。每种方案都有其适用场景和优缺点,读者可根据实际需求选择适合的分布式锁实现。
摘要由CSDN通过智能技术生成

方案一:数据库乐观锁

乐观锁通常实现基于数据版本(version)的记录机制实现的,比如有一张红包表(t_bonus),有一个字段(left_count)记录礼物的剩余个数,用户每领取一个奖品,对应的left_count减1,在并发的情况下如何要保证left_count不为负数,乐观锁的实现方式为在红包表上添加一个版本号字段(version),默认为0。

异常实现流程

-- 可能会发生的异常情况

-- 线程1查询,当前left_count为1,则有记录

select * from t_bonus where id = 10001 and left_count > 0

-- 线程2查询,当前left_count为1,也有记录

select * from t_bonus where id = 10001 and left_count > 0

-- 线程1完成领取记录,修改left_count为0,

update t_bonus set left_count = left_count - 1 where id = 10001

-- 线程2完成领取记录,修改left_count为-1,产生脏数据

update t_bonus set left_count = left_count - 1 where id = 10001

通过乐观锁实现

-- 添加版本号控制字段

ALTER TABLE table ADD COLUMN version INT DEFAULT '0' NOT NULL AFTER t_bonus;

-- 线程1查询,当前left_count为1,则有记录,当前版本号为1234

select left_count, version from t_bonus where id = 10001 and left_count > 0

-- 线程2查询,当前left_count为1,有记录,当前版本号为1234

select left_count, version from t_bonus where id = 10001 and left_count > 0

-- 线程1,更新完成后当前的version为1235,update状态为1,更新成功

update t_bonus set version = 1235, left_count = left_count-1 where id = 10001 and version = 1234

-- 线程2,更新由于当前的version为1235,udpate状态为0,更新失败,再针对相关业务做异常处理

update t_bonus set version = 1235, left_count = left_count-1 where id = 10001 and version = 1234

方案二:基于Redis的分布式锁

SETNX命令(SET if Not eXists)\

语法:SETNX key value\

功能:原子性操作,当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。\

Expire命令\

语法:expire(key, expireTime)\

功能:key设置过期时间\

GETSET命令\

语法:GETSET key value\

功能:将给定 key 的值设为 value ,并返回 key 的旧值 (old value),当 key 存在但不是字符串类型时,返回一个错误,当key不存在时,返回nil。\

GET命令\

语法:GET key\

功能:返回 key 所关联的字符串值,如果 key 不存在那么返回特殊值 nil 。\

DEL命令\

语法:DEL key [KEY …]\

功能:删除给定的一个或多个 key ,不存在的 key 会被忽略。

第一种:使用redis的setnx()、expire()方法,用于分布式锁

setnx(lockkey, 1) 如果返回0,则说明占位失败;如果返回1,则说明占位成功

expire()命令对lockkey设置超时时间,为的是避免死锁问题。

执行完业务代码后,可以通过delete命令删除key。

这个方案其实是可以解决日常工作中的需求的,但从技术方案的探讨上来说,可能还有一些可以完善的地方。比如,如果在第一步setnx执行成功后,在expire()命令执行成功前,发生了宕机的现象,那么就依然会出现死锁的问题

第二种:使用redis的setnx()、get()、getset()方法,用于分布式锁,解决死锁问题

setnx(lockkey, 当前时间+过期超时时间) ,如果返回1,则获取锁成功;如果返回0则没有获取到锁,转向2。

get(lockkey)获取值oldExpireTime ,并将这个value值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转向3。

计算newExpireTime=当前时间+过期超时时间,然后getset(lockkey, newExpireTime) 会返回当前lockkey的值currentExpireTime。

判断currentExpireTime与oldExpireTime 是否相等,如果相等,说明当前getset设置成功,获取到了锁。如果不相等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试。

在获取到锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,如果小于锁设置的超时时间,则直接执行delete释放锁;如果大于锁设置的超时时间,则不需要再锁进行处理。

import cn.com.tpig.cache.redis.RedisService;

import cn.com.tpig.utils.SpringUtils;

/**

* Created by IDEA

* User: shma1664

* Date: 2016-08-16 14:01

* Desc: redis分布式锁

*/

public final class RedisLockUtil {

private static final int defaultExpire = 60;

private RedisLockUtil() {

//

}

/**

* 加锁

* @param key redis key

* @param expire 过期时间,单位秒

* @return true:加锁成功,false,加锁失败

*/

public static boolean lock(String key, int expire) {

RedisService redisService = SpringUtils.getBean(RedisService.class);

long status = redisService.setnx(key, "1");

if(status == 1) {

redisService.expire(key, expire);

return true;

}

return false;

}

public static boolean lock(String key) {

return lock2(key, defaultExpire);

}

/**

* 加锁

* @param key redis key

* @param expire 过期时间,单位秒

* @return true:加锁成功,false,加锁失败

*/

public static boolean lock2(String key, int expire) {

RedisService redisService = SpringUtils.getBean

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值