淺解.Net分佈式鎖的實現

序言 

我晚上有在公司多呆會兒的習慣,因此不少晚上我都是最後一個離開公司的。固然也有一些同事,跟我同樣喜歡在公司多搞會兒。這篇文章就要從,去年年底一個多搞會的晚上提及,那是一個夜黑風高的晚上,公司應該沒有幾我的在啦,我司一技術男悠悠的走到個人背後,忽然一句:「還沒走啊?」!「我日,嚇死我啦,你也沒走啊」。此同事如今已被裁人,走啦,當晚他問我啦一個問題,至此時也沒有機會告知,今天我就在這裏就簡單描述下他當時的問題,其實實現起來簡單的不值一提,不過任何一個簡單的問題每每都會有不少中解決方案,探索找到最佳的解決方案,而後把細節作好,那就是技術的精髓與樂趣所在。我這裏只拋磚一下,但願能給我老同事一個思路。nginx

回到問題

首先有以下二張表,字段有IsBuyed(0:未使用,1:已使用),ProductNo:產品編號,Count:使用次數。redis

 

就是針對這張表作需求擴展的。算法

一、每次請求過來,都隨機拿到一個未使用過的產品編號數據庫

       public int GetNo()
        {
            using (IDbConnection conn = GetConn())
            {
                return conn.ExecuteScalar<int>("select top 1 ProductNo from  AStore where isBuyed=0 order by newid()");
            }
        }

二、每次請求過來,即爲使用產品一次,使用未使用過的產品一次需產品的IsBuyed=1 , Count=Count+1 。分佈式

 public bool UsingStore(int no)
        {
            using (IDbConnection conn = GetConn())
            {
                return conn.Execute("update AStore set isBuyed=1  where  and productNo=" + no) > 0;
            }
        }
        public bool MinusStore(int no)
        {
            using (IDbConnection conn = GetConn())
            {
                return conn.Execute("update BStore set [count]=[count]+1 where  and productNo=" + no) > 0;
            }
        }

三、寫一個接口,部署在集羣環境中,模擬請求3秒內一萬個請求,來消費表中只有10個的產品,最終結果爲產品不能被屢次使用,若是存在屢次使用則產品的count將大於1,即爲失敗。同窗若是你看到啦,問題我給你復原的跟你說的沒多少出入吧?memcached

.Net實現分佈式鎖

解決問題我就一步步來遞進,慢慢深刻,直至痛楚!!首先我把同事操做數據上面的2個方法先貼出來。性能

public bool UsingStore(int no)
        {
            using (IDbConnection conn = GetConn())
            {
                return conn.Execute("update AStore set isBuyed=1  where productNo=" + no) > 0;
            }
        }
        public bool MinusStore(int no)
        {
            using (IDbConnection conn = GetConn())
            {
                return conn.Execute("update BStore set [count]=[count]+1 where  productNo=" + no) > 0;
            }
        }
        public int GetNo()
        {
            using (IDbConnection conn = GetConn())
            {
                return conn.ExecuteScalar<int>("select top 1 ProductNo from  AStore where isBuyed=0 order by newid()");
            }
        }

初涉茅廬的同窗可能會這樣寫接口。 學習

 public JsonResult Using1()
        {
            //獲取未使用的產品編號
            var no = data.GetNo();
            if (no != 0)
            {
                //使用此產品
                data.MinusStore(no);
                data.UsingStore(no);
                return Json(new { success = true, ip = Request.ServerVariables.Get("Local_Addr").ToString() + " : " + HttpContext.Request.Url.Port }, JsonRequestBehavior.AllowGet);
            }
            else
            {
                //無產品可以使用
                return Json(new { success = false, ip = Request.ServerVariables.Get("Local_Addr").ToString() + " : " + HttpContext.Request.Url.Port }, JsonRequestBehavior.AllowGet);
            }
        }

單機部署,1萬個請求過來下啊。下面咱們看看數據庫的結果是什麼?下面是3次實現結果。每執行一次,執行一下下面的腳本。測試

select * from [dbo].[AStore]
update AStore set isbuyed=0,count=0

