博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
浅解.Net分布式锁的实现
阅读量:5806 次
发布时间:2019-06-18

本文共 5786 字,大约阅读时间需要 19 分钟。

序言 

我晚上有在公司多呆会儿的习惯,所以很多晚上我都是最后一个离开公司的。当然也有一些同事,跟我一样喜欢在公司多搞会儿。这篇文章就要从,去年年末一个多搞会的晚上说起,那是一个夜黑风高的晚上,公司应该没有几个人在啦,我司一技术男悠悠的走到我的背后,突然一句:“还没走啊?”!“我日,吓死我啦,你也没走啊”。此同事现在已被裁员,走啦,当晚他问我啦一个问题,至此时也没有机会告知,今天我就在这里就简单描述下他当时的问题,其实实现起来简单的不值一提,不过任何一个简单的问题往往都会有很多中解决方案,探索找到最佳的解决方案,然后把细节做好,那就是技术的精髓与乐趣所在。我这里只抛砖一下,希望能给我老同事一个思路。

回到问题

首先有如下二张表,字段有IsBuyed(0:未使用,1:已使用),ProductNo:产品编号,Count:使用次数。

 

就是针对这张表做需求扩展的。

1、每次请求过来,都随机拿到一个未使用过的产品编号

public int GetNo()        {            using (IDbConnection conn = GetConn())            {                return conn.ExecuteScalar
("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
("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实现的分布式锁,我这里就不说明啦。同事,你好自珍重,祝多生孩子,多挣钱啊。

总结

接下来是大家最喜欢的总结内容啦,内容有二,如下:

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

2、博客里面有没有很清楚的说明白,或者你有更好的方式,那么欢迎加入左上方的2个交流群,我们一起学习探讨。

转载地址:http://jwubx.baihongyu.com/

你可能感兴趣的文章
白话算法(7) 生成全排列的几种思路(二) 康托展开
查看>>
d3 v4实现饼状图,折线标注
查看>>
微软的云策略
查看>>
Valid Parentheses
查看>>
【ES6】数值的扩展
查看>>
性能测试之稳定性测试
查看>>
ES6的 Iterator 遍历器
查看>>
2019届高二(下)半期考试题(文科)
查看>>
【REDO】删除REDO LOG重做日志组后需要手工删除对应的日志文件(转)
查看>>
nginx 301跳转到带www域名方法rewrite(转)
查看>>
AIX 配置vncserver
查看>>
windows下Python 3.x图形图像处理库PIL的安装
查看>>
【IL】IL生成exe的方法
查看>>
network
查看>>
SettingsNotePad++
查看>>
centos7安装cacti-1.0
查看>>
3个概念,入门 Vue 组件开发
查看>>
没有JS的前端:体积更小、速度更快!
查看>>
数据指标/表现度量系统(Performance Measurement System)综述
查看>>
GitHub宣布推出Electron 1.0和Devtron,并将提供无限制的私有代码库
查看>>