Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/cmake/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Azure 水平扩展写入时如何避免并发问题?_Azure_Scalability_Sharding_Microservices_Horizontal Scaling - Fatal编程技术网

Azure 水平扩展写入时如何避免并发问题?

Azure 水平扩展写入时如何避免并发问题?,azure,scalability,sharding,microservices,horizontal-scaling,Azure,Scalability,Sharding,Microservices,Horizontal Scaling,假设有一个辅助服务从队列接收消息,从文档数据库读取具有指定Id的产品,根据消息应用一些操作逻辑,最后将更新后的产品写回数据库(a) 当处理不同的产品时,这项工作可以安全地并行进行,因此我们可以水平缩放(b)。但是,如果多个服务实例在同一个产品上工作,我们可能会遇到并发问题,或者数据库出现并发异常,在这种情况下,我们应该应用一些重试逻辑(重试可能再次失败等等) 问题:我们如何避免这种情况?是否有一种方法可以确保两个实例不在同一个产品上工作 示例/用例:一家在线商店在productA、produc

假设有一个辅助服务从队列接收消息,从文档数据库读取具有指定Id的产品,根据消息应用一些操作逻辑,最后将更新后的产品写回数据库(a)

当处理不同的产品时,这项工作可以安全地并行进行,因此我们可以水平缩放(b)。但是,如果多个服务实例在同一个产品上工作,我们可能会遇到并发问题,或者数据库出现并发异常,在这种情况下,我们应该应用一些重试逻辑(重试可能再次失败等等)

问题:我们如何避免这种情况?是否有一种方法可以确保两个实例不在同一个产品上工作

示例/用例:一家在线商店在productA、productB和productC上大甩卖,一个小时内就结束了,数百名顾客正在购买。对于每次购买,都会有一条消息排队(productId、numberOfItems、price)目标:我们如何运行工作服务的三个实例,并确保productA的所有消息都以instanceA、productB到instanceB和productC到instanceC结尾(不会导致并发问题)

注意事项:我的服务是用C#编写的,作为工作角色托管在Azure上,我使用Azure队列进行消息传递,我正在考虑使用Mongo进行存储。此外,实体ID是
GUID

更多的是关于技术/设计,所以如果你使用不同的工具来解决问题,我仍然感兴趣。

1)我能想到的每一个高规模数据解决方案都内置了一些东西来精确地处理这种冲突。详细信息将取决于您对数据存储的最终选择。在传统关系数据库的情况下,这是在没有任何附加工作的情况下实现的。有关适当的详细信息,请参阅所选技术的文档

2) 了解您的数据模型和使用模式。适当地设计数据存储。不要为你没有的规模而设计。针对最常见的使用模式进行优化


3) 挑战你的假设。实际上,您是否必须频繁地从多个角色中变异同一实体?有时答案是肯定的,但通常你可以简单地创建一个新的实体来反映更新。也就是说,采用日志记录/方法,而不是单一实体方法。最终,单个实体上的大量更新将永远无法扩展。

对于这种情况,我使用blob租约。基本上,我使用某个已知存储帐户中的实体ID创建了一个blob。当worker 1拾取实体时,它尝试获取blob上的租约(并创建blob本身,如果它不存在)。如果两者都成功,那么我允许对消息进行处理。之后一定要解除租约。 如果未成功,则将消息转储回队列

我遵循史蒂夫·马克思(Steve Marx)在这里最初描述的方法,尽管经过调整以使用新的存储库

评论后编辑: 如果您有一个潜在的高比率的消息都与同一个实体交谈(正如您的推荐所暗示的),我会在某处重新设计您的方法。。实体结构或消息传递结构


例如:考虑CQRS设计模式,并独立于每个消息的处理存储变化。因此,产品实体现在是各种工作人员对实体所做的所有更改的集合,按顺序重新应用并重新水化为单个对象

任何试图在同一集合(如订单)中的不同项目上分配负载的解决方案都注定会失败。原因是,如果您的交易率很高,您必须开始执行以下操作之一:

  • 让节点彼此对话(
    嘿,伙计们,有人在用这个吗?
  • 将ID生成划分为段(节点a创建ID 1-1000,节点B 1001-1999)等,然后让它们处理自己的段
  • 将集合动态划分为段(并让每个节点处理一个段)
  • 那么这些方法有什么问题

    第一种方法是简单地在数据库中复制事务。除非您可以花费大量时间优化策略,否则最好依赖事务

    后两个选项将降低性能,因为您必须根据ID动态路由消息,并且在运行时更改策略以包括新插入的消息。它最终将失败

    解决 这里有两种解决方案,您也可以结合使用

    自动重试 相反,您有一个从消息队列读取的入口点

    里面有这样的东西:

    while (true)
    {
        var message = queue.Read();
        Process(message);
    }
    
    要获得非常简单的容错,您可以做的是在失败时重试:

    while (true)
    {
        for (i = 0; i < 3; i++)
        {
           try
           {
                var message = queue.Read();
                Process(message);
                break; //exit for loop
           }
           catch (Exception ex)
           {
               //log
               //no throw = for loop runs the next attempt
           }
        }
    }
    
    while(true)
    {
    对于(i=0;i<3;i++)
    {
    尝试
    {
    var message=queue.Read();
    过程(消息);
    break;//退出循环
    }
    捕获(例外情况除外)
    {
    //日志
    //no throw=for循环运行下一次尝试
    }
    }
    }
    
    当然,您可以捕获数据库异常(或者更确切地说是事务失败)来重播这些消息

    微服务 我知道,微服务是一个流行词。但在这种情况下,它是一个很好的解决方案。与其用一个单一的核心来处理所有消息,不如将应用程序分成更小的部分。或者在您的情况下,只需停用对某些类型消息的处理

    如果有五个节点运行应用程序,则可以确保节点A接收与订单相关的消息,节点B接收与装运等相关的消息

    通过这样做,您仍然可以水平扩展您的应用程序,您不会遇到冲突,并且只需要很少的努力(
    // Queue[X] is the queue for product X
    // QueueMain is the main queue 
    DoWork(ProductType X)
    {
      if (Queue[X].empty())
      {
        product = QueueMain().pop()
        if (product.type != X)
        {
          Queue[product.type].push(product) 
          return;
        }
      }else
      {
         product = Queue[X].pop()
      }
    
      //process product...
    }