zoukankan      html  css  js  c++  java
  • 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 (包含链接)

  • 相关阅读:
    动态规划算法介绍——概念、意义及应用、例题
    两个大数相减
    删除apache的签名的shell脚本
    C++中智能指针的设计和使用
    eclipse save action不起作用
    [leetcode]51. N-QueensN皇后
    [leetcode]33. Search in Rotated Sorted Array旋转过有序数组里找目标值
    [leetcode]88. Merge Sorted Array归并有序数组
    [leetcode]636. Exclusive Time of Functions函数独占时间
    [leetcode]257. Binary Tree Paths二叉树路径
  • 原文地址:https://www.cnblogs.com/marsblog/p/3291692.html
Copyright © 2011-2022 走看看