如何使用redis實現(xiàn)分布式鎖

如何使用redis實現(xiàn)分布式鎖

使用redis實現(xiàn)分布式鎖

redis特性介紹

1、支持豐富的數(shù)據(jù)類型,如String、List、Map、Set、ZSet等。

2、支持數(shù)據(jù)持久化,RDB和AOF兩種方式

3、支持集群工作模式,分區(qū)容錯性強

4、單線程,順序處理命令

5、支持事務(wù)

6、支持發(fā)布與訂閱

Redis實現(xiàn)分布式鎖使用了SETNX命令:

SETNX key value

將key的值設(shè)為value?,當且僅當key不存在。

若給定的key已經(jīng)存在,則SETNX不做任何動作。

SETNX?是『SET if Not eXists』(如果不存在,則 SET)的簡寫。

可用版本:>= 1.0.0時間復雜度:O(1)返回值:

設(shè)置成功,返回?1?。

設(shè)置失敗,返回?0?。

redis>?EXISTS?job????????????????#?job?不存在 (integer)?0  redis>?SETNX?job?"programmer"????#?job?設(shè)置成功 (integer)?1  redis>?SETNX?job?"code-farmer"???#?嘗試覆蓋?job?,失敗 (integer)?0  redis>?GET?job???????????????????#?沒有被覆蓋 "programmer"

首先,我們需要封裝一個公共的Redis訪問工具類。該類需要注入RedisTemplate實例和ValueOperations實例,使用ValueOperations實例是因為Redis實現(xiàn)的分布式鎖使用了最簡單的String類型。另外,我們需要封裝3個方法,分別是setIfObsent (String key, String value)、 expire (String key, long timeout, TimeUnit unit) 、delete (String key) ,分別對應Redis的SETNX、expire、del命令。以下是Redis訪問工具類的具體實現(xiàn):

@Component public?class?RedisDao?{  	@Autowired 	private?RedisTemplate?redisTemplate; 	 	@Resource(name="redisTemplate") 	private?ValueOperations<object>?valOpsObj; 	 	/** 	?*?如果key不存在,就存儲一個key-value,相當于SETNX命令 	?*?@param?key??????鍵 	?*?@param?value????值,可以為空 	?*?@return 	?*/ 	public?boolean?setIfObsent?(String?key,?String?value)?{ 		return?valOpsObj.setIfAbsent(key,?value); 	} 	 	/** 	?*?為key設(shè)置失效時間 	?*?@param?key???????鍵 	?*?@param?timeout???時間大小 	?*?@param?unit??????時間單位 	?*/ 	public?boolean?expire?(String?key,?long?timeout,?TimeUnit?unit)?{ 		return?redisTemplate.expire(key,?timeout,?unit); 	} 	 	/** 	?*?刪除key 	?*?@param?key?鍵 	?*/ 	public?void?delete?(String?key)?{ 		redisTemplate.delete(key); 	} }</object>

完成了Redis訪問工具類的實現(xiàn),現(xiàn)在需要考慮的是如何去模擬競爭分布式鎖。因為Redis本身就是支持分布式集群的,所以只需要模擬出多線程處理業(yè)務(wù)場景。這里采用線程池來模擬,以下是測試類的具體實現(xiàn):

