Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/node.js/41.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Node.js 在上一个作业完成之前,不要处理下一个作业(Redis?)_Node.js_Typescript_Redis - Fatal编程技术网

Node.js 在上一个作业完成之前,不要处理下一个作业(Redis?)

Node.js 在上一个作业完成之前,不要处理下一个作业(Redis?),node.js,typescript,redis,Node.js,Typescript,Redis,基本上,每个客户机(具有与其关联的clientId)都可以推送消息,重要的是,在第一条消息完成处理之前,不会处理来自同一客户机的第二条消息(即使客户端可以连续发送多条消息,并且它们是有序的,并且发送消息的多个客户端在理想情况下不应该相互干扰)。而且,重要的是,一个作业不应该被处理两次 我认为使用Redis可能会解决这个问题,我开始使用bull库进行一些快速原型设计,但我显然做得不好,我希望有人知道如何进行 这就是我迄今为止所尝试的: 使用clientId作为作业名称,为一个进程创建作业并将其添加

基本上,每个客户机(具有与其关联的
clientId
)都可以推送消息,重要的是,在第一条消息完成处理之前,不会处理来自同一客户机的第二条消息(即使客户端可以连续发送多条消息,并且它们是有序的,并且发送消息的多个客户端在理想情况下不应该相互干扰)。而且,重要的是,一个作业不应该被处理两次

我认为使用Redis可能会解决这个问题,我开始使用bull库进行一些快速原型设计,但我显然做得不好,我希望有人知道如何进行

