你們都聽過1萬小時定律,可事實真的是這樣嗎?作了1萬小時的CRUD,不還只會CRUD嗎,這年頭不適當的更新自身下技術棧,出門和別人聊天吹牛的時候,都沒拿不出手,(⊙o⊙)…Redis沒入門的童鞋不推薦往下看,先去腦補下Redis入門(點我直達),SpringBoot整合Redis的教程(點我直達),這篇不會講淺的知識點!!!!html
首先,爲了確保分佈式鎖可用,至少要知足如下三個條件java
兩種實現,下面都會有講到web
能夠保證在分佈式部署的應用集羣中,同一個方法在同一操做只能被一臺機器上的一個線程執行。面試
網址:https://start.spring.io/redis
一、啓動類上加上註解@EnableSchedulingspring
二、執行方法上加上註解@Scheduledapache
準備3臺Linux服務器,並將打好的jar包,上傳至3臺服務器中,而後啓動緩存
nohup java -jar jar名稱 &
一、先安裝lsof:yum install lsof
二、驗證:lsof -i:8080
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
主要是爲了防止已失效的鏈接請求報文段忽然又傳到了B,於是報文錯亂問題,假定A發出的第一個鏈接請求報文段並無丟失,而是在某些網絡節點長時間滯留,一直延遲到鏈接釋放之後的某個時間纔到達B,原本這是一個早已經失效的報文段。但B收到失效的鏈接請求報文段後,就誤認爲是A又發出一個新的鏈接請求,因而就向A發出確認報文段,贊成創建鏈接。服務器
假定不採用三次握手,那麼只要B發出確認,新的鏈接就創建,這樣一直等待A發來數據,B的許多資源就這樣白白浪費了。網絡
第一次揮手:主動關閉方發送一個FIN,用來關閉主動方到被動關閉方的數據傳送,也就是主動關閉方告訴被動關閉方:我已經不 會再給你發數據了(固然,在fin包以前發送出去的數據,若是沒有收到對應的ack確認報文,主動關閉方依然會重發這些數據),可是,此時主動關閉方還可 以接受數據。
第二次揮手:被動關閉方收到FIN包後,發送一個ACK給對方,確認序號爲收到序號+1(與SYN相同,一個FIN佔用一個序號)。
第三次揮手:被動關閉方發送一個FIN,用來關閉被動關閉方到主動關閉方的數據傳送,也就是告訴主動關閉方,個人數據也發送完了,不會再給你發數據了。
第四次揮手:主動關閉方收到FIN後,發送一個ACK給被動關閉方,確認序號爲收到序號+1,至此,完成四次揮手。
確保數據可以完整傳輸
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.cyb</groupId> <artifactId>yb-mobile-redis</artifactId> <version>0.0.1-SNAPSHOT</version> <name>yb-mobile-redis</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
spring.redis.database=0
spring.redis.host=192.168.199.142
spring.redis.port=6379
spring.redis.password=12345678
server.port=9001
package com.cyb.ybmobileredis.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.*; import org.springframework.stereotype.Service; import java.io.Serializable; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; /** * @ClassName:RedisService * @Description:TODO * @Author:chenyb * @Date:2020/8/16 5:39 下午 * @Versiion:1.0 */ @Service public class RedisService { @Autowired private RedisTemplate redisTemplate; private static double size = Math.pow(2, 32); /** * 寫入緩存 * * @param key * @param offset 位 8Bit=1Byte * @return */ public boolean setBit(String key, long offset, boolean isShow) { boolean result = false; try { ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue(); operations.setBit(key, offset, isShow); result = true; } catch (Exception e) { e.printStackTrace(); } return result; } /** * 寫入緩存 * * @param key * @param offset * @return */ public boolean getBit(String key, long offset) { boolean result = false; try { ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue(); result = operations.getBit(key, offset); } catch (Exception e) { e.printStackTrace(); } return result; } /** * 寫入緩存 * * @param key * @param value * @return */ public boolean set(final String key, Object value) { boolean result = false; try { ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue(); operations.set(key, value); result = true; } catch (Exception e) { e.printStackTrace(); } return result; } /** * 寫入緩存設置時效時間 * * @param key * @param value * @return */ public boolean set(final String key, Object value, Long expireTime) { boolean result = false; try { ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue(); operations.set(key, value); redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); result = true; } catch (Exception e) { e.printStackTrace(); } return result; } /** * 批量刪除對應的value * * @param keys */ public void remove(final String... keys) { for (String key : keys) { remove(key); } } /** * 刪除對應的value * * @param key */ public void remove(final String key) { if (exists(key)) { redisTemplate.delete(key); } } /** * 判斷緩存中是否有對應的value * * @param key * @return */ public boolean exists(final String key) { return redisTemplate.hasKey(key); } /** * 讀取緩存 * * @param key * @return */ public Object get(final String key) { Object result = null; ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue(); result = operations.get(key); return result; } /** * 哈希 添加 * * @param key * @param hashKey * @param value */ public void hmSet(String key, Object hashKey, Object value) { HashOperations<String, Object, Object> hash = redisTemplate.opsForHash(); hash.put(key, hashKey, value); } /** * 哈希獲取數據 * * @param key * @param hashKey * @return */ public Object hmGet(String key, Object hashKey) { HashOperations<String, Object, Object> hash = redisTemplate.opsForHash(); return hash.get(key, hashKey); } /** * 列表添加 * * @param k * @param v */ public void lPush(String k, Object v) { ListOperations<String, Object> list = redisTemplate.opsForList(); list.rightPush(k, v); } /** * 列表獲取 * * @param k * @param l * @param l1 * @return */ public List<Object> lRange(String k, long l, long l1) { ListOperations<String, Object> list = redisTemplate.opsForList(); return list.range(k, l, l1); } /** * 集合添加 * * @param key * @param value */ public void add(String key, Object value) { SetOperations<String, Object> set = redisTemplate.opsForSet(); set.add(key, value); } /** * 集合獲取 * * @param key * @return */ public Set<Object> setMembers(String key) { SetOperations<String, Object> set = redisTemplate.opsForSet(); return set.members(key); } /** * 有序集合添加 * * @param key * @param value * @param scoure */ public void zAdd(String key, Object value, double scoure) { ZSetOperations<String, Object> zset = redisTemplate.opsForZSet(); zset.add(key, value, scoure); } /** * 有序集合獲取 * * @param key * @param scoure * @param scoure1 * @return */ public Set<Object> rangeByScore(String key, double scoure, double scoure1) { ZSetOperations<String, Object> zset = redisTemplate.opsForZSet(); redisTemplate.opsForValue(); return zset.rangeByScore(key, scoure, scoure1); } //第一次加載的時候將數據加載到redis中 public void saveDataToRedis(String name) { double index = Math.abs(name.hashCode() % size); long indexLong = new Double(index).longValue(); boolean availableUsers = setBit("availableUsers", indexLong, true); } //第一次加載的時候將數據加載到redis中 public boolean getDataToRedis(String name) { double index = Math.abs(name.hashCode() % size); long indexLong = new Double(index).longValue(); return getBit("availableUsers", indexLong); } /** * 有序集合獲取排名 * * @param key 集合名稱 * @param value 值 */ public Long zRank(String key, Object value) { ZSetOperations<String, Object> zset = redisTemplate.opsForZSet(); return zset.rank(key,value); } /** * 有序集合獲取排名 * * @param key */ public Set<ZSetOperations.TypedTuple<Object>> zRankWithScore(String key, long start,long end) { ZSetOperations<String, Object> zset = redisTemplate.opsForZSet(); Set<ZSetOperations.TypedTuple<Object>> ret = zset.rangeWithScores(key,start,end); return ret; } /** * 有序集合添加 * * @param key * @param value */ public Double zSetScore(String key, Object value) { ZSetOperations<String, Object> zset = redisTemplate.opsForZSet(); return zset.score(key,value); } /** * 有序集合添加分數 * * @param key * @param value * @param scoure */ public void incrementScore(String key, Object value, double scoure) { ZSetOperations<String, Object> zset = redisTemplate.opsForZSet(); zset.incrementScore(key, value, scoure); } /** * 有序集合獲取排名 * * @param key */ public Set<ZSetOperations.TypedTuple<Object>> reverseZRankWithScore(String key, long start,long end) { ZSetOperations<String, Object> zset = redisTemplate.opsForZSet(); Set<ZSetOperations.TypedTuple<Object>> ret = zset.reverseRangeByScoreWithScores(key,start,end); return ret; } /** * 有序集合獲取排名 * * @param key */ public Set<ZSetOperations.TypedTuple<Object>> reverseZRankWithRank(String key, long start, long end) { ZSetOperations<String, Object> zset = redisTemplate.opsForZSet(); Set<ZSetOperations.TypedTuple<Object>> ret = zset.reverseRangeWithScores(key, start, end); return ret; } }
package com.cyb.ybmobileredis.schedule; import com.cyb.ybmobileredis.service.RedisService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.Enumeration; /** * @ClassName:LockNxExJob * @Description:分佈式獲取鎖和釋放鎖 * @Author:chenyb * @Date:2020/8/16 5:44 下午 * @Versiion:1.0 */ @Service public class LockNxExJob { private static final Logger logger = LoggerFactory.getLogger(LockNxExJob.class); @Autowired private RedisService redisService; @Autowired private RedisTemplate redisTemplate; private static String LOCK_PREFIX = "prefix_"; @Scheduled(fixedRate = 8000) public void lockJob() { String lock = LOCK_PREFIX + "LockNxExJob"; boolean nxRet=false; try{ //redistemplate setnx操做 nxRet = redisTemplate.opsForValue().setIfAbsent(lock,getHostIp()); Object lockValue = redisService.get(lock); System.out.println(lockValue); //獲取鎖失敗 if(!nxRet){ String value = (String)redisService.get(lock); //打印當前佔用鎖的服務器IP logger.info(System.currentTimeMillis()+" get lock fail,lock belong to:{}",value); return; }else{ redisTemplate.opsForValue().set(lock,getHostIp(),3600); //獲取鎖成功 logger.info(System.currentTimeMillis()+" start lock lockNxExJob success"); Thread.sleep(4000); } }catch (Exception e){ logger.error("lock error",e); }finally { if (nxRet){ System.out.println("釋放鎖成功"); redisService.remove(lock); } } } /** * 獲取本機內網IP地址方法 * @return */ private static String getHostIp(){ try{ Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces(); while (allNetInterfaces.hasMoreElements()){ NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement(); Enumeration<InetAddress> addresses = netInterface.getInetAddresses(); while (addresses.hasMoreElements()){ InetAddress ip = (InetAddress) addresses.nextElement(); if (ip != null && ip instanceof Inet4Address && !ip.isLoopbackAddress() //loopback地址即本機地址,IPv4的loopback範圍是127.0.0.0 ~ 127.255.255.255 && ip.getHostAddress().indexOf(":")==-1){ return ip.getHostAddress(); } } } }catch(Exception e){ e.printStackTrace(); } return null; } }
打個jar運行在Linux上,一個在本地運行,一個獲取鎖成功,一個獲取鎖失敗
上面咱們已經使用代碼實現了分佈式鎖的功能,同一時刻只能一把鎖獲取成功。從上圖能夠看出,極端狀況下,第一個Server獲取鎖成功後,服務或者Redis宕機了,會致使Redis鎖沒法釋放的問題,其餘的Server就一直獲取鎖失敗。
先把項目跑起來,獲取鎖以後,立馬kill -9 進程id,殺掉當前進程,而後在運行項目,控制檯就會一直提示,獲取鎖失敗了。
local lockKey = KEYS[1] local lockValue = KEYS[2] -- setnx info local result_1 = redis.call('SETNX',lockKey,lockValue) if result_1 == true then local result_2 = redis.call('SETEX',lockKey,3600,lockValue) return result_1 else return result_1 end
@Autowired private RedisTemplate redisTemplate; private DefaultRedisScript<Boolean> lockScript; /** * 獲取lua結果 * * @param key 鍵 * @param value 值 * @return */ public Boolean luaExpress(String key, String value) { lockScript = new DefaultRedisScript<>(); lockScript.setScriptSource( new ResourceScriptSource(new ClassPathResource("add.lua")) ); //設置返回值 lockScript.setResultType(Boolean.class); //封裝參數 List<Object> keyList = new ArrayList<>(); keyList.add(key); keyList.add(value); Boolean result = (Boolean) redisTemplate.execute(lockScript, keyList); return result; }
package com.cyb.ybmobileredis.schedule; import com.cyb.ybmobileredis.service.RedisService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scripting.support.ResourceScriptSource; import org.springframework.stereotype.Service; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; /** * @ClassName:LockNxExJob * @Description:分佈式獲取鎖和釋放鎖 * @Author:chenyb * @Date:2020/8/16 5:44 下午 * @Versiion:1.0 */ @Service public class LockNxExJob { private static final Logger logger = LoggerFactory.getLogger(LockNxExJob.class); @Autowired private RedisService redisService; @Autowired private RedisTemplate redisTemplate; private static String LOCK_PREFIX = "prefix_"; private DefaultRedisScript<Boolean> lockScript; //通常分佈式鎖 // @Scheduled(fixedRate = 8000) // public void lockJob() { // String lock = LOCK_PREFIX + "LockNxExJob"; // boolean nxRet = false; // try { // //redistemplate setnx操做 // nxRet = redisTemplate.opsForValue().setIfAbsent(lock, getHostIp()); // Object lockValue = redisService.get(lock); // System.out.println(lockValue); // //獲取鎖失敗 // if (!nxRet) { // String value = (String) redisService.get(lock); // //打印當前佔用鎖的服務器IP // logger.info(System.currentTimeMillis() + " get lock fail,lock belong to:{}", value); // return; // } else { // redisTemplate.opsForValue().set(lock, getHostIp(), 3600000); // // //獲取鎖成功 // logger.info(System.currentTimeMillis() + " start lock lockNxExJob success"); // Thread.sleep(4000); // } // } catch (Exception e) { // logger.error("lock error", e); // // } finally { // if (nxRet) { // System.out.println("釋放鎖成功"); // redisService.remove(lock); // } // } // } /** * lua腳本方式分佈式鎖 */ @Scheduled(fixedRate = 8000) public void luaLockJob() { String lock = LOCK_PREFIX + "LockNxExJob"; boolean nxRet = false; try { //redistemplate setnx操做 nxRet = luaExpress(lock,getHostIp()); Object lockValue = redisService.get(lock); System.out.println(lockValue); //獲取鎖失敗 if (!nxRet) { String value = (String) redisService.get(lock); //打印當前佔用鎖的服務器IP logger.info(System.currentTimeMillis() + " lua get lock fail,lock belong to:{}", value); return; } else { redisTemplate.opsForValue().set(lock, getHostIp(), 3600000); //獲取鎖成功 logger.info(System.currentTimeMillis() + " lua start lock lockNxExJob success"); Thread.sleep(4000); } } catch (Exception e) { logger.error("lua lock error", e); } finally { if (nxRet) { System.out.println("lua 釋放鎖成功"); redisService.remove(lock); } } } /** * 獲取lua結果 * * @param key 鍵 * @param value 值 * @return */ public Boolean luaExpress(String key, String value) { lockScript = new DefaultRedisScript<>(); lockScript.setScriptSource( new ResourceScriptSource(new ClassPathResource("add.lua")) ); //設置返回值 lockScript.setResultType(Boolean.class); //封裝參數 List<Object> keyList = new ArrayList<>(); keyList.add(key); keyList.add(value); Boolean result = (Boolean) redisTemplate.execute(lockScript, keyList); return result; } /** * 獲取本機內網IP地址方法 * * @return */ private static String getHostIp() { try { Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces(); while (allNetInterfaces.hasMoreElements()) { NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement(); Enumeration<InetAddress> addresses = netInterface.getInetAddresses(); while (addresses.hasMoreElements()) { InetAddress ip = (InetAddress) addresses.nextElement(); if (ip != null && ip instanceof Inet4Address && !ip.isLoopbackAddress() //loopback地址即本機地址,IPv4的loopback範圍是127.0.0.0 ~ 127.255.255.255 && ip.getHostAddress().indexOf(":") == -1) { return ip.getHostAddress(); } } } } catch (Exception e) { e.printStackTrace(); } return null; } }
只須要添加RedisConfig.java配置文件便可
package com.cyb.ybmobileredis.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * @ClassName:RedisConfig * @Description:Redis配置類 * @Author:chenyb * @Date:2020/8/16 11:48 下午 * @Versiion:1.0 */ @Configuration //當前類爲配置類 public class RedisConfig { @Bean //redisTemplate注入到Spring容器 public RedisTemplate<String,String> redisTemplate(RedisConnectionFactory factory){ RedisTemplate<String,String> redisTemplate=new RedisTemplate<>(); RedisSerializer<String> redisSerializer = new StringRedisSerializer(); redisTemplate.setConnectionFactory(factory); //key序列化 redisTemplate.setKeySerializer(redisSerializer); //value序列化 redisTemplate.setValueSerializer(redisSerializer); //value hashmap序列化 redisTemplate.setHashKeySerializer(redisSerializer); //key hashmap序列化 redisTemplate.setHashValueSerializer(redisSerializer); return redisTemplate; } }
RedisConnection實現分佈式鎖的方式,採用redisTemplate操做redisConnection實現setnx和setex兩個命令連用
package com.cyb.ybmobileredis.schedule; import com.cyb.ybmobileredis.service.RedisService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisStringCommands; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.core.types.Expiration; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scripting.support.ResourceScriptSource; import org.springframework.stereotype.Service; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; /** * @ClassName:LockNxExJob * @Description:分佈式獲取鎖和釋放鎖 * @Author:chenyb * @Date:2020/8/16 5:44 下午 * @Versiion:1.0 */ @Service public class LockNxExJob { private static final Logger logger = LoggerFactory.getLogger(LockNxExJob.class); @Autowired private RedisService redisService; @Autowired private RedisTemplate redisTemplate; private static String LOCK_PREFIX = "prefix_"; private DefaultRedisScript<Boolean> lockScript; //通常分佈式鎖 // @Scheduled(fixedRate = 8000) // public void lockJob() { // String lock = LOCK_PREFIX + "LockNxExJob"; // boolean nxRet = false; // try { // //redistemplate setnx操做 // nxRet = redisTemplate.opsForValue().setIfAbsent(lock, getHostIp()); // Object lockValue = redisService.get(lock); // System.out.println(lockValue); // //獲取鎖失敗 // if (!nxRet) { // String value = (String) redisService.get(lock); // //打印當前佔用鎖的服務器IP // logger.info(System.currentTimeMillis() + " get lock fail,lock belong to:{}", value); // return; // } else { // redisTemplate.opsForValue().set(lock, getHostIp(), 3600000); // // //獲取鎖成功 // logger.info(System.currentTimeMillis() + " start lock lockNxExJob success"); // Thread.sleep(4000); // } // } catch (Exception e) { // logger.error("lock error", e); // // } finally { // if (nxRet) { // System.out.println("釋放鎖成功"); // redisService.remove(lock); // } // } // } /** * lua腳本方式分佈式鎖 */ // @Scheduled(fixedRate = 8000) // public void luaLockJob() { // String lock = LOCK_PREFIX + "LockNxExJob"; // boolean nxRet = false; // try { // //redistemplate setnx操做 // //nxRet = luaExpress(lock,getHostIp()); // nxRet = setLock(lock,600); // Object lockValue = redisService.get(lock); // System.out.println(lockValue); // //獲取鎖失敗 // if (!nxRet) { // String value = (String) redisService.get(lock); // //打印當前佔用鎖的服務器IP // logger.info(System.currentTimeMillis() + " lua get lock fail,lock belong to:{}", value); // return; // } else { // redisTemplate.opsForValue().set(lock, getHostIp(), 3600000); // // //獲取鎖成功 // logger.info(System.currentTimeMillis() + " lua start lock lockNxExJob success"); // Thread.sleep(4000); // } // } catch (Exception e) { // logger.error("lua lock error", e); // // } finally { // if (nxRet) { // System.out.println("lua 釋放鎖成功"); // redisService.remove(lock); // } // } // } /** * setnx和setex連用分佈式鎖 */ @Scheduled(fixedRate = 8000) public void setLockJob() { String lock = LOCK_PREFIX + "LockNxExJob"; boolean nxRet = false; try { //redistemplate setnx操做 //nxRet = luaExpress(lock,getHostIp()); nxRet = setLock(lock,getHostIp(),3); Object lockValue = redisService.get(lock); System.out.println(lockValue); //獲取鎖失敗 if (!nxRet) { String value = (String) redisService.get(lock); //打印當前佔用鎖的服務器IP logger.info(System.currentTimeMillis() + " setnx and setex get lock fail,lock belong to:{}", value); return; } else { redisTemplate.opsForValue().set(lock, getHostIp(), 3600000); //獲取鎖成功 logger.info(System.currentTimeMillis() + " setnx and setex start lock lockNxExJob success"); Thread.sleep(4000); } } catch (Exception e) { logger.error(" setnx and setex lock error", e); } finally { if (nxRet) { System.out.println(" setnx and setex 釋放鎖成功"); redisService.remove(lock); } } } /** * setnx和setex連用 * @param key 鍵 * @param value 值 * @param expire 超時時間 * @return */ public boolean setLock(String key,String value,long expire){ try{ Boolean result=(boolean)redisTemplate.execute(new RedisCallback<Boolean>() { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { return connection.set(key.getBytes(),value.getBytes(),Expiration.seconds(expire),RedisStringCommands.SetOption.ifAbsent()); } }); return result; }catch (Exception e){ logger.error("set redis occured an exception",e); } return false; } /** * 獲取lua結果 * * @param key 鍵 * @param value 值 * @return */ public Boolean luaExpress(String key, String value) { lockScript = new DefaultRedisScript<>(); lockScript.setScriptSource( new ResourceScriptSource(new ClassPathResource("add.lua")) ); //設置返回值 lockScript.setResultType(Boolean.class); //封裝參數 List<Object> keyList = new ArrayList<>(); keyList.add(key); keyList.add(value); Boolean result = (Boolean) redisTemplate.execute(lockScript, keyList); return result; } /** * 獲取本機內網IP地址方法 * * @return */ private static String getHostIp() { try { Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces(); while (allNetInterfaces.hasMoreElements()) { NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement(); Enumeration<InetAddress> addresses = netInterface.getInetAddresses(); while (addresses.hasMoreElements()) { InetAddress ip = (InetAddress) addresses.nextElement(); if (ip != null && ip instanceof Inet4Address && !ip.isLoopbackAddress() //loopback地址即本機地址,IPv4的loopback範圍是127.0.0.0 ~ 127.255.255.255 && ip.getHostAddress().indexOf(":") == -1) { return ip.getHostAddress(); } } } } catch (Exception e) { e.printStackTrace(); } return null; } }
上面幾個案例,已經實現了分佈式鎖的功能,可是極端狀況下,ServerA程序還沒執行完,ServerB程序執行完,把鎖釋放掉了,就會形成A的鎖釋放掉了,這不是扯嘛,ServerA還沒執行完,鎖就被其餘人釋放了。解決方案:釋放的時候,使用lua,經過get方法獲取value,判斷value是否等於本機ip,是本身的才能釋放
package com.cyb.ybmobileredis.schedule; import com.cyb.ybmobileredis.service.RedisService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisStringCommands; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.core.types.Expiration; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scripting.support.ResourceScriptSource; import org.springframework.stereotype.Service; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; /** * @ClassName:LockNxExJob * @Description:分佈式獲取鎖和釋放鎖 * @Author:chenyb * @Date:2020/8/16 5:44 下午 * @Versiion:1.0 */ @Service public class LockNxExJob { private static final Logger logger = LoggerFactory.getLogger(LockNxExJob.class); @Autowired private RedisService redisService; @Autowired private RedisTemplate redisTemplate; private static String LOCK_PREFIX = "prefix_"; private DefaultRedisScript<Boolean> lockScript; //通常分佈式鎖 // @Scheduled(fixedRate = 8000) // public void lockJob() { // String lock = LOCK_PREFIX + "LockNxExJob"; // boolean nxRet = false; // try { // //redistemplate setnx操做 // nxRet = redisTemplate.opsForValue().setIfAbsent(lock, getHostIp()); // Object lockValue = redisService.get(lock); // System.out.println(lockValue); // //獲取鎖失敗 // if (!nxRet) { // String value = (String) redisService.get(lock); // //打印當前佔用鎖的服務器IP // logger.info(System.currentTimeMillis() + " get lock fail,lock belong to:{}", value); // return; // } else { // redisTemplate.opsForValue().set(lock, getHostIp(), 3600000); // // //獲取鎖成功 // logger.info(System.currentTimeMillis() + " start lock lockNxExJob success"); // Thread.sleep(4000); // } // } catch (Exception e) { // logger.error("lock error", e); // // } finally { // if (nxRet) { // System.out.println("釋放鎖成功"); // redisService.remove(lock); // } // } // } /** * lua腳本方式分佈式鎖 */ // @Scheduled(fixedRate = 8000) // public void luaLockJob() { // String lock = LOCK_PREFIX + "LockNxExJob"; // boolean nxRet = false; // try { // //redistemplate setnx操做 // //nxRet = luaExpress(lock,getHostIp()); // nxRet = setLock(lock,600); // Object lockValue = redisService.get(lock); // System.out.println(lockValue); // //獲取鎖失敗 // if (!nxRet) { // String value = (String) redisService.get(lock); // //打印當前佔用鎖的服務器IP // logger.info(System.currentTimeMillis() + " lua get lock fail,lock belong to:{}", value); // return; // } else { // redisTemplate.opsForValue().set(lock, getHostIp(), 3600000); // // //獲取鎖成功 // logger.info(System.currentTimeMillis() + " lua start lock lockNxExJob success"); // Thread.sleep(4000); // } // } catch (Exception e) { // logger.error("lua lock error", e); // // } finally { // if (nxRet) { // System.out.println("lua 釋放鎖成功"); // redisService.remove(lock); // } // } // } /** * setnx和setex連用分佈式鎖 */ @Scheduled(fixedRate = 8000) public void setLockJob() { String lock = LOCK_PREFIX + "LockNxExJob"; boolean nxRet = false; try { //redistemplate setnx操做 //nxRet = luaExpress(lock,getHostIp()); System.out.println("hostIp1="+getHostIp()); nxRet = setLock(lock, getHostIp(), 30); Object lockValue = redisService.get(lock); System.out.println(lockValue); //獲取鎖失敗 if (!nxRet) { String value = (String) redisService.get(lock); //打印當前佔用鎖的服務器IP logger.info(System.currentTimeMillis() + " setnx and setex get lock fail,lock belong to:{}", value); return; } else { //獲取鎖成功 logger.info(System.currentTimeMillis() + " setnx and setex start lock lockNxExJob success"); Thread.sleep(4000); } } catch (Exception e) { logger.error(" setnx and setex lock error", e); } finally { if (nxRet) { System.out.println(" setnx and setex 釋放鎖成功"); //redisService.remove(lock); //使用lua腳本釋放鎖 System.out.println("hostIp2="+getHostIp()); Boolean result = releaseLock(lock, getHostIp()); System.out.println("狀態:"+result); } } } /** * 釋放鎖操做 * * @param key 鍵 * @param value 值 * @return */ private boolean releaseLock(String key, String value) { lockScript = new DefaultRedisScript<Boolean>(); lockScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("unlock.lua"))); lockScript.setResultType(Boolean.class); //封裝參數 List<Object> keyList = new ArrayList<>(); keyList.add(key); keyList.add(value); Boolean result = (Boolean) redisTemplate.execute(lockScript, keyList); return result; } /** * setnx和setex連用 * * @param key 鍵 * @param value 值 * @param expire 超時時間 * @return */ public boolean setLock(String key, String value, long expire) { try { Boolean result = (boolean) redisTemplate.execute(new RedisCallback<Boolean>() { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { return connection.set(key.getBytes(), value.getBytes(), Expiration.seconds(expire), RedisStringCommands.SetOption.ifAbsent()); } }); return result; } catch (Exception e) { logger.error("set redis occured an exception", e); } return false; } /** * 獲取lua結果 * * @param key 鍵 * @param value 值 * @return */ public Boolean luaExpress(String key, String value) { lockScript = new DefaultRedisScript<>(); lockScript.setScriptSource( new ResourceScriptSource(new ClassPathResource("add.lua")) ); //設置返回值 lockScript.setResultType(Boolean.class); //封裝參數 List<Object> keyList = new ArrayList<>(); keyList.add(key); keyList.add(value); Boolean result = (Boolean) redisTemplate.execute(lockScript, keyList); return result; } /** * 獲取本機內網IP地址方法 * * @return */ private static String getHostIp() { try { Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces(); while (allNetInterfaces.hasMoreElements()) { NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement(); Enumeration<InetAddress> addresses = netInterface.getInetAddresses(); while (addresses.hasMoreElements()) { InetAddress ip = (InetAddress) addresses.nextElement(); if (ip != null && ip instanceof Inet4Address && !ip.isLoopbackAddress() //loopback地址即本機地址,IPv4的loopback範圍是127.0.0.0 ~ 127.255.255.255 && ip.getHostAddress().indexOf(":") == -1) { return ip.getHostAddress(); } } } } catch (Exception e) { e.printStackTrace(); } return null; } }
local lockKey = KEYS[1] local lockValue = KEYS[2] -- get key local result_1 = redis.call('get', lockKey) if result_1 == lockValue then local result_2= redis.call('del', lockKey) return result_2 else return false end
爲了演示方便,我把失效時間設置短一點,8秒
嫖都嫖完了,難道你忍心不點贊關注嘛,O(∩_∩)O哈哈~~~~~今天,先到這,後續繼續寫Redis秒殺系統的設計。
連接: https://pan.baidu.com/s/1uVoRQs8K3_zTfHXSTeP0uA 密碼: k9sv