原文 : 浅解.Net分布式锁的实现

 

序言

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

回到问题

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

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

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 表: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实现的分布式锁,我这里就不说明啦

.net 分布式锁的更多相关文章

  1. 使用redis构建可靠分布式锁

    关于分布式锁的概念,具体实现方式,直接参阅下面两个帖子,这里就不多介绍了. 分布式锁的多种实现方式 分布式锁总结 对于分布式锁的几种实现方式的优劣,这里再列举下 1. 数据库实现方式 优点:易理解 缺 ...

  2. 分布式锁1 Java常用技术方案

    前言:       由于在平时的工作中,线上服务器是分布式多台部署的,经常会面临解决分布式场景下数据一致性的问题,那么就要利用分布式锁来解决这些问题.所以自己结合实际工作中的一些经验和网上看到的一些资 ...

  3. .net 分布式架构之分布式锁实现

    分布式锁 经常用于在解决分布式环境下的业务一致性和协调分布式环境. 实际业务场景中,比如说解决并发一瞬间的重复下单,重复确认收货,重复发现金券等. 使用分布式锁的场景一般不能太多. 开源地址:http ...

  4. 分布式任务&分布式锁(li)

    目前系统中存在批量审批.批量授权等各个操作,批量操作中可能因为处理机器.线程不同,造成刷新缓存丢失授权等信息,如批量审批同一用户权限多个权限申请后,流程平台并发的发送多个http请求到acl不同服务器 ...

  5. jedisLock—redis分布式锁实现

    一.使用分布式锁要满足的几个条件: 系统是一个分布式系统(关键是分布式,单机的可以使用ReentrantLock或者synchronized代码块来实现) 共享资源(各个系统访问同一个资源,资源的载体 ...

  6. ZooKeeper 笔记(6) 分布式锁

    目前分布式锁,比较成熟.主流的方案有基于redis及基于zookeeper的二种方案. 大体来讲,基于redis的分布式锁核心指令为SETNX,即如果目标key存在,写入缓存失败返回0,反之如果目标k ...

  7. 一次基于etcd的分布式锁自动延时失败问题的排查

    今天在测试基于etcd的分布式锁过程中,在测试获取锁后,释放之前超出TTL时长的情况下自动延长TTL这部分功能,在延长指定key的TTL时总是返回404错误信息,在对目标KEY更新TTL时目标KEY已 ...

  8. 基于redis 实现分布式锁的方案

    在电商项目中,经常有秒杀这样的活动促销,在并发访问下,很容易出现上述问题.如果在库存操作上,加锁就可以避免库存卖超的问题.分布式锁使分布式系统之间同步访问共享资源的一种方式 基于redis实现分布式锁 ...

  9. 用Redis实现分布式锁

    Redis有一系列的命令,特点是以NX结尾,NX是Not eXists的缩写,如SETNX命令就应该理解为:SET if Not eXists.这系列的命令非常有用,这里讲使用SETNX来实现分布式锁 ...

  10. Curator Zookeeper分布式锁

    Curator Zookeeper分布式锁 pom.xml中添加如下配置 <!-- https://mvnrepository.com/artifact/org.apache.curator/c ...

随机推荐

  1. MySQ彻底删除与安装配置

    彻底删除 1.查看 MySQL 安装了哪些东西 rpm -qa |grep -i mysql 2.卸载 -.el7.x86_64 -.el7.x86_64 .noarch -.el7.x86_64 - ...

  2. 【每日一包0009】group-array

    [github地址:https://github.com/ABCDdouyae...] group-array 对数组里面的多项按照指定的key进行整合 用法:group-array(arr, key ...

  3. 剑指offer:关于复制构造函数

    1:首先参看代码: #include "stdafx.h" #include "iostream" using namespace std; class A { ...

  4. yconsole使用说明

    介绍: yconsole是yhd用于管理缓存的工具(python实现),它的主要功能是为各个应用分配和管理缓存,以及错误修复.和它配合使用的还有ycache-client.yagent.zookeep ...

  5. leetcode-easy-sorting and searching- 278 First Bad Version

    mycode  96.42 # The isBadVersion API is already defined for you. # @param version, an integer # @ret ...

  6. Git检出和提交至远程仓库

    步骤一:首先需要一个Github账号,还没有的话先去注册:https://github.com/,我们使用Git需要先安装Git工具,这里给出下载地址:https://git-for-windows. ...

  7. jdbc 对sqlite的基本操作

    1.向数据库中创建表 public void addTable( String dbpath) { //创建表单的sql语句 String createtablesql= " CREATE ...

  8. Jmeter之乱码 (三)

    使用Jmeter执行JDBC请求,往MySQL数据库中插入数据,如下图: 执行结果如下: 解决方案: 在JDBC Connection Configuration中的Database URL后加上&a ...

  9. 利用jquery的淡入淡出函数(fadeIn和fadeOut)--实现轮播

    首先说下,我在网上找的例子全是用的UL 实现,其实大可不必,只要是能包含img标签的HTML标签都可以做轮播效果.利用jquery的淡入淡出函数(fadeIn和fadeOut).废话也不多说,边上代码 ...

  10. document的属性操作

    document对象: 1.直接操作: 对象.属性 = 值; 如下面一段代码: <a href  = "...">跳转</a> <script> ...