今天就跟大家聊聊有關(guān)Java中怎么使用redis 實(shí)現(xiàn)分布式鎖,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
成都創(chuàng)新互聯(lián)公司專(zhuān)注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于網(wǎng)站設(shè)計(jì)制作、做網(wǎng)站、盈江網(wǎng)絡(luò)推廣、微信平臺(tái)小程序開(kāi)發(fā)、盈江網(wǎng)絡(luò)營(yíng)銷(xiāo)、盈江企業(yè)策劃、盈江品牌公關(guān)、搜索引擎seo、人物專(zhuān)訪、企業(yè)宣傳片、企業(yè)代運(yùn)營(yíng)等,從售前售中售后,我們都將竭誠(chéng)為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);成都創(chuàng)新互聯(lián)公司為所有大學(xué)生創(chuàng)業(yè)者提供盈江建站搭建服務(wù),24小時(shí)服務(wù)熱線:18980820575,官方網(wǎng)址:www.cdcxhl.com
支持立即獲取鎖方式,如果獲取到返回true,獲取不到則返回false;
支持等待獲取鎖方式,如果獲取到,直接返回true,獲取不到在等待一小段時(shí)間,在這一小段時(shí)間內(nèi)反復(fù)嘗試,如果嘗試成功,則返回true,等待時(shí)間過(guò)后還獲取不到則返回false;
不能產(chǎn)生死鎖的情況;
不能釋放非自己加的鎖;
下面我們用實(shí)例來(lái)演示在 Java 中利用 redis 實(shí)現(xiàn)分布式鎖服務(wù)
通過(guò) redis 來(lái)實(shí)現(xiàn)分布式鎖的加鎖邏輯如下所示:
redis 分布式鎖上鎖實(shí)現(xiàn)邏輯
根據(jù)這個(gè)邏輯,實(shí)現(xiàn)上鎖的核心代碼如下所示:
jedis.select(dbIndex); String key = KEY_PRE + key; String value = fetchLockValue(); if(jedis.exists(key)){ jedis.set(key,value); jedis.expire(key,lockExpirseTime); return value; }
表面上看這段代碼好像沒(méi)有什么問(wèn)題,實(shí)際上并不能在分布式環(huán)境中正確的實(shí)現(xiàn)加鎖的操作。要能夠正確的實(shí)現(xiàn)加鎖操作,“判斷 key 是否存在”、“保存 key-value”、“設(shè)置 key 的過(guò)期時(shí)間”這三步操作必須是原子操作。如果不是原子操作,那么可能會(huì)出現(xiàn)下面兩種情況:
在“判斷 key 是否存在”得出 key 不存在的結(jié)果步驟后,“保存 key-value”步驟前,另一個(gè)客戶端執(zhí)行同樣的邏輯,并且執(zhí)行到了“判斷 key 是否存在”步驟,同樣得出了 key 不存在的結(jié)果。這樣回導(dǎo)致多個(gè)客戶端獲得了同一把鎖;
在客戶端執(zhí)行完“保存 key-value”步驟后,需要設(shè)置一個(gè) key 的過(guò)期時(shí)間,防止客戶端因?yàn)榇a質(zhì)量未解鎖,在或者進(jìn)程崩潰未解鎖導(dǎo)致的死鎖情況。在“保存 key-value”步驟之后,“設(shè)置 key 的過(guò)期時(shí)間”步驟之前,可能進(jìn)程崩潰,導(dǎo)致“設(shè)置 key 的過(guò)期時(shí)間”步驟失敗;
redis 在2.6.12版本之后,對(duì) set 命令進(jìn)行了擴(kuò)充,能夠規(guī)避上面的兩個(gè)問(wèn)題。新版的 redis set 命令的參數(shù)如下
SET key value [EX seconds] [PX milliseconds] [NX|XX]
新版的 set 命令增加了 EX 、 PX 、 NX|XX 參數(shù)選項(xiàng)。他們的含義如下
EX seconds – 設(shè)置鍵 key 的過(guò)期時(shí)間,單位時(shí)秒 PX milliseconds – 設(shè)置鍵 key 的過(guò)期時(shí)間,單位時(shí)毫秒 NX – 只有鍵 key 不存在的時(shí)候才會(huì)設(shè)置 key 的值 XX – 只有鍵 key 存在的時(shí)候才會(huì)設(shè)置 key 的值
這樣,原來(lái)的三步操作就可以在一個(gè) set 的原子操作里面來(lái)完成,規(guī)避了上面我們提到的兩個(gè)問(wèn)題。新版的 redis 加鎖核心代碼修改如下所示:
jedis = redisConnection.getJedis(); jedis.select(dbIndex); String key = KEY_PRE + key; String value = fetchLockValue(); if ("OK".equals(jedis.set(key, value, "NX", "EX", lockExpirseTime))) { return value; }
解鎖的基本流程如下:
redis 分布式鎖解鎖實(shí)現(xiàn)邏輯
根據(jù)這個(gè)邏輯,在 Java 中解鎖的核心代碼如下所示:
jedis.select(dbIndex); String key = KEY_PRE + key; if(jedis.exists(key) && value.equals(jedis.get(key))){ jedis.del(key); return true; } return false;
和加鎖的時(shí)候一樣,key 是否存在、判斷是否自己持有鎖、刪除 key-value 這三步操作需要是原子操作,否則當(dāng)一個(gè)客戶端執(zhí)行完“判斷是否自己持有鎖”步驟后,得出自己持有鎖的結(jié)論,此時(shí)鎖的過(guò)期時(shí)間到了,自動(dòng)被 redis 釋放了,同時(shí)另一個(gè)客戶端又基于這個(gè) key 加鎖成功,如果第一個(gè)客戶端還繼續(xù)執(zhí)行刪除 key-value的操作,就將不屬于自己的鎖給釋放了。這顯然是不運(yùn)行的。在這里我們利用 redis 執(zhí)行 Lua 腳本的能力來(lái)解決原子操作的問(wèn)題。修改后的解鎖核心代碼如下所示:
jedis.select(dbIndex); String key = KEY_PRE + key; String command = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; if (1L.equals(jedis.eval(command, Collections.singletonList(key), Collections.singletonList(value)))) { return true; }
另外,判斷是否自己持有鎖的機(jī)制是用加鎖的時(shí)候的 key-value 來(lái)判斷當(dāng)前的 key 的值是否等于自己持有鎖時(shí)獲得的值。所以加鎖的時(shí)候的 value 必須是一個(gè)全局唯一的字符串。
package com.x9710.common.redis.impl; import com.x9710.common.redis.LockService; import com.x9710.common.redis.RedisConnection; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import redis.clients.jedis.Jedis; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Collections; import java.util.Date; import java.util.UUID; /** * 分布式鎖 redis 實(shí)現(xiàn) * * @author 楊高超 * @since 2017-12-14 */ public class LockServiceRedisImpl implements LockService { private static Log log = LogFactory.getLog(LockServiceRedisImpl.class); private static String SET_SUCCESS = "OK"; private static String KEY_PRE = "REDIS_LOCK_"; private DateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS"); private RedisConnection redisConnection; private Integer dbIndex; private Integer lockExpirseTime; private Integer tryExpirseTime; public void setRedisConnection(RedisConnection redisConnection) { this.redisConnection = redisConnection; } public void setDbIndex(Integer dbIndex) { this.dbIndex = dbIndex; } public void setLockExpirseTime(Integer lockExpirseTime) { this.lockExpirseTime = lockExpirseTime; } public void setTryExpirseTime(Integer tryExpirseTime) { this.tryExpirseTime = tryExpirseTime; } public String lock(String key) { Jedis jedis = null; try { jedis = redisConnection.getJedis(); jedis.select(dbIndex); key = KEY_PRE + key; String value = fetchLockValue(); if (SET_SUCCESS.equals(jedis.set(key, value, "NX", "EX", lockExpirseTime))) { log.debug("Reids Lock key : " + key + ",value : " + value); return value; } } catch (Exception e) { e.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } } return null; } public String tryLock(String key) { Jedis jedis = null; try { jedis = redisConnection.getJedis(); jedis.select(dbIndex); key = KEY_PRE + key; String value = fetchLockValue(); Long firstTryTime = new Date().getTime(); do { if (SET_SUCCESS.equals(jedis.set(key, value, "NX", "EX", lockExpirseTime))) { log.debug("Reids Lock key : " + key + ",value : " + value); return value; } log.info("Redis lock failure,waiting try next"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } while ((new Date().getTime() - tryExpirseTime * 1000) < firstTryTime); } catch (Exception e) { e.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } } return null; } public boolean unLock(String key, String value) { Long RELEASE_SUCCESS = 1L; Jedis jedis = null; try { jedis = redisConnection.getJedis(); jedis.select(dbIndex); key = KEY_PRE + key; String command = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; if (RELEASE_SUCCESS.equals(jedis.eval(command, Collections.singletonList(key), Collections.singletonList(value)))) { return true; } } catch (Exception e) { e.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } } return false; } /** * 生成加鎖的唯一字符串 * * @return 唯一字符串 */ private String fetchLockValue() { return UUID.randomUUID().toString() + "_" + df.format(new Date()); } }
package com.x9710.common.redis.test; import com.x9710.common.redis.RedisConnection; import com.x9710.common.redis.impl.LockServiceRedisImpl; public class RedisLockTest { public static void main(String[] args) { for (int i = 0; i < 9; i++) { new Thread(new Runnable() { public void run() { RedisConnection redisConnection = RedisConnectionUtil.create(); LockServiceRedisImpl lockServiceRedis = new LockServiceRedisImpl(); lockServiceRedis.setRedisConnection(redisConnection); lockServiceRedis.setDbIndex(15); lockServiceRedis.setLockExpirseTime(20); String key = "20171228"; String value = lockServiceRedis.lock(key); try { if (value != null) { System.out.println(Thread.currentThread().getName() + " lock key = " + key + " success! "); Thread.sleep(25 * 1000); }else{ System.out.println(Thread.currentThread().getName() + " lock key = " + key + " failure! "); } } catch (Exception e) { e.printStackTrace(); } finally { if (value == null) { value = ""; } System.out.println(Thread.currentThread().getName() + " unlock key = " + key + " " + lockServiceRedis.unLock(key, value)); } } }).start(); } } }
測(cè)試結(jié)果
Thread-1 lock key = 20171228 failure! Thread-2 lock key = 20171228 failure! Thread-4 lock key = 20171228 failure! Thread-8 lock key = 20171228 failure! Thread-7 lock key = 20171228 failure! Thread-3 lock key = 20171228 failure! Thread-5 lock key = 20171228 failure! Thread-0 lock key = 20171228 failure! Thread-6 lock key = 20171228 success! Thread-1 unlock key = 20171228 false Thread-2 unlock key = 20171228 false Thread-4 unlock key = 20171228 false Thread-8 unlock key = 20171228 false Thread-3 unlock key = 20171228 false Thread-5 unlock key = 20171228 false Thread-0 unlock key = 20171228 false Thread-7 unlock key = 20171228 false Thread-6 unlock key = 20171228 true
看完上述內(nèi)容,你們對(duì)Java中怎么使用Redis 實(shí)現(xiàn)分布式鎖有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝大家的支持。