Fork me on GitHub

python操作kafka

kafka消息队列

  • 当数据量大到一定程度,我们用kafka做消息中间件为了是实现高可用,多副本(避免数据丢失),高并发(同时支持多个客户端读写)。
  • kafka本身是用scala语言编写,生产者比如我们nginx,Flume(日志),dataX,web程序等。我们消费者我们可以用python程序,SparkStreaming,Java程序,Flink等,而kafka数据消费需要记录消费的偏移量。

1.kafka特点

1.解耦:
  允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
2.冗余:
  消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的"插入-获取-删除"范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。
3.扩展性:
  因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。
4.灵活性 & 峰值处理能力:
  在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
5.可恢复性:
  系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
6.顺序保证:
  在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。(Kafka 保证一个 Partition 内的消息的有序性)
7.缓冲:
  有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。
8.异步通信:
  很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。

2.kafka架构


3.kafka角色

1.producer:
  消息生产者,发布消息到 kafka 集群的终端或服务。
2.broker:
  kafka 集群中安装Kafka的服务器。
3.topic:
  每条发布到 kafka 集群的消息属于的类别,即 kafka 是面向 topic 的(相当于数据库中的表)
4.partition:
  partition 是物理上的概念,每个 topic 包含一个或多个 partition。kafka 分配的单位是 partition。partition分区越多并发越强,分区分为leader分区和follower分区,分区的leader和follower是zk选举的。
5.consumer:
  从 kafka 集群中消费消息的终端或服务。从指定Topic的leader分区拉取数据,消费者会管理偏移量(记录数据读取位置,避免重复读取)
6.Consumer group:
  high-level consumer API 中,每个 consumer 都属于一个 consumer group,每条消息只能被 consumer group 中的一个 Consumer 消费,但可以被多个 consumer group 消费。
7.replica:
  partition 的副本,保障 partition 的高可用。
8.leader:
  replica 中的一个角色, producer 和 consumer 只跟 leader 交互。
9.follower:
  replica 中的一个角色,从 leader 中复制数据。
10.zookeeper:
  kafka 通过 zookeeper 来存储集群的 meta 信息

注意:数据如果被消费了,Kafka中数据并没有删除,只是标记被哪个消费者组消费了。

4.Kafka集群安装

  • 准备3台机器

    10.0.0.134 linux01
    10.0.0.131 linux02
    10.0.0.132 linux03
    # zk集群需要安装奇数个3台或5台... kafka不需要安装那么多
    
  • 安装jdk 自行百度

  • 安装zookeeper集群,并且启动,安装zookeeper集群

  • 准备号kafka安装包,拖拽到虚拟机,解压到/bigdata/目录下

    tar -zxvf kafka_2.11-1.1.1.tgz -C /bigdata/
    
  • 配置server.properties

    vi /bigdata/kafka_2.11-1.1.1/config/server.properties
    
    每台机器配置唯一的broker.id
    broker.id = 1
    log.dirs kafka存放数据目录
    num.partitions 分区数量
    log.retention.hours 保留数据时间
    zookeeper.connect 配置zookeeper
    delete.topic.enable=true # 生产环境不允许删除topic数据,测试环境可以设置true
    # linux01配置如下
    broker.id = 1
    log.dirs=/data/kafka
    zookeeper.connect=linux01:2181,linux02:2181,linux03:2181
    num.partitions=2
    log.retention.hours=168
    delete.topic.enable=true
    # scp到其他机器
    scp -r /bigdata/kafka_2.11-1.1.1/ linux02:$CMD
    scp -r /bigdata/kafka_2.11-1.1.1/ linux03:$CMD
    # 更改linux02,linxu03 的brker.id
    

5.kafka启动

  • 指定server.properties 后台启动
/bigdata/kafka_2.11-1.1.1/bin/kafka-server-start.sh -daemon /bigdata/kafka_2.11-1.1.1/config/server.properties 
  • 其他机器一样
# 日志查看
less /bigdata/kafka_2.11-1.1.1/logs/server.log
  • 这样kafka集群启动成功

  • 进入zookeeper查看

ls / # 可以看到多了很多节点



[zk: localhost:2181(CONNECTED) 1] ls /brokers
[ids, topics, seqid]# ids linux01,linux02,linux03节点信息。 topics表
[zk: localhost:2181(CONNECTED) 2] ls /brokers/ids
[1, 2, 3]

