kafka

学习kafka必会名词

  1. producer:生产者,就是它来生产“鸡蛋”的。
  2. consumer:消费者,生出的“鸡蛋”它来消费。
  3. topic:你把它理解为标签,生产者每生产出来一个鸡蛋就贴上一个标签(topic),消费者可不是谁生产的“鸡蛋”都吃的,这样不同的生产者生产出来的“鸡蛋”,消费者就可以选择性的“吃”了。相当于“队列”
  4. broker:就是篮子了。
    kafka的架构
    kafka_第1张图片
    一个典型的Kafka包含若干Producer,若干broker(Kafka支持水平扩展,一般broker越多,吞吐量越高),若干Consumer Group,以及一个zookeeper集群,通过zookeeper管理集群配置,选举leader,Producer使用push将消息发送到broker,Consumer使用pull模式从broker订阅并消费消息。
    Kafka会为每一个Consumer Group保留一些metadata信息——当前消费的消息的position,也即offset。这个offset由Consumer控制。正常情况下Consumer会在消费完一条消息后递增该offset。

使用Consumer high level API时,同一Topic的一条消息只能被同一个Consumer Group内的一个Consumer消费,但多个Consumer Group可同时消费这一消息。
kafka_第2张图片

优化:

kafka是一个高吞吐量分布式消息系统,并且提供了持久化。其高性能的有两个重要特点:(1)利用了磁盘连续读写性能远远高于随机读写的特点;(2)并发,将一个topic拆分多个partition。
kafka作为一个集群运行在一个或多个服务器上。
kafka集群存储的消息是以topic为类别记录的。
每个消息(也叫记录record,我习惯叫消息)是由一个key,一个value和时间戳构成。

生产: 网络 —> pagecache(内存) —>磁盘
消费: 磁盘 —> 网络 (使用sendfile将磁盘数据直接拷贝到网卡发送缓冲区)
kafka读写的单位是partition,因此,将一个topic拆分为多个partition可以提高吞吐量。但是,这里有个前提,就是不同partition需 要位于不同的磁盘(可以在同一个机器)。如果多个partition位于同一个磁盘,那么意味着有多个进程同时对一个磁盘的多个文 件进行读写,使得操作系统会对磁盘读写进行频繁调度,也就是破坏了磁盘读写的连续性。

总结一下,kafka在虚拟机环境的优化有三点:
第一,组建较大集群,并保证同一个topic的不同partition位于不同虚拟机(所以在不同的磁盘)
第二,监控,对于消费过慢的partition(所在的broker),暂停写入(生产),等待消费
第三,将kafka安装在系统盘,数据盘(/opt)完全用于消息存储。数据盘上不安装其他服务

保留物理内存的1/2以上给系统,以便保证pagecache的分配。

# broker处理消息的最大线程数 处理网络io
num.network.threads=Ncpu+1
# broker处理磁盘IO的线程数
num.io.threads=2Ncpu-3Ncpu
// 提高producer写入吞吐量,需要定期批量写文件
#每当producer写入10000条消息时,刷数据到磁盘
log.flush.interval.messages=10000
# 每间隔1秒钟时间,刷数据到磁盘
log.flush.interval.ms=1000
//日志保留配置,定期清理
# 保留三天,也可以更短 
log.retention.hours=72
# 段文件配置1GB,有利于快速回收磁盘空间,重启kafka加载也会加快(如果文件过小,则文件数量比较多,
# kafka启动时是单线程扫描目录(log.dir)下所有数据文件)
log.segment.bytes=1073741824
//replica复制配置
num.replica.fetchers 配置多可以提高follower的I/O并发度,单位时间内leader持有跟多请求,相应负载会增大,需要根据机器硬件资源做权衡
replica.fetch.min.bytes=1  默认配置为1字节,否则读取消息不及时
replica.fetch.max.bytes= 5  * 1024 * 1024 默认为1MB,这个值太小,5MB为宜,根据业务情况调整
replica.fetch.wait.max.ms  follow拉取频率,频率过高,会导致cpu飙升,因为leader无数据同步,leader会积压大量无效请求情况

分布式:
每个分区在Kafka集群的若干服务中都有副本,这样这些持有副本的服务可以共同处理数据和请求,副本数量是可以配置的。副本使Kafka具备了容错能力。
每个分区都由一个服务器作为“leader”,零或若干服务器作为“followers”,leader负责处理消息的读和写,followers则去复制leader.如果leader down了,followers中的一台则会自动成为leader。集群中的每个服务都会同时扮演两个角色:作为它所持有的一部分分区的leader,同时作为其他分区的followers,这样集群就会据有较好的负载均衡。

