Kafka: 从客户端API到事务消息的工程实践

Kafka客户端API体系


五大API类型及作用

API类型功能描述典型场景
AdminClient集群管理(Topic/Partition操作)动态创建Topic
Producer消息发布订单事件推送
Consumer消息订阅库存系统消费订单
Streams流式数据处理实时订单金额统计
Connect外部系统集成(DB/ES等)MySQL订单表同步到Kafka

重点 API 复杂度分析

  • Consumer API:包含多种消费模型(最复杂)
    • 手动提交(至少一次语义)
    • 消费者组再平衡
    • 分区分配策略
  • AdminClient:类比 kafka-topics.sh 命令功能

Kafka服务启停管理

关键路径: /opt/kafka(默认安装目录)

操作流程:

1 ) 启动服务(后台模式)

bin/kafka-server-start.sh -daemon config/server.properties 
  • 关键验证:
    • 检查日志输出 server.log 是否包含 "started (kafka.server.KafkaServer)"
    • 通过进程命令确认:ps -ef | grep kafka

日志验证点:

  • Creating new session to localhost:2181 表示成功连接ZooKeeper

2 ) 进程状态检查

ps -ef | grep kafka  # 确认进程存在 
tail -f logs/server.log  # 监控实时日志 

3 ) 安全停止服务

bin/kafka-server-stop.sh  # 发送终止信号 
ps -ef | grep kafka  # 验证进程终止 
  • 注意事项:
    • Zookeeper协调的集群需确保关闭顺序,避免元数据损坏

消息生产与消费介绍


1 ) 控制台生产者

bin/kafka-console-producer.sh \
 --bootstrap-server localhost:9092 \
 --topic order_events 
> {"orderId":"001", "amount":60.00}

2 ) 控制台消费者

bin/kafka-console-consumer.sh \
 --bootstrap-server localhost:9092 \
 --topic order_events \
 --from-beginning
  • 消费验证:实时输出 {"orderId":"001", "amount":60.00}

Topic生产消费全流程


1 ) Topic创建与验证 (3分区1副本)

bin/kafka-topics.sh --create \
  --zookeeper localhost:2181 \
  --replication-factor 1 \
  --partitions 3 \
  --topic orders 

验证命令:

bin/kafka-topics.sh --list --zookeeper localhost:2181
输出:orders

2 ) 生产者消息发送

bin/kafka-console-producer.sh \
  --broker-list localhost:9092 \
  --topic orders 
 
> {"orderId":"001", "amount":60.00}  # JSON格式消息 
> {"orderId":"002", "amount":80.00}

连接失败处理: 检查server.propertiesadvertised.listeners=PLAINTEXT://your-real-ip:9092

3 ) 消费者消息订阅

bin/kafka-console-consumer.sh \
  --bootstrap-server localhost:9092 \
  --topic orders \
  --from-beginning 
 
输出:
{"orderId":"001", "amount":60.00}
{"orderId":"002", "amount":80.00}

实时消息传递验证: 新消息生产后消费者立即呈现

Kafka核心概念解析


1 ) Topic(主题)

分布式消息存储单元,解耦生产者与消费者:

# 创建Topic(3分区2副本)
bin/kafka-topics.sh --create \
  --bootstrap-server localhost:9092 \
  --replication-factor 2 \
  --partitions 3 \
  --topic order_events

2 ) Producer/Consumer模型

角色职责关键参数
Producer推送消息到Topic(如订单创建)bootstrap.servers
Consumer从Topic拉取消息进行处理group.id, auto.offset.reset

Kafka核心架构解析


3.1 三角通信模型

组件关系:

  • 生产者(Producer) → 推送消息至Topic
  • Topic → 持久化存储消息的逻辑分区
  • 消费者(Consumer) ← 从Topic拉取消息处理

3.2 核心概念定义

术语定义业务类比
Topic消息分类容器(含1-n个Partition)订单处理队列
PartitionTopic的物理子分区订单分拣流水线
Producer消息发布者订单创建系统
Consumer消息订阅处理者订单仓储系统
BrokerKafka服务节点消息处理中心
ZooKeeper集群协调服务分布式系统调度中心

NestJS 工程集成方案


1 ) 项目初始化

创建 NestJS 项目 
nest new kafka-microservices
cd kafka-microservices
 
