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. 多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)

    前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...

  3. java中的锁

    java中有哪些锁 这个问题在我看了一遍<java并发编程>后尽然无法回答,说明自己对于锁的概念了解的不够.于是再次翻看了一下书里的内容,突然有点打开脑门的感觉.看来确实是要学习的最好方式 ...

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

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

  5. 如何在高并发环境下设计出无锁的数据库操作(Java版本)

    一个在线2k的游戏,每秒钟并发都吓死人.传统的hibernate直接插库基本上是不可行的.我就一步步推导出一个无锁的数据库操作. 1. 并发中如何无锁. 一个很简单的思路,把并发转化成为单线程.Jav ...

  6. 如何定位Oracle数据库被锁阻塞会话的根源

    首先再次明确下,数据库因为要同时保证数据的并发性和一致性,所以操作有锁等待是正常的. 只有那些长时间没有提交或回滚的事物,阻塞了其他业务正常操作,才是需要去定位处理的. 1.单实例环境 2.RAC环境 ...

  7. java 线程 Lock 锁使用Condition实现线程的等待(await)与通知(signal)

    一.Condition 类 在前面我们学习与synchronized锁配合的线程等待(Object.wait)与线程通知(Object.notify),那么对于JDK1.5 的 java.util.c ...

  8. Android 死锁和重入锁

    死锁的定义: 1.一般的死锁 一般的死锁是指多个线程的执行必须同时拥有多个资源,由于不同的线程需要的资源被不同的线程占用,最终导致僵持的状态,这就是一般死锁的定义. package com.cxt.t ...

  9. Xcode 锁终端

    锁终端 输入: <1>cd /Applications/Xcode.app 回车 结果显示: Xcode.app 输入: <2>sudo chown -hR root:whee ...

  10. mysql 行级锁的使用以及死锁的预防

    一.前言 mysql的InnoDB,支持事务和行级锁,可以使用行锁来处理用户提现等业务.使用mysql锁的时候有时候会出现死锁,要做好死锁的预防. 二.MySQL行级锁 行级锁又分共享锁和排他锁. 共 ...

随机推荐

  1. VS2012载入DLL编译出现试图载入格式不对的程序; 以及执行出现Mixed mode assembly is built against version &#39;v2.0.50727&#39; of the

    VS2012载入DLL编译出现试图载入格式不对的程序:以及执行出现Mixed mode assembly is built against version 'v2.0.50727' of therun ...

  2. leetcode-combination sum and combination sum II

    Combination sum: Given a set of candidate numbers (C) and a target number (T), find all unique combi ...

  3. CSS负margin的影响

    原文 简书原文:https://www.jianshu.com/p/87677fd2ea34 相关文章: CSS负边距margin的应用:https://www.cnblogs.com/shcrk/p ...

  4. iOS开发:父子控制器简介:

    #import "ViewController.h" #import "ScoietyViewController.h" #import "HotVi ...

  5. 【源代码】Timer和TimerTask源代码剖析

    Timer是java.util包中的一个工具类,提供了定时器的功能. 我们能够构造一个Timer对象,然后调用其schedule方法在某个特定的时间或者若干延时之后去运行一个特定的任务.甚至你能够让其 ...

  6. 利用Powershell获取公司内部机器的资源信息,作为企业兴许资产管理的基本途径!

    今天一个哥们问我是否用Powershell 实现.我好久没有写脚本,脚本的协作和调试还是费了一些时间,兴许调试了下.运作没有问题,大家能够參考以下的脚本来丰富您企业须要做的一些事情,脚本代码例如以下: ...

  7. NDK 配置及简单项目

    转载请标明出处:http://blog.csdn.net/xx326664162/article/details/50998720 文章出自:薛瑄的博客 你也能够查看我的其它同类文章,也会让你有一定的 ...

  8. IHookHelper的用法

    1.实例化IHookHelper 对象: IHookHelper m_hookHelper = new HookHelperClass();  m_hookHelper.Hook = this.axM ...

  9. web项目中配置多个数据源

    web项目中配置多个数据源 spring + mybatis 多数据源配置有两种解决方案 1.配置多个不同的数据源,使用一个sessionFactory,在业务逻辑使用的时候自动切换到不同的数据源,  ...

  10. [TypeScript] Use the never type to avoid code with dead ends using TypeScript

    Example 1: A never stop while loop return a never type. function run(): never { while(true){ let foo ...