使用Redisson实现分布式锁
官网提供了中英的教程与介绍。
1. 前言
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(
BitSet
,Set
,Multimap
,SortedSet
,Map
,List
,Queue
,BlockingQueue
,Deque
,BlockingDeque
,Semaphore
,Lock
,AtomicLong
,CountDownLatch
,Publish / Subscribe
,Bloom filter
,Remote service
,Spring cache
,Executor service
,Live Object service
,Scheduler service
) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
1.1 引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.3</version>
</dependency>
1.2 构建RedissonClient
对象
程序构建方式
@Bean
public RedissonClient redissonClient() throws IOException {
Config config = new Config();
config.setTransportMode(TransportMode.NIO);
//使用单节点模式
config.useSingleServer()
//可以用"rediss://"来启用SSL连接
.setAddress("redis://localhost:6379");
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
文件方式配置 (具体看官网)
2. 简单使用
详细的锁介绍看官网(点击打开)
@Autowired
private RedissonClient redisson;
private Integer count = 1000;
private ExecutorService pool = Executors.newFixedThreadPool(100);
@Test
public void Test() throws InterruptedException {
this.noLock();
this.youLock();
}
/**
* 使用可重入锁,加锁,数字输出正常了
*/
public void youLock() throws InterruptedException {
//获取锁
RLock lock = redisson.getLock("youLock");
for (int i = 1; i <= 1000; i++) {
pool.execute(() -> {
lock.lock(); //上锁
try {
count--;
log.info("integer为{}", count);
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
});
}
TimeUnit.MINUTES.sleep(1);
}
/**
* 没有使用到锁,输出了重复的数字,或者没有到0
*/
public void noLock() throws InterruptedException {
for (int i = 1; i <= 1000; i++) {
pool.execute(() -> {
count--;
log.info("integer为{}", count);
});
}
TimeUnit.MINUTES.sleep(1);
}
3. 结合AOP实现分布式锁
自行加上aop依赖。
3.1 定义上锁RedisLock
注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RedisLock {
/** 锁的key */
String key();
/** 锁的过期秒数,默认是10秒 */
int expire() default 10;
/** 尝试加锁,最多等待时间, 默认10秒 */
long waitTime() default 10L;
/** 锁的超时,时间单位 默认秒 */
TimeUnit timeUnit() default TimeUnit.SECONDS;
}
3.2 编写aop
切面类
(使用环绕通知) (注解方式配置aop)
@Aspect
@Component
public class LockMethodAspect {
private Logger log = LoggerFactory.getLogger(getClass());
private RedissonClient redisson;
@Autowired
public LockMethodAspect(RedissonClient redisson) {
this.redisson = redisson;
}
/**
* 切入点
*
* @author: zhihao
* @date: 2020/4/8
*/
@Pointcut("@annotation(com.zhihao.annotation.RedisLock)")
public void onLock() {
}
/**
* 环绕通知
*
* @param point
* @return java.lang.Object
* @author: zhihao
* @date: 2020/4/8
*/
@Around("onLock()")
public Object around(ProceedingJoinPoint point) throws InterruptedException {
//1.获取方法签名
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
Object[] args = point.getArgs();
RedisLock redisLock = method.getAnnotation(RedisLock.class);
//获取锁的key
String key = redisLock.key();
if (StrUtil.isBlank(key)) {
throw new RuntimeException("分布式锁键不能为空");
}
//获取锁
RLock lock = redisson.getLock(key);
//尝试加锁
boolean tryLock = lock.tryLock(redisLock.waitTime(), redisLock.expire(), redisLock.timeUnit());
if (tryLock){
try {
return point.proceed();
} catch (Throwable throwable) {
log.info("执行出现问题:{}", throwable.getMessage());
throwable.printStackTrace();
}finally {
//释放锁
if (lock.isLocked()) {
lock.unlock();
}
}
}
return null;
}
}
3.3 进行测试
@SpringBootTest
@RunWith(value = SpringRunner.class)
@Slf4j
public class RedissonTest {
private Integer count = 1000;
@Autowired
private RedissonClient redissonClient;
private ExecutorService pool = Executors.newFixedThreadPool(100);
@Test
public void Test() throws InterruptedException {
// IntStream.range(0, 1000).forEach(i -> pool.execute(() -> this.noLock()));
// 测试类中使用AOP需要手动代理
RedissonTest target = new RedissonTest();
AspectJProxyFactory factory = new AspectJProxyFactory(target);
LockMethodAspect aspect = new LockMethodAspect(redissonClient);
factory.addAspect(aspect);
RedissonTest proxy = factory.getProxy();
IntStream.range(0, 1000).forEach(i -> pool.execute(() -> proxy.youLock()));
TimeUnit.SECONDS.sleep(30);
}
/**
* 使用加锁,数字输出正常了
*/
@RedisLock(key = "youLock")
public void youLock() {
count--;
log.info("integer为{}", count);
}
/**
* 没有使用到线程安全的输出了重复的数字,或者没有顺序
*/
public void noLock() {
count--;
log.info("integer为{}", count);
}
}