redis連接池代碼略過
實現類并沒有使用lua畏浆,而是使用了JDK自帶的System.currentTimeMillis(),因此要求分布式各臺服務器直接時間同步,否則會出現超時時間錯誤地相互覆蓋問題
redis實現類
import org.apache.log4j.Logger;
import redis.clients.jedis.Jedis;
/**
* redis分布式鎖
*/
public class RedisLock {
private static Logger logger = Logger.getLogger(RedisLock.class);
/**
* 鎖等待時間垮斯,防止線程饑餓
*/
private int TIMEOUT_MSECS = 10 * 1000;
/**
* 鎖超時時間郎仆,防止線程在入鎖以后,無限的執(zhí)行等待
*/
private int EXPIRE_MSECS = 60 * 1000;
private static int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;
/**
* Lock key path.
*/
private String lockKey;
/**
* Java本地與redis對應的鎖
*/
private volatile boolean locked = false;
private static Jedis jedis;
/**
* 構造器
* 默認鎖等待時間10*1000ms兜蠕,鎖超時時間60*1000ms
* @param jedis jedis連接池
* @param lockKey 鎖的key名稱
*/
public RedisLock(Jedis jedis, String lockKey) {
this.jedis = jedis;
this.lockKey = lockKey + "_lock";
}
/**
* 構造器
* 默認鎖超時時間60*1000ms
* @param jedis jedis連接池
* @param lockKey 鎖的key名稱
* @param timeoutMsecs 鎖超時時間
*/
public RedisLock(Jedis jedis, String lockKey, int timeoutMsecs) {
this(jedis, lockKey);
this.TIMEOUT_MSECS = timeoutMsecs;
}
/**
* 默認構造器
* @param jedis
* @param lockKey
* @param timeoutMsecs
* @param expireMsecs
*/
public RedisLock(Jedis jedis, String lockKey, int timeoutMsecs, int expireMsecs) {
this(jedis, lockKey, timeoutMsecs);
this.EXPIRE_MSECS = expireMsecs;
}
/**
* @return lock key
*/
public String getLockKey() {
return lockKey;
}
/**
* 根據key獲取值
*
* @param key
* @return
*/
private String get(final String key) {
Object obj = null;
try {
obj = jedis.get(key);
} catch (Throwable e) {
}
return obj != null ? obj.toString() : null;
}
/**
* setNX
* @param key
* @param value
* @return
*/
private boolean setNX(final String key, final String value) {
Object obj = null;
try {
obj = jedis.setnx(key, value);
} catch (Throwable e) {
logger.error(String.format("setNX redis error, key : %s", key), e);
}
return obj != null ? (Boolean) obj : false;
}
/**
* getSet
* @param key
* @param value
* @return
*/
private String getSet(final String key, final String value) {
Object obj = null;
try {
obj = jedis.getSet(key, value);
} catch (Throwable e) {
logger.error(String.format("getSet redis error, key : %s", key), e);
}
return obj != null ? (String) obj : null;
}
/**
* 獲得 lock.
* 實現思路: 主要是使用了redis 的setnx命令,緩存了鎖.
* reids緩存的key是鎖的key,所有的共享, value是鎖的到期時間(注意:這里把過期時間放在value了,沒有時間上設置其超時時間)
* 執(zhí)行過程:
* 1.通過setnx嘗試設置某個key的值,成功(當前沒有這個鎖)則返回,成功獲得鎖
* 2.鎖已經存在則獲取鎖的到期時間,和當前時間比較,超時的話,則設置新的值
*
* @return true if lock is acquired, false acquire timeouted
* @throws InterruptedException in case of thread interruption
*/
public synchronized boolean lock() throws InterruptedException {
int timeout = TIMEOUT_MSECS;
while (timeout >= 0) {
long expires = System.currentTimeMillis() + EXPIRE_MSECS + 1;
String expiresStr = String.valueOf(expires); //鎖到期時間
if (this.setNX(lockKey, expiresStr)) {
// lock acquired
locked = true;
return true;
}
String currentValueStr = this.get(lockKey); //redis里的時間
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
//判斷是否為空扰肌,不為空的情況下,如果被其他線程設置了值熊杨,則第二個條件判斷是過不去的
// lock is expired
String oldValueStr = this.getSet(lockKey, expiresStr);
//獲取上一個鎖到期時間曙旭,并設置現在的鎖到期時間,
//只有一個線程才能獲取上一個線上的設置時間晶府,因為jedis.getSet是同步的
if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
//防止誤刪(覆蓋桂躏,因為key是相同的)了他人的鎖——這里達不到效果,這里值會被覆蓋郊霎,但是因為什么相差了很少的時間沼头,所以可以接受
//[分布式的情況下]:如果這個時候,多個線程恰好都到了這里书劝,但是只有一個線程的設置值和當前值相同进倍,他才有權利獲取鎖
// lock acquired
locked = true;
return true;
}
}
timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;
/*
延遲100 毫秒, 這里使用隨機時間可能會好一點,可以防止饑餓進程的出現,即,當同時到達多個進程,
只會有一個進程獲得鎖,其他的都用同樣的頻率進行嘗試,后面有來了一些進行,也以同樣的頻率申請鎖,這將可能導致前面來的鎖得不到滿足.
使用隨機的等待時間可以一定程度上保證公平性
*/
Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
}
return false;
}
/**
* Acqurired lock release.
*/
public synchronized void unlock() {
if (locked) {
jedis.del(lockKey);
locked = false;
}
}
}
測試類
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
/**
* 測試類
*/
public class main {
public static void main(String[] args) {
final RedisLock redisLock = new RedisLock(RedisUtil.getJedis(), "test");
final CountDownLatch down = new CountDownLatch(1);
for (int i = 0; i < 30; i++) {
new Thread(new Runnable() {
public void run() {
try {
down.await();
redisLock.lock();
} catch (Exception e) {
}
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss|SSS");
String orederNo = sdf.format(new Date());
System.out.println("生成的訂單號為:" + orederNo);
try {
redisLock.unlock();
} catch (Exception e) {
}
}
}).start();
}
down.countDown();
}
}
測試結果
[QC] INFO [main] RedisUtil.getJedis(77) | init Redis success
生成的訂單號為:15:51:44|643
生成的訂單號為:15:51:44|643
生成的訂單號為:15:51:44|643
生成的訂單號為:15:51:44|643
生成的訂單號為:15:51:44|643
生成的訂單號為:15:51:44|643
生成的訂單號為:15:51:44|648
生成的訂單號為:15:51:44|655
生成的訂單號為:15:51:44|657
生成的訂單號為:15:51:44|659
生成的訂單號為:15:51:44|661
生成的訂單號為:15:51:44|664
生成的訂單號為:15:51:44|665
生成的訂單號為:15:51:44|669
生成的訂單號為:15:51:44|676
生成的訂單號為:15:51:44|678
生成的訂單號為:15:51:44|680
生成的訂單號為:15:51:44|686
生成的訂單號為:15:51:44|688
生成的訂單號為:15:51:44|690
生成的訂單號為:15:51:44|693
生成的訂單號為:15:51:44|695
生成的訂單號為:15:51:44|697
生成的訂單號為:15:51:44|699
生成的訂單號為:15:51:44|702
生成的訂單號為:15:51:44|713
生成的訂單號為:15:51:44|714
生成的訂單號為:15:51:44|723
生成的訂單號為:15:51:44|725
生成的訂單號為:15:51:44|729
Process finished with exit code 0
對比之下,ZooKeeper的測試類
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
/**
* curator分布式鎖實現
*/
public class Recipes_lock {
static String lock_path = "/curator_recipes_lock_path";
static CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("127.0.0.1:2181")
.retryPolicy(new ExponentialBackoffRetry(1000, 3)).build();
public static void main(String[] args) throws Exception {
client.start();
final InterProcessMutex lock = new InterProcessMutex(client, lock_path);
final CountDownLatch down = new CountDownLatch(1);
for (int i = 0; i < 30; i++) {
new Thread(new Runnable() {
public void run() {
try {
down.await();
lock.acquire();
} catch (Exception e) {
}
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss|SSS");
String orederNo = sdf.format(new Date());
System.out.println("生成的訂單號為:" + orederNo);
try {
lock.release();
} catch (Exception e) {
}
}
}).start();
}
down.countDown();
}
}
ZooKeeper的測試結果
生成的訂單號為:15:32:33|728
生成的訂單號為:15:32:33|781
生成的訂單號為:15:32:33|849
生成的訂單號為:15:32:33|879
生成的訂單號為:15:32:33|897
生成的訂單號為:15:32:33|972
生成的訂單號為:15:32:34|013
生成的訂單號為:15:32:34|057
生成的訂單號為:15:32:34|130
生成的訂單號為:15:32:34|197
生成的訂單號為:15:32:34|232
生成的訂單號為:15:32:34|282
生成的訂單號為:15:32:34|367
生成的訂單號為:15:32:34|404
生成的訂單號為:15:32:34|425
生成的訂單號為:15:32:34|461
生成的訂單號為:15:32:34|482
生成的訂單號為:15:32:34|515
生成的訂單號為:15:32:34|550
生成的訂單號為:15:32:34|573
生成的訂單號為:15:32:34|600
生成的訂單號為:15:32:34|624
生成的訂單號為:15:32:34|647
生成的訂單號為:15:32:34|670
生成的訂單號為:15:32:34|715
生成的訂單號為:15:32:34|752
生成的訂單號為:15:32:34|774
生成的訂單號為:15:32:34|793
生成的訂單號為:15:32:34|811
生成的訂單號為:15:32:34|836
對比兩個結果來看
redis
生成的訂單號為:15:51:44|643
...
生成的訂單號為:15:51:44|729
ZooKeeper
生成的訂單號為:15:32:33|728
...
生成的訂單號為:15:32:34|836