在微服务中,高并发下,为了保持数据一致性,引入分布式锁的机制来解决问题
准备工作:
以下通过Parallel模拟多线程并发的情景,简单看下Parallel
适用场合:
1)数据并行(数据的重复性操作) Parallel.For
2)任务并行 任务并发运行不同的操作 Parallel.Invoke
3)流水线:任务并行与数据并行
Parallel.Invoke示例如下,定义四个方法BasketBall,FootBall,Tennis,Swim
1 static void BasketBall() 2 { 3 Console.WriteLine("BasketBall"); 4 } 5 6 static void FootBall() 7 { 8 Console.WriteLine("FootBall"); 9 } 10 11 static void Tennis() 12 { 13 Console.WriteLine("Tennis"); 14 } 15 16 static void Swim() 17 { 18 Console.WriteLine("Swim"); 19 } 20
Parallel.Invoke无序执行:
1 public static void ParallelInvoke() 2 { 3 Parallel.Invoke(() => BasketBall(), () => FootBall(), () => Tennis(), () => Swim()); 4 Console.WriteLine("运动完成"); 5 }
执行两次,结果如下:
Parallel.For 为固定数目的独立for循环提供了负载均衡,模拟并发操作
1 Parallel.For(1, 20, (int i) => 2 { 3 Task.Run(() => 4 { 5 Console.WriteLine($"当前线程Id:{Thread.CurrentThread.ManagedThreadId.ToString("00")},当前序号:{i}"); 6 }); 7 });
结果如下:
正文:
为什么要加锁?主要是为了防止在高并发的情况下,多线程同时处理同一个变量导致数据不准确的问题
(1)不加锁的情况:设置库存量为10
1 //在Redis中配置 2 using (var client = new RedisClient("localhost", 6379)) 3 { 4 client.Set<int>("StockNum", 10);//库存量10 5 client.Set<int>("OrderNum", 0);//订单量0 6 } 7 8 Parallel.For(1, 10, (i) => 9 { 10 using (var client = new RedisClient("localhost", 6379)) 11 { 12 int stocknum = client.Get<int>("StockNum"); 13 if (stocknum > 0) 14 { 15 //库存量-1 16 client.Set<int>("StockNum", stocknum - 1); 17 //订单量+1 18 long ordernum = client.Incr("OrderNum"); 19 Console.WriteLine($"当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")},库存量:{stocknum},订单量:{ordernum}"); 20 } 21 } 22 23 });
结果如下:库存量还未减少的情况下,重复下单,导致超卖
(2)加锁,Lock,通过控制共享存储块的状态,使多线程变为单线程处理
1 using (var client = new RedisClient("localhost", 6379)) 2 { 3 client.Set<int>("StockNum", 10);//库存量10 4 client.Set<int>("OrderNum", 0);//订单量0 5 } 6 7 Parallel.For(0, 10, (i) => 8 { 9 using (var client = new RedisClient("localhost", 6379)) 10 { 11 12 lock (olock) 13 { 14 int stocknum = client.Get<int>("StockNum"); 15 if (stocknum > 0) 16 { 17 //库存量-1 18 client.Set<int>("StockNum", stocknum - 1); 19 //订单量+1 20 long ordernum = client.Incr("OrderNum"); 21 Console.WriteLine($"当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")},库存量:{stocknum},订单量:{ordernum}"); 22 } 23 } 24 } 25 });
结果如下:可见在多进程情况下,库存量和订单量之和恒定
(3) 然而目前是微服务项目,多个微服务并不能共享同一个存储块,Lock无法适用于分布式的场景
本次通过设置Redis的Add方法来解决,如果设置的Key已存在,则无法再添加。若不存在,则添加该Key,业务完成后,删除该key,让其他线程重复上述操作
1 string LockKey = "DistributedLock"; 2 3 using (var client = new RedisClient("localhost", 6379)) 4 { 5 client.Set<int>("StockNum", 10);//库存量10 6 client.Set<int>("OrderNum", 0);//订单量0 7 } 8 9 Parallel.For(0, 10, (i) => 10 { 11 using (var client = new RedisClient("localhost", 6379)) 12 { 13 //已存在,则返回false 14 bool isLock = client.Add(LockKey, LockKey, TimeSpan.FromSeconds(100)); 15 if (isLock) 16 { 17 try 18 { 19 int stocknum = client.Get<int>("StockNum"); 20 if (stocknum > 0) 21 { 22 //库存量-1 23 stocknum--; 24 client.Set<int>("StockNum", stocknum); 25 //订单量+1 26 long ordernum = client.Incr("OrderNum"); 27 Console.WriteLine($"当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")},库存量:{stocknum},订单量:{ordernum}"); 28 } 29 } 30 catch 31 { 32 throw; 33 } 34 finally { 35 client.Remove(LockKey); 36 } 37 } 38 else 39 { 40 Console.WriteLine("未拿到锁!"); 41 } 42 43 } 44 45 });
结果如下:只有拿到锁,才能进行库存和下单的业务操作
(4)优化未拿到锁时的情景
增加锁的过期时间和等待时间,但会导致请求的阻塞,影响性能,所以对时间的设置要谨慎
以上,仅用于学习和总结!