使用自定义队列处理器获取WebJob上的Singleton行为

时间:2019-02-05 20:07:25

标签: c# azure-webjobs azure-queues azure-storage-queues azure-webjobs-triggered

我的团队一直在尝试使我们的某些WebJobs custom queue processors具有Singleton行为,但是我们实际上并没有通过[Singleton(Mode = SingletonMode.Listener)]属性或设置QueueProcessorFactoryContext.BatchSize = 1来获得这种行为。 。这导致每晚执行的过程立即一次猛击数据库-其中许多过程超时-变得有些头疼。

这差不多就是我们的CustomQueueProcessorFactory的样子:

    public class CustomQueueProcessorFactory : IQueueProcessorFactory
    {
        public QueueProcessor Create(QueueProcessorFactoryContext context)
        {
            if (context == null)
                throw new ArgumentNullException(nameof(context));

            if (context.Queue.Name == Constants.UploadQueueName 
                || context.Queue.Name == Constants.BuildQueueName)
            {
                context.BatchSize = 1;
            }

            return new QueueProcessor(context);
        }
    }

在配置我们的JobHost时引用了它:

    var config = new JobHostConfiguration();
    config.Queues.QueueProcessorFactory = new CustomQueueProcessorFactory();

我们还使用QueueTrigger设置了一些功能,例如:

    public static async Task ExecuteBackgroundRequest([QueueTrigger(Constants.BackgroundQueueName)] BackgroundRequest background, TextWriter logger)
    {
        await ExecuteRequest(background, logger);
    }

    [Singleton(Mode = SingletonMode.Listener)]
    public static async Task ExecuteUploadRequest([QueueTrigger(Constants.UploadQueueName)] BackgroundRequest background, TextWriter logger)
    {
        await ExecuteRequest(background, logger);
    }

    [Singleton(Mode = SingletonMode.Listener)]
    public static async Task ExecuteBuildRequest([QueueTrigger(Constants.BuildQueueName)] BackgroundRequest background, TextWriter logger)
    {
        await ExecuteRequest(background, logger);
    }

我们使用的软件包Microsoft.Azure.WebJobs(+ .Core.Extensions)v2.0.0和WindowsAzure.Storage v8.0.0已经过时,因此一个潜在的解决方案是一直在探索的是对WebJobs(v3.0.4)最新稳定版本的更新。由于配置已完全重做,并且所有类都已移至新位置,因此打开了一个全新的蠕虫罐。该文档似乎稀疏/分散,因此我还没有确定我可以在每个QueueProcessor基础上自定义属性的位置(或什至),例如将某些队列的BatchSize设置为1,而将其他队列的BatchSize设置为1。

是否存在某些版本的WebJobs,可以在其中使用上述CustomQueueProcessorFactory逻辑来限制BatchSize?或者,Singleton属性实际上将确保一次只有一个后台进程正在访问特定队列?可以在最新版本的WebJobs中配置QueueProcessorFactories吗?

在任何这些问题上的帮助将不胜感激!

2 个答案:

答案 0 :(得分:0)

WebJobs SDK通过其SingletonAttribute简化了常见的分布式锁定方案。您可以简单地将SingletonAttribute应用于作业函数,以确保该函数的所有调用都将被序列化,即使在扩展实例中也是如此。如果您的函数需要访问其他分布式资源或执行不应/不能同时执行的其他操作,这将很有用。

[Singleton]
public static async Task ProcessImage([BlobTrigger("images")] Stream image)
{
     // Process the image
}

在此示例中,在任何给定时间仅将运行ProcessImage函数的单个实例。当通过将新图像添加到图像容器触发该功能时,运行时将首先尝试获取锁(blob租约)。一旦获取,将在函数执行期间保持锁定(并更新Blob租约),以确保没有其他实例将运行。如果在运行此函数时触发了另一个函数实例,它将等待该锁,并定期轮询该锁。

Singleton使用Azure Blob Leases来实现分布式锁定。 SDK负责管理Blob租约,租约续约等所有复杂性。

单锁详细信息也显示在WebJobs仪表板上,包括正在进行的函数执行的当前锁状态,以及函数在获取锁之前等待了多长时间。您可以使用这些详细信息来查看和管理锁争用。

enter image description here

非并发方案将需要使用Singleton。一些触发器通过其配置设置具有对并发管理的内在支持。在这种情况下,使用内置支持可能会更有效。您可以使用这些设置来确保函数在单个实例上运行单例。为确保仅单个函数实例在扩展实例中运行,此外,您可以在函数上应用侦听器级别的Singleton锁(例如[Singleton(Mode = SingletonMode.Listener)])。一些触发器的配置旋钮是:

QueueTrigger -您可以将JobHostConfiguration.Queues.BatchSize设置为1 ServiceBusTrigger -您可以将ServiceBusConfiguration.MessageOptions.MaxConcurrentCalls设置为1 FileTrigger -您可以将FileProcessor.MaxDegreeOfParallelism设置为1 但是,对于固有不支持并发控制的触发器,或者如果您想通过Singleton范围(请参见下文)进行更高级的锁定,Singleton是正确的选择。

我也想说,使用双锁实现如下所示的CustomQueueProcessorFactory:-

 if (_instance == null)
                {
                    lock (SyncObject)
                    {
                        if (_instance == null)
                        {
                            _instance = new CustomQueueProcessor();
                        }
                    }
                }
                return _instance;

答案 1 :(得分:0)

因此,如果我理解正确,即使在横向扩展之后,您仍希望多个功能按顺序处理其触发消息。我说的对吗?

我认为您可以使用带有scoping参数的SingletonAttribute来强制执行此操作:

public static async Task ExecuteBackgroundRequest([QueueTrigger(Constants.BackgroundQueueName)] BackgroundRequest background, TextWriter logger)
{
    await ExecuteRequest(background, logger);
}

[Singleton("DatabaseSync", SingletonScope.Host)]
public static async Task ExecuteUploadRequest([QueueTrigger(Constants.UploadQueueName)] BackgroundRequest background, TextWriter logger)
{
    await ExecuteRequest(background, logger);
}

[Singleton("DatabaseSync", SingletonScope.Host)]
public static async Task ExecuteBuildRequest(
    [QueueTrigger(Constants.BuildQueueName)] BackgroundRequest background, TextWriter logger)
{
    await ExecuteRequest(background, logger);
}