顺序执行节点应用程序中收到的Webhook

时间:2019-06-30 08:29:43

标签: node.js multithreading design-patterns producer-consumer task-queue

我有一个使用koa的节点应用程序。它从外部应用程序接收特定资源上的webhook。

为说明起见,假设网络挂钩通过POST请求向我发送了以下类型的对象:

{
  'resource_id':'<SomeID>',
  'resource_origin':'<SomeResourceOrigin>',
  'value' : '<SomeValue>'
}

我想顺序执行来自同一来源的所有资源,以避免与执行相关的资源不同步。

我当时正在考虑使用数据库作为锁,并使用cron对相同来源的每个资源依次执行我的进程。

但是我不确定这是最有效的方法。

所以我的问题在这里:

您是否知道一些方法/程序包/服务,允许我使用可以为每个起源实现的全局队列,以确保来自同一起源的资源将被同步执行,而无需按顺序处理所有Webhooks?如果不使用数据库,那就更好了。

2 个答案:

答案 0 :(得分:1)

如果我是你,我将从序列化所有Webhooks的处理开始。换句话说,无论您来自何处,我都建议您一次处理它们。在您的nodejs应用程序中使用一个简单的队列。

(一旦您确信自己可以正常工作,就可以根据来源对它们进行序列化了。)

首先,构造函数(将其称为IJavaScriptExecutor js = (IJavaScriptExecutor)driver; js.ExecuteScript("return.document.getElementsByTagName('ins')[0].remove();"); driver.FindElement(By.XPath("//input[@id='order_terms']")).Click(); )以将传入的Webhooks作为Promise或异步函数进行处理。然后,您可以使用具有此大纲的代码来调用它们。

handleOneWebhook()

您传递给let busy= false async function handleManyWebhooks (queue) { if (busy) return busy = true while (queue.length > 0) { const item = queue.shift() await handleOneWebhook (item) } busy = false } 的{​​{1}}是一个简单的数组,其中每个元素都是POST请求中的对象。您将其用作队列:queue每个对象将其放入队列,handleManyWebhooks将其删除。

然后,每当您收到一个Webhook POST对象时,便使用带有此轮廓的代码。

push()

即使您为每个传入的对象调用一次handleManyWebhooks,shift()标志也确保它一次只处理一个。

请注意,这是一个非常简单的解决方案。一旦它正常工作,就会提出两个可能的改进。

  1. 对队列使用比简单数组更有效的方法。 const queue = [] ... function handlePostObject (postObject) { queue.push(postObject) handleManyWebooks (queue) } 不太快。

  2. 为每个单独的来源创建一个单独的队列对象,并使用其自己的busy标志。然后,您将能够并行化处理来自不同来源的Webhooks,同时仍可以序列化来自每个来源的Webhooks流。

答案 1 :(得分:0)

我决定使用的解决方案

帖子讨论的简短摘要

正如 Ivan Rubinson 那样,我知道我的问题只是一个producer-consumer problem

所以我最终选择使用RabbitMQ,因为我要处理大量的Webhook。对于需要处理少量请求且不想使用外部工具的人们,O. Jones answer是解决问题的真正好方法。

解决方案设计

我最终安装并配置了RabbitMQ服务器,然后为Web钩的每个来源创建了一个队列。

制作人

在生产者端,当我接收到Web挂接数据时,我向与Web挂接源相对应的队列发送一条消息,其中包含序列化信息,该序列化信息实际上需要处理数据库中该行的ID以使消息成为尽可能轻。

消费者

在使用者方面,我为每个原始队列创建一个使用者函数,并将获取策略设置为一个,以在每个队列中逐一处理消息,最后,我将通道策略设置为在发送下一条消息之前等待确认消息。希望此配置使用者逐个消息地进行消息并解决最初的问题。

实施

制作人

   async function create(){
        await   amqp.connect(RBMQ_CONNECTION_STRING).then(async (conn)=>{
            await conn.createChannel().then(async (ch)=>{
                global.channel_publisher=ch;
            });
        });
    }

    async function sendtask(queue,task){
        if(!global.channel_publisher){
            await create();
        }
        global.channel_publisher.assertQueue(queue).then((ok)=>{
            global.channel_publisher.sendToQueue(queue, Buffer.from(task));
        });
    }

我在收到网钩的地方使用了sendtask(queue,task)功能

消费者

   async function create(){
      await amqp.connect(RBMQ_CONNECTION_STRING).then(async (conn)=>{
         await conn.createChannel().then(async (ch)=>{
            ch.prefetch(1);
            global.channel_consumer=ch;
          });
       });
    }

   async function consumeTask(queue){
       if(!global.channel_consumer){
           await create();
       }

       global.channel_consumer.assertQueue(queue).then((ok)=>{
          global.channel_consumer.consume(queue,(message)=>{
               const args=message.content.toString().split(';');

                    await processWebhooks(args);
                    global.channel_consumer.ack(message);
           });
       });
   }

当我不得不处理新的Web钩子时,我使用consumeTask(queue)。我也用它来初始化数据库中所有已知来源的应用程序。