一、什麼是鎖html
鎖是爲了解決多線程或者多進程資源競爭的問題。git
同一進程的多個線程資源競爭能夠用lock解決。github
lock 關鍵字可確保當一個線程位於代碼的臨界區時,另外一個線程不會進入該臨界區。 若是其餘線程嘗試進入鎖定的代碼,則它將一直等待(即被阻止),直到該對象被釋放。redis
class Test { //定義一個私有成員變量,用於Lock private static object lockobj = new object(); void DoSomething() { lock (lockobj) { //須要鎖定的代碼塊 } } }
多進程之間解決資源競爭問題咱們則須要引入分佈式鎖。經過一個協調者來解決,一般的解決辦法是經過redis來解決,這裏不展開redis分佈式鎖的討論。 接下來咱們來聊聊如何本身實現一個分佈式鎖(不依賴於redis)。 數據庫
二、分佈式鎖是個什麼鬼多線程
分佈式鎖是分佈式、微服務中一個必然要討論的話題。他爲的是解決多進程多線程資源競爭的問題。負載均衡
下面咱們以訂單系統下單扣減庫存爲例聊一聊扣減庫存的問題。框架
三個客戶KA、KB、KC同時下單購買物品P1,請求經過負載均衡器分發到訂單服務A、訂單服務B、訂單服務C。這個時候三個服務同時要對數據庫中的P1物品判斷庫存是否充足。假設庫存剩餘10個,KA須要購買6個、KB須要購買6個、KC須要購買6個。分佈式
正常狀況下服務A、B、C都查詢了庫存大於購買的數量,那麼三個服務都判斷能夠下單。此時咱們能夠看到,她們都進行下單明顯剩餘庫存不足18個,那麼就會出現超賣的問題。那咱們怎麼辦。咱們第一時間會想到鎖,不過在分佈式環境下程序自帶的Lock已經不能解決咱們的問題。
微服務
消息隊列也能夠解決這個問題,不過這裏咱們不討論,咱們要討論的是用鎖來解決。
這個時候咱們須要一個協調者來協調三個服務同時只能有一個請求進入下單代碼塊。原理同本地鎖同樣(當一個線程位於代碼的臨界區時,另外一個線程不會進入該臨界區。 若是其餘線程嘗試進入鎖定的代碼,則它將一直等待(即被阻止),直到該對象被釋放)。另外咱們還須要注意的是,若是鎖的擁有者出現問題,不能及時釋放鎖。那麼就會致使其餘服務一直等待。那麼就會出現死鎖的問題,所以咱們也必須一如超時機制。在咱們預設的處理時間內不能釋放鎖則須要協調者自動釋放鎖。防止出現死鎖。
下面咱們來看看微服務框架Anno是如何實現一個分佈式鎖。
若是對Anno微服務框架不瞭解能夠看這裏《【開源】.net微服務開發引擎Anno開源啦》
二、實現一個分佈式鎖
using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; namespace ConsoleTest { using Anno.Const; using Anno.EngineData; using Anno.Loader; using Anno.Rpc.Client; using Anno.Rpc.Server; using Autofac; public class DLockTest { public void Handle() { Init(); To: List<Task> ts = new List<Task>(); Console.WriteLine("請輸入線程數:"); int.TryParse(Console.ReadLine(), out int n); for (int i = 0; i < n; i++) { var task = Task.Factory.StartNew(() => { DLTest1("Anno"); }); ts.Add(task); //var taskXX = Task.Factory.StartNew(() => { DLTest1("Viper"); }); //ts.Add(taskXX); //var taskJJ = Task.Factory.StartNew(() => { DLTest1("Key001"); }); //ts.Add(taskJJ); } Task.WaitAll(ts.ToArray()); goto To; } private void DLTest1(string lk = "duyanming") { try { Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss:ffff} {System.Threading.Thread.CurrentThread.ManagedThreadId} DLTest1拉取鎖({lk})"); using (DLock dLock = new DLock(lk, 10000)) { Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss:ffff} {System.Threading.Thread.CurrentThread.ManagedThreadId} DLTest1進入鎖({lk})"); System.Threading.Thread.Sleep(50); } Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss:ffff} {System.Threading.Thread.CurrentThread.ManagedThreadId} DLTest1離開鎖({lk})"); } catch (Exception e) { Console.WriteLine(e.Message); } } void Init() { //SettingService.AppName = "DLockTest"; //SettingService.Local.IpAddress = "127.0.0.1"; //SettingService.Local.Port = 6660; IocLoader.GetAutoFacContainerBuilder().RegisterType(typeof(RpcConnectorImpl)).As(typeof(IRpcConnector)).SingleInstance(); IocLoader.Build(); DefaultConfigManager.SetDefaultConnectionPool(100, Environment.ProcessorCount * 2, 50); DefaultConfigManager.SetDefaultConfiguration("DLockTest", "127.0.0.1", 6660, false); } } }
GitHub地址:https://github.com/duyanming/Anno.Core/blob/master/test/ConsoleTest/DLockTest.cs
不一樣類型的鎖能夠同時進入相互不影響
var task = Task.Factory.StartNew(() => { DLTest1("Anno"); }); ts.Add(task); var taskXX = Task.Factory.StartNew(() => { DLTest1("Viper"); }); ts.Add(taskXX); var taskJJ = Task.Factory.StartNew(() => { DLTest1("Key001"); }); ts.Add(taskJJ);
上圖咱們開了12個進程同時進入DLTest1 方法,
using (DLock dLock = new DLock(lk, 10000))設置超時時間10秒。
關鍵代碼:
private void DLTest1(string lk = "duyanming") { try { Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss:ffff} {System.Threading.Thread.CurrentThread.ManagedThreadId} DLTest1拉取鎖({lk})"); using (DLock dLock = new DLock(lk, 10000)) { Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss:ffff} {System.Threading.Thread.CurrentThread.ManagedThreadId} DLTest1進入鎖({lk})"); System.Threading.Thread.Sleep(50); } Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss:ffff} {System.Threading.Thread.CurrentThread.ManagedThreadId} DLTest1離開鎖({lk})"); } catch (Exception e) { Console.WriteLine(e.Message); } }
全部源碼均可以在 Anno中找到。
Anno核心源碼:https://github.com/duyanming/Anno.Core
Viper示例項目:https://github.com/duyanming/Viper
體驗地址:http://140.143.207.244/Home/Login
QQ交流羣:478399354