包括四大核心接口

Producer API允许了应用可以向Kafka中的topics发布消息;
Consumer API允许了应用可以订阅Kafka中的topics,并消费消息;
Streams API允许应用可以作为消息流的处理者,比如可以从topicA中消费消息,处理的结果发布到topicB中;
Connector API提供Kafka与现有的应用或系统适配功能,比如与数据库连接器可以捕获表结构的变化;

kafka_第3张图片
topic:对消息的归纳。就像消息队列,生产者写入消息,消费者读取消息,topic支持多个生产者或者消费者同时订阅,topic由多个partition组成,每个partition消息都有序,topic由多个partition,系统根据算法分配到指定分区,如果需要所有消息都有序,最好只用一个分区。Topic分成一个或多个Partition,每个Partition在物理上对应一个文件夹,该文件夹下存储这个Partition的所有消息和索引文件。
kafka_第4张图片
不同消费者对同一分区的消息读取互不干扰,消费者可以通过设置消息位移(offset)来控制自己想要获取的数据,比如可以从头读取,最新数据读取,重读读取等功能。
kafka_第5张图片
Topic被分为四个分区(P0-P4)分别被分配在两个节点上,另外还有两个消费者组(GA,GB),其中GA有两个消费者实例,GB有四个消费者实例。
kafka_第6张图片
上面可以看出topic有一个原则就是:
若消费者数小于partition数,且消费者数为一个,那么它就消费所有消息;
若消费者数小于partition数,假设消费者数为N,partition数为M,那么每个消费者能消费的分区数为M/N或M/N+1;
若消费者数等于partition数,那么每个消费者都会均等分配到一个分区的消息;
若消费者数大于partition数,则将会出现部分消费者得不到消息分区,出现空闲的情况;

数据持久化
Kafka也不是partition一有数据就立马将数据写到磁盘上,它会先缓存一部分,等到足够多数据量或等待一定的时间再批量写入(flush)。也就是说先写缓存,然后数据量足够大了再批量写入磁盘。
生产者消费者都是跟主分区互动,备份分区不做读写,如果有一个挂壁了,那就选举出备份分区
kafka_第7张图片
kafka_第8张图片
消费者组可以一次取三个数据量
kafka_第9张图片
有这么几种可能的delivery guarantee:

At most once 消息可能会丢,但绝不会重复传输
At least one 消息绝不会丢,但可能会重复传输
Exactly once 每条消息肯定会被传输一次且仅传输一次,很多时候这是用户所想要的。  
  当Producer向broker发送消息时,一旦这条消息被commit,因数replication的存在,它就不会丢。但是如果Producer发送数据给broker后,遇到网络问题而造成通信中断,那Producer就无法判断该条消息是否已经commit。虽然Kafka无法确定网络故障期间发生了什么,但是Producer可以生成一种类似于主键的东西,发生故障时幂等性的重试多次,这样就做到了Exactly once。目前这一Feature还并未实现,有希望在Kafka未来的版本中实现。(所以目前默认情况下一条消息从Producer到broker是确保了At least once,可通过设置Producer异步发送实现At most once)。
读完消息先commit消费状态(保存offset)再处理消息。这种模式下,如果Consumer在commit后还没来得及处理消息就crash了,下次重新开始工作后就无法读到刚刚已提交而未处理的消息,这就对应于At most once

读完消息先处理再commit消费状态(保存offset)。这种模式下,如果在处理完消息之后commit之前Consumer crash了,下次重新开始工作时还会处理刚刚未commit的消息,实际上该消息已经被处理过了。这就对应于At least once。在很多使用场景下,消息都有一个主键,所以消息的处理往往具有幂等性,即多次处理这一条消息跟只处理一次是等效的,那就可以认为是Exactly once。

如果一定要做到Exactly once,就需要协调offset和实际操作的输出。经典的做法是引入两阶段提交。如果能让offset和操作输入存在同一个地方,会更简洁和通用。这种方式可能更好,因为许多输出系统可能不支持两阶段提交。比如,Consumer拿到数据后可能把数据放到HDFS,如果把最新的offset和数据本身一起写到HDFS,那就可以保证数据的输出和offset的更新要么都完成,要么都不完成,间接实现Exactly once。

