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