@RestController @RequestMapping("test") public?class?TestController?{  	private?static?final?Logger?LOG?=?LoggerFactory.getLogger(TestController.class);??//日志對象 	@Autowired 	private?RedisDao?redisDao; 	//定義的分布式鎖key 	private?static?final?String?LOCK_KEY?=?"MyTestLock"; 	 	@RequestMapping(value={"testRedisLock"},?method=RequestMethod.GET) 	public?void?testRedisLock?()?{ 		ExecutorService?executorService?=?Executors.newFixedThreadPool(5); 		for?(int?i?=?0;?i?<p>通過上面這段代碼,可能會產(chǎn)生以下幾個疑問:</p><p>線程如果獲取分布式鎖失敗,為什么不嘗試重新獲取鎖?</p><p>線程獲取分布式鎖成功后,設(shè)置了鎖的失效時間,這個失效時間長短如何確定?</p><p>線程業(yè)務(wù)處理結(jié)束后,為什么要做刪除鎖的操作?</p><p>針對這幾個疑問,我們可以來討論下。</p><p>第一,Redis的SETNX命令,如果key已經(jīng)存在,則不會做任何操作,所以SETNX實現(xiàn)的分布式鎖并不是可重入鎖。當然,也可以自己通過代碼實現(xiàn)重試n次或者直至獲取到分布式鎖為止。但是,這不能保證競爭的公平性,某個線程會因為一直等待鎖而阻塞。因此,Redis實現(xiàn)的分布式鎖更適用于對共享資源一寫多讀的場景。</p><p>第二,分布式鎖必須設(shè)置失效時間,而且失效時間必須大于業(yè)務(wù)處理所需的時間(保證數(shù)據(jù)一致性)。所以,在測試階段盡可能準確的預測出業(yè)務(wù)正常處理所需的時間,設(shè)置失效時間是防止因為業(yè)務(wù)處理過程的某些原因?qū)е滤梨i的情況。</p><p>第三,業(yè)務(wù)處理結(jié)束,必須要做刪除鎖的操作。</p><p>上面設(shè)置分布式鎖和為鎖設(shè)置失效時間是通過兩個操作步驟完成的,更合理的方式應該是把設(shè)置分布式鎖和為鎖設(shè)置失效時間通過一個操作完成。要么都成功,要么都失敗。實現(xiàn)代碼如下:</p><pre class="brush:sql;toolbar:false">/** *?Redis訪問工具類 */ @Component public?class?RedisDao?{  	private?static?Logger?logger?=?LoggerFactory.getLogger(RedisDao.class); 	 	@Autowired 	private?StringRedisTemplate?stringRedisTemplate; 	 	/** 	?*?設(shè)置分布式鎖???? 	?*?@param?key?????鍵 	?*?@param?value???值 	?*?@param?timeout?失效時間 	?*?@return 	?*/ 	public?boolean?setDistributeLock?(String?key,?String?value,?long?timeout)?{ 		RedisConnection?connection?=?null; 		boolean?flag?=?false; 		try?{ 			//獲取一個連接 			connection?=?stringRedisTemplate.getConnectionFactory().getConnection(); 			//設(shè)置分布式鎖的同時為鎖設(shè)置失效時間 			connection.set(key.getBytes(),?value.getBytes(),?Expiration.seconds(timeout),?RedisStringCommands.SetOption.SET_IF_ABSENT); 			flag?=?true; 		}?catch?(Exception?e)?{ 			logger.error("set?automic?lock?error:",?e); 		}?finally?{ 			//使用后關(guān)閉連接 			connection.close(); 		} 		return?flag; 	} 	 	/** 	?*?查詢key的失效時間 	?*?@param?key???????鍵 	?*?@param?timeUnit??時間單位 	?*?@return 	?*/ 	public?long?ttl?(String?key,?TimeUnit?timeUnit)?{ 		return?stringRedisTemplate.getExpire(key,?timeUnit); 	} }  /** *?單元測試類 */ @RunWith(SpringRunner.class) @SpringBootTest public?class?Demo1ApplicationTests?{  	private?static?final?Logger?LOG?=?LoggerFactory.getLogger(Demo1ApplicationTests.class); 		 	@Autowired 	private?RedisDao?redisDao; 	 	@Test 	public?void?testDistributeLock?()?{ 		String?key?=?"MyDistributeLock"; 		//設(shè)置分布式鎖,失效時間20s 		boolean?result?=?redisDao.setDistributeLock(key,?"1",?20); 		if?(result)?{ 			LOG.info("設(shè)置分布式鎖成功"); 			long?ttl?=?redisDao.ttl(key,?TimeUnit.SECONDS); 			LOG.info("{}距離失效還有{}s",?key,?ttl); 		} 	} }

運行單元測試類,結(jié)果如下:

2019-05-15?13:07:10.827?-?設(shè)置分布式鎖成功 2019-05-15?13:07:10.838?-?MyDistributeLock距離失效還有19s

更多Redis相關(guān)知識,請訪問Redis使用教程欄目!

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點贊9 分享