• redis在.net架构中的应用(2)--并发和原子操作不可兼得


    在上一篇文章中,我主要向大家介绍了利用servicestack连接redis以及一些redis的基本数据类型,传送门

    本文中,我将通过一个具体应用场景为大家介绍redis中的并发和原子操作

    其中用到的redis命令,请大家去redis官网查询http://www.redis.io/commands

    一个投票统计的应用场景

    假设我要做一个实时统计投票数的应用,这个投票总共有A、B、C、D四个选项,因为是一个高并发的场景,所以我准备用redis来存储投票数

      我们首先利用redis-cli模拟这个过程,打开命令终端,新建一个hash类型的key,叫做TicketCount, 编号为1,然后我们将选项作为其field值,

    redis命令如下:

    然后我们利用servicestack模拟投票场景,代码如下:

     1  static void Main(string[] args)
     2         {
     3 
     4             RedisClient[] redisCli = new RedisClient[8]{
     5                 new RedisClient("192.168.101.165", 6379),
     6                 new RedisClient("192.168.101.165", 6379),
     7                 new RedisClient("192.168.101.165", 6379),
     8                 new RedisClient("192.168.101.165", 6379),
     9                 new RedisClient("192.168.101.165", 6379),
    10                 new RedisClient("192.168.101.165", 6379),
    11                 new RedisClient("192.168.101.165", 6379),
    12                 new RedisClient("192.168.101.165", 6379)
    13               };
    14             ThreadPool.QueueUserWorkItem(o =>
    15              {
    16                  testTicketCount(redisCli[0], "A");
    17              });
    18             ThreadPool.QueueUserWorkItem(o =>
    19             {
    20                 testTicketCount(redisCli[1], "A");
    21             });
    22             ThreadPool.QueueUserWorkItem(o =>
    23             {
    24                 testTicketCount(redisCli[2], "B");
    25             });
    26             ThreadPool.QueueUserWorkItem(o =>
    27             {
    28                 testTicketCount(redisCli[3], "B");
    29             });
    30             ThreadPool.QueueUserWorkItem(o =>
    31             {
    32                 testTicketCount(redisCli[4], "C");
    33             });
    34             ThreadPool.QueueUserWorkItem(o =>
    35             {
    36                 testTicketCount(redisCli[5], "C");
    37             });
    38             ThreadPool.QueueUserWorkItem(o =>
    39             {
    40                 testTicketCount(redisCli[6], "D");
    41             });
    42             ThreadPool.QueueUserWorkItem(o =>
    43             {
    44                 testTicketCount(redisCli[7], "D");
    45             });
    46           
    47             Console.Read();
    48         }
    49         /// <summary>
    50         /// 对某个选项进行投票,投票数加1
    51         /// </summary>
    52         /// <param name="rediscli"></param>
    53         /// <param name="field"></param>
    54         internal static void testTicketCount(IRedisClient rediscli, string field)
    55         {
    56             int k = 0;
    57             for (int i = 0; i <= 99; i++)
    58             {
    59 
    60                 k = int.Parse(rediscli.GetValueFromHash("TicketCount", field))+1;
    61                 rediscli.SetEntryInHash("TicketCount", field, k.ToString());
    62             }
    63         }
    View Code

    我们设想的结果是A、B、C、D四个选项各获得了200票,但是对多线程比较熟悉的同学马上就能看出问题了,

    事实上最终的结果是

    没错,在一个线程执行GetValueFromHash和SetEntryInHash两条命令的时候,另一个线程修改了key的值,破坏了操作的原子性

    这个过程中,A选项明显丢掉了一张投票。

    Nosql中的原子性

    要保证数据操作的原子性,在传统的RDBMS应用中,我们首先想到的就是事物和锁,但是在这个场景中,我们需要获得保证{get key;set key}这两步

    操作的原子性,我们需要获得key的值,所以无法用到事物。

    让我们回到nosql的本质上来,来谈谈锁的运用,有兴趣的同学可以看看关于nosql的CAP原则和传统的RDBMS的ACID原则:

    从上图中,我们可以看到,NoSQL系统更加注重性能,是不保证一客户端的两个操作的原子性保证的

    (redis中的事物比较特殊,我将会在下一篇文章中讨论)

    三 利用hincrby

    幸亏redis还提供hincrby命令,也就是直接对hset某个字段的值加上某个整数

    也就是对某个hash key中的某个value 值 提供加一操作。我们可以将TicketCount函数修改一下:

     1     internal static void testTicketCount(IRedisClient rediscli, string field)
     2         {
     3             int k = 0;
     4             for (int i = 0; i <= 99; i++)
     5             {
     6                 rediscli.IncrementValueInHash("TicketCount", field, 1);
     7                 //k = int.Parse(rediscli.GetValueFromHash("TicketCount", field))+1;
     8                 //rediscli.SetEntryInHash("TicketCount", field, k.ToString());
     9             }
    10         }

    但是问题是,如果换了一个应用场景,A中存储的不是数值而是字符串呢?

    四 并发和原子操作

    这两个特性是完全矛盾的,nosql的设计理念就是为了支持高并发,所以它注定就对原子操作的支持性不高。

    因为原子操作必然要涉及到数据库级别的锁,会带来各种性能损耗。

    至于redis中的事物,完全是和redis的实现机制有关的,我将会在下一篇文章中和大家讨论

    有同学提到了乐观锁机制,servicestack中也已经实现了乐观锁,我也会在下一篇中提到

    作者:Mars

    出处:http://www.cnblogs.com/marsblog/

    本文基于署名-非商业性使用 3.0中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 Mars (包含链接)

  • 相关阅读:
    C++编程中的小规范(转)
    windows的消息大全(收集)
    C中的一些好玩的事(一)转
    C++ 温故而知新(三)
    C++温故而知新(二)
    C++的内存分布(一)转
    C++ 温故而知新(一)
    C++反射机制模拟
    VS2010 下配置opeNGL遇到的问题
    SharedPreference.Editor的apply和commit方法异同
  • 原文地址:https://www.cnblogs.com/marsblog/p/3291692.html
Copyright © 2020-2023  润新知