[消息中间件]kafka+flink(实战)

是的,Kafka和Flink可以集成。Flink提供了专门的Kafka连接器来从Kafka主题读取数据和向Kafka主题写入数据。在Python中,我们可以使用PyFlink的API来实现。

以下是使用PyFlink实现从Kafka读取数据并处理,然后再写回Kafka的完整示例。我们将使用Flink的DataStream API。

步骤:

  1. 创建一个执行环境。
  2. 添加Kafka源连接器,用于从Kafka主题读取数据。
  3. 对数据流进行转换处理(这里我们做一个简单的处理,例如将消息转为大写)。
  4. 添加Kafka汇连接器,将处理后的数据写入到另一个Kafka主题。
  5. 执行作业。

注意:我们需要确保有可用的Kafka集群,并且已经创建了输入和输出主题。

在编写代码之前,需要安装PyFlink和Kafka连接器相关的依赖。可以使用以下命令安装:

pip install apache-flink

pip install apache-flink-kafka

另外,需要下载Flink的Kafka连接器jar包。因为Flink的Kafka连接器不是默认包含的,所以需要在代码中指定jar包。

但是,请注意,PyFlink的Kafka连接器可能需要在环境中指定额外的jar包。我们可以通过设置环境的jar包路径来添加。

下面是一个完整的示例代码,假设我们有一个Kafka主题作为源,另一个主题作为汇。我们将从源主题读取字符串,将每个字符串转为大写,然后写入汇主题。

是的,Kafka和Flink可以很好地集成。Flink提供了专门的Kafka连接器来高效地处理Kafka数据流。

Kafka和Flink集成架构

复制代码
Kafka Producer → Kafka Topic → Flink Kafka Source → Flink Processing → Flink Kafka Sink → Kafka Topic

完整示例代码

1. 环境准备

首先安装必要的包:

bash 复制代码
pip install pyflink apache-flink apache-flink-kafka

2. 完整的Flink+Kafka集成代码

python 复制代码
from pyflink.datastream import StreamExecutionEnvironment, TimeCharacteristic
from pyflink.datastream.connectors import FlinkKafkaConsumer, FlinkKafkaProducer
from pyflink.datastream.formats import JsonRowDeserializationSchema, JsonRowSerializationSchema
from pyflink.common.typeinfo import Types
from pyflink.common.serialization import SimpleStringSchema
from pyflink.common import WatermarkStrategy
from pyflink.datastream.window import TumblingProcessingTimeWindows
from pyflink.datastream.functions import MapFunction, ProcessWindowFunction
from pyflink.common.watermark_strategy import TimestampAssigner
from pyflink.common.time import Time
import json
import datetime

