• kafka-node的学习使用


    kafka-node地址:

    下面代码只是消费信息的

    const kafka = require("kafka-node");
    const Client = kafka.KafkaClient;
    const Offset = kafka.Offset;
    const Consumer = kafka.Consumer;
    
    function toKafka() {
      const client = new Client({ kafkaHost: "192.168.100.129:9092" });
      const offset = new Offset(client);
      console.log("连接kafka中");
    
      const topics = [
        {
          topic: "test",
          partition: 0,
          offset: 0,
        },
      ];
    
      const options = {
        //  自动提交配置   (false 不会提交偏移量,每次都从头开始读取)
        autoCommit: true,
        autoCommitIntervalMs: 5000,
        //  如果设置为true,则consumer将从有效负载中的给定偏移量中获取消息
        fromOffset: false,
      };
    
      const consumer = new Consumer(client, topics, options);
    
      consumer.on("message", function(message) {
        console.log(message);
      });
    
      consumer.on("error", function(message) {
        console.log(message);
        console.log("kafka错误");
      });
      //addTopics(topics, cb, fromOffset) fromOffset:true, 从指定的offset中获取消息,否则,将从主题最后提交的offset中获取消息。
      // consumer.addTopics(
      //   [{ topic: "", offset: 1 }],
      //   function(err, data) {
      //     console.log("根据指定的偏移量获取值" + data);
      //   },
      //   true,
      // );
    
      consumer.on("offsetOutOfRange", function(topic) {
        topic.maxNum = 2;
        offset.fetch([topic], function(err, offsets) {
          const min = Math.min.apply(null, offsets[topic.topic][topic.partition]);
          consumer.setOffset(topic.topic, topic.partition, min);
        });
      });
      console.log("连接kafka后");
    }
    
    module.exports = toKafka;
    

    估计后面再真正生产中可能会出现别的问题,后续在接着记录

      最近几天出现了重复消费信息和消息丢失的情况。主要原因是因为node.js是异步的,而且出现错误就导致项目停止,这样被消费的消息就没办法提交。还有就是会出现重复的offset信息。本身对kafka就不是很懂,kafka-node的文档说明也比较少,实在是想不到用什么好方法来解决这个问题,就是用了笨方法来解决了。下面是主要的代码实现

    //kafka 关键代码
    
    //定义上次消费的偏移量
    let parkTopicsNum = parkTopicNum;
    let userTopicsNum = userTopicNum;
    const options = {
        // Auto commit config
        //  自动提交配置   (false 不会提交偏移量,每次都从头开始读取)
        autoCommit: false,
        autoCommitMsgCount: 100,
        autoCommitIntervalMs: 1000,
        // Fetch message config
        fetchMaxWaitMs: 100,
        fetchMinBytes: 1,
        fetchMaxBytes: 1024 * 10,
        //  如果设置为true,则consumer将从有效负载中的给定偏移量中获取消息
        fromOffset: true,
        fromBeginning: false,
      };
    
      //手动设置从哪个偏移量开始拉取消息
      const consumer = new Consumer(client, topics, options);
      consumer.addTopics(
        [{ topic: KFK.parkTopics, offset: parkTopicNum + 1 }, { topic: KFK.userTopics, offset: userTopicNum + 1 }],
        function(err, added) {},
        true,
      );
    
      consumer.on("message", async function(message) {
        log.info(message);
        if (global.kafkaMsg === false) {
          global.kafkaMsg = true;
          if (message.topic === "parkDomainDataChanged") {
            if (message.offset > parkTopicsNum) {
              parkTopicsNum += 1;
              batis.query("apiGoalkeeper.insretKafka", {
                param: message.value,
                topic: message.topic,
                offset: message.offset,
                cTime: new Date(),
              });
              await updateMsg(message);
            }
          } else if (message.topic === "userDomainDataChanged") {
            if (message.offset > userTopicsNum) {
              userTopicsNum += 1;
              batis.query("apiGoalkeeper.insretKafka", {
                param: message.value,
                topic: message.topic,
                offset: message.offset,
                cTime: new Date(),
              });
            }
          }
          global.kafkaMsg = false;
        }
      });
    
    
    //项目初始化关键代码  根据topic查询数据库中最大的offset
      const parkTopics = (await batis.query("apiGoalkeeper.selectKafka", { topic: KFK.parkTopics }))[0].offset;
      const userTopics = (await batis.query("apiGoalkeeper.selectKafka", { topic: KFK.userTopics }))[0].offset;
      toKafka(parkTopics, userTopics);
    
    //全局锁定义
    global.kafkaMsg = false;
    

    主要的思路是:将每次kafka的消息存放在数据库里面。在初始化的时候先查询topic的最大offset,然后手动设置查询到的offset+1开始拉取消息。每次拉取到的消息都会和上一次消费的偏移量对比,只有大于上一次偏移量的才会进行处理。这样能保证不会丢失消息,也不会报错导致项目停止,下次启动的时候还是消费之前的消息。 这里使用了加锁,可以保证无论来了多少kefka的消息,只要上一个消息没有处理完成,下面的消息就没办法进入。

     consumer.on("message", async function(message) {
        log.info(message)
        if (message.topic === "parkDomainDataChanged") {
          if (message.offset > parkTopicsNum) {
            parkTopicsNum += 1;
            await batis.query("apiGoalkeeper.insretKafka", {
              param: message.value,
              topic: message.topic,
              offset: message.offset,
              cTime: new Date(),
            });
            await updateMsg(message);
          }
        } else if (message.topic === "userDomainDataChanged") {
          if (message.offset > userTopicsNum) {
            userTopicsNum += 1;
            await batis.query("apiGoalkeeper.insretKafka", {
              param: message.value,
              topic: message.topic,
              offset: message.offset,
              cTime: new Date(),
            });
          }
        }
      });
    

    consumer.on这个方法只要有消息进入貌似就代表被消费了,我因为加锁了,在锁还还没有开放的时候,有消息进入默认被消费了,等锁开放的时候,此消息就没办法在获取到了。
    最终解决方案
      但是这样还是出了问题,最终的解决方案是使用队列做,也因为我们只是消费方,所以不需要管消息丢失,只需要处理重复消息就可以了。消息队列是我们公司全栈写的一个npm库

    const KFK = require("../../config").kafka;
    const log = require("../../src/tools/Log");
    const kafka = require("kafka-node");
    const Client = kafka.KafkaClient;
    const Offset = kafka.Offset;
    const Consumer = kafka.Consumer;
    const TaskQueue = require("@wolfx/async-task-queue");
    const taskQueue = new TaskQueue();
    
    function toKafka() {
      const client = new Client({ kafkaHost: KFK.kafkaUrl });
      const offset = new Offset(client);
      console.log("连接kafka成功");
      const topics = [
        {
          topic: KFK.userTopics,
          partition: 0,
          offset: 0,
        },
        {
          topic: KFK.parkTopics,
          partition: 0,
          offset: 0,
        },
      ];
      const options = {
        //  自动提交配置   (false 不会提交偏移量,每次都从头开始读取)
        autoCommit: true,
        autoCommitIntervalMs: 5000,
        //  如果设置为true,则consumer将从有效负载中的给定偏移量中获取消息
        fromOffset: false,
      };
      
      const consumer = new Consumer(client, topics, options);
    
      consumer.on("message", async function(message) {
        //进来的消息进入队列,这样无论多少消息都会进入队列等待,
        taskQueue.add(async () => {
          //从库中查询该消息是否已经被消费过
          const offset = (await batis.query("kafka.selectOffset", { topic: message.topic, offset: message.offset }))[0]
            .offset;
          if (offset < 1) {
            //只有为消费过的消息才会进行业务处理
            log.info("kafka ", message);
          }
        });
      });
      consumer.on("error", function(message) {
        console.log("kafka错误", message);
      });
    }
    
    module.exports = toKafka;
    

    上面也说过consumer.on这个方法只要有消息进入就代表被消费了,虽然上面加锁了,但是会出现消息来了,但是业务逻辑被锁锁住,后面不停的来消息,这样就会丢失消息。这种存放在队列中,不管有多少消息进来,都会进入到队列里面,不需要担心进入consumer.on的消息会丢失,只需要处理重复消息就可以了。

  • 相关阅读:
    性能测试目的和性能测试主要术语
    回数
    python求100以内的素数
    杨辉三角
    裴波那契数列
    day12_框架二sendmail.py代码
    codeforces 1435 E
    codeforces C
    codeforces 1436E
    codeforces 1435 D. Shurikens (贪心)
  • 原文地址:https://www.cnblogs.com/yangk1996/p/12663552.html
Copyright © 2020-2023  润新知