从持久功能按比例发送消息到服务总线

时间:2019-06-17 17:39:56

标签: c# azure orchestration azure-durable-functions

我有一个场景,其中一个活动功能检索了一组记录,该记录的范围可以从1000到100万,并存储在一个对象中。然后,下一个活动功能将使用该对象将消息并行发送到服务总线。

当前,我在此对象上使用for循环将对象中的每个记录发送到服务总线。请让我知道是否有更好的替代模式,其中对象或内容(无论存储在哪里)都将被清空以发送到服务总线,并且该功能会自动扩展,而不会将处理限于for循环。

  • 已使用来自协调功能的函数的for循环来调用对象中记录的活动功能。
  • 查看了活动功能的扩展,并针对一组18000条记录,它扩展了多达15个实例,并在4分钟内处理了整个集合。
  • 该功能当前正在使用消费计划。检查是否只有该功能应用正在使用此消费计划,并且未共享该消费计划。
  • 消息发送到的主题有另一个服务正在侦听,以读取消息。
  • 编排和活动功能的实例计数默认情况下为可用。
    for(int i=0;i<number_messages;i++)
    {
       taskList[i] = 
    context.CallActivityAsync<string>("Sendtoservicebus", 
       (messages[i],runId,CorrelationId,Code));
     }

    try
     {
      await Task.WhenAll(taskList);
     }
    catch (AggregateException ae)
     {
      ae.Flatten();
     }

应该通过适当扩展活动功能,将消息快速发送到服务总线。

1 个答案:

答案 0 :(得分:1)

我建议您使用批处理发送消息。

Azure Service Bus客户端支持批量发送消息(QueueClient和TopicClient的SendBatch和SendBatchAsync方法)。但是,单个批次的大小必须保持在256k字节以下,否则整个批次将被拒绝。

我们将以一个简单的用例开始:我们知道每条消息的大小。它由假设的Func getSize函数定义。这是一个有用的扩展方法,它将根据度量函数和最大块大小来拆分任意集合:

public static List<List<T>> ChunkBy<T>(this IEnumerable<T> source, Func<T, long> metric, long maxChunkSize)
{
    return source
        .Aggregate(
            new
            {
                Sum = 0L,
                Current = (List<T>)null,
                Result = new List<List<T>>()
            },
            (agg, item) =>
            {
                var value = metric(item);
                if (agg.Current == null || agg.Sum + value > maxChunkSize)
                {
                    var current = new List<T> { item };
                    agg.Result.Add(current);
                    return new { Sum = value, Current = current, agg.Result };
                }

                agg.Current.Add(item);
                return new { Sum = agg.Sum + value, agg.Current, agg.Result };
            })
        .Result;
}

现在,SendBigBatchAsync的实现很简单:

public async Task SendBigBatchAsync(IEnumerable<T> messages, Func<T, long> getSize)
{
    var chunks = messages.ChunkBy(getSize, MaxServiceBusMessage);
    foreach (var chunk in chunks)
    {
        var brokeredMessages = chunk.Select(m => new BrokeredMessage(m));
        await client.SendBatchAsync(brokeredMessages);
    }
}

private const long MaxServiceBusMessage = 256000;
private readonly QueueClient client;

我们如何确定每封邮件的大小?我们如何实现getSize函数?

BrokeredMessage类公开Size属性,因此可能很想通过以下方式重写我们的方法:

public async Task SendBigBatchAsync<T>(IEnumerable<T> messages)
{
    var brokeredMessages = messages.Select(m => new BrokeredMessage(m));
    var chunks = brokeredMessages.ChunkBy(bm => bm.Size, MaxServiceBusMessage);
    foreach (var chunk in chunks)
    {
        await client.SendBatchAsync(chunk);
    }
}

我要考虑的最后一种可能性是实际上使自己违反了批处理的最大大小,但随后处理了异常,重试发送操作,并根据失败消息的实际测量大小调整了以后的计算。尝试发送SendBatch后知道大小,即使操作失败,也可以使用此信息。

// Sender is reused across requests
public class BatchSender
{
    private readonly QueueClient queueClient;
    private long batchSizeLimit = 262000;
    private long headerSizeEstimate = 54; // start with the smallest header possible

    public BatchSender(QueueClient queueClient)
    {
        this.queueClient = queueClient;
    }

    public async Task SendBigBatchAsync<T>(IEnumerable<T> messages)
    {
        var packets = (from m in messages
                     let bm = new BrokeredMessage(m)
                     select new { Source = m, Brokered = bm, BodySize = bm.Size }).ToList();
        var chunks = packets.ChunkBy(p => this.headerSizeEstimate + p.Brokered.Size, this.batchSizeLimit);
        foreach (var chunk in chunks)
        {
            try
            {
                await this.queueClient.SendBatchAsync(chunk.Select(p => p.Brokered));
            }
            catch (MessageSizeExceededException)
            {
                var maxHeader = packets.Max(p => p.Brokered.Size - p.BodySize);
                if (maxHeader > this.headerSizeEstimate)
                {
                    // If failed messages had bigger headers, remember this header size 
                    // as max observed and use it in future calculations
                    this.headerSizeEstimate = maxHeader;
                }
                else
                {
                    // Reduce max batch size to 95% of current value
                    this.batchSizeLimit = (long)(this.batchSizeLimit * .95);
                }

                // Re-send the failed chunk
                await this.SendBigBatchAsync(packets.Select(p => p.Source));
            }

        }
    }
}

您可以将此博客用作其他reference。希望对您有所帮助。