如果消费者组中的某个消费者挂了,那么其中一个消费者可能就要消费两个partition了
如果只有三个partition,而消费者组有4个消费者,那么一个消费者会空闲
如果多加入一个消费者组,无论是新增的消费者组还是原本的消费者组,都能消费topic的全部数据。(消费者组之间从逻辑上它们是独立的)

生产者发送到一个特定的Topic的分区上,消息将会按照它们发送的顺序依次加入,也就是说,如果一个消息M1和M2使用相同的producer发送,M1先发送,那么M1将比M2的offset低,并且优先的出现在日志中。
消费者收到的消息也是此顺序。
如果一个Topic配置了复制因子(replication factor)为N, 那么可以允许N-1服务器宕机而不丢失任何已经提交(committed)的消息。

zookeeper是kafka的一个重要依赖:
探测broker和consumer的添加或移除。
负责维护所有partition的领导者/从属者关系(主分区和备份分区),如果主分区挂了,需要选举出备份分区作为主分区。
维护topic、partition等元配置信息
kafka_第10张图片

目前生产者发送消息(request.required.acks)有三种方式。
acks = 0: producer不会等待broker发送ack ,因为发送消息网络超时或broker crash(1.Partition的Leader还没有commit消息 2.Leader与Follower数据不同步),既有可能丢失也可能会重发。
acks = 1: 当leader接收到消息之后发送ack,丢会重发,丢的概率很小
acks = -1: 当所有的follower都同步消息成功后发送ack. 丢失消息可能性比较低。
Kafka中有两种consumer接口,分别为Low-level API和High-levelAPI

(1). Low-level API SimpleConsumer
这套接口比较复杂的,使用者必须要考虑很多事情,优点就是对Kafka可以有完全的控制。

(2). High-level API ZookeeperConsumerConnector
High-level API使用比较简单,已经封装了对partition和offset的管理,默认是会定期自动commit offset,这样可能会丢数据的,因为consumer可能拿到数据没有处理完crash。 High-level API接口的特点,自动管理,使用简单,但是对Kafka的控制不够灵活。

Kafka的客户端缓冲机制
也就是说,消息会先写入一个内存缓冲中,然后直到多条消息组成了一个Batch,才会一次网络通信把Batch发送过去。
kafka_第11张图片
改善如下
kafka_第12张图片
那么此时有人说了,如果我现在把一个缓冲池里的内存资源都占满了,现在缓冲池里暂时没有内存块了,怎么办呢?
很简单,阻塞你的写入操作,不让你继续写入消息了。把你给阻塞住,不停的等待,直到有内存块释放出来,然后再继续让你写入消息。

总结

  1. Kafka天然是分布式的,往一个topic丢数据,实际上就是往多个broker的partition存储数据
  2. Kafka会将partition以消息日志的方式(落磁盘)存储起来,通过 顺序访问IO和缓存(等到一定的量或时间)才真正把数据写到磁盘上,来提高速度。
  3. Kafka会将数据写到partition,单个partition的写入是有顺序的。如果要保证全局有序,那只能写入一个partition中。如果要消费也有序,消费者也只能有一个。
  4. 凡是分布式就无法避免网络抖动/机器宕机等问题的发生,很有可能消费者A读取了数据,还没来得及消费,就挂掉了。Zookeeper发现消费者A挂了,让消费者B去消费原本消费者A的分区,等消费者A重连的时候,发现已经重复消费同一条数据了。
  5. 很多问题,先看看能不能通过现有配置解决掉(学多了框架,你就会发现很多官方的就已经支持解决了,你做的可能改改配置/参数就完事了)
  6. 顺序保证,保证数据会按照特定顺序取处理,避免了数据不一致的情况。
  7. 缓冲,消息队列通过缓冲层来帮助任务最高的执行,写入队列的处理会尽可能快速。
  8. 异步通信,很多时候,用户不需要立即处理,可以把消息先放到队列,在需要的时候就可以处理。

