kafka接入学习

什么是kafka?

Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键因素。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。 对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka的目的是通过Hadoop的并行加载机制来统一线上和离线的消息处理,也是为了通过集群机来提供实时的消费。

kafka有哪些优点?

1.它是一种高容错的消息发布订阅系统(因为它的数据是持久化到硬盘,可以重复读取)

2.它是高效率,有很大的吞吐量

3.它的扩展性极高,是用zookeeper集群来管理Consumer和Broker节点的动态加入与退出

4.支持实时流的方式传输数据


怎么样使用kafka?

1.首先要配置一下zookeeper的集群,这里是配置一下Zookeeper.properties

#记录节点的信息已经Kafka节点和Consumer的一些值
dataDir=/data/zookeeper
# the port at which the clients will connect
#占用到的端口
clientPort=2181
# disable the per-ip limit on the number of connections since this is a non-production config
maxClientCnxns=0
#心跳最长时间确定节点的存活
tickTime=2000
syncLimit=2
#初始尝试最多链接5次
initLimit=5
#集群配置
server.1=192.168.196.138:2888:3888
server.2=192.168.196.140:2888:3888
server.3=192.168.196.139:2888:3888

2.然后在配置kafka的broker的节点,这边是配置Conf里面的Server.properties文件

broker.id=0  #broker的唯一标识,集群中也不能一样
port=9092 #broker用到的端口
num.network.threads=2
num.io.threads=8 #开启多少线程同时在执行IO数据持久化
socket.send.buffer.bytes=1048576   #数据接受的缓冲大小
socket.receive.buffer.bytes=1048576  #消费时发出去的缓冲大小
socket.request.max.bytes=104857600  #一个请求会接受的最大数据量
log.dirs=/tmp/kafka-logs   #日志的打印目录
num.partitions=2  #Topic默认的分区数
log.retention.hours=168 
log.segment.bytes=536870912
log.retention.check.interval.ms=60000
log.cleaner.enable=false
zookeeper.connect=localhost:2181,192.168.196.140:2181,192.168.196.139:2181  #zookeeper集群配置
zookeeper.connection.timeout.ms=1000000  
host.name=192.168.196.138  #hostname
delete.topic.enable=true  #配置这个参数删除Topic的时候同时也会删除数据	

3.启动zookeeper集群

./bin/zookeeper-server-start.sh ./conf/zookeeper.properties

4.启动kafk的Broker节点

./bin/kafka-server-start.sh ./conf/server.properties

5.创建Topic

./bin/kafka-topics.sh --create --zookeeper Master:2181 --replication-factor 2 --partitions 2 --topic test-topic

6.可以查看以下kafka集群中存在的Topic

./bin/kafka-topics.sh --list --zookeeper 192.168.196.138:2181
./bin/kafka-topics.sh --describe --zookeeper Master:2181--topic test-topic  #查看某一个分区的具体情况

7.Flume数据源传输数据到Kafka

KafkaSink的代码:这边还需要把要用到的包引入到Flume底下的lib文件夹,具体的包可以在我的百度云盘下载:点击打开链接这里面解压后又一个lib里面的所有包都拷贝进去。

package org.apache.flume.plugins;

/**
 * KAFKA Flume Sink (Kafka 0.8 Beta, Flume 1.5).
 * User: 
 * Date: 2016/03/28
 * Time: PM 4:32
 */

import java.util.Properties;

import kafka.javaapi.producer.Producer;
import kafka.producer.KeyedMessage;
import kafka.producer.ProducerConfig;

import org.apache.commons.lang.StringUtils;
import org.apache.flume.*;
import org.apache.flume.conf.Configurable;
import org.apache.flume.event.EventHelper;
import org.apache.flume.sink.AbstractSink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;

/**
 * kafka sink.
 */
public class KafkaSink extends AbstractSink implements Configurable {

    /**
     * 日志记录
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(KafkaSink.class);

    /**
     * 参数
     */
    private Properties parameters;
    /**
     * 生产者
     */
    private Producer<String, String> producer;
    /**
     * The Context.上下文
     */
    private Context context;

