什麼是分佈式鎖?
要介紹分佈式鎖,首先要提到與分佈式鎖相對應的是線程鎖、進程鎖。java
線程鎖:主要用來給方法、代碼塊加鎖。當某個方法或代碼使用鎖,在同一時刻僅有一個線程執行該方法或該代碼段。線程鎖只在同一JVM中有效果,由於線程鎖的實如今根本上是依靠線程之間共享內存實現的,好比synchronized是共享對象頭,顯示鎖Lock是共享某個變量(state)。git
進程鎖:爲了控制同一操做系統中多個進程訪問某個共享資源,由於進程具備獨立性,各個進程沒法訪問其餘進程的資源,所以沒法經過synchronized等線程鎖實現進程鎖。web
分佈式鎖:當多個進程不在同一個系統中,用分佈式鎖控制多個進程對資源的訪問。
redis
分佈式鎖的使用場景
線程間併發問題和進程間併發問題都是能夠經過分佈式鎖解決的,可是強烈不建議這樣作!由於採用分佈式鎖解決這些小問題是很是消耗資源的!分佈式鎖應該用來解決分佈式狀況下的多進程併發問題纔是最合適的。spring
有這樣一個情境,線程A和線程B都共享某個變量X。apache
若是是單機狀況下(單JVM),線程之間共享內存,只要使用線程鎖就能夠解決併發問題。promise
若是是分佈式狀況下(多JVM),線程A和線程B極可能不是在同一JVM中,這樣線程鎖就沒法起到做用了,這時候就要用到分佈式鎖來解決。
tomcat
分佈式鎖簡介
其實Java世界的」半壁江山」——Spring早就提供了分佈式鎖的實現。早期,分佈式鎖的相關代碼存在於Spring Cloud的子項目Spring Cloud Cluster中,後來被遷到Spring Integration中。springboot
可能有很多童鞋對Spring Integration不是很熟悉,簡單介紹一下——官方說法,這是一個 企業集成模式
的實現;通俗地說,Spring Integration的定位是一個輕量級的ESB,儘管它作了不少ESB不作的事情。順便說一下,Spring Cloud Stream的底層也是Spring Integration。服務器
Spring Integration提供的全局鎖目前爲以下存儲提供了實現:
- Gemfire
- JDBC
- Redis
- Zookeeper
它們使用相同的API抽象——這正是Spring最擅長的。這意味着,不論使用哪一種存儲,你的編碼體驗是同樣的,有一天想更換實現,只須要修改依賴和配置就能夠了,無需修改代碼。
編碼
新建一個sprinboot項目,而後配置相關內容和測試代碼。
1.pom.xml
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.itmuch.cloud</groupId> <artifactId>redisLock</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>redisLock</name> <url>http://maven.apache.org</url> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <!-- 這個須要爲 true 熱部署纔有效 --> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-integration</artifactId> </dependency> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> </dependencies> <properties> <java.version>1.8</java.version> </properties> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
這裏提一下,我以前都是用的1.5.9.RELEASE版本的springboot。但我發現添加了上面紅色字體的三個依賴以後,發現編譯不經過,發聵的信息是版本問題,後來我改成2.0.0.RELEASE版本,OK了。
2.application.yml
server: port: 8080 spring: redis: port: 6379 host: localhost
當前這個應用的端口,咱們設置爲8080,而後Redis服務器的端口固然是默認的6379啦!
3.RedisLockConfiguration.java
@Configuration public class RedisLockConfiguration { @Bean public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) { return new RedisLockRegistry(redisConnectionFactory, "spring-cloud"); } }
這個很好理解,咱們要用到鎖,固然得先註冊一個鎖的Bean對象到spring容器中,以便獲取使用。
4.testController.java
@RestController @RequestMapping(value = "index") public class testController { private final static Logger log = LoggerFactory.getLogger(testController.class); @Autowired private RedisLockRegistry redisLockRegistry; @GetMapping("test") public void test() throws InterruptedException { Lock lock = redisLockRegistry.obtain("lock"); boolean b1 = lock.tryLock(3, TimeUnit.SECONDS); log.info("b1 is : {}", b1); TimeUnit.SECONDS.sleep(5); boolean b2 = lock.tryLock(3, TimeUnit.SECONDS); log.info("b2 is : {}", b2); lock.unlock(); lock.unlock(); }
這個接口類中的代碼若是不太明白,不,無論你明不明白,都建議看一下org.springframework.integration.redis.util.RedisLockRegistry這個類的註釋。
/** * Implementation of {@link LockRegistry} providing a distributed lock using Redis. * Locks are stored under the key {@code registryKey:lockKey}. Locks expire after * (default 60) seconds. Threads unlocking an * expired lock will get an {@link IllegalStateException}. This should be * considered as a critical error because it is possible the protected * resources were compromised. * <p> * Locks are reentrant. * <p> * <b>However, locks are scoped by the registry; a lock from a different registry with the * same key (even if the registry uses the same 'registryKey') are different * locks, and the second cannot be acquired by the same thread while the first is * locked.</b> * <p> * <b>Note: This is not intended for low latency applications.</b> It is intended * for resource locking across multiple JVMs. * <p> * {@link Condition}s are not supported.
測試
這樣,咱們一個工程就開發完了,你能夠再複製一份工程,RedisLock2.只要把端口號改了就行,好比改成8081.而後同時啓動倆工程。
我通常都是一個放在eclipse中跑,一個在終端經過命令行啓動,這樣簡潔一點。
接下來,你打開兩個網頁,輸好地址,而後快速依次訪問兩個工程的接口:
http://localhost:8081/index/test
http://localhost:8080/index/test
而後看兩個控制檯的結果
先看第一個:兩個都是true,說明同一個線程是能夠得到到鎖的,正如上面註釋
Locks are reentrant.
再看第二個:第一次是false,由於上一個線程鎖住了,尚未釋放,因此它是獲取不到的。而第二次返回true,說明得到到了,由於第一個工程中跑的線程已經釋放了鎖。
另外,你若是要看到現象,你開啓RedisClient.你訪問其中一個工程的接口,快速刷新redis對應的db(個人是db0)
你不停的刷新db0,你會看到這個spring-cloud的鎖key,過幾秒就消失了,由於被釋放了嘛。這也證明了咱們的運行結果。
若是你代碼中不釋放鎖,那麼這個spring-cloud的鎖key過60秒會自動消失,正如上面註釋所描述的那樣。
代碼下載地址:https://gitee.com/fengyuduke/my_open_resources/blob/master/redisLock.zip