这就是我迄今为止所尝试的:
  • 使用
    clientId
    作为作业名称,为一个进程创建作业并将其添加到相同的队列名称中
  • 在两个单独的进程上等待大量随机时间时消耗作业
  • 我尝试添加由我正在使用的库(
    bull
    )提供的默认锁定,但它锁定在jobId上,该ID对于每个作业都是唯一的,而不是在clientId上
  • 我希望发生的事情:
    • 在前一个消费者完成处理之前,其中一个消费者无法从同一
      clientId
      接受作业
    • 但是,他们应该能够并行地(异步地)从不同的
      clientId
      s获取项目。(我还没有做到这一点,我现在只处理一个
      clientId
    我得到的是:
    • 两个消费者都从队列中消费尽可能多的项目,而无需等待前一个项目完成
      clientId
    Redis是这项工作的合适工具吗? 示例代码 这是使用3个不同的进程运行的,您可能会这样称呼它们(尽管我使用不同的选项卡来更清楚地查看正在发生的事情)

    npx ts node consume.ts&
    npx ts node consume.ts&
    npx ts node create.ts&
    
    我不熟悉node.js。但对于Redis,我会试试这个

    假设你有客户端1,客户端2,它们都是事件的发布者。 你有三台机器,消费者1,消费者2,消费者3

  • 在redis中建立任务列表,例如作业列表
  • 客户端将(LPUSH)作业以特定形式放入此作业列表中,如“客户端1:[jobcontent]”、“客户端2:[jobcontent]”
  • 每个消费者都会将作业分块取出(Redis的RPOP命令)并进行处理。 例如,消费者_1取出一个作业,内容是CLIENT_1:[jobcontent]。它解析内容并识别它来自CLIENT_1。然后它想检查其他消费者是否已经在处理CLIENT_1,如果没有,它将锁定密钥以指示它正在处理CLIENT_1
  • 它接着使用Redis SETNX命令(如果密钥不存在则设置)设置一个“CLIENT_1_PROCESSING”密钥,内容为“consumer_1”,并设置一个适当的超时。例如,任务通常需要一分钟才能完成,您将密钥超时设置为五分钟,以防consumer_1崩溃并无限期地保持锁

    如果SETNX返回0,则意味着它无法获取客户机_1的锁(有人已经在处理客户机_1的作业)。然后它使用Redis LPUSH命令将作业(值为“客户机_1:[jobcontent]”)返回到作业_列表的左侧。然后它可能会等待一段时间(睡眠几秒钟),并从列表右侧RPOP另一个任务。如果这次SETNX返回1,则consumer_1获取锁。它继续处理作业,完成后,删除“CLIENT_1_PROCESSING”的密钥,释放锁。然后继续RPOP另一个作业,依此类推

    需要考虑的一些事项:

  • 工作列表不公平,例如,较早的工作可能稍后处理
  • 锁定部分有点初级,但已经足够了
  • ----------更新--------------

    我想出了另一种方法来让任务井然有序

    为每个客户机(制作人)建立一个列表。像“客户机列表”一样,将作业推到列表的左侧。 将所有客户机名称保存在“客户机名称”列表中,其中包含值“客户机名称1”、“客户机名称2”等

    对于每个消费者(处理器),迭代“客户机名称列表”,例如,消费者1获得一个“客户机1”,检查客户机1的密钥是否已锁定(有人已经在处理客户机1的任务),如果未锁定,右键从客户机1列表中弹出一个值(作业)并锁定客户机1。如果客户机1已锁定,(可能会休眠一秒钟)然后迭代到下一个客户端,例如“client_2”,并检查密钥等等


    这样,每个客户端(任务生产者)的任务按其输入顺序进行处理。

    如果没有人有更好的主意:如果您有n个消费者,您需要编写1个调度程序来侦听队列,将cleintId散列为n个不同的名称,推送命名的作业,每个消费者都会在作业上侦听其名称……谢谢,尽管我必须说这比我想象的要复杂然而,问题是,我理解你的描述的方式,似乎如果锁已经获得,你将作业放在左侧,因此客户端1作业将按顺序处理,这是我的要求中的一个重要部分。我没有理解吗?也许我没有理解,但每个消费者都需要迭代hr通过此解决方案中的所有
    clientId
    s,因为如果您从正在迭代的列表中删除
    clientId
    ,则如果另一个进程正在删除
    client\u names\u list
    列表,则访问消息时可能会出现问题。对吗?是的,每个消费者都需要迭代所有clientId。它从获取clientIdde>客户端名称列表
    客户端名称列表
    只保留客户端的所有ID,为什么要删除它?如果要删除客户端,首先需要检查此客户端的任务列表是否为空(所有任务都已完成)
    // ./setup.ts
    import Queue from 'bull';
    import * as uuid from 'uuid';
    
    // Check that when a message is taken from a place, no other message is taken
    
    // TO do that test, have two processes that process messages and one that sets messages, and make the job take a long time
    
    // queue for each room https://stackoverflow.com/questions/54178462/how-does-redis-pubsub-subscribe-mechanism-works/54243792#54243792
    // https://groups.google.com/forum/#!topic/redis-db/R09u__3Jzfk
    
    // Make a job not be called stalled, waiting enough time https://github.com/OptimalBits/bull/issues/210#issuecomment-190818353
    
    export async function sleep(ms: number): Promise<void> {
      return new Promise((resolve) => {
        setTimeout(resolve, ms);
      });
    }
    export interface JobData {
      id: string;
      v: number;
    }
    export const queue = new Queue<JobData>('messages', 'redis://127.0.0.1:6379');
    
    queue.on('error', (err) => {
      console.error('Uncaught error on queue.', err);
      process.exit(1);
    });
    
    export function clientId(): string {
      return uuid.v4();
    }
    
    export function randomWait(minms: number, maxms: number): Promise<void> {
      const ms = Math.random() * (maxms - minms) + minms;
      return sleep(ms);
    }
    
    // Make a job not be called stalled, waiting enough time https://github.com/OptimalBits/bull/issues/210#issuecomment-190818353
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    //@ts-ignore
    queue.LOCK_RENEW_TIME = 5 * 60 * 1000;
    
    
    // ./create.ts
    import { queue, randomWait } from './setup';
    
    const MIN_WAIT = 300;
    const MAX_WAIT = 1500;
    async function createJobs(n = 10): Promise<void> {
      await randomWait(MIN_WAIT, MAX_WAIT);
      // always same Id
      const clientId = Math.random() > 1 ? 'zero' : 'one';
      for (let index = 0; index < n; index++) {
        await randomWait(MIN_WAIT, MAX_WAIT);
        const job = { id: clientId, v: index };
        await queue.add(clientId, job).catch(console.error);
        console.log('Added job', job);
      }
    }
    
    export async function create(nIds = 10, nItems = 10): Promise<void> {
      const jobs = [];
      await randomWait(MIN_WAIT, MAX_WAIT);
      for (let index = 0; index < nIds; index++) {
        await randomWait(MIN_WAIT, MAX_WAIT);
        jobs.push(createJobs(nItems));
        await randomWait(MIN_WAIT, MAX_WAIT);
      }
      await randomWait(MIN_WAIT, MAX_WAIT);
      await Promise.all(jobs)
      process.exit();
    }
    
    (function mainCreate(): void {
      create().catch((err) => {
        console.error(err);
        process.exit(1);
      });
    })();
    
    
    // ./consume.ts
    import { queue, randomWait, clientId } from './setup';
    
    function startProcessor(minWait = 5000, maxWait = 10000): void {
      queue
        .process('*', 100, async (job) => {
          console.log('LOCKING: ', job.lockKey());
          await job.takeLock();
          const name = job.name;
          const processingId = clientId().split('-', 1)[0];
          try {
            console.log('START: ', processingId, '\tjobName:', name);
            await randomWait(minWait, maxWait);
            const data = job.data;
            console.log('PROCESSING: ', processingId, '\tjobName:', name, '\tdata:', data);
            await randomWait(minWait, maxWait);
            console.log('PROCESSED: ', processingId, '\tjobName:', name, '\tdata:', data);
            await randomWait(minWait, maxWait);
            console.log('FINISHED: ', processingId, '\tjobName:', name, '\tdata:', data);
          } catch (err) {
            console.error(err);
          } finally {
            await job.releaseLock();
          }
        })
        .catch(console.error); // Catches initialization
    }
    
    startProcessor();