安装 Kafka 依赖
npm install kafkajs @nestjs/microservices

2 ) 配置模块封装

// kafka.config.ts
import { Kafka, Partitioners } from 'kafkajs';
 
export const KAFKA_CLIENT = new Kafka({
  brokers: [process.env.KAFKA_BROKER || 'localhost:9092'],
  clientId: 'order-service',
  ssl: process.env.KAFKA_SSL === 'true',
  sasl: process.env.KAFKA_USER ? {
    mechanism: 'plain',
    username: process.env.KAFKA_USER,
    password: process.env.KAFKA_PASSWORD
  } : null
});
 
export const PRODUCER_CONFIG = {
  createPartitioner: Partitioners.LegacyPartitioner,
  allowAutoTopicCreation: true,
};

工程示例:1


1 ) 方案1:基础生产者-消费者实现

// kafka.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import { Kafka, Producer, Consumer } from 'kafkajs';
 
@Injectable()
export class KafkaService implements OnModuleInit {
  private kafka = new Kafka({ brokers: ['localhost:9092'] });
  private producer: Producer;
  private consumer: Consumer;
 
  async onModuleInit() {
    this.producer = this.kafka.producer();
    await this.producer.connect();
    
    this.consumer = this.kafka.consumer({ groupId: 'order-group' });
    await this.consumer.connect();
    await this.consumer.subscribe({ topic: 'orders', fromBeginning: true });
    
    await this.consumer.run({
      eachMessage: async ({ topic, partition, message }) => {
        this.processOrder(message.value.toString());
      },
    });
  }
 
  private processOrder(orderData: string) {
    const order = JSON.parse(orderData);
    console.log(`处理订单: ${order.orderId}, 金额: ${order.amount}`);
    // 业务处理逻辑
  }
 
  async sendOrder(orderId: string, amount: number) {
    await this.producer.send({
      topic: 'orders',
      messages: [{ value: JSON.stringify({ orderId, amount }) }],
    });
  }
}

2 ) 方案2:事务性消息处理

// transactional.producer.ts 
import { Injectable } from '@nestjs/common';
import { Kafka, Partitioners } from 'kafkajs';
 
@Injectable()
export class TransactionalProducer {
  private kafka = new Kafka({
    brokers: ['kafka1:9092', 'kafka2:9092'],
    transactionTimeout: 60000,
  });
  private producer = this.kafka.producer({
    idempotent: true,
    createPartitioner: Partitioners.LegacyPartitioner,
    transactionalId: 'order-tx-producer'
  });
 
  async executeOrderTransaction(orderId: string, amount: number) {
    const transaction = await this.producer.transaction();
    try {
      await transaction.send({
        topic: 'orders',
        messages: [{ value: JSON.stringify({ orderId, amount }) }],
      });
 
      // 模拟关联操作(如扣减库存)
      await transaction.send({
        topic: 'inventory',
        messages: [{ value: JSON.stringify({ orderId, action: 'deduct' }) }],
      });
 
      await transaction.commit();
    } catch (error) {
      await transaction.abort();
      throw new Error(`事务失败: ${error.message}`);
    }
  }
}

3 ) 方案3:Schema注册集成

// schema-registry.consumer.ts
import { Injectable } from '@nestjs/common';
import { Kafka, Consumer } from 'kafkajs';
import { SchemaRegistry, readAVSC } from '@kafkajs/confluent-schema-registry';
 
@Injectable()
export class SchemaConsumer {
  private registry = new SchemaRegistry({ host: 'http://schema-registry:8081' });
  private consumer: Consumer;
 
  constructor() {
    const kafka = new Kafka({ brokers: ['kafka:9092'] });
    this.consumer = kafka.consumer({ groupId: 'schema-group' });
  }
 
  async start() {
    await this.consumer.connect();
    await this.consumer.subscribe({ topic: 'avro-orders' });
    
    await this.consumer.run({
      eachMessage: async ({ message }) => {
        const decoded = await this.registry.decode(message.value);
        console.log('Schema解码:', decoded);
      }
    });
  }
}

工程示例:2


1 ) 方案1:基础生产者实现

// src/kafka/producer.service.ts 
import { Injectable } from '@nestjs/common';
import { Producer, Kafka } from 'kafkajs';
 
