微服务-分布式锁(8)

在微服务中,高并发下,为了保持数据一致性,引入分布式锁的机制来解决问题

准备工作
以下通过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)优化未拿到锁时的情景

增加锁的过期时间和等待时间,但会导致请求的阻塞,影响性能,所以对时间的设置要谨慎

以上,仅用于学习和总结!

原文地址:https://www.cnblogs.com/ywkcode/p/15066681.html