    /**
     * Configure void. 参数设置
     * 
     * @param context
     *            the context
     */
    @Override
    public void configure(Context context) {
        this.context = context;
        ImmutableMap<String, String> props = context.getParameters();

        parameters = new Properties();
        for (String key : props.keySet()) {
            String value = props.get(key);
            this.parameters.put(key, value);
        }
    }
    /**
     * Start void.
     */
    @Override
    public synchronized void start() {
        super.start();
        ProducerConfig config = new ProducerConfig(this.parameters);
        this.producer = new Producer<String, String>(config);
    }

    /**
     * Process status.
     * 
     * @return the status
     * @throws EventDeliveryException
     *             the event delivery exception
     */
    @Override
    public Status process() throws EventDeliveryException {
        Status status = null;

        // Start transaction
        Channel ch = getChannel();
        Transaction txn = ch.getTransaction();
        txn.begin();
        try {
            // This try clause includes whatever Channel operations you want to do
            Event event = ch.take();

            String partitionKey = (String) parameters.get(KafkaFlumeConstans.PARTITION_KEY_NAME);
            String encoding = StringUtils.defaultIfEmpty(
                    (String) this.parameters.get(KafkaFlumeConstans.ENCODING_KEY_NAME),
                    KafkaFlumeConstans.DEFAULT_ENCODING);
            String topic = Preconditions.checkNotNull(
                    (String) this.parameters.get(KafkaFlumeConstans.CUSTOME_TOPIC_KEY_NAME),
                    "custom.topic.name is required");

            String eventData = new String(event.getBody(), encoding);

            KeyedMessage<String, String> data;

            // if partition key does'nt exist
            if (StringUtils.isEmpty(partitionKey)) {
                data = new KeyedMessage<String, String>(topic, eventData);
            } else {
                data = new KeyedMessage<String, String>(topic, partitionKey, eventData);
            }

            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("Send Message to Kafka : [" + eventData + "] -- [" + EventHelper.dumpEvent(event) + "]");
            }

            producer.send(data);
            txn.commit();
            status = Status.READY;
        } catch (Throwable t) {
            txn.rollback();
            status = Status.BACKOFF;

            // re-throw all Errors
            if (t instanceof Error) {
                throw (Error) t;
            }
        } finally {
            txn.close();
        }
        return status;
    }

    /**
     * Stop void.
     */
    @Override
    public void stop() {
        producer.close();
    }
}


还要写一个分区策略,简单的分区策略SinglePartition

 @Override
    public int partition(Object key, int numPartitions) {
        try {
            int partitionNum = Integer.parseInt((String) key);
            return Math.abs(Integer.parseInt((String) key) % numPartitions);
        } catch (Exception e) {
            return Math.abs(key.hashCode() % numPartitions);
        }
    }


8.写kafka消费着去消费集群中所在Topic的数据.

1.Consumer所需要用到的静态配置数据

package com.test.kafka;

/**
 * Created by root on 16-3-13.
 */
public class KafkaProperties {
    public final static String zkConnect="192.168.196.138:2181,192.168.196.139:2181,192.168.196.140:2181";
    public final static String groupId="group1";
    public final static String groupId2="group2";
    public final static String topic="kafkaToptic";
    public final static int kafkaProduceBufferSize=64*1024;
    public final static int connectionTimeOut=20000;
    public final static int reconnectInterval=10000;
    public final static String topic2="topic2";
    public final static String topic3="topic3";


}
2.消费者的具体实现

package com.test.kafka;

import kafka.consumer.ConsumerConfig;
import kafka.consumer.ConsumerIterator;
import kafka.consumer.KafkaStream;
import kafka.javaapi.consumer.ConsumerConnector;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * Created by root on 16-3-14.
 */
public class KafkaConsumer2 extends Thread {

    private final String topic;
    private final ConsumerConnector consumer;
    public KafkaConsumer2(String topic){
        consumer = kafka.consumer.Consumer.createJavaConsumerConnector(createConsumerConfig());

        this.topic = topic;
    }

    private static ConsumerConfig createConsumerConfig(){
        Properties props = new Properties();
        props.put("zookeeper.connect",KafkaProperties.zkConnect);
        props.put("group.id",KafkaProperties.groupId2);
        props.put("zookeeper.session.timeout.ms","40000");
        props.put("zookeeper.sync.time.ms","200");
        props.put("auto.commit.interval.ms","1000");

        return new ConsumerConfig(props);
    }

