使用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);
}
}