表:astore 表:bstore 優化

由結果能夠看出,單機部署接口的狀況下,還使一些產品被屢次消費,這很顯然不符合同窗的要求。

那麼咱們進一步改進這個接口,使用單機鎖,鎖此方法,來實現此接口,以下。

 public JsonResult Using()
        {
            string key = "%……¥%¥%77123嗎,bnjhg%……%……&+orderno";
            //鎖此操做          
            lock (key)
            {
                //獲取未使用的產品編號
                var no = data.GetNo();
                if (no != 0)
                {
                    //使用此產品
                    data.MinusStore(no);
                    data.UsingStore(no);
                    return Json(new { success = true, ip = Request.ServerVariables.Get("Local_Addr").ToString() + " : " + HttpContext.Request.Url.Port }, JsonRequestBehavior.AllowGet);
                }
                else
                {
                    //此產品已使用過
                    return Json(new { success = false, ip = Request.ServerVariables.Get("Local_Addr").ToString() + " : " + HttpContext.Request.Url.Port }, JsonRequestBehavior.AllowGet);
                }
            }
        }

單機部署此接口,1000個請求來測試此接口

結果以下:

表:astore表:bstore 

哇塞,貌似同事的問題解決啦,哈哈,同事不急,這只是單機部署下的結果,若是把這個接口集羣部署的話是什麼結果呢?

使用nginx作集羣部署,搞5個站點作測試,對得起嗎,同事?

 upstream servers{
       server 192.168.10.150:9000 weight=1; 
       server 172.18.11.79:1112 weight=1;
       server 192.168.10.150:1114 weight=1;
       server 192.168.10.150:1115 weight=1;
       server 192.168.10.150:1116 weight=1;  
 }
 server{
      keepalive_requests 1200;
      listen 8080;
      server_name abc.nginx3.com;
      location ~*^.+$ { 
          proxy_pass http://servers;
        }
}

再來看此接口運行的結果。結果以下:

表:astore表:bstore 

由圖能夠看出,站點部署的集羣對的住你,結果可令咱們不滿意啊,顯然一個產品仍是存在屢次消費的狀況,這種鎖對集羣部署無用,而且還要排隊,性能也跟不上來。咱們來進一步改寫這個接口。以下:

 public JsonResult Using3()
        {
            //鎖此操做
            string key = "%……¥%¥%77123嗎,bnjhg%……%……&+orderno";
            lock (key)
            {
                //獲取未使用的產品編號
                var no = data.GetNo();
                //單號作爲key插入memcached,值爲true。
                var getResult = AMemcached.cache.Add("Miaodan_ProNo:" + no, true);
                if (getResult)
                {
                    //使用此產品
                    data.MinusStore(no);
                    data.UsingStore(no);
                    return Json(new { success = true, ip = Request.ServerVariables.Get("Local_Addr").ToString() + " : " + HttpContext.Request.Url.Port }, JsonRequestBehavior.AllowGet);
                }
                else
                {
                    //此產品已使用過
                    return Json(new { success = false, ip = Request.ServerVariables.Get("Local_Addr").ToString() + " : " + HttpContext.Request.Url.Port }, JsonRequestBehavior.AllowGet);
                }               
            }
        }

在集羣下跑此接口看結果,結果以下。

表:astore表:bstore 

功能實現,同事能夠安息啦。不過這裏還有不少優化,和分佈式鎖帶來的弊端,好比一單被分佈式鎖,鎖住業務即使後續算法沒有使用該產品,怎麼優雅的釋放鎖,怎麼解決遇到已經使用過的產品後再此分配新資源等等,固然也有其餘一些實現方案,好比基於redis,zookeeper實現的分佈式鎖,我這裏就不說明啦。同事,你好自珍重,祝多生孩子,多掙錢啊。

總結

接下來是你們最喜歡的總結內容啦,內容有二,以下:

一、但願能關注我其餘的文章。

二、博客裏面有沒有很清楚的說明白,或者你有更好的方式,那麼歡迎加入左上方的2個交流羣,咱們一塊兒學習探討。

相關文章
相關標籤/搜索