    @Override
    public void run() {
        Map<String,Integer> topicCountMap = new HashMap<String,Integer>();
        topicCountMap.put(topic, new Integer(1));
        Map<String,List<KafkaStream<byte[],byte[]>>> consumerMap = consumer.createMessageStreams(topicCountMap);
        KafkaStream<byte[],byte[]> stream = consumerMap.get(topic).get(0);
        ConsumerIterator<byte[],byte[]> it = stream.iterator();
        while(it.hasNext()){
            System.out.println("receiced:"+new String(it.next().message()));
            try {
                sleep(3000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }

    }
}

这里还需要把用到的Kafka相关的包给引进来,直接用maven引进就可以,下次直接补充。

我的海量日志采集架构

我这边实现一个海量日志采集平台,主要是利用Flume采集服务平台的实时日志,然后对日志进行过滤复制处理,其中正则配置只选择JSON格式的日志,复制两份日志数据,一份存入HDFS,一份传入Kafka转为实时流。其中在HDFS和Kafka两条线都做了负载均衡处理。一张整体框架图还有一张详细的数据路径图






架构实现

该架构的其中一条线已经在Flume的那篇博客讲述过,这边就不在重复,这边只进行Kafka这条线进行实现分析。这里上文已经知道消费者怎么进行消费,所以只需要在Flume端配置联通这个数据传输通道就能将数据稳定传输,Flume客户端启动在Flume那篇博客已经写得很清楚了。这里也不做过多介绍。
接下去写Flume接入Kafka的那个MasterClient2.conf的配置如下:
Flume配置

client2.sources = source1
client2.sinks  = sink1 sink2
client2.channels = channel1

#Define a sink group which for Load balancing
client1.sinkgroups = g1
client1.sinkgroups.g1.sinks = sink1 sink2
client1.sinkgroups.g1.processor.type = load_balance
client1.sinkgroups.g1.processor.backoff = true
client1.sinkgroups.g1.processor.selector = round_robin

#Describe source
client2.sources.source1.type = avro
client2.sources.source1.channels = channel1
client2.sources.source1.port = 6666
client2.sources.source1.bind = 0.0.0.0

#Describe sink1
#client2.sinks.sink1.type = logger
client2.sinks.sink1.type = org.apache.flume.plugins.KafkaSink
client2.sinks.sink1.metadata.broker.list=192.168.196.138:9092,192.168.196.139:9092,192.168.196.140:9092
client2.sinks.sink1.zk.connect=192.168.196.138:2181,192.168.196.139:2181,192.168.196.140:2181
client2.sinks.sink1.partition.key=0
client2.sinks.sink1.partitioner.class=org.apache.flume.plugins.SinglePartition
client2.sinks.sink1.serializer.class=kafka.serializer.StringEncoder
client2.sinks.sink1.request.required.acks=1
client2.sinks.sink1.max.message.size=1000000
client2.sinks.sink1.producer.type=async
client2.sinks.sink1.custom.encoding=UTF-8
client2.sinks.sink1.custom.topic.name=kafkaToptic

#Describe sink2
#client2.sinks.sink2.type = logger
client2.sinks.sink2.type = org.apache.flume.plugins.KafkaSink
client2.sinks.sink2.metadata.broker.list=192.168.196.138:9092,192.168.196.139:9092,192.168.196.140:9092
client2.sinks.sink2.zk.connect=192.168.196.138:2181,192.168.196.139:2181,192.168.196.140:2181
client2.sinks.sink2.partition.key=0
client2.sinks.sink2.partitioner.class=org.apache.flume.plugins.SinglePartition
client2.sinks.sink2.serializer.class=kafka.serializer.StringEncoder
client2.sinks.sink2.request.required.acks=1
client2.sinks.sink2.max.message.size=1000000
client2.sinks.sink2.producer.type=async
client2.sinks.sink2.custom.encoding=UTF-8
client2.sinks.sink2.custom.topic.name=kafkaToptic

#Describe channel
client2.channels.channel1.type = memory
client2.channels.channel1.capacity = 10000
client2.channels.channel1.transactionCapacity = 1000

#bind the source/sink with channel
client2.sinks.sink1.channel = channel1
client2.sources.source1.channels = channel1

然后启动三个Flume客户端后在开启日志模拟器,开启消费者程序就可以开始看到日志的传输,在2G内存,20G硬盘,1核处理器的集群单个节点上数据的传输延迟大概是0.8秒。


第一章 需求 对方的网页文件接入到我方服务器。每个文件要知道其:网站、采集时间等。 通过消息队列将多个远程服务器上的【数据文件】源源不断的接入到本地服务器,数据文件是下载下来的网页,传输过来的时候还要带上来自的网站和下载日期等信息。 目前讨论消息队列用Kafka。 第二章 基本方案 1、我现在能想到的就是: 远程服务器编写程序, 不停读取文件的内容成为一个字符串,然后再加上来自的网站、下载日期等信息,组合成一个JSON字符串,通过调用kafka的生产者API将字符串写入Kafka。 2、JSON数据格式设计: { “source”: “来源网站名” , “filename” : “html文件名”, “download-date”: “下载日期” , ”content” : “一个html文件的全部字符串 “ } 注:假设.js \.css \.png文件都不需要 3、消息主题的设计: 一个网站对应一个topic,对应到本地的一个文件夹。 4、其他: 如果只是传输文件,不考虑加上网站、下载日期等信息的话,我知道的用Flume可能更方便,但是要加上网站、下载日期的信息(类似主题订阅的功能),flume就不太方便了? 5、可能存在的顾虑:网页数据文件对数据顺序性的要求的程度。 Kafka单分区的情况小支持消息有序性, 多分区下确保消息有序的方案还需深入调研。 如果之后一个主题下对应的网页文件没有特别的传输顺序要求,这一块应该会容易处理很多。
<think>好的,我现在需要帮助用户解决在NestJS中集成Kafka的问题。首先,我得回顾一下自己对NestJS和Kafka的了解。NestJS是一个基于Node.js的框架,支持TypeScript,常用于构建高效、可扩展的服务器端应用。而Kafka是一个分布式流处理平台,常用于处理实时数据流,支持高吞吐量和容错性。所以用户的需求应该是如何在NestJS应用中配置和使用Kafka进行消息的生产和消费。 接下来,我应该考虑NestJS中集成Kafka的常见方法。记得NestJS有一个模块化的系统,可以通过导入不同的模块来扩展功能。可能需要使用到@nestjs/microservices模块,这个模块支持多种传输层,包括Kafka。所以,首先需要安装相关的依赖包,比如kafkajs,因为NestJS的Microservices模块通常与kafkajs库配合使用。 然后,配置部分。用户需要知道如何在NestJS中配置Kafka的客户端和生产者、消费者的设置。可能需要创建一个Kafka客户端实例,指定brokers的地址和其他配置参数。比如,在app.module.ts中通过registerMicroservice来配置Kafka的连接参数,如brokers列表、客户端ID等。 接下来是消息的生产和消费。生产者部分,用户可能需要一个服务类,使用ClientKafka来发送消息到指定的topic。消费者部分,则需要使用@MessagePattern或@EventPattern装饰器来订阅特定的topic,并处理接收到的消息。需要注意的是,消费者需要作为微服务启动,以便监听Kafka的消息。 另外,关于代码示例,需要确保语法正确,特别是装饰器的使用和模块的导入。比如,在模块中导入ClientsModule,并使用registerAsync来异步配置Kafka客户端,可能涉及到环境变量的使用,比如从配置服务中获取brokers地址。 还有可能涉及到消息序列化和反序列化的处理。Kafka消息通常以缓冲区的形式传输,所以可能需要配置编码器(serializer)和解码器(deserializer),比如使用JSON格式进行序列化。 用户可能还会关心如何运行和测试这个集成。需要指导他们启动Kafka服务器,创建所需的topic,并运行NestJS应用。同时,可能需要使用命令行工具如kafka-console-producer和kafka-console-consumer来测试消息的发送和接收。 此外,需要注意NestJS的版本是否与所使用的@nestjs/microservices版本兼容,以及kafkajs的版本是否适配。如果用户遇到连接问题,可能需要检查Kafka broker的地址是否正确,防火墙设置是否允许访问,以及Kafka的配置是否正确,比如advertised.listeners的设置。 最后,应该考虑用户可能的后续问题,比如如何处理错误、实现重试机制,或者如何扩展消费者组。这时候可以提到NestJS的拦截器、过滤器,或者Kafka本身的消费者组机制。例如,断路器模式可以使用@nestjs/circuit-breaker或者之前提到的@UseFilters装饰器,如引用中的CircuitBreakerFilter[^2]。 总结步骤大致如下: 1. 安装必要的依赖包。 2. 配置Kafka客户端和微服务模块。 3. 创建生产者服务发送消息。 4. 创建消费者控制器处理消息。 5. 启动微服务监听。 6. 测试生产和消费流程。 现在需要把这些步骤转化为结构化的回答,确保代码示例正确,配置参数清晰,并且引用相关的文档或资源。同时,生成相关问题,帮助用户进一步学习,比如配置多个brokers、处理消息序列化问题或者性能优化方法。</think>### NestJS 集成 Kafka 的配置与示例 #### 1. 安装依赖 首先安装 Kafka 客户端库和 NestJS 微服务模块: ```bash npm install kafkajs @nestjs/microservices ``` #### 2. 配置 Kafka 连接 在 `app.module.ts` 中注册 Kafka 微服务: ```typescript import { Module } from &#39;@nestjs/common&#39;; import { ClientsModule, Transport } from &#39;@nestjs/microservices&#39;; @Module({ imports: [ ClientsModule.register([ { name: &#39;KAFKA_SERVICE&#39;, transport: Transport.KAFKA, options: { client: { brokers: [&#39;localhost:9092&#39;], // Kafka Broker 地址 clientId: &#39;nestjs-kafka-client&#39;, }, consumer: { groupId: &#39;nestjs-group&#39; // 消费者组 ID } } } ]) ] }) export class AppModule {} ``` #### 3. 生产者服务示例 创建生产者服务 `kafka.producer.service.ts`: ```typescript import { Injectable } from &#39;@nestjs/common&#39;; import { ClientKafka } from &#39;@nestjs/microservices&#39;; @Injectable() export class KafkaProducerService { constructor(@Inject(&#39;KAFKA_SERVICE&#39;) private client: ClientKafka) {} async sendMessage(topic: string, message: string) { await this.client.emit(topic, { value: message }).toPromise(); } } ``` #### 4. 消费者控制器示例 创建消费者控制器 `kafka.consumer.controller.ts`: ```typescript import { Controller } from &#39;@nestjs/common&#39;; import { MessagePattern, Payload } from &#39;@nestjs/microservices&#39;; @Controller() export class KafkaConsumerController { @MessagePattern(&#39;test-topic&#39;) // 订阅的 Topic async handleMessage(@Payload() message: any) { console.log(&#39;Received message:&#39;, message.value); } } ``` #### 5. 启动微服务监听 在 `main.ts` 中启动微服务: ```typescript import { NestFactory } from &#39;@nestjs/core&#39;; import { MicroserviceOptions } from &#39;@nestjs/microservices&#39;; import { AppModule } from &#39;./app.module&#39;; async function bootstrap() { const app = await NestFactory.create(AppModule); app.connectMicroservice<MicroserviceOptions>({ transport: Transport.KAFKA, options: { client: { brokers: [&#39;localhost:9092&#39;] }, consumer: { groupId: &#39;nestjs-group&#39; } } }); await app.startAllMicroservices(); await app.listen(3000); } bootstrap(); ``` #### 6. 测试流程 1. 启动 Kafka 服务并创建 Topic `test-topic` 2. 运行 NestJS 应用:`npm run start` 3. 使用生产者发送消息: ```typescript // 在控制器中调用 await this.kafkaProducerService.sendMessage(&#39;test-topic&#39;, &#39;Hello Kafka!&#39;); ``` --- ### 配置说明 - **Broker 地址**:根据实际环境修改 `brokers` 参数,集群环境需配置多个地址,例如 `[&#39;kafka1:9092&#39;, &#39;kafka2:9092&#39;]` - **序列化**:可通过 `serializer` 和 `deserializer` 配置消息编解码(默认使用 JSON) - **重试机制**:在 `client` 配置中添加 `retry` 参数实现连接重试[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值