要实现Kafka HA,要将replica均匀分布到整个集群上,topic的Partition数量大于Broker数量,Kafka分配Replica的算法如下:

  1. 将所有Broker(假设共n个Broker)和待分配的Partition排序
  2. 将第i个Partition分配到第(i mod n)个Broker上
  3. 将第i个Partition的第j个Replica分配到第((i + j) mode n)个Broker上
    消息传递同步策略
    Producer再发布消息到某个Partition时,先通过ZooKeeper找到并发送消息到Partition的Leader,leader会将消息写入本地的log,灭个Follower都从Leader pull数据。Follower存储的数据顺序与Leader能保持一致。Follower接收消息并写入后,给Leader发送ACK,Leader收到了ISR(ISR:同步副本列表)所有的Replica的ACK,消息就被commit了。为了提高性能,每个Follower再接收到数据后,立马向Leader发送ACK,而非等到数据写入Log中,对于已经commit的消息,Kafka只能保证它被存于多个Replica的内存中,而不能保证它们被持久化到磁盘中,也就不能完全保证异常发生后该条消息一定能被Consumer消费。Consumer读消息也是从Leader读取,只有被commit过的消息才会暴露给Consumer。
    kafka_第13张图片
    对于Kafka而言,定义一个Broker是否“活着”包含两个条件:
    一是它必须维护与ZooKeeper的session(这个通过ZooKeeper的Heartbeat机制来实现)。
    二是Follower必须能够及时将Leader的消息复制过来,不能“落后太多”。
    Leader会跟踪与其保持同步的Replica列表,该列表称为ISR(即in-sync Replica)。如果一个Follower宕机,或者落后太多,Leader将把它从ISR中移除。这里所描述的“落后太多”指Follower复制的消息落后于Leader后的条数超过预定值。

log end offset (LEO),表示log中最后的message,每个replica partition存储的最后一条消息的offset。
high watermark (HW),表示已经被commited的message,HW以下的数据都是各个replicas间同步的,一致的。而以上的数据可能是脏数据,部分replica写成功,但最终失败了
flushed offset,前面说了为了效率message不是立刻被flush到disk的,而是periodically的flush到disk,所以这个offset表示哪些message是在disk上persisted的
broker
ack设置为0的情况下,类似于客户端进行了一次one way的rpc操作,不需要等待broker端的response。

针对ack为1和-1的情况,下面做些分析:
broker端接收到producer端发送过来的消息,先往leader partition上顺序写入这条消息,更新LEO和HW。
针对ack为1的情况,直接response给Producer端。
针对ack为-1的情况,创建一个延迟操作(DelayedProduce),延迟操作有个超时时间timeout.ms。
client找到leader,
写请求:
leader写入本地log,然后每个followers通过socket channel获取更新,写入本地log,然后发送ack到leader ,leader发现已经收到所有follower发送的ack,表示message已经被committed,通知client,写成功
leader递增HW,并且定期广播HW到所有的followers,follower会定期去checkpoint HW数据,因为这个很重要,follower必须通过HW来判断那些数据是有效的(committed)
读请求:
从leader读,注意只有HW下的数据会被读到,即只有committed过的数据会被读到

Broker失效场景
毫无疑问,这里需要考虑容错的问题
follower失败,很简单,leader可以直接把这个follower drop掉
当follower comeback的时候,需要truncate掉HW以上的数据,然后和leader同步,完成后,leader会把这个follower加会ISR

leader失败比较复杂一些,在写请求不同的阶段分为3种cases,
真正写数据前,简单,client重发
数据写完后,简单,直接选个新leader,继续
数据写入一半,这个有点麻烦,client会超时重发,如果保证在某些replica上,相同message不被写两次

当leader失败的时候,需要重新选一个leader,ISR里面所有followers都可以申请成为leader
依赖zookeeper的分布式锁,谁先register上,谁就是leader
新的leader会将它的LEO作为新的HW,其他的follower自然需要truncate,追赶leader
4、延时分析
kafka之间是无法互相发现对方的,每个kafka向zk注册,说我是A节点(broker.id),我是B节点,这样组成了一个kafka集群。每个人通过zk来发现彼此。
Leader Election算法
Leader选举本质上是一个分布式锁,有两种方式实现基于ZooKeeper的分布式锁:
节点名称唯一性:多个客户端创建一个节点,只有成功创建节点的客户端才能获得锁
临时顺序节点:所有客户端在某个目录下创建自己的临时顺序节点,只有序号最小的才获得锁

