我晚上有在公司多呆会儿的习惯,因此不少晚上我都是最后一个离开公司的。固然也有一些同事,跟我同样喜欢在公司多搞会儿。这篇文章就要从,去年年底一个多搞会的晚上提及,那是一个夜黑风高的晚上,公司应该没有几我的在啦,我司一技术男悠悠的走到个人背后,忽然一句:“还没走啊?”!“我日,吓死我啦,你也没走啊”。此同事如今已被裁人,走啦,当晚他问我啦一个问题,至此时也没有机会告知,今天我就在这里就简单描述下他当时的问题,其实实现起来简单的不值一提,不过任何一个简单的问题每每都会有不少中解决方案,探索找到最佳的解决方案,而后把细节作好,那就是技术的精髓与乐趣所在。我这里只抛砖一下,但愿能给我老同事一个思路。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个交流群,咱们一块儿学习探讨。