class KafkaFlinkIntegration:
    def __init__(self):
        # 创建执行环境
        self.env = StreamExecutionEnvironment.get_execution_environment()
        # 设置事件时间
        self.env.set_stream_time_characteristic(TimeCharacteristic.EventTime)
        # 设置并行度
        self.env.set_parallelism(1)
        
        # Kafka配置
        self.kafka_brokers = "localhost:9092"
        self.source_topic = "flink-input-topic"
        self.sink_topic = "flink-output-topic"
        self.group_id = "flink-consumer-group"
        
        # 添加Kafka连接器jar包(需要下载对应的jar包)
        kafka_connector_path = "/path/to/flink-sql-connector-kafka-1.16.0.jar"
        self.env.add_jars(f"file://{kafka_connector_path}")
    
    def create_kafka_source(self):
        """创建Kafka源"""
        # 方式1:使用SimpleStringSchema(简单字符串)
        properties = {
            'bootstrap.servers': self.kafka_brokers,
            'group.id': self.group_id,
            'auto.offset.reset': 'latest'
        }
        
        kafka_source = FlinkKafkaConsumer(
            topics=self.source_topic,
            deserialization_schema=SimpleStringSchema(),
            properties=properties
        )
        
        # 设置起始消费位置
        kafka_source.set_start_from_earliest()
        
        return kafka_source
    
    def create_kafka_sink(self):
        """创建Kafka Sink"""
        properties = {
            'bootstrap.servers': self.kafka_brokers
        }
        
        kafka_sink = FlinkKafkaProducer(
            topic=self.sink_topic,
            serialization_schema=SimpleStringSchema(),
            producer_config=properties
        )
        
        return kafka_sink
    
    def process_string_data(self):
        """处理字符串数据(简单示例)"""
        print("开始处理字符串数据...")
        
        # 创建数据源
        source_stream = self.env.add_source(self.create_kafka_source())
        
        # 数据处理:转换为大写并添加处理时间
        class UpperCaseMap(MapFunction):
            def map(self, value):
                return f"{datetime.datetime.now()}: {value.upper()}"
        
        processed_stream = source_stream \
            .map(UpperCaseMap(), output_type=Types.STRING())
        
        # 输出到控制台(用于调试)
        processed_stream.print()
        
        # 写入Kafka
        processed_stream.add_sink(self.create_kafka_sink())
        
        # 执行作业
        self.env.execute("Kafka-Flink-String-Processor")
    
    def process_json_data(self):
        """处理JSON数据(复杂示例)"""
        print("开始处理JSON数据...")
        
        # 定义JSON模式
        json_schema = {
            'type': 'row',
            'fields': [
                {'name': 'user_id', 'type': 'string'},
                {'name': 'product_id', 'type': 'string'},
                {'name': 'amount', 'type': 'double'},
                {'name': 'timestamp', 'type': 'bigint'}
            ]
        }
        
        # 创建JSON反序列化器
        deserialization_schema = JsonRowDeserializationSchema.Builder() \
            .type_info(Types.ROW([
                Types.STRING(),
                Types.STRING(),
                Types.DOUBLE(),
                Types.LONG()
            ])).build()
        
        # 创建Kafka源(JSON格式)
        properties = {
            'bootstrap.servers': self.kafka_brokers,
            'group.id': self.group_id
        }
        
        kafka_source = FlinkKafkaConsumer(
            topics=self.source_topic,
            deserialization_schema=deserialization_schema,
            properties=properties
        )
        
        # 添加时间戳和水印
        class TimestampExtractor(TimestampAssigner):
            def extract_timestamp(self, value, record_timestamp):
                # 假设JSON中有timestamp字段
                return value[3]  # timestamp是第4个字段(索引3)
        
        source_stream = self.env.add_source(kafka_source) \
            .assign_timestamps_and_watermarks(
                WatermarkStrategy.for_monotonous_timestamps()
                .with_timestamp_assigner(TimestampExtractor())
            )
        
        # 数据处理:按用户统计交易金额
        class UserTransactionAggregator(ProcessWindowFunction):
            def process(self, key, context, elements, out):
                user_id = key[0]
                total_amount = sum([element[2] for element in elements])
                window_end = context.window().end
                
                result = {
                    'user_id': user_id,
                    'total_amount': total_amount,
                    'window_end': window_end,
                    'transaction_count': len(elements)
                }
                out.collect(json.dumps(result))
        
        # 窗口计算:每10秒计算一次
        result_stream = source_stream \
            .key_by(lambda x: x[0])  # 按user_id分组 \
            .window(TumblingProcessingTimeWindows.of(Time.seconds(10))) \
            .process(UserTransactionAggregator(), 
                     output_type=Types.STRING())
        
        # 输出结果到控制台
        result_stream.print()
        
        # 创建JSON序列化器用于写入Kafka
        serialization_schema = JsonRowSerializationSchema.Builder() \
            .with_type_info(Types.ROW([
                Types.STRING(),
                Types.DOUBLE(),
                Types.LONG(),
                Types.INT()
            ])).build()
        
        # 写入Kafka
        kafka_sink = FlinkKafkaProducer(
            topic=self.sink_topic,
            serialization_schema=SimpleStringSchema(),
            producer_config=properties
        )
        
        result_stream.add_sink(kafka_sink)
        
        # 执行作业
        self.env.execute("Kafka-Flink-JSON-Processor")
    
    def word_count_example(self):
        """单词计数示例"""
        print("开始单词计数示例...")
        
        source_stream = self.env.add_source(self.create_kafka_source())
        
        # 单词计数逻辑
        word_counts = source_stream \
            .flat_map(lambda x: x.lower().split(), output_type=Types.STRING()) \
            .map(lambda x: (x, 1), output_type=Types.TUPLE([Types.STRING(), Types.INT()])) \
            .key_by(lambda x: x[0]) \
            .reduce(lambda x, y: (x[0], x[1] + y[1]))
        
        # 转换为JSON格式
        class ToJsonMap(MapFunction):
            def map(self, value):
                result = {
                    'word': value[0],
                    'count': value[1],
                    'timestamp': datetime.datetime.now().isoformat()
                }
                return json.dumps(result)
        
        json_stream = word_counts.map(ToJsonMap(), output_type=Types.STRING())
        
        # 输出到控制台和Kafka
        json_stream.print()
        json_stream.add_sink(self.create_kafka_sink())
        
        self.env.execute("Word-Count-Processor")


class KafkaProducerExample:
    """Kafka生产者示例,用于生成测试数据"""
    def __init__(self):
        from kafka import KafkaProducer
        import time
        import random
        
        self.producer = KafkaProducer(
            bootstrap_servers='localhost:9092',
            value_serializer=lambda v: json.dumps(v).encode('utf-8')
        )
        
    def produce_string_data(self, topic="flink-input-topic"):
        """生产字符串数据"""
        messages = [
            "hello world",
            "flink kafka integration",
            "stream processing is powerful",
            "big data processing",
            "real-time analytics"
        ]
        
        for i in range(10):
            msg = f"{messages[i % len(messages)]} - {i}"
            self.producer.send(topic, value=msg)
            print(f"发送消息: {msg}")
            time.sleep(1)
        
        self.producer.flush()
    
    def produce_json_data(self, topic="flink-input-topic"):
        """生产JSON数据"""
        users = ["user1", "user2", "user3", "user4", "user5"]
        products = ["productA", "productB", "productC"]
        
        for i in range(20):
            transaction = {
                'user_id': random.choice(users),
                'product_id': random.choice(products),
                'amount': round(random.uniform(10.0, 1000.0), 2),
                'timestamp': int(time.time() * 1000)
            }
            
            self.producer.send(topic, value=transaction)
            print(f"发送交易数据: {transaction}")
            time.sleep(0.5)
        
        self.producer.flush()


