生产者-消费者模型是系统架构中最常用的一种模型了,它在对于降低耦合度方面有着极大的作用。而一条消息从生产者出发到被消费者接受的过程中,是由消息队列来管理的。而消息队列就是用来对消息进行存储和分配,在多个生产者和消费者同时工作时,还要考虑读写冲突等线程安全问题。所以说,消息队列对于生产者-消费者模型的稳定性和可靠性方面起着至关重要的作用。对于这样一种经典的模型,消息队列的开源框架自然不在少数,例如经典的RabbitMQ和ActiveMQ,它们都在长期的实践运用中证明了自己的价值。
Kafka本质上也属于一个消息队列,但是设计初衷是作为轻量级,高吞吐的日志处理系统。因此与传统的消息队列存在一定的差异。
Kafka由生产者,消费者和集群构成。其中,在集群中,是通过Zookeeper来进行相互协作的。而其提供的生产者和消费者实现中,也是由ZooKeeper来交互并保存状态信息。结构如下图所示:
在Kafka中,消息是按照Topic
这个概念来进行分类的。不论生产还是订阅消息的时候,都要定义好Topic,而读写的消息也只会针对这个Topic。
为了利用分布式系统,实现负载均衡,对于每个Topic又定义了partition
的概念。顾名思义,partition就是将一组topic的消息分成多个组进行存储。这些partition会存放在不同的server上,从而避免某一个server存放了过多的消息。而对于消息分配到哪一个partition,则是由生产者来定义的。而生产者可以通过实现round-robin等方式来设定一个消息所对应的分区,当然,糟糕的生产者也可以破坏这一平衡。
在可靠性方面,kafka提供了replication选项来对消息进行备份,备份的对象是每一个partition。有意思的是,备份并不是强制的,备份的数量也是可控的,使用者可以根据业务需求和服务器的存储空间来决定这些参数。
可以看出,Kafka相对于其他成熟的MQ来说,有许多功能尚不具备。例如,kafka并没有提供JMS中的”事务性”“消息确认机制”“消息分组”等企业级特性。但是,Kafka的高吞吐量特性非常适合于类似日志处理等高速生产和消费的业务。同时,在并行化方面,kafka的批量式订阅也能提供很多方便的操作。总而言之,如果业务功能相对简单,且对少量的消息丢失可以容忍的情况下,kafka还是非常适合的。
kafka本身不存在任何安装过程,下载好release版本并解压后,就可以直接通过bin文件夹下的脚本进行操作了。下载地址可以从这个网址获取:https://www.apache.org/dyn/closer.cgi?path=/kafka/0.9.0.0/kafka_2.11-0.9.0.0.tgz
其中,config/server.properties给出了一个集群配置信息的示例,对于一些常用选项也进行了注释。其中,比较重要的属性有broker.id,listeners,zookeeper.connect,log.dirs等。其中,broker.id表示当前server的id;listener用来定义当前server绑定的端口;zookeeper.connect是用来定义zookeeper的信息,如果之前zookeeper的默认端口没改且安装在本机,zookeeper.connect也可以保持默认。
如果想要在一台机器上开启多个集群进程,实现本机分布式的效果,可以定义多个server.properties。只需要保持broker.id和listner端口唯一就可以了。一下命令就开启了三个kafka进程:
bin/kafka-server-start.sh config/server.properties > log/server0.log 2>&1 & # port 9092
bin/kafka-server-start.sh config/server-1.properties > log/server1.log 2>&1 & # port 9093
bin/kafka-server-start.sh config/server-2.properties > log/server2.log 2>&1 & # port 9094
kakfka常用操作包括:
bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 --partitions 3 --topic my-topic
bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic my-replicated-topic
bin/kafka-topics.sh --list --zookeeper localhost:2181
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic my-topic
bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic my-topic --from-beginning
其他更多操作可以查看官网说明,或者bin下的脚本都有帮助功能。
Kafka是为了消息持久化设计的,可能也真因为如此,它并不提供消息删除机制。而在测试时,想要清空现有的消息,最好的办法就是创建一个测试的topic,然后在需要的时候删除这个topic,再重建就可以了。
但是在实际删除过程中,会发现kafka会给予提示:
Topic test-group is marked for deletion.
Note: This will have no impact if delete.topic.enable is not set to true.
意思是topic并没有被真正意义上的删除,只是加了个标记而已。想要彻底删除一个topic,则必须在配置文件(config/server.properties,config/server-1.properties,config/server-2.properties)中添加delete.topic.enable=true这样一行,然后重启kafka进程就可以了。
当尝试在另一台机器上向本机发送消息或者接受消息的时候,会发现连接超时。特别的,当使用消费者模式连接过来的时候,从日志中可以看出,消费者正在尝试连接localhost的kafka端口。而这时kafka是在另一台机器上运行的,自然就没有办法连接成功。
原来,在配置文件中有这样一个属性advertised.host.name,它的描述是这样写的:
Hostname to publish to ZooKeeper for clients to use. In IaaS environments, this may need to be different from the interface to which the broker binds. If this is not set, it will use the value for “host.name” if configured. Otherwise it will use the value returned from java.net.InetAddress.getCanonicalHostName()
原来,当生产者或消费者连接过来的时候,kafka会通过zookeeper发送一个目标ip,而这个ip就是发送和接受消息的地址。这样做的目的是,在IaaS体系下,收发消息的ip可能和当前的ip不同,所以client会根据这个advertised.host.name提供的ip来进行后续连接。
默认情况下,这个advertised.host.name会直接使用host.name的值,而host.name在默认情况下又是没有配置的,所以最终返回的信息为localhost。而对于client来说,localhost指向的是自己,不是kafka集群。
因此,在这里只需要设定advertised.host.name为kafka自身的ip即可。
Kafka自身提供了Java上实现的生产者和各个语言的消费者。就生产者而言,肯定是无法满足需求的。但是kafka本身也只是定义了一套协议,只要符合流程,任何语言都可以实现生产者和消费者的功能。当然,github上存在不少各个语言下,对kafka生产者和消费者的实现。
对于需要用python实现生产者的开发者来说,PyKafka是一个不错的选择。Python下还存在着另一个kafka工程,kafka-python,关于这两者的比较,可以参阅这个讨论。总得来说,pykafka在生产者的稳定性,消费者的平衡性方面都有优化处理,略胜一筹。