zoukankan      html  css  js  c++  java
  • 并发和原子操作不可兼得

    并发和原子操作不可兼得

    在上一篇文章中,我主要向大家介绍了利用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模拟投票场景,代码如下:

     View Code

    static void Main(string[] args)
    {

    RedisClient[] redisCli = new RedisClient[8]{
    new RedisClient("192.168.101.165", 6379),
    new RedisClient("192.168.101.165", 6379),
    new RedisClient("192.168.101.165", 6379),
    new RedisClient("192.168.101.165", 6379),
    new RedisClient("192.168.101.165", 6379),
    new RedisClient("192.168.101.165", 6379),
    new RedisClient("192.168.101.165", 6379),
    new RedisClient("192.168.101.165", 6379)
    };
    ThreadPool.QueueUserWorkItem(o =>
    {
    testTicketCount(redisCli[0], "A");
    });
    ThreadPool.QueueUserWorkItem(o =>
    {
    testTicketCount(redisCli[1], "A");
    });
    ThreadPool.QueueUserWorkItem(o =>
    {
    testTicketCount(redisCli[2], "B");
    });
    ThreadPool.QueueUserWorkItem(o =>
    {
    testTicketCount(redisCli[3], "B");
    });
    ThreadPool.QueueUserWorkItem(o =>
    {
    testTicketCount(redisCli[4], "C");
    });
    ThreadPool.QueueUserWorkItem(o =>
    {
    testTicketCount(redisCli[5], "C");
    });
    ThreadPool.QueueUserWorkItem(o =>
    {
    testTicketCount(redisCli[6], "D");
    });
    ThreadPool.QueueUserWorkItem(o =>
    {
    testTicketCount(redisCli[7], "D");
    });

    Console.Read();
    }
    /// <summary>
    /// 对某个选项进行投票,投票数加1
    /// </summary>
    /// <param name="rediscli"></param>
    /// <param name="field"></param>
    internal static void testTicketCount(IRedisClient rediscli, string field)
    {
    int k = 0;
    for (int i = 0; i <= 99; i++)
    {

    k = int.Parse(rediscli.GetValueFromHash("TicketCount", field))+1;
    rediscli.SetEntryInHash("TicketCount", field, k.ToString());
    }
    }

    我们设想的结果是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/

  • 相关阅读:
    《Troubleshooting Windows 7 Inside Out》文摘-1
    快与慢、空和满
    学习心得-4
    word::替换::突出显示
    word
    system.run
    kafka server.properties
    zookeeper.conf
    elasticsearch
    filebeat.yml
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3292127.html
Copyright © 2011-2022 走看看