def main():
    """主函数"""
    print("=" * 50)
    print("Kafka-Flink 集成示例")
    print("=" * 50)
    
    try:
        # 运行模式选择
        mode = input("请选择运行模式:\n1. Flink消费者\n2. Kafka生产者\n选择(1/2): ")
        
        if mode == "1":
            # Flink处理程序
            processor = KafkaFlinkIntegration()
            
            # 选择处理类型
            print("\n选择处理类型:")
            print("1. 字符串处理")
            print("2. JSON数据处理")
            print("3. 单词计数")
            
            choice = input("选择(1/2/3): ")
            
            if choice == "1":
                processor.process_string_data()
            elif choice == "2":
                processor.process_json_data()
            elif choice == "3":
                processor.word_count_example()
            else:
                print("无效选择")
                
        elif mode == "2":
            # Kafka生产者
            producer = KafkaProducerExample()
            
            print("\n选择数据生成类型:")
            print("1. 生成字符串数据")
            print("2. 生成JSON交易数据")
            
            choice = input("选择(1/2): ")
            
            if choice == "1":
                producer.produce_string_data()
            elif choice == "2":
                producer.produce_json_data()
            else:
                print("无效选择")
        else:
            print("无效选择")
            
    except Exception as e:
        print(f"发生错误: {e}")
        print("请确保:")
        print("1. Kafka服务正在运行 (localhost:9092)")
        print("2. 已创建topic: flink-input-topic 和 flink-output-topic")
        print("3. 已下载flink-sql-connector-kafka jar包")


if __name__ == "__main__":
    main()

3. 简化的部署脚本

python 复制代码
# setup_kafka_topics.py
from kafka.admin import KafkaAdminClient, NewTopic
from kafka.errors import TopicAlreadyExistsError

def setup_kafka_topics():
    """设置Kafka topic"""
    admin_client = KafkaAdminClient(
        bootstrap_servers="localhost:9092",
        client_id='flink-setup'
    )
    
    topics = [
        NewTopic(name="flink-input-topic", num_partitions=3, replication_factor=1),
        NewTopic(name="flink-output-topic", num_partitions=3, replication_factor=1)
    ]
    
    try:
        admin_client.create_topics(new_topics=topics, validate_only=False)
        print("成功创建topic")
    except TopicAlreadyExistsError:
        print("topic已存在")
    finally:
        admin_client.close()

if __name__ == "__main__":
    setup_kafka_topics()

4. 依赖配置文件

yaml 复制代码
# requirements.txt
apache-flink==1.16.0
apache-flink-kafka==1.16.0
kafka-python==2.0.2
pyflink==1.16.0

部署步骤

  1. 启动Kafka服务
bash 复制代码
# 启动Zookeeper
bin/zookeeper-server-start.sh config/zookeeper.properties

# 启动Kafka
bin/kafka-server-start.sh config/server.properties
  1. 创建topic
bash 复制代码
bin/kafka-topics.sh --create --topic flink-input-topic --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1
bin/kafka-topics.sh --create --topic flink-output-topic --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1
  1. 下载Kafka连接器 从Flink官网下载对应版本的flink-sql-connector-kafka jar包

  2. 运行程序

bash 复制代码
python kafka_flink_integration.py

关键特性

  1. Exactly-Once语义:Flink提供端到端的精确一次处理保证
  2. Checkpoint机制:定期保存状态,故障时可恢复
  3. 窗口操作:支持滚动窗口、滑动窗口、会话窗口
  4. 状态管理:自动管理算子状态

这种集成模式特别适合:

  • 实时ETL管道
  • 实时风控系统
  • 实时推荐系统
  • IoT数据处理
  • 实时监控和告警系统
相关推荐
Hello.Reader9 小时前
Flink SQL 的 TRUNCATE 用法详解(Batch 模式)
sql·flink·batch
Jackyzhe11 小时前
Flink源码阅读:状态管理
大数据·flink
Jackeyzhe1 天前
Flink源码阅读:如何生成StreamGraph
flink
驾数者1 天前
Flink SQL模式识别:MATCH_RECOGNIZE复杂事件处理
数据库·sql·flink
小技工丨1 天前
【01】Apache Flink 2025年技术现状与发展趋势
大数据·flink·apache
青云交1 天前
Java 大视界 -- 基于 Java+Flink 构建实时电商交易风控系统实战(436)
java·redis·flink·规则引擎·drools·实时风控·电商交易
梦里不知身是客111 天前
flink的反压查看火焰图
大数据·flink
Jackyzhe1 天前
Flink源码阅读:集群启动
大数据·flink
Hello.Reader1 天前
Flink SQL INSERT 语句单表写入、多表分流、分区覆盖与 StatementSet
数据库·sql·flink