1、鎖的發展html
系統結構由傳統的「單應用服務--》SOA --》微服務 --》無服務器」 的演進過程當中,場景愈來愈複雜,由單體應用的但進程中多線程併發的內存鎖,隨着互聯網場景愈來愈複雜,在複雜的系統交互過程當中存在大量的併發。分佈式併發鎖概念就營運而生web
2、鎖的介紹redis
一、進程下單線程模式,更改成多錢成併發模式,因而就產生了線程鎖,也就是常說的內存鎖,是基於單線程中多線程的鎖控制,基於這種方式又演變出來文件鎖等,可是都是基於控制多線程併發的鎖機制sql
二、分佈式以及微服務的興起,程序逐步從單進程演變爲多進程。進程之間就會產生須要處理併發業務,須要實現業務鎖,因此就產生了分佈式鎖的解決方案。數據庫
目前市面上解決分佈式鎖的方案主要有如下幾種:api
(1)基於數據庫表作樂觀鎖(樂觀鎖和悲觀鎖定義請參照-鎖實例介紹找那個單進程內併發鎖的博客),用於分佈式鎖。緩存
(2)使用memcached的add()方法,用於分佈式鎖。服務器
(3)基於redisson實現分佈式鎖(redis官方推薦)多線程
不經常使用可是能夠用於技術方案探討的:併發
(1)使用memcached的cas()方法,用於分佈式鎖。
(2)使用redis的setnx()、get()、getset()方法,使用redis的setnx()、expire()方法,用於分佈式鎖,使用watch、multi、exec命令,用於分佈式鎖。這幾種方法都能根據redis的特性實現分佈式鎖,可是在集羣狀況下,不少極端場景會出現問題
(3)使用zookeeper,用於分佈式鎖。
3、鎖實例介紹
一、單進程多線程併發鎖。
請參看博客:http://www.cnblogs.com/dennyzhangdd/p/6925473.html
http://www.cnblogs.com/zhimingyang/p/5702752.html(ReenTrantLock源碼解析)
注意: 因爲1.5將Synchronized優化後,使得性能大大提高,那麼何時使用什麼狀況下使用ReenTrantLock:
答案是,若是你須要實現ReenTrantLock的三個獨有功能時。
ReenTrantLock獨有的能力:
(1) ReenTrantLock能夠指定是公平鎖仍是非公平鎖。而synchronized只能是非公平鎖。所謂的公平鎖就是先等待的線程先得到鎖。
(2)ReenTrantLock提供了一個Condition(條件)類,用來實現分組喚醒須要喚醒的線程們,而不是像synchronized要麼隨機喚醒一個線程要麼喚醒所有線程。
(3) ReenTrantLock提供了一種可以中斷等待鎖的線程的機制,經過lock.lockInterruptibly()來實現這個機制。
二、分佈式併發鎖
(1)數據庫實現樂觀鎖(新手推薦)
樂觀鎖上文說了是一種思想,相對悲觀鎖而言,樂觀鎖假設認爲數據通常狀況下不會產生併發衝突,因此在數據進行提交更新的時候,纔會正式對數據是否產生併發衝突進行檢測,若是發現併發衝突了,則讓返回用戶錯誤的信息,讓用戶決定如何去作。
具體實現描述:
(2)memcached實現分佈式鎖(沒有實現過,不作過多評價)
memcached帶有add函數,利用add函數的特性便可實現分佈式鎖。add和set的區別在於:若是多線程併發set,則每一個set都會成功,但最後存儲的值以最後的set的線程爲準。而add的話則相反,add會添加第一個到達的值,並返回true,後續的添加則都會返回false。利用該點便可很輕鬆地實現分佈式鎖。
併發高效。
--》memcached採用列入LRU置換策略,因此若是內存不夠,可能致使緩存中的鎖信息丟失。
--》memcached沒法持久化,一旦重啓,將致使信息丟失。
(3)redis實現分佈式鎖方法(老死機推薦)
在講解redis鎖時,這裏只講解在集羣狀況下的分佈式鎖實現(單點redis和集羣redis的實現原理是不同的)
以下是一篇分佈式鎖的官方翻譯微博:http://ifeve.com/redis-lock/,目前redis官方推薦使用的Redisson就提供了分佈式鎖和相關服務。
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>2.7.0</version> </dependency>
import org.redisson.Redisson; import org.redisson.api.RAtomicLong; import org.redisson.config.Config; public class RedissonManager { private static final String RAtomicName = "genId_"; private static Config config = new Config(); private static Redisson redisson = null; public static void init(String key,String value){ try { /* config.useClusterServers() //這是用的集羣server .setScanInterval(2000) //設置集羣狀態掃描時間 .setMasterConnectionPoolSize(10000) //設置鏈接數 .setSlaveConnectionPoolSize(10000) .addNodeAddress("127.0.0.1:6379");*/ if(key==null || "".equals(key)){ key=RAtomicName; } config.useSingleServer().setAddress("127.0.0.1:6379"); redisson = (Redisson) Redisson.create(config); //清空自增的ID數字 RAtomicLong atomicLong = redisson.getAtomicLong(key); long pValue=1; if(value!=null && !"".equals(value)){ pValue = Long.parseLong(value); } atomicLong.set(pValue); }catch (Exception e){ e.printStackTrace(); } } public static Redisson getRedisson(){ return redisson; } /** 獲取redis中的原子ID */ public static Long nextID(){ RAtomicLong atomicLong = getRedisson().getAtomicLong(RAtomicName); //原子性的獲取下一個ID,遞增1 atomicLong.incrementAndGet(); return atomicLong.get(); } }
public class DistributedRedisLock { private static Redisson redisson = RedissonManager.getRedisson(); private static final String LOCK_TITLE = "redisLock_"; public static boolean acquire(String lockName){ String key = LOCK_TITLE + lockName; RLock mylock = redisson.getLock(key); mylock.lock(2, TimeUnit.MINUTES); //lock提供帶timeout參數,timeout結束強制解鎖,防止死鎖 System.err.println("======lock======"+Thread.currentThread().getName()); return true; } public static void release(String lockName){ String key = LOCK_TITLE + lockName; RLock mylock = redisson.getLock(key); mylock.unlock(); System.err.println("======unlock======"+Thread.currentThread().getName()); } }
@RequestMapping("/redder") @ResponseBody public String redder() throws IOException{ String key = "test123"; DistributedRedisLock.acquire(key); Long result = RedissonManager.nextID(); DistributedRedisLock.release(key); return ""+result; }
4、總結
目前對於分佈式鎖的場景很是多,可是如今目前主要的這幾種中redis分佈式鎖的方式目前市場上用的最多。同時筆者也更推薦redisson的方式。
比較總結
一、單JVM鎖
(1)synchronized同步鎖(基於JVM源生synchronized關鍵字實現,新手推薦)
適用於低併發的狀況,性能穩定。
(2)ReentrantLock可重入鎖(基於JDK實現,需顯示獲取鎖,釋放鎖,須要指定公平、非公平或condition時使用。)
適用於低、高併發的狀況,性能較高
(3)ReentrantReadWriteLock可重入讀寫鎖(基於JDK實現,需顯示獲取鎖,釋放鎖。老司機推薦)
適用於讀多寫少的狀況。性能高。
(4)StampedLock戳鎖(基於JDK實現,需顯示獲取鎖,釋放鎖。老司機推薦)
JDK8纔有,適用於高併發且讀遠大於寫時,支持樂觀讀,票據校驗失敗後可升級悲觀讀鎖,性能極高!
二、分佈式鎖
(1)悲觀鎖:select for update(基於數據庫鎖實現,不推薦)
sql直接使用,但水很深。涉及數據庫ACID原理+隔離級別+不一樣數據庫規範
(2)樂觀鎖:版本控制(基於數據庫鎖實現,新手推薦)
本身實現字段版本控制
(3)redisson(基於redis緩存實現,老司機推薦)
性能極高,支持除了分佈式鎖外還實現了分佈式對象、分佈式集合等極端強大的功能
(4)zookeeper(基於zookeeper實現,老司機推薦)
性能較高,除支持分佈式鎖外,還實現了master選舉、節點監聽()、分佈式隊列、Barrier、AtomicLong等計數器
(5)memcached(基於memcached實現,老司機推薦)
有明顯的缺點,重啓會丟失鎖,性能很高,可是有弊端,根據不一樣的場景選擇使用