分布式锁
在一些特定业务场景,比如秒杀、提现等情况,需要保证特定业务的执行顺序才能正确的处理业务,而不会出现茅台多卖,薅羊毛等问题出现。在单机部署的系统中,我们可以通过JDK提供的synchronized关键字、ReentranLock,或者基于AQS定制化锁,单机部署的情况下,锁是在多线程之间共享的,但是分布式部署的场景下,却无法提供多进程间的共享。因此需要使用分布式锁来进行多进程间共享。
分布式锁的实现方式有很多种,SoFast框架内置提供基于Redis的分布式锁实现,如果使用该功能,请现在pom.xml中添加依赖:
<dependency>
<groupId>com.sofast.cloud</groupId>
<artifactId>so-fast-lock-starter</artifactId>
</dependency>
在配置文件中正常进行Redis配置即可:
spring:
redis:
# 地址
host: xxxx
port: xxxx
# 密码
password: xxxxxxx
# 连接超时时间
timeout: 15s
jedis:
pool:
# 连接池中的最小空闲连接
min-idle: 3
# 连接池中的最大空闲连接
max-idle: 10
# 连接池的最大数据库连接数
max-active: 50
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
database: 4
使用
在业务开发中,如果要使用分布式锁,请参考以下两种方式:
1.注解方式
SOFAST框架中封装了分布式锁注解,通过注解可以非常方便的开启分布式锁
例如:@RedisLock(redisKey = "test-redis-lock-1", waitTime = 5)
使用分布式锁注解,必须提供两个属性:
redisKey:指明在redis中存储的key,请保证唯一性
waitTime:获取锁时的最大等待时间,单位秒
注意点:waitTime需要根据业务情况设置等待时间,目前redis锁的默认最大锁定时间为60秒,因此该值必须在60秒以内,实际上再业务处理中也很难达到60秒的等待时间。
该分布式锁可用于解决Schedule定时任务多节点部署的问题,可通过使用分布式锁,防止任务重复执行:
1.添加RedisLock注解,并配置参数,waitTime需要根据业务时间处理,例如配置3秒,那么在实际的业务执行中,要保证3秒钟不释放锁,即可保证锁的成功。实现方式很多,例如可显示的强制线程锁定6秒:Thread.Sleep(6000)
2.自定义方式
自定义方式在使用上更加灵活,能满足各种业务场景,但是在处理上要麻烦一些,锁的获取、释放等需要业务编码进行控制。
注入Bean
@Autowired
RedisLockRegistry lockRegistry;
定义一个锁
Lock lock = lockRegistry.obtain("test-key");
注意:这里的key是在redis中存储的key,要保证业务唯一性。key的粒度要尽量小,比如:「xxx业务-xxx用户-xxx方法」等组成的key,可以减少锁冲突。
持有锁,并进行业务处理
if (lock.tryLock(3, TimeUnit.SECONDS)) {
// 持有锁成功,进行业务处理
}
注意:tryLock时,可以指定时间参数,该参数表示等待获取锁的最大时间。
业务处理完成后,释放锁
lock.unlock();
注意:unlock一定要在finally中执行,保证可以正确的释放锁。