基於redis分散式鎖
阿新 • • 發佈:2019-02-19
錯誤方法:setnx獲取鎖,拿到鎖用expire給鎖加一個過期時間,防止鎖忘記釋放。如果setnx執行之後expire執行之前,執行緒死掉,那鎖就永遠得不到釋放,發生死鎖。Long result = jedis.setnx(lockKey, requestId);if (result == 1) {// 執行緒死掉,無法設定過期時間,發生死鎖jedis.expire(lockKey, expireTime);}最佳實踐:set=setnx和expire,一條指令,把setnx和expire原子化結合起來。set key value [ex seconds] [px milliseconds] [nx|xx]ex seconds: 為鍵設定秒級過期時間。px milliseconds: 為鍵設定毫秒級過期時間。nx: 鍵必須不存在, 才可以設定成功, 用於新增。
xx: 與nx相反, 鍵必須存在, 才可以設定成功, 用於更新。
redis連接獲取工具類
package com.hong.api.redis.lock; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; /** * 單機模式 */ public class RedisUtil { private static final String redisHost = "192.168.1.200"; private static final int port = 6379; private static JedisPoolConfig config = null; private static JedisPool pool = null; static { // 利用Redis連線池,保證多個執行緒利用多個連線,充分模擬併發性 config = new JedisPoolConfig(); config.setMaxIdle(10); config.setMaxTotal(30); config.setMaxWaitMillis(100000); // config.setTestOnBorrow(true); // config.setTestOnReturn(true); pool = new JedisPool(config, redisHost, port); } public static Jedis getJedis() { return pool.getResource(); } public static int getNumActive() { return pool.getNumActive(); } public static void returnResource(Jedis jedis) { // pool.returnBrokenResource(jedis); pool.returnResource(jedis); } }
獲取分散式鎖
package com.hong.api.redis.lock; import java.util.Random; import java.util.UUID; import redis.clients.jedis.Jedis; import redis.clients.jedis.Transaction; public class RedisLock { /** * 獲取鎖:生成一個UUID,作為Key的標識,不斷輪詢lockName,直到set成功,表示成功獲取鎖。 */ public static TmpResult getLock(String lockNameKey) { String value = UUID.randomUUID().toString(); Jedis jedis = null; for (; ; ) { try { jedis = RedisUtil.getJedis(); if ("OK".equals(jedis.set(lockNameKey, value, "NX", "EX", 10))) { System.out.println("get lock"); RedisUtil.returnResource(jedis); TmpResult tmpResult = new TmpResult(); tmpResult.setJedis(jedis); tmpResult.setValue(value); return tmpResult; } // 阻塞之前,先放入redis pool,避免消費者執行緒太多把redis pool全部佔滿 RedisUtil.returnResource(jedis); // 時間短,競爭比較激烈,效能差 // Thread.currentThread().sleep(100); Thread.currentThread().sleep(new Random().nextInt(5000)); // System.out.println("get lock fail"); } catch (Throwable t) { t.printStackTrace(); if (jedis != null) { RedisUtil.returnResource(jedis); } } } } /** * 釋放鎖:對lockName做watch,開啟一個事務,刪除以LockName為key的鎖,刪除後此鎖對於其他執行緒為可爭搶。 */ public static void relaseLock(String lockNameKey, TmpResult tmpResult) { Jedis jedis = null; try { jedis = tmpResult.getJedis(); while (true) { jedis.watch(lockNameKey); if (jedis.get(lockNameKey).equals(tmpResult.getValue())) { Transaction tx = jedis.multi(); tx.del(lockNameKey); tx.exec(); return; } jedis.unwatch(); } } finally { if (jedis != null) { RedisUtil.returnResource(jedis); } } } }
package com.hong.api.redis.lock; import redis.clients.jedis.Jedis; public class TmpResult { private Jedis jedis; private String value; public Jedis getJedis() { return jedis; } public void setJedis(Jedis jedis) { this.jedis = jedis; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } }
測試
package com.hong.api.redis.lock;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class RedisLockTest {
private static String lockNameKey = "lock_key";
private static int count = 0;
private static AtomicInteger Countor = new AtomicInteger(0);
private static int ThLeng = 1024;
// CountDownLatch保證主執行緒在全部執行緒結束之後退出
private static CountDownLatch latch = new CountDownLatch(ThLeng);
private static ExecutorService service = Executors.newFixedThreadPool(ThLeng);
public static void main(String args[]) {
for (int i = 0; i < ThLeng; ++i) {
String threadName = "thread-" + i;
Thread t = new Thread(new SubAddThread(threadName));
System.out.println(threadName + " init...");
service.submit(t);
}
service.shutdown();
try {
latch.await();
} catch (Throwable t) {
t.printStackTrace();
}
System.out.println(Countor.get());
System.out.println(count);
}
public static class SubAddThread implements Runnable {
private String threadName;
public SubAddThread(String threadName) {
this.threadName = threadName;
}
@Override
public void run() {
System.out.println(threadName + " starting...");
for (int i = 0; i < 50; ++i) {
TmpResult tmpResult = RedisLock.getLock(lockNameKey);
// System.out.println(threadName + " get Lock");
count++;
Countor.incrementAndGet();
RedisLock.relaseLock(lockNameKey, tmpResult);
System.out.println(threadName + " " + count);
}
latch.countDown();
System.out.println(threadName + " over");
}
}
}