J并发是程序开发中不可避免的问题,根据系统面向用户、功能场景的不同,并发的重视程度会有不同。从程序的角度来说,并发意味着相同的时间点执行了相同的代码,而有些情况是不被允许的,比如:转账、抢购占库存等,如果没有做好临界条件的验证,会带来非常严重的后果。追根结底是因为并发引起的数据不一致问题,面对并发,我们通常会采用锁来优化。

  场景模拟

  如下模拟抢购的示例代码(C#):

  //有10个商品库存privatestaticintstockCount=10;publicboolBuy(){//模拟执行的逻辑代码花费的时间Thread.Sleep(newRandom().Next(100,500));if(stockCount0){stockCount--;returntrue;}returnfalse;}

  vartest=newTest();Parallel.For(1,16,(i)={varstopwatch=newStopwatch();stopwatch.Start();vardata=test.Buy();stopwatch.Stop();Console.WriteLine($ThreadId:{Thread.CurrentThread.ManagedThreadId},Result:{data},Time:{stopwatch.ElapsedMilliseconds});});Console.ReadKey();

  模拟并行调用 Buy 方法 15 次(内部使用的是线程池,所以 ThreadId 会有重复),实际上只有 10 个库存,返回结果却显示 11 个请求都购买成功了。

  

  concurrent

  单机部署模式解决方案

  在单机部署模式下,我们只需要加 lock(){} 就可以解决问题:

  //有10个商品库存privatestaticintstockCount=10;privatestaticobjectobj=newobject();publicboolBuy(){lock(obj){//模拟执行的逻辑代码花费的时间Thread.Sleep(newRandom().Next(100,500));if(stockCount0){stockCount--;returntrue;}returnfalse;}}

  

  concurrent with lock

  从输出结果中可以看出,确实只有10个请求是显示购买成功,但同时发现部分请求的执行时间明显变长,这就是加锁带来的最直观影响,当某个线程获得锁之后,在没有释放之前,其他线程只能继续等待,并发越高,更多的线程需要等待轮流被处理。

  各种语言一般都提供了锁的实现,用法大同小异,语言本身实现的锁只能作用于当前进程内,所以在单机模式部署的系统中使用基本没什么问题。

  集群部署模式解决方案(分布式锁)

  在集群模式下,系统部署于多台机器(一个系统运行在多个进程中),语言本身实现的锁只能确保当前进程内有效(基于内存),多进程就没办法共享锁状态,这时我们就得考虑采用分布式锁,分布式锁可以采用数据库、ZooKeeper、Redis等来实现,最终都是为了达到在不同的进程、线程内能共享锁状态的目的。

  这里将介绍基于 Redis 的RedLock.net来解决分布式下的并发问题,RedLock.net 是 RedLock 分布式锁算法的 .NET 版实现 (大部分语言都有对应的实现,查看) ,RedLock 分布式锁算法是由 Redis 的作者提出。

  RedLock 简介

  RedLock 的思想是使用多台 Redis Master ,节点完全独立,节点间不需要进行数据同步,因为 Master-Slave 架构一旦 Master 发生故障时数据没有复制到 Slave,被选为 Master 的 Slave 就丢掉了锁,另一个客户端就可以再次拿到锁。锁通过 setNX(原子操作) 命令设置,在有效时间内当获得锁的数量大于 (n/2+1) 代表成功,失败后需要向所有节点发送释放锁的消息。

  获取锁:

  SETresource_namemy_random_valueNXPX30000

  释放锁:

  ifredis.call(get,KEYS[1])==ARGV[1]thenreturnredis.call(del,KEYS[1])elsereturn0end

  RedLock.net 集成

  创建 .NETCore API 项目

  Nuget 安装 RedLock.net

  Install-PackageRedLock.net

  appsettings.json 添加 redis 配置

  {RedisUrl:127.0.0.1:6379,//多个用,分割...}

  添加 ProductService.cs,模拟商品购买

  //有10个商品库存,如果同时启动多个API服务进行测试,这里改成存数据库或其他方式privatestaticintstockCount=10;publicasyncTaskboolBuyAsync(){//模拟执行的逻辑代码花费的时间awaitTask.Delay(newRandom().Next(100,500));if(stockCount0){stockCount--;returntrue;}returnfalse;}

  修改 Startup.cs ,创建 RedLockFactory

  定义 RedLockFactory 变量:

  privateRedLockFactorylockFactory;

  添加方法:

  privateRedLockFactoryGetRedLockFactory(){varredisUrl=Configuration[RedisUrl];if(string.IsNullOrEmpty(redisUrl)){thrownewArgumentException(RedisUrl不能为空);}varurls=redisUrl.Split(,).ToList();varendPoints=newListRedLockEndPoint();foreach(variteminurls){vararr=item.Split(:);endPoints.Add(newDnsEndPoint(arr[0],Convert.ToInt32(arr[1])));}returnRedLockFactory.Create(endPoints);}

  在 ConfigureServices 注入 IDistributedLockFactory:

  lockFactory=GetRedLockFactory();services.AddSingleton(typeof(IDistributedLockFactory),lockFactory);services.AddScoped(typeof(ProductService));

  修改 Configure,应用程序结束时释放 lockFactory :

  publicvoidConfigure(IApplicationBuilderapp,IHostingEnvironmentenv,IApplicationLifetimelifetime){...lifetime.ApplicationStopping.Register(()={lockFactory.Dispose();});}

  在 Controller 添加方法 DistributedLockTest

  privatereadonlyIDistributedLockFactory_distributedLockFactory;privatereadonlyProductService_productService;publicHomeController(IDistributedLockFactorydistributedLockFactory,ProductServiceproductService){_distributedLockFactory=distributedLockFactory;_productService=productService;}[HttpGet]publicasyncTaskboolDistributedLockTest(){varproductId=id;//resource锁定的对象//expiryTime锁定过期时间,锁区域内的逻辑执行如果超过过期时间,锁将被释放//waitTime等待时间,相同的resource如果当前的锁被其他线程占用,最多等待时间//retryTime等待时间内,多久尝试获取一次using(varredLock=await_distributedLockFactory.CreateLockAsync(productId,TimeSpan.FromSeconds(5),TimeSpan.FromSeconds(1),TimeSpan.FromMilliseconds(20))){if(redLock.IsAcquired){varresult=await_productService.BuyAsync();returnresult;}else{Console.WriteLine($获取锁失败:{DateTime.Now});}}returnfalse;}

  调用接口测试

  Parallel.For(1,16,(i)={varstopwatch=newStopwatch();stopwatch.Start();vardata=GetAsync($http://localhost:5000/home/distributedLockTest).Result;stopwatch.Stop();Console.WriteLine($ThreadId:{Thread.CurrentThread.ManagedThreadId},Result:{data},Time:{stopwatch.ElapsedMilliseconds});});

  

  redLock

  如果使用锁,必然对性能上会有一定影响,我们需要根据实际场景来判断是真正需要。在指定锁过期时间时要相对合理,避免出现锁已过期,但逻辑还没执行完成,这样就失去了锁的意义,当然这种情况下我们还可以考虑重入锁。

  最后推荐一下微软开源的一个基于 Actor 模型的分布式框架Orleans,也可以达到分布式锁的效果。

  

RedLock 实现分布式锁的更多相关文章

  1. 在.Net中使用RedLock实现分布式锁

    ⒈简介 RedLock 分布式锁算法由 Redis 的作者提出,大部分语言都有对应的实现,查看,RedLock.net 是 RedLock 分布式锁算法的 .NET 版实现,用来解决分布式下的并发问题 ...

  2. 分布式锁之三:Redlock实现分布式锁

    之前写过一篇文章<如何在springcloud分布式系统中实现分布式锁?>,由于自己仅仅是阅读了相关的书籍,和查阅了相关的资料,就认为那样的是可行的.那篇文章实现的大概思路是用setNx命 ...

  3. 如何用Redlock实现分布式锁

    转载请标明出处: http://blog.csdn.net/forezp/article/details/70305336 本文出自方志朋的博客 之前写过一篇文章<如何在springcloud分 ...

  4. .Net 下基于Redlock redis 分布式锁实现

    Redlock-cs (C#/.NET implementation). RedLock.net (C#/.NET implementation). Includes async and lock e ...

  5. Java分布式锁看这篇就够了

    ### 什么是锁? 在单进程的系统中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量. 而同步的本质是通过锁来实现的 ...

  6. 基于 Redis 做分布式锁

    基于 REDIS 的 SETNX().EXPIRE() 方法做分布式锁 setnx() setnx 的含义就是 SET if Not Exists,其主要有两个参数 setnx(key, value) ...

  7. Redisson实现Redis分布式锁的N种姿势(转)

    Redis几种架构 Redis发展到现在,几种常见的部署架构有: 单机模式: 主从模式: 哨兵模式: 集群模式: 我们首先基于这些架构讲解Redisson普通分布式锁实现,需要注意的是,只有充分了解普 ...

  8. SpringBoot电商项目实战 — Redis实现分布式锁

    最近有小伙伴发消息说,在Springboot系列文第二篇,zookeeper是不是漏掉了?关于这个问题,其实我在写第二篇的时候已经考虑过,但基于本次系列文章是实战练习,在项目里你能看到Zookeepe ...

  9. 项目中用到了Redis分布式锁,了解一下背后的原理

    前言 以前在学校做小项目的时候,用到Redis,基本也只是用来当作缓存.现在博主在某金融平台实习,发现Redis在生产中并不只是当作缓存这么简单.在我接触到的项目中,Redis起到了一个分布式锁的作用 ...

随机推荐

  1. 02-django查询

      目录 (一)查询 1 .基本查询(等于.大于.包含字符.日期.字段比较.逻辑) 2 .关联查询(即为join查询)(一对多.多对多.一对一) 3 .聚合查询(统计查询) (二)关联对象(已知A表的 ...

  2. RocketMQ Java 客户端实现

    本章介绍使用 Java 实现RocketMQ 的客户端. 以及各种消息的方式的实现. 本章实现了以下几种消息的实现方式: 一:普通消息 普通的消息分为三种: 1> 可靠的同步消息 可靠的同步传输 ...

  3. 马尔可夫随机场(Markov random fields) 概率无向图模型 马尔科夫网(Markov network)

    上面两篇博客,解释了概率有向图(贝叶斯网),和用其解释条件独立.本篇将研究马尔可夫随机场(Markov random fields),也叫无向图模型,或称为马尔科夫网(Markov network) ...

  4. 从iOS的图片圆角想到渲染

    圆角是一种很常见的视图效果,相比于直角,它更加柔和优美,易于接受.设置圆角会带来一定的性能损耗,如何提高性能是一个需要重点讨论的话题. 大家常见的圆角代码x.layer.cornerRadius = ...

  5. 微信小程序组件radio

    表单组件radio:官方文档 Demo Code: Page({ data: { items: [ {name: 'USA', value: '美国'}, {name: 'CHN', value: ' ...

  6. maven shade插件小记

    maven shade plugin插件小用 项目中一直使用assembly插件来整合依赖包到一个胖jar,在做这个akka http项目的时候,在scala ide的run/debug中都执行正常, ...

  7. Mybatis 中 update 语句 动态 语句

    <update id="updateAdministrationAsset" parameterType="com.opple.fa.assetcard.entit ...

  8. Hibernate与autoCommit

    JDBC 的autoCommit属性 对于每一个 JDBC connection,都有一个autoCommit属性,只有执行commit后,该connection中的操作(statement操作)才会 ...

  9. 【Linux学习】3.Linux常见配置文件

    一./etc 配置文件/etc/passwd 用户数据库,其中的域给出了用户名.真实姓名.家目录.加密口令和用户的其他信息 /etc/group 类似/etc/passwd ,但说明的不是用户而是组. ...

  10. 20145316 《Java程序设计》第8周学习总结

    20145316 <Java程序设计>第8周学习总结 教材学习内容总结 NIO&NIO2 NIO使用频道(channel)衔接数据节点,对数据区的标记提供了clear(),rewi ...