方法一 基于数据实现分布式锁
select * from lock_table where xxx_code = 'xxx' for update;
对于该条数据加锁,可以实现分布式锁
加锁后,可以进行select,不可以进行再次加锁或者更改
释放时,运行 commit 即可
在java项目中需要加事务,否则mapper查询后自动commit
优点
简单方便、易于理解、易于操作
缺点
并发量大时,对数据库压力较大
建议
作为锁的数据库与业务数据库分开
方法二 基于redis实现分布式锁
获取锁
set resource_name my_random_value NX PX 30000
resource_name:资源名称,可根据不同的业务区分不同的锁
my_random_value:随机值,每个线程的随机值都不同,用于释放锁时的校验
NX: key不存在时设置成功,key存在则设置不成功
PX: 自动失效时间,出现异常情况,锁可以过期失效
实现原理
利用NX的原子性,多个线程并发时,只有一个线程可以设置成功
设置成功即获得锁,可以执行后续的业务处理
如果出现异常,过了锁的有效期,锁自动释放
释放锁( 需要校验 value 是否相等)
采用lua脚本
if redis.call( "get" ,KEYS[ 1 ] ) == ARGV[ 1 ]
then return redis.call( "del" ,KEYS[ 1 ] )
else
return 0
end
Demo代码:
@RestController
public class RedisController {
@Autowired
private RedisTemplate redisTemplate;
public String redisLock ( ) {
String key = "redisKey" ;
String value = UUID . randomUUID ( ) . toString ( ) ;
RedisCallback < Boolean > redisCallback = redisConnection -> {
RedisStringCommands. SetOption setOption = RedisStringCommands. SetOption . ifAbsent ( ) ;
Expiration expiration = Expiration . seconds ( 30 ) ;
byte [ ] redisKey = redisTemplate. getKeySerializer ( ) . serialize ( key) ;
byte [ ] redisValue = redisTemplate. getValueSerializer ( ) . serialize ( value) ;
Boolean result = redisConnection. set ( redisKey, redisValue, expiration, setOption) ;
return result;
} ;
Boolean result = ( Boolean ) redisTemplate. execute ( redisCallback) ;
if ( result) System . out. println ( "获取成功" ) ;
String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] \n" +
"\tthen return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
"\treturn 0\n" +
"end" ;
RedisScript < Boolean > redisScript = RedisScript . of ( script, Boolean . class ) ;
List < String > keys = Arrays . asList ( key) ;
Boolean result1 = ( Boolean ) redisTemplate. execute ( redisScript, keys, value) ;
if ( result1) System . out. println ( "释放锁成功" ) ;
return "" ;
}
}
Redis分布式锁抽象类:
package com. example. distributelock. lock ;
import lombok. extern. slf4j. Slf4j ;
import org. springframework. data. redis. connection. RedisStringCommands ;
import org. springframework. data. redis. core. RedisCallback ;
import org. springframework. data. redis. core. RedisTemplate ;
import org. springframework. data. redis. core. script. RedisScript ;
import org. springframework. data. redis. core. types. Expiration ;
import java. util. Arrays ;
import java. util. List ;
import java. util. UUID ;
@Slf4j
public class RedisLock implements AutoCloseable {
private RedisTemplate redisTemplate;
private String key;
private String value;
private int expireTime;
public RedisLock ( RedisTemplate redisTemplate, String key, int expireTime) {
this . redisTemplate = redisTemplate;
this . key = key;
this . expireTime= expireTime;
this . value = UUID . randomUUID ( ) . toString ( ) ;
}
public boolean getLock ( ) {
RedisCallback < Boolean > redisCallback = connection -> {
RedisStringCommands. SetOption setOption = RedisStringCommands. SetOption . ifAbsent ( ) ;
Expiration expiration = Expiration . seconds ( expireTime) ;
byte [ ] redisKey = redisTemplate. getKeySerializer ( ) . serialize ( key) ;
byte [ ] redisValue = redisTemplate. getValueSerializer ( ) . serialize ( value) ;
Boolean result = connection. set ( redisKey, redisValue, expiration, setOption) ;
return result;
} ;
Boolean lock = ( Boolean ) redisTemplate. execute ( redisCallback) ;
return lock;
}
public boolean unLock ( ) {
String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end" ;
RedisScript < Boolean > redisScript = RedisScript . of ( script, Boolean . class ) ;
List < String > keys = Arrays . asList ( key) ;
Boolean result = ( Boolean ) redisTemplate. execute ( redisScript, keys, value) ;
log. info ( "释放锁的结果:" + result) ;
return result;
}
@Override
public void close ( ) throws Exception {
unLock ( ) ;
}
}
{
try ( RedisLock redisLock = new RedisLock ( XXX , XXX , XXX ) )
这样写时,自动调用close ( ) 进行unlock
}
方法三 基于zk实现分布式锁
package com. springboot. test. controller ;
import org. apache. zookeeper. * ;
import org. apache. zookeeper. data. Stat ;
import java. io. IOException ;
import java. nio. charset. StandardCharsets ;
import java. util. Collections ;
import java. util. List ;
public class ZkLock implements AutoCloseable , Watcher {
private ZooKeeper zooKeeper;
private String znode;
public ZkLock ( ) throws IOException {
this . zooKeeper = new ZooKeeper ( "localhost:2181" ,
10000 , this ) ;
}
public Boolean getLock ( String code) {
try {
Stat stat = zooKeeper. exists ( "/" + code, false ) ;
if ( stat== null ) {
zooKeeper. create ( "/" + code, code. getBytes ( StandardCharsets . UTF_8 ) ,
ZooDefs. Ids . OPEN_ACL_UNSAFE , CreateMode . PERSISTENT ) ;
}
znode = zooKeeper. create ( "/" + code + "/" + code + "_" , code. getBytes ( StandardCharsets . UTF_8 ) ,
ZooDefs. Ids . OPEN_ACL_UNSAFE , CreateMode . EPHEMERAL_SEQUENTIAL ) ;
List < String > childrenNodes = zooKeeper. getChildren ( "/" + code, false ) ;
Collections . sort ( childrenNodes) ;
String firstNode = childrenNodes. get ( 0 ) ;
if ( znode. endsWith ( firstNode) ) return true ;
String lastNode = firstNode;
for ( String node : childrenNodes) {
if ( znode. endsWith ( node) ) {
zooKeeper. exists ( "/" + code+ "/" + lastNode, true ) ;
} else {
lastNode = node;
}
}
synchronized ( this ) {
wait ( ) ;
}
return true ;
} catch ( Exception e) {
e. printStackTrace ( ) ;
}
return false ;
}
@Override
public void close ( ) throws Exception {
zooKeeper. delete ( znode, - 1 ) ;
zooKeeper. close ( ) ;
}
@Override
public void process ( WatchedEvent watchedEvent) {
if ( watchedEvent. getType ( ) == Event. EventType. NodeDeleted ) {
synchronized ( this ) {
notify ( ) ;
}
}
}
}
方法四 基于curator实现分布式锁
RetryPolicy retryPolicy = new ExponentialBackoffRetry ( 1000 , 3 ) ;
CuratorFramework client = CuratorFrameworkFactory . newClient ( "localhost:2181" , retryPolicy) ;
client. start ( ) ;
InterProcessMutex lock = new InterProcessMutex ( client, "/order" ) ;
if ( lock. acquire ( 30 , TimeUnit . SECONDS ) ) {
try {
} finally {
lock. release ( ) ;
}
}
client. close ( ) ;
方法五 基于redisson实现分布式锁
Config config = new Config ( ) ;
config. useSingleServer ( ) . setAddress ( "redis://192.168.73.130:6379" ) ;
RedissonClient redisson = Redisson . create ( config) ;
RLock rLock = redisson. getLock ( "order" ) ;
try {
rLock. lock ( 30 , TimeUnit . SECONDS ) ;
log. info ( "我获得了锁!!!" ) ;
Thread . sleep ( 10000 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
} finally {
log. info ( "我释放了锁!!" ) ;
rLock. unlock ( ) ;
}
{
spring boot
@Autowired
private RedissonClient redisson;
@RequestMapping ( "redissonLock" )
public String redissonLock ( ) {
RLock rLock = redisson. getLock ( "order" ) ;
log. info ( "我进入了方法!!" ) ;
try {
rLock. lock ( 30 , TimeUnit . SECONDS ) ;
log. info ( "我获得了锁!!!" ) ;
Thread . sleep ( 10000 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
} finally {
log. info ( "我释放了锁!!" ) ;
rLock. unlock ( ) ;
}
log. info ( "方法执行完成!!" ) ;
return "方法执行完成!!" ;
}
}
对比
数据库 实现简单、易于理解 对数据库压力大
Redis 易于理解 自己实现、不支持阻塞
Zookeeper 支持阻塞 需理解Zookeeper、程序复杂
Curator 提供锁的方法 依赖Zookeeper,强一致
Redisson 提供锁的方法,可阻塞
不推荐自己编写的分布式锁
推荐Redisson和Curator实现的分布式锁