# 查看ids 为1 的信息 也就是linux01信息
[zk: localhost:2181(CONNECTED) 3] get /brokers/ids/1
{"listener_security_protocol_map":{"PLAINTEXT":"PLAINTEXT"},"endpoints":["PLAINTEXT://linux01:9092"],"jmx_port":-1,"host":"linux01","timestamp":"1617293687406","port":9092,"version":4}
cZxid = 0x1900000035
ctime = Fri Apr 02 00:14:46 CST 2021
mZxid = 0x1900000035
mtime = Fri Apr 02 00:14:46 CST 2021
pZxid = 0x1900000035
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x3788e3497850000
dataLength = 184
numChildren = 0

  • shell命令
# 1.查看有多少个topic
/bigdata/kafka_2.11-1.1.1/bin/kafka-topics.sh --list --zookeeper linux01:2181
	# linux01:2181 指定zk地址
# 2.创建一个topic
/bigdata/kafka_2.11-1.1.1/bin/kafka-topics.sh --zookeeper localhost:2181 --create --topic wordcount --replication-factor 3 --partitions 3
#  创建名为wordcount 的 topic
# --replication-factor 保存3份副本
# --partitions 指定分区3个
# 3.生产者
/bigdata/kafka_2.11-1.1.1/bin/kafka-console-producer.sh --broker-list linux01:9092,linux02:9092,linux03:9092 --topic wordcount
# 消费者
/bigdata/kafka_2.11-1.1.1/bin/kafka-console-consumer.sh --bootstrap-server linux01:9092,linux02:9092,linux03:9092 --topic wordcount --from-beginning
# --from-beginning 从头开始消费,如果不指定会从消费者启动后开始消费

#/bigdata/kafka_2.11-1.1.1/bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic wordcount
# Topic  topic名称
# PartitionCount  分区数量
# ReplicationFactor  副本数量
# Partition 哪个分区
# Leader: broker上leader是谁
# Replicas 副本存放位置
# Isr 同步顺序
  • 总结:
1.Kafka的生成者直接向Broker的Leader分区写入数据,不需要连接Zookeeper
2.Kafka的消费者(老版本API需要连接Zookeeper,获取Broker信息和偏移量),新的Api不需要连接Zookeeper。

6.客户端连接

  • 客户端连接之前需要更改server.properties配置
listeners=PLAINTEXT://<本机IP>:9092
advertised.listeners=PLAINTEXT://<本机IP>:9092
zookeeper.connect=localhost:2181改为zookeeper.connect=<集群中机器IP>:2181
e.g.:zookeeper.connect=10.0.0.70:2181,10.0.0.71:2181,10.0.0.72:2181

7.python客户端操作kafka

  • 这里介绍pykafka,pykafka安装
pip install pykafka
  • 消费者
class KafkaConsumer(object):
    def __init__(self, hosts, topic):
        self.client = KafkaClient(hosts=hosts)
        self.topic = self.client.topics[topic.encode()]
    def simple_consumer(self, offset=0):
        """
        指定消费
        :param offset:
        :return:
        """
        partitions = self.topic.partitions
        print("分区:", partitions)
        print("last_offset", self.topic.latest_available_offsets())
        # 自动提交消费
        consumer = self.topic.get_simple_consumer(b"simple_consumer",
                                                  partitions=[partitions[1]], auto_commit_enable=True,auto_commit_interval_ms=1)
        # for i in range(100):
        #
        #     message = consumer.consume()
        #     print(message.value.decode())
        #     # 当前消费分区offset情况
        #     print("offset_list = ",consumer.held_offsets)# {1: 5}  key为分区, 第几个




    def balance_consumer(self, offset=0):
        """
        balance consumer 消费kafka,无重复消费
        :param offset:
        :return:
        """
        # managed=True 设置后,使用新式reblance分区方法,不需要使用zk,而False是通过zk来实现reblance的需要使用zk
        consumer = self.topic.get_balanced_consumer(b"balance_consumer", managed=True, auto_commit_enable=True,auto_commit_interval_ms=1)
        partitions = self.topic.partitions
        print("partitions", partitions)
        earliest_offsets = self.topic.earliest_available_offsets()
        print("earliest_offsets", earliest_offsets)
        last_offsets = self.topic.latest_available_offsets()
        print("最近可用offset {}".format(last_offsets))
        offset = consumer.held_offsets
        print("当前消费者分区offset情况{}".format(offset))
        while True:
            msg = consumer.consume()
            offset = consumer.held_offsets
            print("{}, 当前消费者分区offset情况{}".format(msg.value.decode(), offset))
    def balance_consumer_by_id(self):
        """
        根据consumer_id消费
        :return:
        """
        consumer = self.topic.get_simple_consumer(consumer_group=b'test_group', auto_commit_enable=True,
                                             auto_commit_interval_ms=1, consumer_id=b'test_id')

        for message in consumer:
            if message is not None:
                print(message.offset, message.value.decode('utf-8'))

  • 生产者
