├── LICENSE ├── README.md ├── README.zh_cn.md ├── doc ├── dlock-architecture.png ├── throughput0.png └── throughput1.png ├── pom.xml └── src ├── main └── java │ └── com │ └── baidu │ └── fsg │ └── dlock │ ├── DistributedReentrantLock.java │ ├── domain │ ├── DLockConfig.java │ ├── DLockEntity.java │ ├── DLockStatus.java │ └── DLockType.java │ ├── exception │ ├── DLockProcessException.java │ ├── OptimisticLockingException.java │ └── RedisProcessException.java │ ├── jedis │ └── JedisClient.java │ ├── processor │ ├── DLockProcessor.java │ └── impl │ │ └── RedisLockProcessor.java │ ├── support │ └── DLockGenerator.java │ └── utils │ ├── EnumUtils.java │ ├── NetUtils.java │ ├── ReflectionUtils.java │ └── ValuedEnum.java └── test ├── java └── com │ └── baidu │ └── fsg │ └── dlock │ ├── DLockGeneratorTest.java │ ├── DLockSimpleTest.java │ └── DistributedReentrantLockTest.java └── resources ├── dlock ├── config-dlock.properties ├── redis.properties └── spring-dlock.xml └── logback.xml /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Baidu, Inc. All Rights Reserved 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright (c) 2017 Baidu, Inc. All Rights Reserved 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DLock - Distributed Lock 2 | ========================== 3 | [In Chinese 中文版](README.zh_cn.md) 4 | 5 | DLock is a Java implemented, effective and reliable Distributed Lock. It uses Redis to store lock object, 6 | and executes atomic operation on lock via [Lua](https://en.wikipedia.org/wiki/Lua_(programming_language)) script. 7 | Based on Redis expire mechanism, DLock implements lock [lease](https://en.wikipedia.org/wiki/Lease_(computer_science)) 8 | to ensure release. In order to provide high performance, DLock adopts process level lock model and uses 9 | variant CLH queue to manage lock competitors. 10 | 11 | Requirements:[Java8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)+、 12 | [Redis2.6.12](https://redis.io/download)+(Require Redis Set -> NX PX command) 13 | 14 | Architecture 15 | -------- 16 | ![DLock](doc/dlock-architecture.png) 17 | 18 | #### Features #### 19 | * Atomic lock operation 20 | 21 | One Lock Operation will correspond to one Lua script. Since Redis can execute Lua script atomically, 22 | Lock Operation such as lock, release and expand lease will be atomic. 23 | 24 | * Reentrant ability 25 | 26 | The variable ```holdCount ```is maintained by local DLock object. ```holdCount``` will be increased by one when 27 | locker re-enter, and decreased by one when leave. 28 | 29 | * Lock lease 30 | 31 | On the basis of Redis expire mechanism, the lock lease is integrated to release lock after the locker 32 | crashed. That is to say, infinite lock hold will never happen. 33 | 34 | * High performance lock model 35 | 36 | A lock-free variant CLH queue is used to maintain the competitor threads. And the retry thread will periodically 37 | awake the CLH queue's head thread to retry, so that only one thread per process can participate in lock competition, 38 | which will avoid unnecessary lock competition. Meanwhile, the unfair lock is also provided for throughput. 39 | 40 | 41 | Quick Start 42 | ------------ 43 | Here we have a demo with 3 steps to introduce how to integrate DLock into Spring based projects.
44 | 45 | ### Step 1: Install Java8, Maven, Redis 46 | If you have already installed Maven, JDK and Redis, just skip to next.
47 | Download [Java8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html), 48 | [Maven](https://maven.apache.org/download.cgi) and [Redis2.6.12](https://redis.io/download), then install. For maven, 49 | extracting and setting MAVEN_HOME is enough. 50 | Download, extract and compile Redis with: 51 | ```sh 52 | wget http://download.redis.io/releases/redis-3.2.6.tar.gz 53 | tar xzf redis-3.2.6.tar.gz 54 | cd redis-3.2.6 55 | make 56 | ``` 57 | The binaries that are now compiled are available in the src directory. Run Redis with: 58 | ```sh 59 | src/redis-server 60 | ``` 61 | Now, Redis server is ready to accept connections on port 6379 by default. 62 | 63 | #### Set JAVA_HOME & MAVEN_HOME 64 | Here is a sample script to set JAVA_HOME and MAVEN_HOME 65 | ```shell 66 | export MAVEN_HOME=/xxx/xxx/software/maven/apache-maven-3.3.9 67 | export PATH=$MAVEN_HOME/bin:$PATH 68 | JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home"; 69 | export JAVA_HOME; 70 | ``` 71 | 72 | ### Step 2: Reset Redis configuration 73 | Reset property of 'redis.host' and 'redis.port' in [redis.properties](src/test/resources/dlock/redis.properties) 74 | 75 | 76 | ### Step 3: Run UnitTest 77 | DLock implements the interface of `java.util.concurrent.Lock`, and so, you can use DLock like that.
78 | [DLockSimpleTest](src/test/java/com/baidu/fsg/dlock/DLockSimpleTest.java) shows basic use of DLock;
79 | [DistributedReentrantLockTest](src/test/java/com/baidu/fsg/dlock/DistributedReentrantLockTest.java) show more complex use such like multi-competitors, reentrant lock. 80 | 81 | DLock TPS 82 | ------------- 83 | DLock will group lock competitors into several groups, only one competitor of a group is able to compete, and 84 | competitors within a group obtain the competition chance sequentially by default. This will avoid unnecessary 85 | competition. To verify that, we compare DLock with tradition Lock Model(```tradition```) using the concept 'Lock Trip', 86 | and a 'Lock Trip' consists of ```lock()```, ```calculate()```, and ```unlock()```. 87 | In experiment, we set ```calculate()``` time and DLock's lease time to 10ms and 60ms separately, and vary concurrency 88 | from 8 to 128 with step 8. Finally, the TPS ratio of DLock to ```tradition``` is calculated, i.e:
89 | R = TPSDLock / TPStradition
90 | 91 | |threads|8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128 92 | |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| 93 | |R|1.01 |1.07 |1.21 |1.45 |1.56 |1.60 |1.66 |1.67 |1.69 |1.70 |1.71 |1.72 |1.74 |1.74 |1.67 |1.71 | 94 | 95 | ####Attention 96 | The Lock Model's TPS is related to ```calculate()```, so we just consider the performance trend.
97 | ![DLock VS Tradition](doc/throughput0.png) 98 | 99 | However, R value has nothing to with ```calculate()``` time, and so it is significant.
100 | ![R](doc/throughput1.png) 101 | 102 | From above data, we find ```tradition``` has comparable performance to DLock in low concurrency environment. 103 | With the increase of concurrency, ```tradition```'s performance degrade rapidly. But DLock is still good, this is 104 | because DLock will group competitors into a few groups with only one active competitor per group, as a result, DLock's 105 | performance will not worsen with concurrency increase. Furthermore, DLock implements non-fair Lock, which can respond 106 | to high priority request. -------------------------------------------------------------------------------- /README.zh_cn.md: -------------------------------------------------------------------------------- 1 | DLock - Distributed Lock 2 | ========================== 3 | [In English](README.md) 4 | 5 | DLock是由Java实现的,一套高效高可靠的分布式锁方案。 6 | 使用Redis存储锁,通过[Lua](https://en.wikipedia.org/wiki/Lua_(programming_language))脚本进行原子性锁操作, 7 | 实现了基于Redis过期机制的[lease](https://en.wikipedia.org/wiki/Lease_(computer_science)),并提供了一种基于变种CLH队列的进程级锁竞争模型。 8 | 9 | 依赖版本:[Java8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)及以上版本、[Redis-2.6.12](http://redis.cn)及以上版本(使用到Redis Set -> NX PX指令) 10 | 11 | 架构设计 12 | -------- 13 | ![DLock](doc/dlock-architecture.png) 14 | 15 | #### 特点 #### 16 | * 原子性 17 | 18 | 加锁、释放锁、延长租约等锁操作,均通过Lua脚本操作Redis,保证锁操作的原子性。 19 | 20 | * 可重入性 21 | 22 | 由本地锁对象内部存储持有者重入次数,等于零时释放锁, 从而保证锁的可重入. 23 | 24 | * 锁租约 25 | 26 | 基于Redis过期机制,实现了锁的租约和自动续租,既保证锁持有者有充足时间完成相应动作, 又避免持有者crash后锁不被释放的情形,提高了锁的可用性。 27 | 28 | * 高性能锁模型 29 | 30 | 采用lock-free的变种CLH锁队列维护竞争线程,并由重试线程唤醒Head去竞争锁, 从而将锁竞争粒度限定在进程级, 有效避免不必要的锁竞争. 此外还实现了非公平锁,以提升吞吐量。 31 | 32 | 33 | Quick Start 34 | ------------ 35 | 这里介绍如何在基于Spring的项目中使用DLock,具体流程如下:
36 | 37 | ### 步骤1: 安装依赖Java8、Maven、Redis 38 | 下载[Java8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)、[maven](https://maven.apache.org/download.cgi)和[Redis2.6.12](http://redis.cn),然后安装部署。 39 | 对于Redis,可执行下述脚本来下载,解压和编译; 40 | ```sh 41 | wget http://download.redis.io/releases/redis-3.2.6.tar.gz 42 | tar xzf redis-3.2.6.tar.gz 43 | cd redis-3.2.6 44 | make 45 | ``` 46 | 再执行下述脚本来部署Redis; 47 | ```sh 48 | src/redis-server 49 | ``` 50 | 至此, Redis节点已在默认端口6379监听服务 51 | 52 | #### 设置环境变量 53 | maven无须安装,设置好MAVEN_HOME即可。可像下述脚本这样设置JAVA_HOME和MAVEN_HOME,如已设置请忽略。 54 | ```shell 55 | export MAVEN_HOME=/xxx/xxx/software/maven/apache-maven-3.3.9 56 | export PATH=$MAVEN_HOME/bin:$PATH 57 | JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home"; 58 | export JAVA_HOME; 59 | ``` 60 | ### 步骤2: 修改Redis连接配置 61 | 修改[redis.properties](src/test/resources/dlock/redis.properties)配置中, redis.host和redis.port为本地redis的配置。 62 | 63 | ### 步骤3: 运行示例单测 64 | DLock实现了JAVA的锁接口`java.util.concurrent.Lock`,其语法与Lock一致,无额外使用成本。
65 | 单测[DLockSimpleTest](src/test/java/com/baidu/fsg/dlock/DLockSimpleTest.java),展示了锁的基本用法;
66 | 单测[DistributedReentrantLockTest](src/test/java/com/baidu/fsg/dlock/DistributedReentrantLockTest.java),展示了如重入、多线程/进程竞争下锁等场景 67 | 68 | 69 | DLock TPS 70 | ---------- 71 | DLock进程级的锁模型,采用了变种CLH队列维护待竞争线程,仅令单一线程参与竞争,从而有效降低无效锁竞争,提升整体性能。 72 | 为此,这里将传统锁模型(所有线程一起去竞争)与DLock的性能进行对比。以单位时间(秒)一次完整锁操作(获取锁 -> 计算 -> 释放锁)作为衡量指标。 73 | 在实验时,将获取锁后的计算时间设置为10ms,DLock的锁lease设置为60ms,并统计线程数分别为8至128(间隔为8)时的完整锁操作速度,并以百分比的形式展示,即:
74 | R = TPSDLock / TPStradition
75 | 数据如下表所示: 76 | 77 | |threads|8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128 78 | |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| 79 | |R|1.01 |1.07 |1.21 |1.45 |1.56 |1.60 |1.66 |1.67 |1.69 |1.70 |1.71 |1.72 |1.74 |1.74 |1.67 |1.71 | 80 | 81 | ####注意 82 | 由于完整锁操作的TPS值跟持有锁的时间有关,因此,单纯关注TPS值是没有意义的,这里比较TPS的变化趋势。
83 | 84 | ![DLock VS Tradition](doc/throughput0.png) 85 | 86 | 变量R代表两种锁模型的TPS比值,与持有锁的时间无关,其数值是有意义的。
87 | 88 | ![R](doc/throughput1.png) 89 | 90 | ####结论 91 | 在并发度很低时,DLock与传统锁模型的性能相当; 随着并发度的不断增加,传统锁模型性能开始下降,但DLock由于会将新增的竞争者添加到CLH队列中 92 | 进行等待(因为此时一起去竞争必然会有大量的线程竞争失败),依次参与锁竞争,减少了无效的锁竞争开销,从而使得锁性能保持不变。 93 | 同时DLock还支持非公平锁, 增加锁处理的吞吐量。 -------------------------------------------------------------------------------- /doc/dlock-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baidu/dlock/9b8a82a0da327c3a4dc7128ad0707d26802f3b43/doc/dlock-architecture.png -------------------------------------------------------------------------------- /doc/throughput0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baidu/dlock/9b8a82a0da327c3a4dc7128ad0707d26802f3b43/doc/throughput0.png -------------------------------------------------------------------------------- /doc/throughput1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baidu/dlock/9b8a82a0da327c3a4dc7128ad0707d26802f3b43/doc/throughput1.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | Distributed-Lock 7 | Distributed lock based on Redis 8 | 9 | com.baidu.fsg 10 | dlock 11 | 1.0.0-SNAPSHOT 12 | jar 13 | 14 | 15 | 16 | UTF-8 17 | 1.8 18 | 4.2.5.RELEASE 19 | 1.7.7 20 | 2.8.0 21 | 22 | 23 | 24 | 25 | 26 | 27 | org.springframework 28 | spring-core 29 | ${spring.version} 30 | 31 | 32 | org.springframework 33 | spring-beans 34 | ${spring.version} 35 | 36 | 37 | org.springframework 38 | spring-context 39 | ${spring.version} 40 | 41 | 42 | 43 | 44 | commons-collections 45 | commons-collections 46 | 3.2.2 47 | 48 | 49 | commons-lang 50 | commons-lang 51 | 2.6 52 | 53 | 54 | 55 | 56 | redis.clients 57 | jedis 58 | ${jedis-version} 59 | 60 | 61 | 62 | 63 | ch.qos.logback 64 | logback-classic 65 | 1.1.3 66 | 67 | 68 | org.slf4j 69 | slf4j-api 70 | ${slf4j-version} 71 | 72 | 73 | org.slf4j 74 | log4j-over-slf4j 75 | ${slf4j-version} 76 | 77 | 78 | 79 | 80 | junit 81 | junit 82 | 4.10 83 | test 84 | 85 | 86 | org.springframework 87 | spring-test 88 | ${spring.version} 89 | test 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | org.apache.maven.plugins 98 | maven-compiler-plugin 99 | 100 | ${jdk.version} 101 | ${jdk.version} 102 | ${project.build.sourceEncoding} 103 | 104 | 3.5.1 105 | 106 | 107 | org.apache.maven.plugins 108 | maven-source-plugin 109 | 2.3 110 | 111 | 112 | package 113 | 114 | jar 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /src/main/java/com/baidu/fsg/dlock/DistributedReentrantLock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Baidu, Inc. All Rights Reserve. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.baidu.fsg.dlock; 17 | 18 | import java.util.concurrent.TimeUnit; 19 | import java.util.concurrent.atomic.AtomicInteger; 20 | import java.util.concurrent.atomic.AtomicReference; 21 | import java.util.concurrent.locks.Condition; 22 | import java.util.concurrent.locks.Lock; 23 | import java.util.concurrent.locks.LockSupport; 24 | 25 | import com.baidu.fsg.dlock.domain.DLockConfig; 26 | import com.baidu.fsg.dlock.domain.DLockEntity; 27 | import com.baidu.fsg.dlock.domain.DLockStatus; 28 | import com.baidu.fsg.dlock.exception.DLockProcessException; 29 | import com.baidu.fsg.dlock.exception.OptimisticLockingException; 30 | import com.baidu.fsg.dlock.processor.DLockProcessor; 31 | import com.baidu.fsg.dlock.utils.NetUtils; 32 | 33 | /** 34 | * DistributedReentrantLock implements the lock,tryLock syntax of {@link Lock} by different mechanisms:
35 | *
  • database
  • 36 | * The database synchronization primitives(line lock with conditional "UPDATE" statement). 37 | * 38 | *
  • redis
  • 39 | * The Atomic redis command & Lua script, guaranteed the atomic operations.
    40 | * The expire mechanisms of redis, guaranteed the lock will be released without expanding lease request, 41 | * so that the other competitor can try to lock.

    42 | * 43 | * We use a variant of CLH lock queue for the competitor threads, provides an unfair implement to make high 44 | * throughput. 45 | * 46 | * @author chenguoqing 47 | * @author yutianbao 48 | */ 49 | public class DistributedReentrantLock implements Lock { 50 | 51 | /** 52 | * Lock configuration 53 | */ 54 | private final DLockConfig lockConfig; 55 | /** 56 | * Lock processor 57 | */ 58 | private final DLockProcessor lockProcessor; 59 | 60 | /** 61 | * Head of the wait queue, lazily initialized. Except for initialization, it is modified only via method setHead. 62 | * Note: If head exists, its waitStatus is guaranteed not to be CANCELLED. 63 | */ 64 | private final AtomicReference head = new AtomicReference<>(); 65 | /** 66 | * Tail of the wait queue, lazily initialized. Modified only via method enq to add new wait node. 67 | */ 68 | private final AtomicReference tail = new AtomicReference<>(); 69 | 70 | /** 71 | * The current owner of exclusive mode synchronization. 72 | */ 73 | private final AtomicReference exclusiveOwnerThread = new AtomicReference<>(); 74 | /** 75 | * Retry thread reference 76 | */ 77 | private final AtomicReference retryLockRef = new AtomicReference<>(); 78 | /** 79 | * Expand lease thread reference 80 | */ 81 | private final AtomicReference expandLockRef = new AtomicReference<>(); 82 | 83 | /** 84 | * Once a thread hold this lock, the thread can reentrant the lock. 85 | * This value represents the count of holding this lock. Default as 0 86 | */ 87 | private final AtomicInteger holdCount = new AtomicInteger(0); 88 | 89 | /** 90 | * CLH Queue Node for holds all parked thread 91 | */ 92 | static class Node { 93 | final AtomicReference prev = new AtomicReference<>(); 94 | final AtomicReference next = new AtomicReference<>(); 95 | final Thread t; 96 | 97 | Node() { 98 | this(null); 99 | } 100 | 101 | Node(Thread t) { 102 | this.t = t; 103 | } 104 | } 105 | 106 | /** 107 | * Constructor with lock configuration and lock processor 108 | */ 109 | public DistributedReentrantLock(DLockConfig lockConfig, DLockProcessor lockProcessor) { 110 | this.lockConfig = lockConfig; 111 | this.lockProcessor = lockProcessor; 112 | } 113 | 114 | @Override 115 | public Condition newCondition() { 116 | throw new UnsupportedOperationException(); 117 | } 118 | 119 | @Override 120 | public void lockInterruptibly() throws InterruptedException { 121 | throw new UnsupportedOperationException(); 122 | } 123 | 124 | @Override 125 | public void lock() { 126 | // lock db record 127 | if (!tryLock()) { 128 | acquireQueued(addWaiter()); 129 | } 130 | } 131 | 132 | final void acquireQueued(final Node node) { 133 | for (;;) { 134 | final Node p = node.prev.get(); 135 | if (p == head.get() && tryLock()) { 136 | head.set(node); 137 | p.next.set(null); // help GC 138 | break; 139 | } 140 | 141 | // if need, start retry thread 142 | if (exclusiveOwnerThread.get() == null) { 143 | startRetryThread(); 144 | } 145 | 146 | // park current thread 147 | LockSupport.park(this); 148 | } 149 | } 150 | 151 | private Node addWaiter() { 152 | Node node = new Node(Thread.currentThread()); 153 | // Try the fast path of enq; backup to full enq on failure 154 | Node pred = tail.get(); 155 | if (pred != null) { 156 | node.prev.set(pred); 157 | if (tail.compareAndSet(pred, node)) { 158 | pred.next.set(node); 159 | return node; 160 | } 161 | } 162 | enq(node); 163 | return node; 164 | } 165 | 166 | private Node enq(final Node node) { 167 | for (;;) { 168 | Node t = tail.get(); 169 | if (t == null) { // Must initialize 170 | Node h = new Node(); // Dummy header 171 | h.next.set(node); 172 | node.prev.set(h); 173 | if (head.compareAndSet(null, h)) { 174 | tail.set(node); 175 | return h; 176 | } 177 | } else { 178 | node.prev.set(t); 179 | if (tail.compareAndSet(t, node)) { 180 | t.next.set(node); 181 | return t; 182 | } 183 | } 184 | } 185 | } 186 | 187 | @Override 188 | public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { 189 | throw new UnsupportedOperationException(); 190 | } 191 | 192 | /** 193 | * Lock redis record through the atomic command Set(key, value, NX, PX, expireTime), only one request will success 194 | * while multiple concurrently requesting. 195 | */ 196 | @Override 197 | public boolean tryLock() { 198 | 199 | // current thread can reentrant, and locked times add once 200 | if (Thread.currentThread() == this.exclusiveOwnerThread.get()) { 201 | this.holdCount.incrementAndGet(); 202 | return true; 203 | } 204 | 205 | DLockEntity newLock = new DLockEntity(); 206 | newLock.setLockTime(System.currentTimeMillis()); 207 | newLock.setLocker(generateLocker()); 208 | newLock.setLockStatus(DLockStatus.PROCESSING); 209 | 210 | boolean locked = false; 211 | try { 212 | // get lock directly 213 | lockProcessor.updateForLock(newLock, lockConfig); 214 | locked = true; 215 | 216 | } catch (OptimisticLockingException | DLockProcessException e) { 217 | // NOPE. Retry in the next round. 218 | } 219 | 220 | if (locked) { 221 | // set exclusive thread 222 | this.exclusiveOwnerThread.set(Thread.currentThread()); 223 | 224 | // locked times reset to one 225 | this.holdCount.set(1); 226 | 227 | // shutdown retry thread 228 | shutdownRetryThread(); 229 | 230 | // start the timer for expand lease time 231 | startExpandLockLeaseThread(newLock); 232 | } 233 | 234 | return locked; 235 | } 236 | 237 | /** 238 | * Attempts to release this lock.

    239 | * 240 | * If the current thread is the holder of this lock then the hold 241 | * count is decremented. If the hold count is now zero then the lock 242 | * is released. If the current thread is not the holder of this 243 | * lock then {@link IllegalMonitorStateException} is thrown. 244 | * 245 | * @throws IllegalMonitorStateException if the current thread does not 246 | * hold this lock 247 | */ 248 | @Override 249 | public void unlock() throws IllegalMonitorStateException { 250 | // lock must be hold by current thread 251 | if (Thread.currentThread() != this.exclusiveOwnerThread.get()) { 252 | throw new IllegalMonitorStateException(); 253 | } 254 | 255 | // lock is still be hold 256 | if (holdCount.decrementAndGet() > 0) { 257 | return; 258 | } 259 | 260 | // clear remote lock 261 | DLockEntity currentLock = new DLockEntity(); 262 | currentLock.setLocker(generateLocker()); 263 | currentLock.setLockStatus(DLockStatus.PROCESSING); 264 | 265 | try { 266 | // release remote lock 267 | lockProcessor.updateForUnlock(currentLock, lockConfig); 268 | 269 | } catch (OptimisticLockingException | DLockProcessException e) { 270 | // NOPE. Lock will deleted automatic after the expire time. 271 | 272 | } finally { 273 | // Release exclusive owner 274 | this.exclusiveOwnerThread.compareAndSet(Thread.currentThread(), null); 275 | 276 | // Shutdown expand thread 277 | shutdownExpandThread(); 278 | 279 | // wake up the head node for compete lock 280 | unparkQueuedNode(); 281 | } 282 | } 283 | 284 | /** 285 | * wake up the head node for compete lock 286 | */ 287 | private void unparkQueuedNode() { 288 | // wake up the head node for compete lock 289 | Node h = head.get(); 290 | if (h != null && h.next.get() != null) { 291 | LockSupport.unpark(h.next.get().t); 292 | } 293 | } 294 | 295 | /** 296 | * Generate current locker. IP_Thread ID 297 | */ 298 | private String generateLocker() { 299 | return NetUtils.getLocalAddress() + "-" + Thread.currentThread().getId(); 300 | } 301 | 302 | /** 303 | * Task for expanding the lock lease 304 | */ 305 | abstract class LockThread extends Thread { 306 | /** 307 | * Synchronizes 308 | */ 309 | final Object sync = new Object(); 310 | /** 311 | * Delay time for start(ms) 312 | */ 313 | final int delay; 314 | /** 315 | * Retry interval(ms) 316 | */ 317 | final int retryInterval; 318 | 319 | final AtomicInteger startState = new AtomicInteger(0); 320 | /** 321 | * Control variable for shutdown 322 | */ 323 | private boolean shouldShutdown = false; 324 | /** 325 | * Is first running 326 | */ 327 | private boolean firstRunning = true; 328 | 329 | LockThread(String name, int delay, int retryInterval) { 330 | setDaemon(true); 331 | this.delay = delay; 332 | this.retryInterval = retryInterval; 333 | setName(name + "-" + getId()); 334 | } 335 | 336 | @Override 337 | public void run() { 338 | while (!shouldShutdown) { 339 | synchronized (sync) { 340 | try { 341 | // first running, delay 342 | if (firstRunning && delay > 0) { 343 | firstRunning = false; 344 | sync.wait(delay); 345 | } 346 | 347 | // execute task 348 | execute(); 349 | 350 | // wait for interval 351 | sync.wait(retryInterval); 352 | 353 | } catch (InterruptedException e) { 354 | shouldShutdown = true; 355 | } 356 | } 357 | } 358 | 359 | // clear associated resources for implementations 360 | beforeShutdown(); 361 | } 362 | 363 | abstract void execute() throws InterruptedException; 364 | 365 | void beforeShutdown() { 366 | } 367 | } 368 | 369 | /** 370 | * Task for expanding the lock lease 371 | */ 372 | private class ExpandLockLeaseThread extends LockThread { 373 | 374 | final DLockEntity lock; 375 | 376 | ExpandLockLeaseThread(DLockEntity lock, int delay, int retryInterval) { 377 | super("ExpandLockLeaseThread", delay, retryInterval); 378 | this.lock = lock; 379 | } 380 | 381 | @Override 382 | void execute() throws InterruptedException { 383 | try { 384 | // set lock time 385 | lock.setLockTime(System.currentTimeMillis()); 386 | 387 | // update lock 388 | lockProcessor.expandLockExpire(lock, lockConfig); 389 | 390 | } catch (OptimisticLockingException e) { 391 | // if lock has been released, kill current thread 392 | throw new InterruptedException("Lock released."); 393 | 394 | } catch (DLockProcessException e) { 395 | // retry 396 | } 397 | } 398 | 399 | @Override 400 | void beforeShutdown() { 401 | expandLockRef.compareAndSet(this, null); 402 | } 403 | } 404 | 405 | private void startExpandLockLeaseThread(DLockEntity lock) { 406 | ExpandLockLeaseThread t = expandLockRef.get(); 407 | 408 | while (t == null || t.getState() == Thread.State.TERMINATED) { 409 | // set new expand lock thread 410 | int retryInterval = (int) (lockConfig.getMillisLease() * 0.75); 411 | expandLockRef.compareAndSet(t, new ExpandLockLeaseThread(lock, 1, retryInterval)); 412 | 413 | // retrieve the new expand thread instance 414 | t = expandLockRef.get(); 415 | } 416 | 417 | if (t.startState.compareAndSet(0, 1)) { 418 | t.start(); 419 | } 420 | } 421 | 422 | private void shutdownExpandThread() { 423 | ExpandLockLeaseThread t = expandLockRef.get(); 424 | if (t != null && t.isAlive()) { 425 | t.interrupt(); 426 | } 427 | } 428 | 429 | /** 430 | * Start when: (1) no threads hold lock; (2) CLH has waiting thread(s). And shutdown when one thread 431 | * posses the lock, because it does not has necessary to start retry thread. 432 | */ 433 | private class RetryLockThread extends LockThread { 434 | 435 | RetryLockThread(int delay, int retryInterval) { 436 | super("RetryLockThread", delay, retryInterval); 437 | } 438 | 439 | @Override 440 | void execute() throws InterruptedException { 441 | 442 | // if existing running thread, kill self 443 | if (exclusiveOwnerThread.get() != null) { 444 | throw new InterruptedException("Has running thread."); 445 | } 446 | 447 | Node h = head.get(); 448 | 449 | // no thread for lock, kill self 450 | if (h == null) { 451 | throw new InterruptedException("No waiting thread."); 452 | } 453 | 454 | boolean needRetry = false; 455 | try { 456 | needRetry = lockProcessor.isLockFree(lockConfig.getLockUniqueKey()); 457 | } catch (DLockProcessException e) { 458 | needRetry = true; 459 | } 460 | 461 | // if the lock has been releases or expired, re-competition 462 | if (needRetry) { 463 | // wake up the head node for compete lock 464 | unparkQueuedNode(); 465 | } 466 | } 467 | 468 | @Override 469 | void beforeShutdown() { 470 | retryLockRef.compareAndSet(this, null); 471 | } 472 | } 473 | 474 | /** 475 | * Start the retry thread 476 | */ 477 | private void startRetryThread() { 478 | RetryLockThread t = retryLockRef.get(); 479 | 480 | while (t == null || t.getState() == Thread.State.TERMINATED) { 481 | retryLockRef.compareAndSet(t, new RetryLockThread((int) (lockConfig.getMillisLease() / 10), 482 | (int) (lockConfig.getMillisLease() / 6))); 483 | 484 | t = retryLockRef.get(); 485 | } 486 | 487 | if (t.startState.compareAndSet(0, 1)) { 488 | t.start(); 489 | } 490 | } 491 | 492 | /** 493 | * Shutdown retry thread 494 | */ 495 | private void shutdownRetryThread() { 496 | RetryLockThread t = retryLockRef.get(); 497 | if (t != null && t.isAlive()) { 498 | t.interrupt(); 499 | } 500 | } 501 | } 502 | -------------------------------------------------------------------------------- /src/main/java/com/baidu/fsg/dlock/domain/DLockConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Baidu, Inc. All Rights Reserve. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.baidu.fsg.dlock.domain; 17 | 18 | import java.io.Serializable; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | import org.apache.commons.lang.StringUtils; 22 | import org.apache.commons.lang.builder.ToStringBuilder; 23 | import org.apache.commons.lang.builder.ToStringStyle; 24 | 25 | /** 26 | * This class representing a distribute lock configuration.
    27 | * The minimum granularity of the lock entity is LockUniqueKey, which consists of $UK_PRE_$LockType_$LockTarget, 28 | * You can set a specified lease time for each lockUniqueKey

    29 | * 30 | * Sample:
    31 | * LockType: USER_LOCK, LockTartget: 2356784, Lease: 500
    32 | * LockType: USER_LOCK, LockTartget: 2356783, Lease: 500

    33 | * 34 | * LockType: BATCH_PROCESS_LOCK, LockTartget: MAP_NODE, Lease: 300
    35 | * LockType: BATCH_PROCESS_LOCK, LockTartget: REDUCE_NODE, Lease: 400 36 | * 37 | * @author yutianbao 38 | */ 39 | public class DLockConfig implements Serializable { 40 | private static final long serialVersionUID = -1332663877601479136L; 41 | 42 | /** Prefix for unique key generating */ 43 | public static final String UK_PRE = "DLOCK"; 44 | 45 | /** Separator for unique key generating */ 46 | public static final String UK_SP = "_"; 47 | 48 | /** 49 | * Lock type represents a group lockTargets with the same type. 50 | * The type is divided by different business scenarios, kind of USER_LOCK, ORDER_LOCK, BATCH_PROCCESS_LOCK... 51 | */ 52 | private final String lockType; 53 | 54 | /** 55 | * Lock target represents a real lock target. lockType: USER_LOCK, lockTarget should be the UserID. 56 | */ 57 | private final String lockTarget; 58 | 59 | /** 60 | * Lock unique key represents the minimum granularity of the lock. 61 | * The naming policy is $UK_PRE_$lockType_$lockTarget 62 | */ 63 | private final String lockUniqueKey; 64 | 65 | /** 66 | * Lock lease duration 67 | */ 68 | private final int lease; 69 | 70 | /** 71 | * Lock Lease time unit 72 | */ 73 | private final TimeUnit leaseTimeUnit; 74 | 75 | /** 76 | * Constructor with lockType & lockTarget & leaseTime & leaseTimeUnit 77 | */ 78 | public DLockConfig(String lockType, String lockTarget, int lease, TimeUnit leaseTimeUnit) { 79 | this.lockType = lockType; 80 | this.lockTarget = lockTarget; 81 | this.lockUniqueKey = UK_PRE + UK_SP + lockType + UK_SP + StringUtils.trimToEmpty(lockTarget); 82 | this.lease = lease; 83 | this.leaseTimeUnit = leaseTimeUnit; 84 | } 85 | 86 | /** 87 | * Getters 88 | */ 89 | public String getLockType() { 90 | return lockType; 91 | } 92 | 93 | public String getLockTarget() { 94 | return lockTarget; 95 | } 96 | 97 | public int getLease() { 98 | return lease; 99 | } 100 | 101 | public TimeUnit getLeaseTimeUnit() { 102 | return leaseTimeUnit; 103 | } 104 | 105 | public String getLockUniqueKey() { 106 | return lockUniqueKey; 107 | } 108 | 109 | /** 110 | * Get the lease of millis unit 111 | */ 112 | public long getMillisLease() { 113 | return leaseTimeUnit.toMillis(lease); 114 | } 115 | 116 | @Override 117 | public String toString() { 118 | return ToStringBuilder.reflectionToString(this, ToStringStyle.DEFAULT_STYLE); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/baidu/fsg/dlock/domain/DLockEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Baidu, Inc. All Rights Reserve. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.baidu.fsg.dlock.domain; 17 | 18 | import java.io.Serializable; 19 | 20 | import org.apache.commons.lang.builder.ToStringBuilder; 21 | import org.apache.commons.lang.builder.ToStringStyle; 22 | 23 | /** 24 | * DLockEntity represents an distributed lock entity, consists of lock status, locker, lockTime. 25 | * 26 | * @author chenguoqing 27 | * @author yutianbao 28 | */ 29 | public class DLockEntity implements Serializable, Cloneable { 30 | private static final long serialVersionUID = 8479390959137749786L; 31 | 32 | /** 33 | * Task status default as {@link DLockStatus#INITIAL} 34 | */ 35 | private DLockStatus lockStatus = DLockStatus.INITIAL; 36 | 37 | /** 38 | * The server ip address that locked the task 39 | */ 40 | private String locker; 41 | 42 | /** 43 | * Lock time for milliseconds 44 | */ 45 | private Long lockTime = -1L; 46 | 47 | /** 48 | * Constructor 49 | */ 50 | public DLockEntity() { 51 | } 52 | 53 | /** 54 | * Getters & Setters 55 | */ 56 | public DLockStatus getLockStatus() { 57 | return lockStatus; 58 | } 59 | 60 | public void setLockStatus(DLockStatus lockStatus) { 61 | this.lockStatus = lockStatus; 62 | } 63 | 64 | public String getLocker() { 65 | return locker; 66 | } 67 | 68 | public void setLocker(String locker) { 69 | this.locker = locker; 70 | } 71 | 72 | public Long getLockTime() { 73 | return lockTime; 74 | } 75 | 76 | public void setLockTime(Long lockTime) { 77 | this.lockTime = lockTime; 78 | } 79 | 80 | @Override 81 | public String toString() { 82 | return ToStringBuilder.reflectionToString(this, ToStringStyle.DEFAULT_STYLE); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/baidu/fsg/dlock/domain/DLockStatus.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Baidu, Inc. All Rights Reserve. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.baidu.fsg.dlock.domain; 17 | 18 | import com.baidu.fsg.dlock.utils.ValuedEnum; 19 | 20 | /** 21 | * Lock status 22 | * 23 | * @author chenguoqing 24 | */ 25 | public enum DLockStatus implements ValuedEnum { 26 | 27 | INITIAL(0), 28 | PROCESSING(1); 29 | 30 | /** 31 | * Lock status 32 | */ 33 | private final int status; 34 | 35 | /** 36 | * Constructor with field of status 37 | */ 38 | DLockStatus(int status) { 39 | this.status = status; 40 | } 41 | 42 | @Override 43 | public Integer value() { 44 | return status; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/baidu/fsg/dlock/domain/DLockType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Baidu, Inc. All Rights Reserve. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.baidu.fsg.dlock.domain; 17 | 18 | import com.baidu.fsg.dlock.utils.ValuedEnum; 19 | 20 | /** 21 | * Distributed Lock type 22 | * 23 | * @author yutianbao 24 | */ 25 | public enum DLockType implements ValuedEnum { 26 | 27 | CUSTOMER_LOCK(0); 28 | 29 | /** 30 | * Lock type 31 | */ 32 | private final Integer type; 33 | 34 | /** 35 | * Constructor with field of type 36 | */ 37 | DLockType(Integer type) { 38 | this.type = type; 39 | } 40 | 41 | @Override 42 | public Integer value() { 43 | return type; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/baidu/fsg/dlock/exception/DLockProcessException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Baidu, Inc. All Rights Reserve. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.baidu.fsg.dlock.exception; 17 | 18 | /** 19 | * DLockProcessException 20 | * 21 | * @author yutianbao 22 | */ 23 | public class DLockProcessException extends RuntimeException { 24 | 25 | /** 26 | * Serial Version UID 27 | */ 28 | private static final long serialVersionUID = -4147467240172878091L; 29 | 30 | /** 31 | * Default constructor 32 | */ 33 | public DLockProcessException() { 34 | super(); 35 | } 36 | 37 | /** 38 | * Constructor with message & cause 39 | * @param message 40 | * @param cause 41 | */ 42 | public DLockProcessException(String message, Throwable cause) { 43 | super(message, cause); 44 | } 45 | 46 | /** 47 | * Constructor with message 48 | * @param message 49 | */ 50 | public DLockProcessException(String message) { 51 | super(message); 52 | } 53 | 54 | /** 55 | * Constructor with cause 56 | * @param cause 57 | */ 58 | public DLockProcessException(Throwable cause) { 59 | super(cause); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/baidu/fsg/dlock/exception/OptimisticLockingException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Baidu, Inc. All Rights Reserve. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.baidu.fsg.dlock.exception; 17 | 18 | /** 19 | * OptimisticLockingException 20 | * 21 | * @author yutianbao 22 | */ 23 | public class OptimisticLockingException extends RuntimeException { 24 | private static final long serialVersionUID = -8879525250880790138L; 25 | 26 | /** 27 | * Default constructor 28 | */ 29 | public OptimisticLockingException() { 30 | super(); 31 | } 32 | 33 | /** 34 | * Constructor with message & cause 35 | * 36 | * @param message 37 | * @param cause 38 | */ 39 | public OptimisticLockingException(String message, Throwable cause) { 40 | super(message, cause); 41 | } 42 | 43 | /** 44 | * Constructor with message 45 | * 46 | * @param message 47 | */ 48 | public OptimisticLockingException(String message) { 49 | super(message); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/baidu/fsg/dlock/exception/RedisProcessException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Baidu, Inc. All Rights Reserve. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.baidu.fsg.dlock.exception; 17 | 18 | /** 19 | * RedisProcessException 20 | * 21 | * @author yutianbao 22 | */ 23 | public class RedisProcessException extends DLockProcessException { 24 | 25 | /** 26 | * Serial Version UID 27 | */ 28 | private static final long serialVersionUID = -4147467240172878091L; 29 | 30 | /** 31 | * Default constructor 32 | */ 33 | public RedisProcessException() { 34 | super(); 35 | } 36 | 37 | /** 38 | * Constructor with message & cause 39 | * @param message 40 | * @param cause 41 | */ 42 | public RedisProcessException(String message, Throwable cause) { 43 | super(message, cause); 44 | } 45 | 46 | /** 47 | * Constructor with message 48 | * @param message 49 | */ 50 | public RedisProcessException(String message) { 51 | super(message); 52 | } 53 | 54 | /** 55 | * Constructor with cause 56 | * @param cause 57 | */ 58 | public RedisProcessException(Throwable cause) { 59 | super(cause); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/baidu/fsg/dlock/jedis/JedisClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Baidu, Inc. All Rights Reserve. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.baidu.fsg.dlock.jedis; 17 | 18 | import java.util.List; 19 | 20 | import javax.annotation.Resource; 21 | 22 | import org.springframework.stereotype.Service; 23 | 24 | import redis.clients.jedis.Jedis; 25 | import redis.clients.jedis.JedisPool; 26 | 27 | /** 28 | * Jedis client 29 | * 30 | * @author yutianbao 31 | */ 32 | @Service 33 | public class JedisClient { 34 | 35 | @Resource 36 | private JedisPool jedisPool; 37 | 38 | /** 39 | * String get command 40 | * 41 | * @param key 42 | * @return 43 | */ 44 | public String get(String key) { 45 | Jedis jedis = null; 46 | try { 47 | jedis = jedisPool.getResource(); 48 | return jedis.get(key); 49 | 50 | } finally { 51 | if (jedis != null) { 52 | jedis.close(); 53 | } 54 | } 55 | } 56 | 57 | /** 58 | * String set command 59 | * 60 | * @param key 61 | * @param value 62 | * @param nxxx 63 | * @param expx 64 | * @param time 65 | * @return 66 | */ 67 | public String set(String key, String value, String nxxx, String expx, long time) { 68 | Jedis jedis = null; 69 | try { 70 | jedis = jedisPool.getResource(); 71 | return jedis.set(key, value, nxxx, expx, time); 72 | 73 | } finally { 74 | if (jedis != null) { 75 | jedis.close(); 76 | } 77 | } 78 | } 79 | 80 | /** 81 | * Eval lua script command 82 | * 83 | * @param script 84 | * @param keys 85 | * @param args 86 | * @return 87 | */ 88 | public Object eval(String script, List keys, List args) { 89 | Jedis jedis = null; 90 | try { 91 | jedis = jedisPool.getResource(); 92 | return jedis.eval(script, keys, args); 93 | 94 | } finally { 95 | if (jedis != null) { 96 | jedis.close(); 97 | } 98 | } 99 | } 100 | 101 | /** 102 | * String delete command 103 | * 104 | * @param key 105 | * @return 106 | */ 107 | public Long del(String key) { 108 | Jedis jedis = null; 109 | try { 110 | jedis = jedisPool.getResource(); 111 | return jedis.del(key); 112 | 113 | } finally { 114 | if (jedis != null) { 115 | jedis.close(); 116 | } 117 | } 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/baidu/fsg/dlock/processor/DLockProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Baidu, Inc. All Rights Reserve. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.baidu.fsg.dlock.processor; 17 | 18 | import com.baidu.fsg.dlock.domain.DLockConfig; 19 | import com.baidu.fsg.dlock.domain.DLockEntity; 20 | 21 | /** 22 | * The distributed lock processor interface for retrieving and updating lock status 23 | * to persistent system(such as Redis/DB). 24 | * 25 | * @author chenguoqing 26 | */ 27 | public interface DLockProcessor { 28 | 29 | /** 30 | * Retrieve the {@link DLockEntity} by the unique key 31 | * 32 | * @param uniqueKey 33 | * @return 34 | */ 35 | DLockEntity load(String uniqueKey); 36 | 37 | /** 38 | * The method implements the "lock" syntax
    39 | *

  • DB
  • 40 | * The implementations should update the (lockStatus,locker,lockTime) with 41 | * DB record lock under the condition (lockStatus=0)

    42 | * 43 | *

  • Redis
  • 44 | * The implementations should set unique key, value(locker), and expire time 45 | * 46 | * @param newLock 47 | * @param lockConfig 48 | * @throw OptimisticLockingFailureException 49 | */ 50 | void updateForLock(DLockEntity newLock, DLockConfig lockConfig); 51 | 52 | /** 53 | * The method implements the "lock" syntax with existing expire lock.
    54 | *
  • DB
  • 55 | * The implementations should update 56 | * (lockStatus,locker,lockTime) with DB line lock under the condition (lockStatus=1 && locker==expireLock.locker)

    57 | * 58 | *

  • Redis
  • 59 | * The implementation is unsupported because of the Redis expire mechanism. 60 | * 61 | * @param expireLock 62 | * @param dbLock 63 | * @param lockConfig 64 | */ 65 | void updateForLockWithExpire(DLockEntity expireLock, DLockEntity dbLock, DLockConfig lockConfig); 66 | 67 | /** 68 | * Expand the lock expire time. It should be protected with DB line lock, it only modify the lockTime field. 69 | * 70 | * @param newLeaseLock 71 | * @param lockConfig 72 | */ 73 | void expandLockExpire(DLockEntity newLeaseLock, DLockConfig lockConfig); 74 | 75 | /** 76 | * The method implements the "unlock" syntax.
    77 | * 78 | *
  • DB
  • 79 | * The implementations should should reset the lock status to INITIAL, and clear locker, 80 | * lockTime fields with optimistic lock condition(lockStatus,locker). The operation should be protected with 81 | * DB line lock.

    82 | * 83 | *
  • Redis
  • 84 | * The implementation should remove key with the right value(locker). 85 | */ 86 | void updateForUnlock(DLockEntity currentLock, DLockConfig lockConfig); 87 | 88 | /** 89 | * Whether the lock is free(released or expired) 90 | * 91 | * @param uniqueKey key 92 | * @return true if lock is released 93 | */ 94 | boolean isLockFree(String uniqueKey); 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/baidu/fsg/dlock/processor/impl/RedisLockProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Baidu, Inc. All Rights Reserve. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.baidu.fsg.dlock.processor.impl; 17 | 18 | import java.util.Arrays; 19 | 20 | import javax.annotation.Resource; 21 | 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | import org.springframework.stereotype.Service; 25 | 26 | import com.baidu.fsg.dlock.domain.DLockConfig; 27 | import com.baidu.fsg.dlock.domain.DLockEntity; 28 | import com.baidu.fsg.dlock.domain.DLockStatus; 29 | import com.baidu.fsg.dlock.exception.OptimisticLockingException; 30 | import com.baidu.fsg.dlock.exception.RedisProcessException; 31 | import com.baidu.fsg.dlock.jedis.JedisClient; 32 | import com.baidu.fsg.dlock.processor.DLockProcessor; 33 | 34 | /** 35 | * The implement of {@link DLockProcessor}. Command set(with NX & PX) & Lua script is used for atomic operations. 36 | * Redis version must be greater than 2.6.12

    37 | * 38 | * DataModel:
    39 | * Key: LockUniqueKey, Value: Locker(IP + ThreadID), Expire: lease duration(ms). 40 | * 41 | * @author yutianbao 42 | */ 43 | @Service 44 | public class RedisLockProcessor implements DLockProcessor { 45 | private static final Logger LOGGER = LoggerFactory.getLogger(RedisLockProcessor.class); 46 | 47 | /** 48 | * Redis command & result code constant 49 | */ 50 | private static final String SET_ARG_NOT_EXIST = "NX"; 51 | private static final String SET_ARG_EXPIRE = "PX"; 52 | private static final String RES_OK = "OK"; 53 | 54 | @Resource 55 | private JedisClient jedisClient; 56 | 57 | /** 58 | * Load by unique key. For redis implement, you can find locker & status from the result entity. 59 | * 60 | * @param uniqueKey key 61 | * @throws RedisProcessException if catch any exception from {@link redis.clients.jedis.Jedis} 62 | */ 63 | @Override 64 | public DLockEntity load(String uniqueKey) throws RedisProcessException { 65 | // GET command 66 | String locker; 67 | try { 68 | locker = jedisClient.get(uniqueKey); 69 | } catch (Exception e) { 70 | LOGGER.warn("Exception occurred by GET command for key:" + uniqueKey, e); 71 | throw new RedisProcessException("Exception occurred by GET command for key:" + uniqueKey, e); 72 | } 73 | 74 | if (locker == null) { 75 | return null; 76 | } 77 | 78 | // build entity 79 | DLockEntity lockEntity = new DLockEntity(); 80 | lockEntity.setLocker(locker); 81 | lockEntity.setLockStatus(DLockStatus.PROCESSING); 82 | 83 | return lockEntity; 84 | } 85 | 86 | /** 87 | * Update for lock using redis SET(NX, PX) command. 88 | * 89 | * @param newLock with locker in it 90 | * @param lockConfig 91 | * @throws RedisProcessException Redis command execute exception 92 | * @throws OptimisticLockingException the lock is hold by the other request. 93 | */ 94 | @Override 95 | public void updateForLock(DLockEntity newLock, DLockConfig lockConfig) 96 | throws RedisProcessException, OptimisticLockingException { 97 | // SET(NX, PX) command 98 | String lockRes; 99 | try { 100 | lockRes = jedisClient.set(lockConfig.getLockUniqueKey(), newLock.getLocker(), SET_ARG_NOT_EXIST, 101 | SET_ARG_EXPIRE, lockConfig.getMillisLease()); 102 | 103 | } catch (Exception e) { 104 | LOGGER.warn("Exception occurred by SET(NX, PX) command for key:" + lockConfig.getLockUniqueKey(), e); 105 | throw new RedisProcessException( 106 | "Exception occurred by SET(NX, PX) command for key:" + lockConfig.getLockUniqueKey(), e); 107 | } 108 | 109 | if (!RES_OK.equals(lockRes)) { 110 | LOGGER.warn("Fail to get lock for key:{} ,locker={}", lockConfig.getLockUniqueKey(), newLock.getLocker()); 111 | throw new OptimisticLockingException( 112 | "Fail to get lock for key:" + lockConfig.getLockUniqueKey() + " ,locker=" + newLock.getLocker()); 113 | } 114 | } 115 | 116 | /** 117 | * The redis expire mechanism guaranteed the expired key is removed automatic. 118 | * It is not necessary to check condition(status=1 && expire=true) 119 | */ 120 | @Override 121 | public void updateForLockWithExpire(DLockEntity expireLock, DLockEntity dbLock, DLockConfig lockConfig) { 122 | throw new UnsupportedOperationException("updateForLockWithExpire is not supported"); 123 | } 124 | 125 | /** 126 | * Extend lease for lock with lua script. 127 | * 128 | * @param leaseLock with locker in it 129 | * @param lockConfig 130 | * @throws RedisProcessException if catch any exception from {@link redis.clients.jedis.Jedis} 131 | * @throws OptimisticLockingException if the lock is released or be hold by another one. 132 | */ 133 | @Override 134 | public void expandLockExpire(DLockEntity leaseLock, DLockConfig lockConfig) 135 | throws RedisProcessException, OptimisticLockingException { 136 | // Expire if key is existed and equal with the specified value(locker). 137 | String leaseScript = "if (redis.call('get', KEYS[1]) == ARGV[1]) then " 138 | + " return redis.call('pexpire', KEYS[1], ARGV[2]); " 139 | + "else" 140 | + " return nil; " 141 | + "end; "; 142 | 143 | Object leaseRes; 144 | try { 145 | leaseRes = jedisClient.eval(leaseScript, Arrays.asList(lockConfig.getLockUniqueKey()), 146 | Arrays.asList(leaseLock.getLocker(), lockConfig.getMillisLease() + "")); 147 | } catch (Exception e) { 148 | LOGGER.warn("Exception occurred by ExpandLease lua script for key:" + lockConfig.getLockUniqueKey(), e); 149 | throw new RedisProcessException( 150 | "Exception occurred by ExpandLease lua script for key:" + lockConfig.getLockUniqueKey(), e); 151 | } 152 | 153 | // null means lua return nil (the lock is released or be hold by the other request) 154 | if (leaseRes == null) { 155 | LOGGER.warn("Fail to lease for key:{} ,locker={}", lockConfig.getLockUniqueKey(), leaseLock.getLocker()); 156 | throw new OptimisticLockingException( 157 | "Fail to lease for key:" + lockConfig.getLockUniqueKey() + " ,locker=" + leaseLock.getLocker()); 158 | } 159 | } 160 | 161 | /** 162 | * Release lock using lua script. 163 | * 164 | * @param currentLock with locker in it 165 | * @param lockConfig 166 | * @throws RedisProcessException if catch any exception from {@link redis.clients.jedis.Jedis} 167 | * @throws OptimisticLockingException if the lock is released or be hold by another one. 168 | */ 169 | @Override 170 | public void updateForUnlock(DLockEntity currentLock, DLockConfig lockConfig) 171 | throws RedisProcessException, OptimisticLockingException { 172 | // Delete if key is existed and equal with the specified value(locker). 173 | String unlockScript = "if (redis.call('get', KEYS[1]) == ARGV[1]) then " 174 | + " return redis.call('del', KEYS[1]); " 175 | + "else " 176 | + " return nil; " 177 | + "end;"; 178 | 179 | Object unlockRes; 180 | try { 181 | unlockRes = jedisClient.eval(unlockScript, Arrays.asList(lockConfig.getLockUniqueKey()), 182 | Arrays.asList(currentLock.getLocker())); 183 | } catch (Exception e) { 184 | LOGGER.warn("Exception occurred by Unlock lua script for key:" + lockConfig.getLockUniqueKey(), e); 185 | throw new RedisProcessException( 186 | "Exception occurred by Unlock lua script for key:" + lockConfig.getLockUniqueKey(), e); 187 | } 188 | 189 | // null means lua return nil (the lock is released or be hold by the other request) 190 | if (unlockRes == null) { 191 | LOGGER.warn("Fail to unlock for key:{} ,locker={}", lockConfig.getLockUniqueKey(), currentLock.getLocker()); 192 | throw new OptimisticLockingException("Fail to unlock for key:" + lockConfig.getLockUniqueKey() 193 | + ",locker=" + currentLock.getLocker()); 194 | } 195 | } 196 | 197 | @Override 198 | public boolean isLockFree(String uniqueKey) { 199 | DLockEntity locked = this.load(uniqueKey); 200 | return locked == null; 201 | } 202 | 203 | } 204 | -------------------------------------------------------------------------------- /src/main/java/com/baidu/fsg/dlock/support/DLockGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Baidu, Inc. All Rights Reserve. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.baidu.fsg.dlock.support; 17 | 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | import java.util.Map.Entry; 21 | import java.util.Properties; 22 | import java.util.concurrent.TimeUnit; 23 | import java.util.concurrent.locks.Lock; 24 | 25 | import javax.annotation.PostConstruct; 26 | import javax.annotation.Resource; 27 | 28 | import org.apache.commons.collections.MapUtils; 29 | import org.apache.commons.lang.StringUtils; 30 | import org.springframework.core.io.support.PropertiesLoaderUtils; 31 | import org.springframework.stereotype.Service; 32 | import org.springframework.util.Assert; 33 | 34 | import com.baidu.fsg.dlock.DistributedReentrantLock; 35 | import com.baidu.fsg.dlock.domain.DLockConfig; 36 | import com.baidu.fsg.dlock.domain.DLockType; 37 | import com.baidu.fsg.dlock.processor.DLockProcessor; 38 | import com.baidu.fsg.dlock.utils.EnumUtils; 39 | 40 | /** 41 | * DLockGenerator represents a generator for {@link DistributedReentrantLock}
    42 | * It will load the lease time of {@link DLockType} configured in file config-dlock.properties, key is DLockType Enum 43 | * name, value is lease time(ms). Sample as below:

    44 | * 45 | * CUSTOMER_LOCK=1000
    46 | * XXXXXXXX_LOCK=2000 47 | * 48 | * @author yutianbao 49 | */ 50 | @Service 51 | public class DLockGenerator { 52 | 53 | /** Default dlock configuration path */ 54 | private static final String DEFAULT_CONF_PATH = "dlock/config-dlock.properties"; 55 | 56 | @Resource 57 | private DLockProcessor lockProcessor; 58 | 59 | /** 60 | * Key for DLockType enum, Value for lease time (ms) 61 | */ 62 | private Map lockConfigMap = new HashMap<>(); 63 | 64 | /** 65 | * dlock properties configuration file path 66 | */ 67 | private String confPath; 68 | 69 | /** 70 | * Load the lease config from properties, and init the lockConfigMap. 71 | */ 72 | @PostConstruct 73 | public void init() { 74 | try { 75 | // Using default path if no specified 76 | confPath = StringUtils.isBlank(confPath) ? DEFAULT_CONF_PATH : confPath; 77 | 78 | // Load lock config from properties 79 | Properties properties = PropertiesLoaderUtils.loadAllProperties(confPath); 80 | 81 | // Build the lockConfigMap 82 | for (Entry propEntry : properties.entrySet()) { 83 | DLockType lockType = EnumUtils.valueOf(DLockType.class, propEntry.getKey().toString()); 84 | Integer lease = Integer.valueOf(propEntry.getValue().toString()); 85 | 86 | if (lockType != null && lease != null) { 87 | lockConfigMap.put(lockType, lease); 88 | } 89 | } 90 | 91 | } catch (Exception e) { 92 | throw new RuntimeException("Load distributed lock config fail", e); 93 | } 94 | } 95 | 96 | /** 97 | * Get lock with a default lease time configured in the config-dlock.properties 98 | * 99 | * @param lockType enum DLockType 100 | * @param lockTarget 101 | * @return 102 | */ 103 | public Lock gen(DLockType lockType, String lockTarget) { 104 | // pre-check 105 | Integer lease = lockConfigMap.get(lockType); 106 | Assert.notNull(lease, "unfound config for DLockType:" + lockType); 107 | 108 | return getLockInstance(lockType.name(), lockTarget, lease, TimeUnit.MILLISECONDS); 109 | } 110 | 111 | /** 112 | * Get lock with a specified lease time 113 | * 114 | * @param lockType enum DLockType 115 | * @param lockTarget 116 | * @param leaseTime 117 | * @return 118 | */ 119 | public Lock gen(DLockType lockType, String lockTarget, int leaseTime) { 120 | // pre-check 121 | Assert.notNull(lockType, "lockType can't be null!"); 122 | Assert.isTrue(leaseTime > 0, "leaseTime must greater than zero!"); 123 | 124 | return getLockInstance(lockType.name(), lockTarget, leaseTime, TimeUnit.MILLISECONDS); 125 | } 126 | 127 | /** 128 | * A free way to make the instance of {@link DistributedReentrantLock} 129 | * 130 | * @param lockTypeStr 131 | * @param lockTarget 132 | * @param lease 133 | * @param leaseTimeUnit 134 | * @return 135 | */ 136 | public Lock gen(String lockTypeStr, String lockTarget, int lease, TimeUnit leaseTimeUnit) { 137 | // pre-check 138 | Assert.isTrue(StringUtils.isNotEmpty(lockTypeStr), "lockTypeStr can't be empty!"); 139 | Assert.isTrue(lease > 0, "leaseTime must greater than zero!"); 140 | Assert.notNull(leaseTimeUnit, "leaseTimeUnit can't be null!"); 141 | 142 | return getLockInstance(lockTypeStr, lockTarget, lease, leaseTimeUnit); 143 | } 144 | 145 | /** 146 | * Get lockConfigMap(unmodifiableMap) 147 | */ 148 | @SuppressWarnings("unchecked") 149 | public Map getLockConfigMap() { 150 | return MapUtils.unmodifiableMap(lockConfigMap); 151 | } 152 | 153 | /** 154 | * Generate instance of DistributedReentrantLock 155 | */ 156 | private Lock getLockInstance(String lockTypeStr, String lockTarget, int lease, TimeUnit leaseTimeUnit) { 157 | DLockConfig dlockConfig = new DLockConfig(lockTypeStr, lockTarget, lease, leaseTimeUnit); 158 | return new DistributedReentrantLock(dlockConfig, lockProcessor); 159 | } 160 | 161 | /** 162 | * Setter for spring field 163 | */ 164 | public void setConfPath(String confPath) { 165 | this.confPath = confPath; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/com/baidu/fsg/dlock/utils/EnumUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Baidu, Inc. All Rights Reserve. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.baidu.fsg.dlock.utils; 17 | 18 | import org.springframework.util.Assert; 19 | 20 | /** 21 | * EnumUtils provides the operations for {@link ValuedEnum} such as Parse, value of... 22 | * 23 | * @author yutianbao 24 | */ 25 | public abstract class EnumUtils { 26 | 27 | /** 28 | * Parse the bounded value into ValuedEnum 29 | * 30 | * @param clz 31 | * @param value 32 | * @return 33 | */ 34 | public static , V> T parse(Class clz, V value) { 35 | Assert.notNull(clz, "clz can not be null"); 36 | if (value == null) { 37 | return null; 38 | } 39 | 40 | for (T t : clz.getEnumConstants()) { 41 | if (value.equals(t.value())) { 42 | return t; 43 | } 44 | } 45 | return null; 46 | } 47 | 48 | /** 49 | * Null-safe valueOf function 50 | * 51 | * @param 52 | * @param enumType 53 | * @param name 54 | * @return 55 | */ 56 | public static > T valueOf(Class enumType, String name) { 57 | if (name == null) { 58 | return null; 59 | } 60 | 61 | return Enum.valueOf(enumType, name); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/baidu/fsg/dlock/utils/NetUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Baidu, Inc. All Rights Reserve. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.baidu.fsg.dlock.utils; 17 | 18 | import java.net.InetAddress; 19 | import java.net.NetworkInterface; 20 | import java.net.SocketException; 21 | import java.util.Enumeration; 22 | 23 | /** 24 | * NetUtils 25 | * 26 | * @author yutianbao 27 | */ 28 | public abstract class NetUtils { 29 | 30 | /** 31 | * Pre-loaded local address 32 | */ 33 | public static InetAddress localAddress; 34 | 35 | static { 36 | try { 37 | localAddress = getLocalInetAddress(); 38 | } catch (SocketException e) { 39 | throw new RuntimeException("fail to get local ip."); 40 | } 41 | } 42 | 43 | /** 44 | * Retrieve the first validated local ip address(the Public and LAN ip addresses are validated). 45 | * 46 | * @return the local address 47 | * @throws SocketException the socket exception 48 | */ 49 | public static InetAddress getLocalInetAddress() throws SocketException { 50 | // enumerates all network interfaces 51 | Enumeration enu = NetworkInterface.getNetworkInterfaces(); 52 | 53 | while (enu.hasMoreElements()) { 54 | NetworkInterface ni = enu.nextElement(); 55 | if (ni.isLoopback()) { 56 | continue; 57 | } 58 | 59 | Enumeration addressEnumeration = ni.getInetAddresses(); 60 | while (addressEnumeration.hasMoreElements()) { 61 | InetAddress address = addressEnumeration.nextElement(); 62 | 63 | // ignores all invalidated addresses 64 | if (address.isLinkLocalAddress() || address.isLoopbackAddress() || address.isAnyLocalAddress()) { 65 | continue; 66 | } 67 | 68 | return address; 69 | } 70 | } 71 | 72 | throw new RuntimeException("No validated local address!"); 73 | } 74 | 75 | /** 76 | * Retrieve local address 77 | * 78 | * @return the string local address 79 | */ 80 | public static String getLocalAddress() { 81 | return localAddress.getHostAddress(); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/baidu/fsg/dlock/utils/ReflectionUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Baidu, Inc. All Rights Reserve. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.baidu.fsg.dlock.utils; 17 | 18 | import java.lang.annotation.Annotation; 19 | import java.lang.reflect.Field; 20 | import java.lang.reflect.Modifier; 21 | import java.util.LinkedList; 22 | import java.util.List; 23 | 24 | /** 25 | * ReflectionUtils 26 | * 27 | * @author yutianbao 28 | */ 29 | public abstract class ReflectionUtils { 30 | 31 | /** 32 | * Void 33 | */ 34 | public static boolean isVoid(Class clazz) { 35 | return clazz == void.class || clazz == Void.class; 36 | } 37 | 38 | /** 39 | * Property 40 | */ 41 | public static Object getProperty(Object target, String name) throws Exception { 42 | Field f = target.getClass().getDeclaredField(name); 43 | f.setAccessible(true); 44 | return f.get(target); 45 | } 46 | 47 | public static void setProperty(Object target, String name, Object value) throws Exception { 48 | Field f = target.getClass().getDeclaredField(name); 49 | f.setAccessible(true); 50 | f.set(target, value); 51 | } 52 | 53 | /** 54 | * Field 55 | */ 56 | public static List getAllFields(Class clazz) throws Exception { 57 | return getAllFields(clazz, null, true); 58 | } 59 | 60 | public static List getAllFields(Class clazz, boolean excludeStaticField) throws Exception { 61 | return getAllFields(clazz, null, excludeStaticField); 62 | } 63 | 64 | public static List getAllFields(Class clazz, Class annotation) throws Exception { 65 | return getAllFields(clazz, annotation, true); 66 | } 67 | 68 | public static List getAllFields(Class clazz, Class annotation, 69 | boolean excludeStaticField) throws Exception { 70 | // Precondition checking 71 | if (clazz == null) { 72 | return null; 73 | } 74 | 75 | List r = new LinkedList(); 76 | Class parent = clazz; 77 | while (parent != null) { 78 | for (Field f : parent.getDeclaredFields()) { 79 | f.setAccessible(true); 80 | 81 | if (excludeStaticField && (f.getModifiers() & Modifier.STATIC) != 0) { 82 | continue; 83 | } 84 | if (annotation != null && !f.isAnnotationPresent(annotation)) { 85 | continue; 86 | } 87 | 88 | r.add(f); 89 | } 90 | 91 | parent = parent.getSuperclass(); 92 | } 93 | return r; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/baidu/fsg/dlock/utils/ValuedEnum.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Baidu, Inc. All Rights Reserve. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.baidu.fsg.dlock.utils; 17 | 18 | /** 19 | * {@code ValuedEnum} defines an enumeration which is bounded to a value, you 20 | * may implements this interface when you defines such kind of enumeration, that 21 | * you can use {@link EnumUtils} to simplify parse and valueOf operation. 22 | * 23 | * @author yutianbao 24 | */ 25 | public interface ValuedEnum { 26 | T value(); 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/baidu/fsg/dlock/DLockGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.baidu.fsg.dlock; 2 | 3 | import com.baidu.fsg.dlock.domain.DLockConfig; 4 | import com.baidu.fsg.dlock.domain.DLockType; 5 | import com.baidu.fsg.dlock.support.DLockGenerator; 6 | import com.baidu.fsg.dlock.utils.ReflectionUtils; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.test.context.ContextConfiguration; 11 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 12 | 13 | import javax.annotation.Resource; 14 | import java.util.concurrent.TimeUnit; 15 | import java.util.concurrent.locks.Lock; 16 | 17 | /** 18 | * Test for {@link DLockGenerator} 19 | * 20 | * @author yutianbao 21 | */ 22 | @RunWith(SpringJUnit4ClassRunner.class) 23 | @ContextConfiguration(locations = { "classpath:dlock/spring-dlock.xml" }) 24 | public class DLockGeneratorTest { 25 | 26 | @Resource 27 | private DLockGenerator lockGenerator; 28 | 29 | @Test 30 | public void testLockGenerate() throws Exception { 31 | Assert.assertNotNull(lockGenerator); 32 | 33 | // generate lock with a default lease time 34 | Lock lock = lockGenerator.gen(DLockType.CUSTOMER_LOCK, "12345"); 35 | checkLock(lock, lockGenerator.getLockConfigMap().get(DLockType.CUSTOMER_LOCK).intValue()); 36 | 37 | // generate lock with a specified lease time 38 | lock = lockGenerator.gen(DLockType.CUSTOMER_LOCK, "12345", 500); 39 | checkLock(lock, 500); 40 | 41 | // generate lock in a free way, you must specify lock type, target, lease time, lease unit 42 | lock = lockGenerator.gen("FAKE_LOCK", "A_TARGET", 1, TimeUnit.SECONDS); 43 | checkLock(lock, 1); 44 | 45 | } 46 | 47 | /** 48 | * Check lock 49 | */ 50 | private void checkLock(Lock lock, int expectedLeaseTime) throws Exception { 51 | DLockConfig lockConfig = (DLockConfig) ReflectionUtils.getProperty(lock, "lockConfig"); 52 | Assert.assertEquals(expectedLeaseTime, lockConfig.getLease()); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/com/baidu/fsg/dlock/DLockSimpleTest.java: -------------------------------------------------------------------------------- 1 | package com.baidu.fsg.dlock; 2 | 3 | import com.baidu.fsg.dlock.support.DLockGenerator; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.test.context.ContextConfiguration; 7 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 8 | 9 | import javax.annotation.Resource; 10 | import java.util.concurrent.TimeUnit; 11 | import java.util.concurrent.locks.Lock; 12 | 13 | /** 14 | * Test for {@link DLockGenerator}, simple use of lock() & tryLock() 15 | * 16 | * @author yutianbao 17 | */ 18 | @RunWith(SpringJUnit4ClassRunner.class) 19 | @ContextConfiguration(locations = {"classpath:dlock/spring-dlock.xml"}) 20 | public class DLockSimpleTest { 21 | 22 | @Resource 23 | private DLockGenerator lockGenerator; 24 | 25 | /** 26 | * Case1: Test for lock() 27 | */ 28 | @Test 29 | public void testLock() { 30 | // generate lock in a free way, you should specify lock type, target, lease time, lease unit 31 | Lock lock = lockGenerator.gen("FAKE_LOCK", "A_TARGET", 1, TimeUnit.SECONDS); 32 | 33 | try { 34 | lock.lock(); 35 | doYourWork(); 36 | 37 | } finally { 38 | // Make sure unlock in the finally code block 39 | lock.unlock(); 40 | } 41 | } 42 | 43 | /** 44 | * Case2: Test for tryLock() 45 | */ 46 | @Test 47 | public void testTryLock() { 48 | // generate lock in a free way, you should specify lock type, target, lease time, lease unit 49 | Lock lock = lockGenerator.gen("FAKE_LOCK", "A_TARGET", 1, TimeUnit.SECONDS); 50 | 51 | if (lock.tryLock()) { 52 | try { 53 | doYourWork(); 54 | } finally { 55 | lock.unlock(); 56 | } 57 | } else { 58 | // perform alternative actions 59 | } 60 | } 61 | 62 | /** 63 | * Do your work 64 | */ 65 | private void doYourWork() { 66 | System.out.println("Just do your work"); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/com/baidu/fsg/dlock/DistributedReentrantLockTest.java: -------------------------------------------------------------------------------- 1 | package com.baidu.fsg.dlock; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Random; 6 | import java.util.concurrent.CountDownLatch; 7 | import java.util.concurrent.TimeUnit; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | import java.util.concurrent.locks.Lock; 10 | 11 | import javax.annotation.Resource; 12 | 13 | import org.apache.commons.lang.StringUtils; 14 | import org.junit.Assert; 15 | import org.junit.Before; 16 | import org.junit.Test; 17 | import org.junit.runner.RunWith; 18 | import org.springframework.test.context.ContextConfiguration; 19 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 20 | 21 | import com.baidu.fsg.dlock.domain.DLockConfig; 22 | import com.baidu.fsg.dlock.jedis.JedisClient; 23 | import com.baidu.fsg.dlock.processor.impl.RedisLockProcessor; 24 | import com.baidu.fsg.dlock.utils.ReflectionUtils; 25 | 26 | /** 27 | * Test for {@link DistributedReentrantLock}.

    28 | * 29 | * Note:No redis mock is provided, to be continue for mock test.
    30 | * If you can't connect to BDRP, try to connect VPN first. 31 | * 32 | * @author yutianbao 33 | */ 34 | @RunWith(SpringJUnit4ClassRunner.class) 35 | @ContextConfiguration(locations = { "classpath:dlock/spring-dlock.xml" }) 36 | public class DistributedReentrantLockTest { 37 | 38 | @Resource 39 | private RedisLockProcessor lockProcessor; 40 | 41 | @Resource 42 | private JedisClient jedisClient; 43 | 44 | /** 45 | * DistributedReentrantLock instance 46 | */ 47 | private Lock lockOnServer1; 48 | 49 | /** 50 | * DistributedReentrantLock. Simulate as another server 51 | */ 52 | private Lock lockOnServer2; 53 | 54 | /** 55 | * DistributedReentrantLock. Used for single server test 56 | */ 57 | private Lock lockSingleServer; 58 | 59 | /** 60 | * CountDownLatch used for multi servers 61 | */ 62 | private static CountDownLatch cdLatch = new CountDownLatch(2); 63 | 64 | @Before 65 | public void setup() { 66 | DLockConfig singleServerLockConfig = new DLockConfig("USER_LOCK", "778899", 1000, TimeUnit.MILLISECONDS); 67 | lockSingleServer = new DistributedReentrantLock(singleServerLockConfig, lockProcessor); 68 | 69 | // The retry thread's execute interval is depended on The lease duration (Retry interval = lease ms * 0.75) 70 | DLockConfig multiServerLockConfig = new DLockConfig("USER_LOCK", "778899", 500, TimeUnit.MILLISECONDS); 71 | lockOnServer1 = new DistributedReentrantLock(multiServerLockConfig, lockProcessor); 72 | lockOnServer2 = new DistributedReentrantLock(multiServerLockConfig, lockProcessor); 73 | 74 | // Delete unique key of the last round test 75 | jedisClient.del(singleServerLockConfig.getLockUniqueKey()); 76 | jedisClient.del(multiServerLockConfig.getLockUniqueKey()); 77 | } 78 | 79 | /** 80 | * Case1: Test for reentrant feature 81 | */ 82 | @Test 83 | public void testReentrant() throws Exception { 84 | // Reentrant caller 85 | reentrantGateOne(lockSingleServer); 86 | 87 | // Assert no holder for this lock 88 | checkHoldCnt(lockSingleServer); 89 | } 90 | 91 | /** 92 | * Case2: Test for one server - multi threads 93 | */ 94 | @Test 95 | public void testSingleServer() throws Exception { 96 | // Thread max work elapse is 2s, greater than the lease duration (1s) 97 | // It means when the thread hold the lock, lease must be expanded. 98 | launchSingleServer(50, "S1", lockSingleServer, 2000); 99 | 100 | // joinThreads(threads); 101 | checkHoldCnt(lockSingleServer); 102 | } 103 | 104 | /** 105 | * Case3: Test for multi servers - multi threads 106 | */ 107 | @Test 108 | public void testMultiServer() throws InterruptedException { 109 | 110 | try { 111 | // Launch servers "S1", "S2" 112 | new ServerThread(50, "S1", lockOnServer1, 2000).start(); 113 | new ServerThread(50, "S2", lockOnServer2, 2000).start(); 114 | 115 | // Check 116 | cdLatch.await(); 117 | 118 | checkHoldCnt(lockOnServer1); 119 | checkHoldCnt(lockOnServer2); 120 | 121 | } catch (Exception e) { 122 | Assert.fail(); 123 | } 124 | } 125 | 126 | /** 127 | * Launch threads on a single server 128 | * 129 | * @param totalThread 130 | * @param serverName 131 | * @param lock 132 | * @param maxWorkElapsed 133 | */ 134 | private static void launchSingleServer(int totalThread, String serverName, Lock lock, int maxWorkElapsed) { 135 | List threads = new ArrayList<>(totalThread); 136 | for (int i = 0; i < totalThread; i++) { 137 | String tName = serverName + "-t" + StringUtils.leftPad(i + "", 2, "0"); 138 | threads.add(i, new RedisTestThread(tName, lock, maxWorkElapsed)); 139 | } 140 | 141 | threads.forEach(t -> t.start()); 142 | threads.forEach(t -> { 143 | try { 144 | t.join(); 145 | } catch (InterruptedException e) { 146 | } 147 | }); 148 | 149 | System.out.println("**** All Done **** " + serverName); 150 | } 151 | 152 | /** 153 | * Check hold cnt of lock 154 | */ 155 | private void checkHoldCnt(Lock lock) throws Exception { 156 | AtomicInteger holdCnt = (AtomicInteger) ReflectionUtils.getProperty(lock, "holdCount"); 157 | Assert.assertEquals(0, holdCnt.get()); 158 | } 159 | 160 | /** 161 | * Server thread 162 | */ 163 | static class ServerThread extends Thread { 164 | int totalThread; 165 | String serverName; 166 | Lock lock; 167 | int maxWorkElapsed; 168 | 169 | ServerThread(int totalThread, String serverName, Lock lock, int maxWorkElapsed) { 170 | super(serverName); 171 | this.totalThread = totalThread; 172 | this.serverName = serverName; 173 | this.lock = lock; 174 | this.maxWorkElapsed = maxWorkElapsed; 175 | setDaemon(true); 176 | } 177 | 178 | @Override 179 | public void run() { 180 | launchSingleServer(totalThread, serverName, lock, maxWorkElapsed); 181 | cdLatch.countDown(); 182 | } 183 | 184 | } 185 | 186 | /** 187 | * Test thread 188 | */ 189 | static class RedisTestThread extends Thread { 190 | 191 | private Lock redisLock; 192 | private int maxWorkElapse; 193 | 194 | RedisTestThread(String name, Lock redisLock, int maxWorkElapse) { 195 | super(name); 196 | this.redisLock = redisLock; 197 | this.maxWorkElapse = maxWorkElapse; 198 | setDaemon(true); 199 | } 200 | 201 | @Override 202 | public void run() { 203 | long start = System.currentTimeMillis(); 204 | 205 | while (System.currentTimeMillis() - start <= 60 * 1000L) { 206 | 207 | try { 208 | long t = System.currentTimeMillis(); 209 | redisLock.lock(); 210 | System.out.println("*********************** Lock block ***********************"); 211 | System.out.println(getName() + " >>>>> get lock time:" + (System.currentTimeMillis() - t)); 212 | 213 | doWork(); 214 | 215 | } catch (InterruptedException e) { 216 | e.printStackTrace(); 217 | } finally { 218 | redisLock.unlock(); 219 | } 220 | 221 | break; 222 | } 223 | System.out.println(getName() + " > Done!"); 224 | System.out.println(); 225 | } 226 | 227 | private void doWork() throws InterruptedException { 228 | int sleepTime = new Random().nextInt(maxWorkElapse); 229 | sleep(sleepTime); 230 | System.out.println(getName() + " >>> worked done for:" + sleepTime); 231 | } 232 | } 233 | 234 | /** 235 | * Reentrant test tool methods 236 | */ 237 | private void reentrantGateOne(Lock lock) { 238 | try { 239 | System.out.println("method1 ready to lock."); 240 | lock.lock(); 241 | System.out.println("method1 lock success. ++"); 242 | 243 | reentrantGateTwo(lock); 244 | Thread.sleep(1000); 245 | 246 | } catch (Exception e) { 247 | e.printStackTrace(); 248 | } finally { 249 | System.out.println("method1 ready to release lock."); 250 | lock.unlock(); 251 | System.out.println("method1 unlocked success. --"); 252 | } 253 | } 254 | 255 | private void reentrantGateTwo(Lock lock) { 256 | try { 257 | System.out.println(">>>>method2 ready to lock."); 258 | lock.lock(); 259 | System.out.println(">>>>method2 lock success. ++"); 260 | 261 | Thread.sleep(1500); 262 | 263 | } catch (Exception e) { 264 | e.printStackTrace(); 265 | } finally { 266 | System.out.println(">>>>method2 ready to release lock."); 267 | lock.unlock(); 268 | System.out.println(">>>>method2 unlocked success. --"); 269 | } 270 | } 271 | 272 | } 273 | -------------------------------------------------------------------------------- /src/test/resources/dlock/config-dlock.properties: -------------------------------------------------------------------------------- 1 | CUSTOMER_LOCK=1000 -------------------------------------------------------------------------------- /src/test/resources/dlock/redis.properties: -------------------------------------------------------------------------------- 1 | redis.pool.maxTotal=32 2 | redis.pool.maxIdle=10 3 | redis.pool.maxWaitMillis=2000 4 | redis.pool.testOnBorrow=true 5 | redis.host=127.0.0.1 6 | redis.port=6379 -------------------------------------------------------------------------------- /src/test/resources/dlock/spring-dlock.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | classpath:/dlock/redis.properties 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | [%d{yyyyMMdd HH:mm:ss.SSS}][%thread]%5level- %msg -[%C:%M] [%file:%line]%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | --------------------------------------------------------------------------------