@Injectable()
export class KafkaProducerService {
  private producer: Producer;
 
  constructor() {
    const kafka = new Kafka({
      brokers: ['localhost:9092'],
    });
    this.producer = kafka.producer();
  }
 
  async connect() {
    await this.producer.connect();
  }
 
  async sendMessage(topic: string, message: any) {
    await this.producer.send({
      topic,
      messages: [{ value: JSON.stringify(message) }],
    });
  }
}

2 ) 方案2:消费者组实现

// src/kafka/consumer.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import { Consumer, Kafka } from 'kafkajs';
 
@Injectable()
export class KafkaConsumerService implements OnModuleInit {
  private consumer: Consumer;
 
  constructor() {
    const kafka = new Kafka({
      brokers: ['localhost:9092'],
    });
    this.consumer = kafka.consumer({ groupId: 'order-service' });
  }
 
  async onModuleInit() {
    await this.consumer.connect();
    await this.consumer.subscribe({ topic: 'order_events' });
    await this.consumer.run({
      eachMessage: async ({ message }) => {
        console.log(`Received: ${message.value.toString()}`);
        // 业务逻辑:订单处理/库存扣减
      },
    });
  }
}

3 ) 方案3:Kafka+Zookeeper配置

docker-compose.yml(生产级配置)
version: '3'
services:
  zookeeper:
    image: confluentinc/cp-zookeeper:7.3.0 
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
 
  kafka:
    image: confluentinc/cp-kafka:7.3.0
    depends_on:
      - zookeeper
    ports:
      - "9092:9092"
    environment:
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092 

工程示例:3


1 ) 方案 1:基础生产者-消费者

// src/orders/producer.service.ts
import { Injectable } from '@nestjs/common';
import { KAFKA_CLIENT, PRODUCER_CONFIG } from './kafka.config';
 
@Injectable()
export class OrderProducer {
  private producer = KAFKA_CLIENT.producer(PRODUCER_CONFIG);
 
  async sendOrderEvent(order: OrderDto) {
    await this.producer.connect();
    await this.producer.send({
      topic: 'ecommerce-orders',
      messages: [
        { 
          key: order.userId,  // 分区路由键
          value: JSON.stringify(order),
          headers: { 'event-type': 'order.created' }
        }
      ],
    });
  }
}
 
// src/orders/consumer.service.ts
import { Kafka, Consumer, EachMessagePayload } from 'kafkajs';
 
@Injectable()
export class OrderConsumer {
  private consumer: Consumer;
 
  constructor() {
    this.consumer = KAFKA_CLIENT.consumer({ groupId: 'order-processors' });
  }
 
  async start() {
    await this.consumer.connect();
    await this.consumer.subscribe({ topic: 'ecommerce-orders' });
    
    await this.consumer.run({
      eachMessage: async ({ topic, partition, message }) => {
        const order = JSON.parse(message.value.toString());
        await this.processOrder(order);
      }
    });
  }
}

2 ) 方案 2:事务性消息处理

// src/payments/transaction.service.ts
import { Injectable } from '@nestjs/common';
import { KAFKA_CLIENT } from '../kafka.config';
 
@Injectable()
export class PaymentService {
  private producer = KAFKA_CLIENT.producer({
    transactionalId: 'payment-tx-producer',
    maxInFlightRequests: 1,
    idempotent: true
  });
 
  async processPayment(payment: PaymentDto) {
    const transaction = await this.producer.transaction();
    
    try {
      // 1. 执行本地事务
      const result = await paymentRepository.save(payment);
      
      // 2. 发送事务消息 
      await transaction.send({
        topic: 'payment-events',
        messages: [{
          value: JSON.stringify({
            id: result.id,
            status: 'completed'
          })
        }]
      });
      
      // 3. 提交事务
      await transaction.commit();
    } catch (error) {
      await transaction.abort();
      throw error;
    }
  }
}

3 ) 方案 3:流处理集成

// src/analytics/stream.processor.ts
import { KafkaStreams } from 'kafka-streams';
import { Injectable } from '@nestjs/common';
 
@Injectable()
export class AnalyticsStream {
  private kStream: any;
 
  constructor() {
    this.kStream = new KafkaStreams({
      kafkaHost: process.env.KAFKA_CLUSTER,
      clientId: 'analytics-processor',
      groupId: 'analytics-group'
    }).getKStream('ecommerce-orders');
 
    this.initPipeline();
  }
 
