前言
最近面試總是會被問到有沒有用過分佈式鎖、redis鎖,由於平時很少接觸到,所以只能很無奈的回答“沒有”。回來之後就惡補了一下,本文主要做下記錄,通過SpringBoot整合redisson來實現分佈式鎖,並結合demo測試結果。
首先看下大佬總結的圖
正文
增加依賴
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.10.6</version>
</dependency>
配置 信息
spring:
# redis
redis:
host: 47.103.5.190
port: 6379
jedis:
pool:
# 連接池最大連接數(使用負值表示沒有限制)
max-active: 100
# 連接池中的最小空閒連接
max-idle: 10
# 連接池最大阻塞等待時間(使用負值表示沒有限制)
max-wait: -1
# 連接超時時間(毫秒)
timeout: 5000
#默認是索引爲0的數據庫
database: 0
配置類
/**
* redisson 配置,下面是單節點配置:
*
* @author gourd
*/
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.password:}")
private String password;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
//單節點
config.useSingleServer().setAddress("redis://" + host + ":" + port);
if (StringUtils.isEmpty(password)) {
config.useSingleServer().setPassword(null);
} else {
config.useSingleServer().setPassword(password);
}
//添加主從配置
// config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
// 集羣模式配置 setScanInterval()掃描間隔時間,單位是毫秒, //可以用"rediss://"來啓用SSL連接
// config.useClusterServers().setScanInterval(2000).addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001").addNodeAddress("redis://127.0.0.1:7002");
return Redisson.create(config);
}
}
Redisson工具類
/**
* redis分佈式鎖幫助類
*
* @author gourd
*
*/
public class RedisLockUtil {
private static DistributedLocker distributedLocker = SpringContextHolder.getBean("distributedLocker",DistributedLocker.class);
/**
* 加鎖
* @param lockKey
* @return
*/
public static RLock lock(String lockKey) {
return distributedLocker.lock(lockKey);
}
/**
* 釋放鎖
* @param lockKey
*/
public static void unlock(String lockKey) {
distributedLocker.unlock(lockKey);
}
/**
* 釋放鎖
* @param lock
*/
public static void unlock(RLock lock) {
distributedLocker.unlock(lock);
}
/**
* 帶超時的鎖
* @param lockKey
* @param timeout 超時時間 單位:秒
*/
public static RLock lock(String lockKey, int timeout) {
return distributedLocker.lock(lockKey, timeout);
}
/**
* 帶超時的鎖
* @param lockKey
* @param unit 時間單位
* @param timeout 超時時間
*/
public static RLock lock(String lockKey, int timeout,TimeUnit unit ) {
return distributedLocker.lock(lockKey, unit, timeout);
}
/**
* 嘗試獲取鎖
* @param lockKey
* @param waitTime 最多等待時間
* @param leaseTime 上鎖後自動釋放鎖時間
* @return
*/
public static boolean tryLock(String lockKey, int waitTime, int leaseTime) {
return distributedLocker.tryLock(lockKey, TimeUnit.SECONDS, waitTime, leaseTime);
}
/**
* 嘗試獲取鎖
* @param lockKey
* @param unit 時間單位
* @param waitTime 最多等待時間
* @param leaseTime 上鎖後自動釋放鎖時間
* @return
*/
public static boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
return distributedLocker.tryLock(lockKey, unit, waitTime, leaseTime);
}
/**
* 獲取計數器
*
* @param name
* @return
*/
public static RCountDownLatch getCountDownLatch(String name){
return distributedLocker.getCountDownLatch(name);
}
/**
* 獲取信號量
*
* @param name
* @return
*/
public static RSemaphore getSemaphore(String name){
return distributedLocker.getSemaphore(name);
}
}
底層封裝
/**
* @author gourd
*/
public interface DistributedLocker {
RLock lock(String lockKey);
RLock lock(String lockKey, int timeout);
RLock lock(String lockKey, TimeUnit unit, int timeout);
boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime);
void unlock(String lockKey);
void unlock(RLock lock);
}
/**
* @author gourd
*/
@Component
public class RedisDistributedLocker implements DistributedLocker {
@Autowired
private RedissonClient redissonClient;
@Override
public RLock lock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock();
return lock;
}
@Override
public RLock lock(String lockKey, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(leaseTime, TimeUnit.SECONDS);
return lock;
}
@Override
public RLock lock(String lockKey, TimeUnit unit ,int timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
return lock;
}
@Override
public boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
return false;
}
}
@Override
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.unlock();
}
@Override
public void unlock(RLock lock) {
lock.unlock();
}
}
測試
模擬併發測試
/**
* redis分佈式鎖控制器
* @author gourd
* @since 2019-07-30
*/
@RestController
@Api(tags = "redisson", description = "redis分佈式鎖控制器" )
@RequestMapping("/redisson" )
@Slf4j
public class RedissonLockController {
/**
* 鎖測試共享變量
*/
private Integer lockCount = 10;
/**
* 無鎖測試共享變量
*/
private Integer count = 10;
/**
* 模擬線程數
*/
private static int threadNum = 10;
/**
* 模擬併發測試加鎖和不加鎖
* @return
*/
@GetMapping("/test")
@ApiOperation(value = "模擬併發測試加鎖和不加鎖")
public void lock(){
// 計數器
final CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < threadNum; i ++) {
MyRunnable myRunnable = new MyRunnable(countDownLatch);
Thread myThread = new Thread(myRunnable);
myThread.start();
}
// 釋放所有線程
countDownLatch.countDown();
}
/**
* 加鎖測試
*/
private void testLockCount() {
String lockKey = "lock-test";
try {
// 加鎖,設置超時時間2s
RedisLockUtil.lock(lockKey,2, TimeUnit.SECONDS);
lockCount--;
log.info("lockCount值:"+lockCount);
}catch (Exception e){
log.error(e.getMessage(),e);
}finally {
// 釋放鎖
RedisLockUtil.unlock(lockKey);
}
}
/**
* 無鎖測試
*/
private void testCount() {
count--;
log.info("count值:"+count);
}
public class MyRunnable implements Runnable {
/**
* 計數器
*/
final CountDownLatch countDownLatch;
public MyRunnable(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
// 阻塞當前線程,直到計時器的值爲0
countDownLatch.await();
} catch (InterruptedException e) {
log.error(e.getMessage(),e);
}
// 無鎖操作
testCount();
// 加鎖操作
testLockCount();
}
}
}
調用接口後打印值:
根據打印結果可以明顯看到,未加鎖的count--後值是亂序的,而加鎖後的結果和我們預期的一樣。
由於條件問題沒辦法測試分佈式的併發。只能模擬單服務的這種併發,但是原理是一樣,希望對大家有幫助。如有錯誤之處,歡迎指正。
====================================================
代碼均已上傳至本人的開源項目
spring-cloud-plus:https://blog.csdn.net/HXNLYW/article/details/104635673