面試總是會被問到有沒有用過分布式鎖、redis 鎖,大部分讀者平時很少接觸到,所以只能很無奈的回答 「沒有」。本文通過 Spring Boot 整合 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:# redisredis: 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 */@ConfigurationpublicclassRedissonConfig{@Value("${spring.redis.host}")privateString host;@Value("${spring.redis.port}")privateString port;@Value("${spring.redis.password:}")privateString password;@BeanpublicRedissonClient redissonClient() {Config config = newConfig();//單節點 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");returnRedisson.create(config);}}Redisson 工具類/** * redis分布式鎖幫助類 * * @author gourd * */publicclassRedisLockUtil{privatestaticDistributedLocker distributedLocker = SpringContextHolder.getBean("distributedLocker",DistributedLocker.class);/** * 加鎖 * @param lockKey * @return */publicstaticRLocklock(String lockKey) {return distributedLocker.lock(lockKey);}/** * 釋放鎖 * @param lockKey */publicstaticvoid unlock(String lockKey) { distributedLocker.unlock(lockKey);}/** * 釋放鎖 * @param lock */publicstaticvoid unlock(RLocklock) { distributedLocker.unlock(lock);}/** * 帶超時的鎖 * @param lockKey * @param timeout 超時時間 單位:秒 */publicstaticRLocklock(String lockKey, int timeout) {return distributedLocker.lock(lockKey, timeout);}/** * 帶超時的鎖 * @param lockKey * @param unit 時間單位 * @param timeout 超時時間 */publicstaticRLocklock(String lockKey, int timeout,TimeUnit unit ) {return distributedLocker.lock(lockKey, unit, timeout);}/** * 嘗試獲取鎖 * @param lockKey * @param waitTime 最多等待時間 * @param leaseTime 上鎖後自動釋放鎖時間 * @return */publicstaticboolean tryLock(String lockKey, int waitTime, int leaseTime) {return distributedLocker.tryLock(lockKey, TimeUnit.SECONDS, waitTime, leaseTime);}/** * 嘗試獲取鎖 * @param lockKey * @param unit 時間單位 * @param waitTime 最多等待時間 * @param leaseTime 上鎖後自動釋放鎖時間 * @return */publicstaticboolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {return distributedLocker.tryLock(lockKey, unit, waitTime, leaseTime);}/** * 獲取計數器 * * @param name * @return */publicstaticRCountDownLatch getCountDownLatch(String name){return distributedLocker.getCountDownLatch(name);}/** * 獲取信號量 * * @param name * @return */publicstaticRSemaphore getSemaphore(String name){return distributedLocker.getSemaphore(name);}}
底層封裝
/*** @author gourd */publicinterfaceDistributedLocker{RLocklock(String lockKey);RLockock(String lockKey, int timeout);RLocklock(String lockKey, TimeUnit unit, int timeout);boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime);void unlock(String lockKey);void unlock(RLocklock);}/** * @author gourd */@ComponentpublicclassRedisDistributedLockerimplementsDistributedLocker{@AutowiredprivateRedissonClient redissonClient;@OverridepublicRLocklock(String lockKey) {RLocklock= redissonClient.getLock(lockKey);lock.lock();returnlock;}@OverridepublicRLocklock(String lockKey, int leaseTime) {RLocklock= redissonClient.getLock(lockKey);lock.lock(leaseTime, TimeUnit.SECONDS);returnlock;}@OverridepublicRLocklock(String lockKey, TimeUnit unit ,int timeout) {RLocklock= redissonClient.getLock(lockKey);lock.lock(timeout, unit);returnlock;}@Overridepublicboolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {RLocklock= redissonClient.getLock(lockKey);try{returnlock.tryLock(waitTime, leaseTime, unit);} catch(InterruptedException e) {returnfalse;}}@Overridepublicvoid unlock(String lockKey) {RLocklock= redissonClient.getLock(lockKey);lock.unlock();}@Overridepublicvoid unlock(RLocklock) {lock.unlock();}}
測試
模擬並發測試
/*** redis分布式鎖控制器 * @author gourd * @since 2019-07-30 */@RestController@Api(tags = "redisson", description = "redis分布式鎖控制器")@RequestMapping("/redisson")@Slf4jpublicclassRedissonLockController{/** * 鎖測試共享變量 */privateInteger lockCount = 10;/** * 無鎖測試共享變量 */privateInteger count = 10;/** * 模擬線程數 */privatestaticint threadNum = 10;/** * 模擬並發測試加鎖和不加鎖 * @return */@GetMapping("/test")@ApiOperation(value = "模擬並發測試加鎖和不加鎖")publicvoidlock(){// 計數器finalCountDownLatch countDownLatch = newCountDownLatch(1);for(int i = 0; i < threadNum; i ++) {MyRunnable myRunnable = newMyRunnable(countDownLatch);Thread myThread = newThread(myRunnable); myThread.start();}// 釋放所有線程 countDownLatch.countDown();}/** * 加鎖測試 */privatevoid testLockCount() {String lockKey = "lock-test";try{// 加鎖,設置超時時間2sRedisLockUtil.lock(lockKey,2, TimeUnit.SECONDS); lockCount--; log.info("lockCount值:"+lockCount);}catch(Exception e){ log.error(e.getMessage(),e);}finally{// 釋放鎖RedisLockUtil.unlock(lockKey);}}/** * 無鎖測試 */privatevoid testCount() { count--; log.info("count值:"+count);}publicclassMyRunnableimplementsRunnable{/** * 計數器 */finalCountDownLatch countDownLatch;publicMyRunnable(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}@Overridepublicvoid run() {try{// 阻塞當前線程,直到計時器的值為0 countDownLatch.await();} catch(InterruptedException e) { log.error(e.getMessage(),e);}// 無鎖操作 testCount();// 加鎖操作 testLockCount();}}}
調用接口後列印值:
測試結果根據列印
結果可以明顯看到,未加鎖的 count-- 後值是亂序的,而加鎖後的結果和我們預期的一樣。
由於條件問題沒辦法測試分布式的並發。只能模擬單服務的這種並發,但是原理是一樣,希望對大家有幫助。如有錯誤之處,歡迎指正