  private initPipeline() {
    this.kStream
      .mapJSONConvenience()  // 自动 JSON 解析
      .filter(({ value }) => value.amount > 1000) // 过滤高额订单
      .tap(({ value }) => this.triggerFraudCheck(value)) // 执行风控检查
      .map(({ value }) => ({
        ...value,
        processedAt: new Date().toISOString()
      }))
      .to('high-value-orders'); // 输出到新 Topic
  }
 
  start() {
    this.kStream.start();
  }
}

运维监控与最佳实践


1 ) 关键配置优化

server.properties 核心参数
num.network.threads=3
num.io.threads=8
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400 
log.retention.hours=168

2 ) NestJS 健康检查

// src/health/health.controller.ts
import { Controller, Get } from '@nestjs/common';
import { KAFKA_CLIENT } from '../kafka.config';
 
@Controller('health')
export class HealthController {
  @Get('kafka')
  async checkKafka() {
    const admin = KAFKA_CLIENT.admin();
    try {
      const clusterInfo = await admin.describeCluster();
      return {
        status: clusterInfo.brokers.length > 0 ? 'UP' : 'DOWN',
        brokers: clusterInfo.brokers.map(b => b.host+':'+b.port)
      };
    } catch (e) {
      return { status: 'DOWN', error: e.message };
    }
  }
}

3 ) 消费者组管理

查看消费者组状态
kafka-consumer-groups.sh \
--bootstrap-server localhost:9092 \
--describe --group order-processors

安全警告:生产环境必须配置 SSL/SASL 认证,使用环境变量管理敏感凭证

Kafka-NestJS集成配置详解


1 ) 必备依赖

npm install kafkajs @nestjs/microservices 
npm install @kafkajs/confluent-schema-registry # 可选Schema注册 

2 ) 动态配置模块

// kafka.config.ts
import { registerAs } from '@nestjs/config';
 
export default registerAs('kafka', () => ({
  brokers: process.env.KAFKA_BROKERS.split(','),
  ssl: process.env.KAFKA_SSL === 'true',
  sasl: process.env.KAFKA_SASL_MECHANISM ? {
    mechanism: process.env.KAFKA_SASL_MECHANISM,
    username: process.env.KAFKA_SASL_USER,
    password: process.env.KAFKA_SASL_PASS
  } : null 
}));

3 ) 消费者组再均衡处理

this.consumer.on(this.consumer.events.GROUP_JOIN, ({ payload }) => {
  console.log(`消费者加入组: ${payload.groupId}`);
});
 
this.consumer.on(this.consumer.events.REBALANCING, () => {
  console.warn('消费者组再均衡中...');
});

4 ) 生产端优化配置

const producer = this.kafka.producer({
  maxInFlightRequests: 5,           // 最大并行请求
  idempotent: true,                 // 幂等性保证 
  transactionTimeout: 30000,         // 事务超时
  compression: CompressionTypes.GZIP // 消息压缩 
});

常见问题解决方案


1 ) 连接问题排查矩阵

错误现象排查点解决方案
BrokerNotAvailableErrorlisteners配置检查advertised.listeners地址
LeaderNotAvailablePartition状态重启Broker或重新选举
Consumer group rebalancing会话超时(session.timeout.ms)调整超时时间(默认45s)
MessageSizeTooLargemax.message.bytes配置增大限制或压缩消息

2 ) 性能优化参数

server.properties
num.network.threads=8 
num.io.threads=32 
socket.send.buffer.bytes=102400 
socket.receive.buffer.bytes=102400 
queued.max.requests=2048

结语:Kafka在微服务架构中的定位


Apache Kafka实现了生产-存储-消费的解耦三角模型,在NestJS微服务中承担核心通信枢纽角色。

通过本文的三种工程方案,开发者可根据业务场景选择:

  1. 基础方案:快速实现消息收发
  2. 事务方案:金融级数据一致性保障
  3. Schema方案:跨服务契约化开发

最佳实践建议:

  • 生产环境启用SSL+SASL认证
  • 使用Avro Schema管理消息格式
  • 监控Consumer Lag指标实时预警
  • Partition数量按消费者实例数配置
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值