class KafkaProducter(object):
    def __init__(self, hosts, topic):
        self.client = KafkaClient(hosts=hosts)
        self.topic = self.client.topics[topic.encode()]
    def get_all_partitions(self,):
        """
        查看所有分区
        :return:
        """
        print("所有分区:", self.topic.partitions)
        return self.topic.partitions
    def get_earliest_available_offsets(self):
        """
        获取最早可用的offset
        :return:
        """
        return self.topic.earliest_available_offsets()


    def get_partition_offset(self):
        """
        获取所有分区最后一次offsets
        :return:
        """
        all_last_offset = self.topic.latest_available_offsets()
        return all_last_offset
    def get_producer(self, data):
        """
        同步生产数据
        :param data:
        :return:
        """
        p = self.topic.get_producer(sync=True)
        p.produce(data.encode())
    def producer_designated_partition(self, data):
        """
        往指定分区写入消息
        :return:
        """
        def assign_patition(pid, key):
            """
            指定特定分区, 这里测试写入第一个分区(id=0)

            需要在获取生产者的时候指定选区函数,
            并且在生产消息的时候额外指定一个key

            :param pid: 为分区列表
            :param key:
            :return:
            """
            print("为消息分配partition {} {}".format(pid, key))
            return pid[0]
        p = self.topic.get_producer(sync=True, partitioner=assign_patition)
        p.produce(data.encode(), partition_key=b"partition_key_0")

        def async_produce_message(self, topic):
            """
            异步生产消息,消息会被推到一个队列里面,
            另外一个线程会在队列中消息大小满足一个阈值(min_queued_messages)
            或到达一段时间(linger_ms)后统一发送,默认5s
            :return:
            """
            topic = self.client.topics[topic.encode()]
            last_offset = topic.latest_available_offsets()
            print("最近的偏移量 offset {}".format(last_offset))

            # 记录最初的偏移量
            old_offset = last_offset[0].offset[0]
            p = topic.get_producer(sync=False, partitioner=lambda pid, key: pid[0])
            p.produce(str(time.time()).encode())
            s_time = time.time()
            while True:
                last_offset = topic.latest_available_offsets()
                print("最近可用offset {}".format(last_offset))
                if last_offset[0].offset[0] != old_offset:
                    e_time = time.time()
                    print('cost time {}'.format(e_time - s_time))
                    break
                time.sleep(1)

    def async_produce_message(self):
        """
        异步生产消费
        :return:
        """
        last_offset = self.get_partition_offset()
        print("最近的偏移量 offset {}".format(last_offset))
        # print(self.get_earliest_available_offsets())
        # 获取某个分区的offset偏移量
        old_offset = last_offset[1].offset[0]
        print("0分区偏移量:", old_offset)
        p = self.topic.get_producer(sync=False, partitioner=lambda pid, key: pid[1])
        p.produce(str(time.time()).encode())
        s_time = time.time()
        while True:
            last_offset = self.get_partition_offset()
            print("最近可用offset", last_offset)
            print("old_offset,,,,,old_offset", old_offset)
            if last_offset[1].offset[0] != old_offset:
                e_time = time.time()
                print('cost time {}'.format(e_time - s_time))
                break
            time.sleep(1)
    def get_produce_message_report(self):
        """
        查看异步发送消报告,默认会等待5s后才能获得报告
        """
        last_offset = self.topic.latest_available_offsets()
        print("最近的偏移量 offset {}".format(last_offset))
        p = self.topic.get_producer(sync=False, delivery_reports=True, partitioner=lambda pid, key: pid[0])
        p.produce(str(time.time()).encode())
        s_time = time.time()
        delivery_report = p.get_delivery_report()
        e_time = time.time()
        print('等待{}s, 递交报告{}'.format(e_time - s_time, delivery_report))
        last_offset = self.topic.latest_available_offsets()
        print("最近的偏移量 offset {}".format(last_offset))
  • 参考

https://blog.csdn.net/zzq900503/article/details/91454185

posted @ 2021-05-17 17:06  是阿凯啊  阅读(4658)  评论(0编辑  收藏  举报