kafka:Producer(生产者)

832 阅读4分钟

前言

Kafka,作为目前在大数据领域应用最为广泛的消息队列,其内部实现和设计有很多值得深入研究和分析的地方。
使用kafka首先需要接触到producer的开发,然后是consumer开发。
下面进行producer的解析。

Producer概要设计

发送简略流程图

image.png

流程如下:
<1>KafkaProducer首先使用序列化器将需要发送的数据进行序列化,
<2>然后通过分区器(partitioner)确定该数据需要发送的Topic的分区,
   kafka提供了一个默认的分区器,
   如果消息指定了key,那么会根据key的hash(哈希)值来确定目标分区,
   如果没有指定key,那么将使用`轮询`的方式确定目标分区,
   这样可以最大程度的均衡每个分区的消息负载,
   确定分区之后,将会进一步确认该分区的leader节点(处理该分区消息读写的主节点),
   消息会进入缓冲池进行缓冲,然后等消息到达一定数量或者大小后进行批量发送
   

同步/异步发送消息

<1>同步发送消息
   优点:可以保证每条消息准确无误的写入了broker,
        对于立即需要发送结果的情况非常适用,
        在producer故障或者宕机的情况也可以保证结果的正确性。
        可以避免消息丢失。

   缺点:由于同步发送需要每条消息都需要及时发送到broker,
        没有缓冲批量操作,性能较低,吞吐量小。

<2>异步发送消息
   优点:可以通过缓冲池对消息进行缓冲,然后进行消息的批量发送,
        大量减少了和broker的交互频率,性能极高,可以通过回调机制获取发送结果.
        吞吐量大。

   缺点:在producer直接断电或者重启等故障,将有可能丢失消息发送结果,
        对消息准确性要求很高的场景不适用

producer使用非常简单

1.初始化生产者对象,配置KafkaProducer()类的参数 
  producer = KafkaProducer(bootstrap_servers=['host1:9092','host2:port']) 
2.构造ProducerRecord消息  #msg = xxxxx
3.调用send方法进行发送  #producer.send('topic_1',msg)
4.最后关闭producer资源 #producer.close()

消息如何分区partition

生产者在在往kafka消息队列发送(send)消息的时候,可以指定topic。
那么如何指定分区呢?

<1>消息分区策略:
   关于 partition 值的计算,分为三种情况:
   1)指明 partition 的情况下,直接将指定的值直接作为 partiton 值;
      但是客户端在指定分区信息时需要考虑数据均衡问题。
   
   2)没有指明 partition 值但有 key 的情况下,
     将 key 的 hash 值与 topic 的 partition 数进行取余得到 partition 值;
   
   3)既没有 partition 值又没有 key 值的情况下,
     第一次调用时随机生成一个整数(后面每次调用在这个整数上自增),
     将这个值与 topic 可用的 partition 总数取余得到 partition 值,
     也就是常说的轮询算法。
            
 <2>自定义分区策略
    可以通过实现org.apache.kafka.clients.producer.Partitioner自定分区策略,
    在构造KafkaProducer是配置参数partitioner.class为自定义的分区类即可

单线程代码实例

from kafka import KafkaProducer

# 通过KafkaProducer类,实例化一个生产者对象
producer = KafkaProducer(bootstrap_servers=['host1:9092','host2:port'])  
#此处ip可以是多个,
#bootstrap_servers = ['0.0.0.1:9092','0.0.0.2:9092','0.0.0.3:9092' ]

for i in range(3):
    msg = "msg%d" % i
    producer.send('topic_1',msg)  # 把消息写到topic中去,test是topic_1
producer.close()

多线程代码实例

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import random
import time
import threading
from kafka import KafkaProducer
from traitlets import log

# 回调函数(成功)
def on_send_success(record_metadata):
    print(record_metadata.topic)
    print(record_metadata.partition)
    print(record_metadata.offset)

# 回调函数(失败)
def on_send_error(excp):
    log.error('I am an errback',exc_info=excp)
    # handle exception

def send_messag(topic,id,interval_time):
    producer = KafkaProducer(bootstrap_servers=['cdh01:9092',
                                                'cdh02:9092',
                                                'cdh03:9092'])
    while True:
        startTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())

        message = "|".join((id,startTime,str(interval_time)))

        producer.send(topic,message.encode("utf-8")).add_callback(on_send_success).add_errback(on_send_error)
        # 间隔多长时间生产一条消息
        time.sleep(interval_time)

interval_times = [60,300,600,1200,1800,3600]

# 开启50个线程
for i in range(0,50):
    deviceId = "%06d" % random.randint(0,999999)

    interval_time = times[random.randint(0, len(interval_times) - 1)]

    recv_thread = threading.Thread(target=send_messag, args=("my-topic", id, interval_time))
    
    # 守护主线程,主线程退出后子线程直接销毁
    recv_thread.setDaemon(True)
    recv_thread.start()

while True:
    #主线程一直运行
    time.sleep(5000)