zoukankan      html  css  js  c++  java
  • 微服务-分布式锁(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)优化未拿到锁时的情景

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

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

  • 相关阅读:
    MyBatis环境配置
    log4j配置不同的类多个日志文件
    Http协议头、代理
    Apache二级域名实现
    Flash Builder 4.7 完美破解
    网页设计方面,哪些中英文字体的组合能有好的视觉效果
    网页设计中最常用的字体
    sublime text 3 插件:package control
    大量实用工具类、开源包,该帖绝对值得你收藏!
    10个简化Web开发者工作的HTML5开发工具
  • 原文地址:https://www.cnblogs.com/ywkcode/p/15066681.html
Copyright © 2011-2022 走看看