我有一个使用koa的节点应用程序。它从外部应用程序接收特定资源上的webhook。
为说明起见,假设网络挂钩通过POST请求向我发送了以下类型的对象:
{
'resource_id':'<SomeID>',
'resource_origin':'<SomeResourceOrigin>',
'value' : '<SomeValue>'
}
我想顺序执行来自同一来源的所有资源,以避免与执行相关的资源不同步。
我当时正在考虑使用数据库作为锁,并使用cron对相同来源的每个资源依次执行我的进程。
但是我不确定这是最有效的方法。
所以我的问题在这里:
您是否知道一些方法/程序包/服务,允许我使用可以为每个起源实现的全局队列,以确保来自同一起源的资源将被同步执行,而无需按顺序处理所有Webhooks?如果不使用数据库,那就更好了。
答案 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()
标志也确保它一次只处理一个。
请注意,这是一个非常简单的解决方案。一旦它正常工作,就会提出两个可能的改进。
对队列使用比简单数组更有效的方法。 const queue = []
...
function handlePostObject (postObject) {
queue.push(postObject)
handleManyWebooks (queue)
}
不太快。
为每个单独的来源创建一个单独的队列对象,并使用其自己的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)
。我也用它来初始化数据库中所有已知来源的应用程序。