一种非常常用的选举leader的方式是“Majority Vote”(“少数服从多数”),如果我们有2f+1个Replica(包含Leader和Follower),那在commit之前必须保证有f+1个Replica复制完消息,为了保证正确选出新的Leader,fail的Replica不能超过f个。因为在剩下的任意f+1个Replica里,至少有一个Replica包含有最新的所有消息。这种方式有个很大的优势,系统的latency只取决于最快的几个Broker,而非最慢那个。Majority Vote也有一些劣势,为了保证Leader Election的正常进行,它所能容忍的fail的follower个数比较少。如果要容忍1个follower挂掉,必须要有3个以上的Replica,如果要容忍2个Follower挂掉,必须要有5个以上的Replica。也就是说,在生产环境下为了保证较高的容错程度,必须要有大量的Replica,而大量的Replica又会在大数据量下导致性能的急剧下降。这就是这种算法更多用在ZooKeeper这种共享集群配置的系统中而很少在需要存储大量数据的系统中使用的原因。

Kafka在ZooKeeper中动态维护了一个ISR(in-sync replicas),这个ISR里的所有Replica都跟上了leader,只有ISR里的成员才有被选为Leader的可能。在这种模式下,对于f+1个Replica,一个Partition能在保证不丢失已经commit的消息的前提下容忍f个Replica的失败。在大多数使用场景中,这种模式是非常有利的。事实上,为了容忍f个Replica的失败,Majority Vote和ISR在commit前需要等待的Replica数量是一样的,但是ISR需要的总的Replica的个数几乎是Majority Vote的一半。

虽然Majority Vote与ISR相比有不需等待最慢的Broker这一优势,但是Kafka作者认为Kafka可以通过Producer选择是否被commit阻塞来改善这一问题,并且节省下来的Replica和磁盘使得ISR模式仍然值得。

如何处理所有Replica都不工作
在ISR中至少有一个follower时,Kafka可以确保已经commit的数据不丢失,但如果某个Partition的所有Replica都宕机了,就无法保证数据不丢失了。这种情况下有两种可行的方案:

1.等待ISR中的任一个Replica“活”过来,并且选它作为Leader
2.选择第一个“活”过来的Replica(不一定是ISR中的)作为Leader

kafka_第14张图片
Kafka中partitions数据一致性:
Kafka中Producer发送消息到Broker,Broker有三种返回方式,分别为noack、leader commit成功就ack、leader和follower同时commit成功才返回ack。第三种方式是数据强一致性。

Kafka中replication复制数据

Kafka的复制机制既不是完全的同步复制,也不是单纯的异步复制。完全同步复制要求All Alive Follower都复制完,这条消息才会被认为commit,这种复制方式极大的影响了吞吐率。而异步复制方式下,Follower异步的从Leader复制数据,数据只要被Leader写入log就被认为已经commit,这种情况下如果Follower都复制完都落后于Leader,而如果Leader突然宕机,则会丢失数据。而Kafka的这种使用ISR的方式则很好的均衡了确保数据不丢失以及吞吐率。Follower可以批量的从Leader复制数据,而且Leader充分利用磁盘顺序读以及send file(zero copy)机制,这样极大的提高复制性能,内部批量写磁盘,大幅减少了Follower与Leader的消息量差。
kafka_第15张图片

kafka_第16张图片
如何保证数据强一致性?
当Producer发送消息到leader partition所在Broker时,首先保证leader commit消息成功,然后创建一个“生产者延迟请求任务”,并判断当前partiton的HW是否大于等于logEndOffset,如果满足条件即表示本次Producer请求partition replicas之间数据已经一致,立即向Producer返回Ack。否则待Follower批量拉取Leader的partition消息时,同时更新Leader ISR中HW,然后检查是否满足上述条件,如果满足向Producer返回Ack。

kafka_第17张图片
内部的网络框架模型
Broker的内部处理流水线化,用自己的NIO框架,分为多个阶段来进行(SEDA),以提高吞吐量和性能,尽量避免Thead盲等待,以下为过程说明
Accept Thread负责与客户端建立连接链路,然后把Socket轮转交给Process Thread
Process Thread负责接收请求和响应数据,Process Thread每次基于Selector事件循环,首先从Response Queue读取响应数据,向客户端回复响应,然后接收到客户端请求后,读取数据放入Request Queue。
Work Thread负责业务逻辑、IO磁盘处理等,负责从Request Queue读取请求,并把处理结果放入Response Queue中,待Process Thread发送出去。

Kafka应用场景:

消息
kafka更好的替换传统的消息系统,消息系统被用于各种场景(解耦数据生产者,缓存未处理的消息,等),与大多数消息系统比较,kafka有更好的吞吐量,内置分区,副本和故障转移,这有利于处理大规模的消息。

