C# 从持久功能向服务总线发送扩展消息

C# 从持久功能向服务总线发送扩展消息,c#,azure,orchestration,azure-durable-functions,C#,Azure,Orchestration,Azure Durable Functions,我有一个场景,其中一个活动函数检索了一组记录,这些记录可以在1000到100万之间的任何位置,并存储在一个对象中。然后,下一个活动函数将使用该对象向服务总线并行发送消息 目前,我在这个对象上使用for循环将对象中的每个记录发送到服务总线。请告诉我是否有更好的替代模式,将对象或内容(无论存储在何处)清空以发送到服务总线,并且功能自动扩展,而不将处理限制为for循环 使用了来自某个函数的for循环,该函数编排调用对象中记录的活动函数 我们已经研究了活动函数的伸缩性,对于一组18000条记录,它已伸

我有一个场景,其中一个活动函数检索了一组记录,这些记录可以在1000到100万之间的任何位置,并存储在一个对象中。然后,下一个活动函数将使用该对象向服务总线并行发送消息

目前,我在这个对象上使用for循环将对象中的每个记录发送到服务总线。请告诉我是否有更好的替代模式,将对象或内容(无论存储在何处)清空以发送到服务总线,并且功能自动扩展,而不将处理限制为for循环

  • 使用了来自某个函数的for循环,该函数编排调用对象中记录的活动函数
  • 我们已经研究了活动函数的伸缩性,对于一组18000条记录,它已伸缩到15个实例,并在4分钟内处理了整个集合
  • 当前该功能正在使用消费计划。已选中以查看只有此功能应用程序正在使用该计划且其未共享
  • 邮件发送到的主题有另一个服务正在侦听,以读取邮件
  • 默认情况下,编排和活动功能的实例计数都可用

for(int i=0;i我建议您使用批处理发送消息

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;
}
公共静态列表ChunkBy(此IEnumerable源代码,Func度量,long maxChunkSize)
{
返回源
.合计(
刚出现的
{
总和=0升,
当前=(列表)null,
结果=新列表()
},
(agg,项目)=>
{
var值=度量(项目);
if(agg.Current==null | | agg.Sum+value>maxChunkSize)
{
var current=新列表{item};
累计结果添加(当前);
返回新的{Sum=value,Current=Current,agg.Result};
}
累计当前添加(项目);
返回新的{Sum=agg.Sum+value,agg.Current,agg.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;
public async Task SendBigBatchAsync(IEnumerable messages,Func getSize)
{
var chunks=messages.ChunkBy(getSize,MaxServiceBusMessage);
foreach(var chunk in chunks)
{
var brokeredMessages=chunk.Select(m=>newbrokeredmessages(m));
等待client.SendBatchAsync(brokeredMessages);
}
}
private const long MaxServiceBusMessage=256000;
私有只读队列客户端;
如何确定每条消息的大小?如何实现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);
    }
}
公共异步任务SendBigBatchAsync(IEnumerable消息)
{
var brokeredMessages=messages.Select(m=>newbrokeredmessages(m));
var chunks=brokeredMessages.ChunkBy(bm=>bm.Size,MaxServiceBusMessage);
foreach(var chunk in chunks)
{
等待client.SendBatchAsync(块);
}
}

我想考虑的最后一种可能性是允许自己违反批处理的最大大小,然后处理异常,重试发送操作并根据失败的消息的实际测量大小调整将来的计算。在尝试发送批处理之后,该大小是已知的,即使操作失败,也可以使用此通知。反倾销

// 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));
            }

        }
    }
}
//发送方可跨请求重用
公共类批处理发送器
{
专用只读队列客户端;
专用长批量SizeLimit=262000;
private long headerSizeEstimate=54;//从尽可能最小的头开始
公共BatchSender(QueueClient QueueClient)
{
this.queueClient=queueClient;
}
公共异步任务SendBigBatchAsync(IEnumerable消息)
{
var数据包=(来自消息中的m)
设bm=newbrokeredmessage(m)
选择new{Source=m,Brokered=bm,BodySize=bm.Size});
var chunks=packets.ChunkBy(p=>this.headerSizeEstimate+p.Brokered.Size,this.batchSizeLimit);
foreach(var chunk in chunks)
{
尝试
{
等待这个.queueClient.SendBatchAsync(chunk.Select(p=>p.Brokered));
}
捕获(MessageSizeExceedeException)
{
var maxHeader=packets.Max(p=>p.Brokered.Size-p.BodySize);
如果(maxHeader>this.headerSizeEstimate)
{
//如果失败消息的标题较大,请记住此标题大小
//如max所观察到的,并在未来的计算中使用
this.headerSizeEstimate=maxHeader;
}
其他的
{
//将最大批量减少到当前值的95%
this.batchSizeLimit=(长)(this.batchSizeLimit*.95);
}
//重新发送失败的区块
等待这个.SendBigBatchAsync(packets.Select(p=>p.Source));
}
}
}
}

您可以进一步使用此博客。希望能有所帮助。

感谢您的详细回复。我们将尝试同样的方法并进行检查。
// 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));
            }

        }
    }
}