1. 程式人生 > >基於redis分散式鎖

基於redis分散式鎖

錯誤方法: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");
        }
    }
}