杭州建站公司,app开发制作全过程,娃哈哈网站建设策划书,重庆市建设项目环境申报表网站并发是程序开发中不可避免的问题#xff0c;根据系统面向用户、功能场景的不同#xff0c;并发的重视程度会有不同。从程序的角度来说#xff0c;并发意味着相同的时间点执行了相同的代码#xff0c;而有些情况是不被允许的#xff0c;比如#xff1a;转账、抢购占库存等… 并发是程序开发中不可避免的问题根据系统面向用户、功能场景的不同并发的重视程度会有不同。从程序的角度来说并发意味着相同的时间点执行了相同的代码而有些情况是不被允许的比如转账、抢购占库存等如果没有做好临界条件的验证会带来非常严重的后果。追根结底是因为并发引起的数据不一致问题面对并发我们通常会采用锁来优化。场景模拟如下模拟抢购的示例代码C#1234567891011121314// 有10个商品库存private static int stockCount 10;public bool Buy(){ // 模拟执行的逻辑代码花费的时间 Thread.Sleep(new Random().Next(100,500)); if (stockCount 0) { stockCount--; return true; } return false;}1234567891011var test new Test();Parallel.For(1, 16, (i) { var stopwatch new Stopwatch(); stopwatch.Start(); var data test.Buy(); stopwatch.Stop(); Console.WriteLine($ThreadId:{Thread.CurrentThread.ManagedThreadId}, Result:{data}, Time:{stopwatch.ElapsedMilliseconds});});Console.ReadKey();模拟并行调用 Buy 方法 15 次内部使用的是线程池所以 ThreadId 会有重复实际上只有 10 个库存返回结果却显示 11 个请求都购买成功了。单机部署模式解决方案在单机部署模式下我们只需要加 lock(){} 就可以解决问题12345678910111213141516171819// 有10个商品库存private static int stockCount 10;private static object obj new object();public bool Buy(){ lock (obj) { // 模拟执行的逻辑代码花费的时间 Thread.Sleep(new Random().Next(100, 500)); if (stockCount 0) { stockCount--; return true; } return false; }}从输出结果中可以看出确实只有10个请求是显示购买成功但同时发现部分请求的执行时间明显变长这就是加锁带来的最直观影响当某个线程获得锁之后在没有释放之前其他线程只能继续等待并发越高更多的线程需要等待轮流被处理。各种语言一般都提供了锁的实现用法大同小异语言本身实现的锁只能作用于当前进程内所以在单机模式部署的系统中使用基本没什么问题。集群部署模式解决方案分布式锁在集群模式下系统部署于多台机器一个系统运行在多个进程中语言本身实现的锁只能确保当前进程内有效基于内存多进程就没办法共享锁状态这时我们就得考虑采用分布式锁分布式锁可以采用 数据库、ZooKeeper、Redis 等来实现最终都是为了达到在不同的进程、线程内能共享锁状态的目的。这里将介绍基于 Redis 的 RedLock.net 来解决分布式下的并发问题RedLock.net 是 RedLock 分布式锁算法的 .NET 版实现 (大部分语言都有对应的实现查看) RedLock 分布式锁算法是由 Redis 的作者提出。RedLock 简介RedLock 的思想是使用多台 Redis Master 节点完全独立节点间不需要进行数据同步因为 Master-Slave 架构一旦 Master 发生故障时数据没有复制到 Slave被选为 Master 的 Slave 就丢掉了锁另一个客户端就可以再次拿到锁。锁通过 setNX原子操作 命令设置在有效时间内当获得锁的数量大于 (n/21) 代表成功失败后需要向所有节点发送释放锁的消息。获取锁1SET resource_name my_random_value NX PX 30000释放锁12345if redis.call(get,KEYS[1]) ARGV[1] then return redis.call(del,KEYS[1])else return 0endRedLock.net 集成创建 .NETCore API 项目Nuget 安装 RedLock.net1Install-Package RedLock.netappsettings.json 添加 redis 配置1234{ RedisUrl: 127.0.0.1:6379, // 多个用,分割 ...}添加 ProductService.cs模拟商品购买12345678910111213// 有10个商品库存如果同时启动多个API服务进行测试这里改成存数据库或其他方式private static int stockCount 10;public async Taskbool BuyAsync(){ // 模拟执行的逻辑代码花费的时间 await Task.Delay(new Random().Next(100, 500)); if (stockCount 0) { stockCount--; return true; } return false;}修改 Startup.cs 创建 RedLockFactory定义 RedLockFactory 变量1private RedLockFactory lockFactory;添加方法12345678910111213141516private RedLockFactory GetRedLockFactory(){ var redisUrl Configuration[RedisUrl]; if (string.IsNullOrEmpty(redisUrl)) { throw new ArgumentException(RedisUrl 不能为空); } var urls redisUrl.Split(,).ToList(); var endPoints new ListRedLockEndPoint(); foreach (var item in urls) { var arr item.Split(:); endPoints.Add(new DnsEndPoint(arr[0], Convert.ToInt32(arr[1]))); } return RedLockFactory.Create(endPoints);}在 ConfigureServices 注入 IDistributedLockFactory123lockFactory GetRedLockFactory();services.AddSingleton(typeof(IDistributedLockFactory), lockFactory);services.AddScoped(typeof(ProductService));修改 Configure应用程序结束时释放 lockFactory 123456789public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime lifetime){ ... lifetime.ApplicationStopping.Register(() { lockFactory.Dispose(); });}在 Controller 添加方法 DistributedLockTest1234567891011121314151617181920212223242526272829303132private readonly IDistributedLockFactory _distributedLockFactory;private readonly ProductService _productService;public HomeController(IDistributedLockFactory distributedLockFactory, ProductService productService){ _distributedLockFactory distributedLockFactory; _productService productService;}[HttpGet]public async Taskbool DistributedLockTest(){ var productId id; // resource 锁定的对象 // expiryTime 锁定过期时间锁区域内的逻辑执行如果超过过期时间锁将被释放 // waitTime 等待时间,相同的 resource 如果当前的锁被其他线程占用,最多等待时间 // retryTime 等待时间内多久尝试获取一次 using (var redLock await _distributedLockFactory.CreateLockAsync(productId, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(20))) { if (redLock.IsAcquired) { var result await _productService.BuyAsync(); return result; } else { Console.WriteLine($获取锁失败{DateTime.Now}); } } return false;}调用接口测试12345678 Parallel.For(1, 16, (i) { var stopwatch new Stopwatch(); stopwatch.Start(); var data GetAsync($http://localhost:5000/home/distributedLockTest).Result; stopwatch.Stop(); Console.WriteLine($ThreadId:{Thread.CurrentThread.ManagedThreadId}, Result:{data}, Time:{stopwatch.ElapsedMilliseconds});});关于 RedLock 分布式锁算法的争议大家可以参考How to do distributed lockingIs Redlock safe?总结如果使用锁必然对性能上会有一定影响我们需要根据实际场景来判断是真正需要。在指定锁过期时间时要相对合理避免出现锁已过期但逻辑还没执行完成这样就失去了锁的意义当然这种情况下我们还可以考虑重入锁。最后推荐一下微软开源的一个基于 Actor 模型的分布式框架 Orleans也可以达到分布式锁的效果。参考链接Distributed locks with RedisRedLock.net原文地址http://beckjin.com/2019/01/06/redLock-net/.NET社区新闻深度好文欢迎访问公众号文章汇总 http://www.csharpkit.com