根据我们的经验,消息往往用于较低的吞吐量,但需要低的端到端延迟,并需要提供强大的耐用性的保证。

在这一领域的kafka比得上传统的消息系统,如的ActiveMQ或RabbitMQ的。

网站活动追踪
kafka原本的使用场景:用户的活动追踪,网站的活动(网页游览,搜索或其他用户的操作信息)发布到不同的话题中心,这些消息可实时处理,实时监测,也可加载到Hadoop或离线处理数据仓库。

每个用户页面视图都会产生非常高的量。

指标
kafka也常常用于监测数据。分布式应用程序生成的统计数据集中聚合。

日志聚合
许多人使用Kafka作为日志聚合解决方案的替代品。日志聚合通常从服务器中收集物理日志文件,并将它们放在中央位置(可能是文件服务器或HDFS)进行处理。Kafka抽象出文件的细节,并将日志或事件数据更清晰地抽象为消息流。这允许更低延迟的处理并更容易支持多个数据源和分布式数据消费。

流处理
kafka中消息处理一般包含多个阶段。其中原始输入数据是从kafka主题消费的,然后汇总,丰富,或者以其他的方式处理转化为新主题,例如,一个推荐新闻文章,文章内容可能从“articles”主题获取;然后进一步处理内容,得到一个处理后的新内容,最后推荐给用户。这种处理是基于单个主题的实时数据流。从0.10.0.0开始,轻量,但功能强大的流处理,就可以这样进行数据处理了。

除了Kafka Streams,还有Apache Storm和Apache Samza可选择。

事件采集
事件采集是一种应用程序的设计风格,其中状态的变化根据时间的顺序记录下来,kafka支持这种非常大的存储日志数据的场景。

提交日志
kafka可以作为一种分布式的外部日志,可帮助节点之间复制数据,并作为失败的节点来恢复数据重新同步,kafka的日志压缩功能很好的支持这种用法,这种用法类似于Apacha BookKeeper项目。

kafka实战

<dependencies>
    <dependency>
      <groupId>org.apache.kafka</groupId>
      <artifactId>kafka_2.11</artifactId>
      <version>1.0.1</version>
    </dependency>
    <dependency>
      <groupId>org.apache.kafka</groupId>
      <artifactId>kafka-clients</artifactId>
      <version>1.0.1</version>
    </dependency>
  </dependencies>
  
//application.properties
#============== kafka ===================
# 指定kafka 代理地址,可以多个
spring.kafka.bootstrap-servers=127.0.0.1:9092

#=============== provider  =======================

spring.kafka.producer.retries=0
# 每次批量发送消息的数量
spring.kafka.producer.batch-size=16384
spring.kafka.producer.buffer-memory=33554432

# 指定消息key和消息体的编解码方式
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer

#=============== consumer  =======================
# 指定默认消费者group id
spring.kafka.consumer.group-id=test-consumer-group

spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.consumer.enable-auto-commit=true
spring.kafka.consumer.auto-commit-interval=100

# 指定消息key和消息体的编解码方式
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
//Producer
@Component
public class TestProducer {

    @Autowired
    private KafkaTemplate<String,String> kafkaTemplate;

    public void send(String msg){
        Message message = new Message();
        message.setId(System.currentTimeMillis());
        message.setMsg(msg);
        message.setSendTime(new Date());
        System.out.println("send: " + JSONObject.toJSONString(message));
        kafkaTemplate.send("test", JSONObject.toJSONString(message));

    }
}
//实体类
public class Message {
    private Long id;
    private String msg;
    private Date sendTime;
}
//发送消息接口
@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private TestProducer testProducer;

    @RequestMapping("/send")
    @ResponseBody
    public String send(@RequestParam(value = "msg")String msg){
        testProducer.send(msg);
        return "发送消息成功";
    }
}
//消费者
@Component
public class TestConsumer {

    @KafkaListener(topics = {"test"})
    public void receive(ConsumerRecord<?,?> record){

        Optional<?> kafkaMessage = Optional.ofNullable(record.value());
        if(kafkaMessage.isPresent()){
            Object message = kafkaMessage.get();
            System.out.println("receive record: " +record);
            System.out.println("receive message: "+message);
        }
    }
}

idea整合kafka示例 idea整合kafka

你可能感兴趣的:(技术总结)