1. 程式人生 > >Redis分散式鎖----樂觀鎖的實現,以秒殺系統為例

Redis分散式鎖----樂觀鎖的實現,以秒殺系統為例

     摘要:本文使用redis來實現樂觀鎖,並以秒殺系統為例項來講解整個過程。

樂觀鎖
      大多數是基於資料版本(version)的記錄機制實現的。即為資料增加一個版本標識,在基於資料庫表的版本解決方案中,一般是通過為資料庫表增加一個”version”欄位來實現讀取出資料時,將此版本號一同讀出,之後更新時,對此版本號加1。此時,將提交資料的版本號與資料庫表對應記錄的當前版本號進行比對,如果提交的資料版本號大於資料庫當前版本號,則予以更新,否則認為是過期資料。redis中可以使用watch命令會監視給定的key,當exec時候如果監視的key從呼叫watch後發生過變化,則整個事務會失敗。也可以呼叫watch多次監視多個key。這樣就可以對指定的key加樂觀鎖了。注意watch的key是對整個連線有效的,事務也一樣。如果連線斷開,監視和事務都會被自動清除。當然了exec,discard,unwatch命令都會清除連線中的所有監視。

Redis事務
Redis中的事務(transaction)是一組命令的集合。事務同命令一樣都是Redis最小的執行單位,一個事務中的命令要麼都執行,要麼都不執行。Redis事務的實現需要用到 MULTI 和 EXEC 兩個命令,事務開始的時候先向Redis伺服器傳送 MULTI 命令,然後依次傳送需要在本次事務中處理的命令,最後再發送 EXEC 命令表示事務命令結束。Redis的事務是下面4個命令來實現 

1.multi,開啟Redis的事務,置客戶端為事務態。
2.exec,提交事務,執行從multi到此命令前的命令佇列,置客戶端為非事務態。
3.discard,取消事務,置客戶端為非事務態。
4.watch,監視鍵值對,作用時如果事務提交exec時發現監視的監視對發生變化,事務將被取消。
 

下面筆者簡單實現一個用redis樂觀鎖實現的秒殺系統

程式碼實現:

package com.github.distribute.lock.redis;

import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

/**
 * redis樂觀鎖例項 
 * @author linbingwen
 *
 */
public class OptimisticLockTest {

	public static void main(String[] args) throws InterruptedException {
		 long starTime=System.currentTimeMillis();
		
		 initPrduct();
		 initClient();
		 printResult();
		 
		long endTime=System.currentTimeMillis();
		long Time=endTime-starTime;
		System.out.println("程式執行時間: "+Time+"ms");   

	}
	
	/**
	 * 輸出結果
	 */
	public static void printResult() {
		Jedis jedis = RedisUtil.getInstance().getJedis();
		Set<String> set = jedis.smembers("clientList");

		int i = 1;
		for (String value : set) {
			System.out.println("第" + i++ + "個搶到商品,"+value + " ");
		}

		RedisUtil.returnResource(jedis);
	}

	/*
	 * 初始化顧客開始搶商品
	 */
	public static void initClient() {
		ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
		int clientNum = 10000;// 模擬客戶數目
		for (int i = 0; i < clientNum; i++) {
			cachedThreadPool.execute(new ClientThread(i));
		}
		cachedThreadPool.shutdown();
		
		while(true){  
	            if(cachedThreadPool.isTerminated()){  
	                System.out.println("所有的執行緒都結束了!");  
	                break;  
	            }  
	            try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}    
	        }  
	}

	/**
	 * 初始化商品個數
	 */
	public static void initPrduct() {
		int prdNum = 100;// 商品個數
		String key = "prdNum";
		String clientList = "clientList";// 搶購到商品的顧客列表
		Jedis jedis = RedisUtil.getInstance().getJedis();

		if (jedis.exists(key)) {
			jedis.del(key);
		}
		
		if (jedis.exists(clientList)) {
			jedis.del(clientList);
		}

		jedis.set(key, String.valueOf(prdNum));// 初始化
		RedisUtil.returnResource(jedis);
	}

}

/**
 * 顧客執行緒
 * 
 * @author linbingwen
 *
 */
class ClientThread implements Runnable {
	Jedis jedis = null;
	String key = "prdNum";// 商品主鍵
	String clientList = "clientList";//// 搶購到商品的顧客列表主鍵
	String clientName;

	public ClientThread(int num) {
		clientName = "編號=" + num;
	}

	public void run() {
		try {
			Thread.sleep((int)(Math.random()*5000));// 隨機睡眠一下
		} catch (InterruptedException e1) {
		}
		while (true) {
			System.out.println("顧客:" + clientName + "開始搶商品");
			jedis = RedisUtil.getInstance().getJedis();
			try {
				jedis.watch(key);
				int prdNum = Integer.parseInt(jedis.get(key));// 當前商品個數
				if (prdNum > 0) {
					Transaction transaction = jedis.multi();
					transaction.set(key, String.valueOf(prdNum - 1));
					List<Object> result = transaction.exec();
					if (result == null || result.isEmpty()) {
						System.out.println("悲劇了,顧客:" + clientName + "沒有搶到商品");// 可能是watch-key被外部修改,或者是資料操作被駁回
					} else {
						jedis.sadd(clientList, clientName);// 搶到商品記錄一下
						System.out.println("好高興,顧客:" + clientName + "搶到商品");
						break;
					}
				} else {
					System.out.println("悲劇了,庫存為0,顧客:" + clientName + "沒有搶到商品");
					break;
				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				jedis.unwatch();
				RedisUtil.returnResource(jedis);
			}

		}
	}

}

和上文的使用悲觀鎖相比,樂觀鎖的實現更加的簡單,併發效能也會更好。

更多技術請關注筆者微信技術公眾號"單例模式"