天天看點

淺解.Net分布式鎖的實作

序言 

我晚上有在公司多呆會兒的習慣,是以很多晚上我都是最後一個離開公司的。當然也有一些同僚,跟我一樣喜歡在公司多搞會兒。這篇文章就要從,去年年末一個多搞會的晚上說起,那是一個夜黑風高的晚上,公司應該沒有幾個人在啦,我司一技術男悠悠的走到我的背後,突然一句:“還沒走啊?”!“我日,吓死我啦,你也沒走啊”。此同僚現在已被裁員,走啦,當晚他問我啦一個問題,至此時也沒有機會告知,今天我就在這裡就簡單描述下他當時的問題,其實實作起來簡單的不值一提,不過任何一個簡單的問題往往都會有很多中解決方案,探索找到最佳的解決方案,然後把細節做好,那就是技術的精髓與樂趣所在。我這裡隻抛磚一下,希望能給我老同僚一個思路。

回到問題

首先有如下二張表,字段有IsBuyed(0:未使用,1:已使用),ProductNo:産品編号,Count:使用次數。

淺解.Net分布式鎖的實作
淺解.Net分布式鎖的實作

就是針對這張表做需求擴充的。

1、每次請求過來,都随機拿到一個未使用過的産品編号

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

2、每次請求過來,即為使用産品一次,使用未使用過的産品一次需産品的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、寫一個接口,部署在叢集環境中,模拟請求3秒内一萬個請求,來消費表中隻有10個的産品,最終結果為産品不能被多次使用,如果存在多次使用則産品的count将大于1,即為失敗。同學如果你看到啦,問題我給你複原的跟你說的沒多少出入吧?

.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 

淺解.Net分布式鎖的實作

表:bstore

淺解.Net分布式鎖的實作

由結果可以看出,單機部署接口的情況下,還使一些産品被多次消費,這很顯然不符合同學的要求。

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

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個請求來測試此接口

淺解.Net分布式鎖的實作

結果如下:

表:astore

淺解.Net分布式鎖的實作
淺解.Net分布式鎖的實作

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

使用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;
        }
}      

再來看此接口運作的結果。結果如下:

淺解.Net分布式鎖的實作

表:bstore

淺解.Net分布式鎖的實作

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

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);
                }               
            }
        }      

在叢集下跑此接口看結果,結果如下。

淺解.Net分布式鎖的實作
淺解.Net分布式鎖的實作

功能實作,同僚可以安息啦。不過這裡還有很多優化,和分布式鎖帶來的弊端,比如一單被分布式鎖,鎖住業務即便後續算法沒有使用該産品,怎麼優雅的釋放鎖,怎麼解決遇到已經使用過的産品後再此配置設定新資源等等,當然也有其他一些實作方案,比如基于redis,zookeeper實作的分布式鎖,我這裡就不說明啦。同僚,你好自珍重,祝多生孩子,多掙錢啊。

總結

接下來是大家最喜歡的總結内容啦,内容有二,如下:

1、希望能關注我其他的文章。

2、部落格裡面有沒有很清楚的說明白,或者你有更好的方式,那麼歡迎加入左上方的2個交流群,我們一起學習探讨。