阻塞队列
- java核心api的一个接口,BlockingQueue
- 解决线程通信的问题- 阻塞方法:put(存)、take(取)。
好处:满足生产者和消费者模式,线程1往队列中存数据,线程二从队列中拿数据;阻塞队列在生产者和消费值之间构建了一个桥梁,避免两个线程直接打交道(如果直接打交道,可能生产者生产速度远远快于消费的速度,生产者还在源源不断生产,没有被阻塞,系统的性能被白白浪费了;反过来消费的速度很快已经没数据了线程2还在等着消费,也浪费了资源);加入阻塞队列后,如果生产的快,当把Queue堆满后,线程1就阻塞了,不会占用系统资源;反过来,Queue空时线程2阻塞,等着从队列里取值,不占用cpu资源。
阻塞队列起到了一个缓冲的作用。
- 实现类
- ArrayBlockingQueue 数组进行数据存储,有界,必须指定队列大小。为了保证线程安全,采用的是ReentrantLock。- LinkedBlockingQueue用链表实现的有界阻塞队列,当构造对象时为指定队列大小时,队列默认大小为Integer.MAX_VALUE。- PriorityBlockingQueue、SynchronousQueue(只存单个元素)、DelayQueue等。
Kafka简介
Kafka是一个分布式的流媒体平台(存储、计算)
应用:消息系统、日志收集、用户行为追踪、流式处理(对数据流)
特点:
高吞吐量:可以处理TB级的海量数据,因为消息持久化
消息持久化:消息存在硬盘里,对硬盘顺序读写(性能高于对内存的随机读取)
高可靠性:分布式的,可以集群部署,一台挂了还有别的
高扩展性:一个集群,如果服务器不够用了再加一台很方便
作为传统消息队列的应用场景:缓存/消峰、解耦、异步通信
Kafka术语:
- Broker(kafka的服务器,集群中每一台服务器就是一个Broker)、Zookeeper(独立的软件,kafka内置;可以管理集群)
- Topic、Partition、Offset
- 消息队列实现的方式大致有两种,一种是点对点(比如阻塞队列),一个生产者一个消费者,每个数据只被一个消费者消费,消费完会删除数据;另一种是发布订阅模式,生产者把消息(多个主题)放到某个位置,可以有很多消费者关注订阅这个位置,读取消息;消息可以被多个消费者同时或先后读到。kafka采用发布订阅模式,比较灵活。存放消息的位置叫做Topic--主题。- Partition:分区,对topic做了分区,多个线程可以同时对多个区写数据,并发能力强。- - 每个分区从前往后追加数据- Offset:消息在分区内的索引
- Leader Replica 、Follower ReplicaReplica 副本,kafka是一个分布式的消息引擎,为了让数据更可靠,通过副本的方式存多份,每个分区有多个副本,提高容错。多个副本在Kafka集群的多个broker中。
Leader Replica主副本,从分区中获取数据时,主副本可以给,可以处理请求。
Follower Replica 随从副本,只是备份,不负责做响应。当主副本挂了,集群会从众多的从副本中选一个作为新的主副本。
常用命令:
Kafka和RabbitMQ
kafka是apache开源的消息队列顶级项目之一,在大数据场景下使用较多;Kafka一个topic支持多个生产者,一个topic支持多个消费者。
RabbitMQ:用于实时的,对可靠性要求较高的消息传递上。 kafka:用于处于活跃的流式数据,大数据量的数据处理上。kafka每秒可以处理几十万条消息。
在实际生产应用中,通常会使用kafka作为消息传输的数据管道,rabbitmq作为交易数据管道。
rabbitmq在金融场景中经常使用,具有较高的严谨性,数据丢失的可能性更小,同时具备更高的实时性;kafka优势主要体现在吞吐量上,虽然可以通过策略实现数据不丢失,但从严谨性角度来讲,大不如rabbitmq;而且由于kafka保证每条消息最少送达一次,有较小的概率会出现数据重复发送的情况;
rabbit为了更高的灵活性和信息安全性,放弃了吞吐量,kafka为了更多的吞吐量,选择了速度,放弃了部分安全性。
生产者
消息发送流程
在消息发送的过程中,涉及到两个线程:main线程和Sender线程
在main线程创建了一个双端队列RecordAccumulator
main线程将消息发送给RecordAccumulator,Sender线程不断从RecordAccumulator中拉取消息发送到 Kafka Broker
目的是希望把生产者的数据导入到Kafka集群中。
首先在main线程中创建了一个Producer对象,然后调用send方法来发送数据,发送数据的过程中可能会遇到拦截器(根据生产环境的业务需求是否增加,一般建议不用),通过序列化器对数据序列化,因为是跨节点通信,需要序列化(用自带的,不用Java的)。希望把数据发到不同的分区,通过分区器规定哪个数据发到哪个分区,首先发送到缓存队列RecordAccumulator里面,队列大小32M,每批次大小16k。
通过Sender线程主动拉取数据,两个条件满足一个(缓存队列中数据积累到批次大小16k或者延续时间到了)即可。每个分区的数据以结点的方式,每一个节点一个队列发送,如果broker没有及时应答最多缓存五个请求。通过selector把底层链路打通,作为输出流。集群收到数据后进行副本的同步,同步完毕后进行一个应答。应答有三种级别0、1、-1。应答成功后清掉对应的请求,同时清理掉分区的数据。如果失败,不断重试(默认重试int的最大值)。
生产者分区
分区的好处:1.便于合理使用存储资源,每个分区在一个Broker上存储,可以把海量的数据按照分区切割程一块一块数据存储在多台Broker上。合理控制分区的任务,可以实现负载均衡。2.提高并行度
分区策略
默认:
- 指明partition的情况下,直接将指明的值作为partition值;
- 没有指明partition值但有key,将key的hash值与topic的partition数进行取余得到partition值;
- 既没有partition值又没有key值,Kafka采用黏性分区,随机选择一个分区,并尽可能一直使用该分区。直到该分区batch满了,再随机一个分区。
还可以自定义分区器:
- 定义一个类实现Partitioner接口
生产者如何提高吞吐量
- 修改缓冲区大小 默认32M,修改为64
- 修改批次大小:默认16k,修改为32
- linger.ms:等待时间从0修改为5-100ms (从有就运修改为一次运很多,要注意和batch大小的平衡,避免延迟存了很多)
- 压缩:snappy,压缩后可以减小数据量,每批次拉取的数据量变多了
数据可靠性分析(数据不丢)
ACK应答级别:
- 0:生产者发送过来的数据,不需要等数据落盘应答。
可能会丢数,当Leader还没同步就挂了(很少使用)
- 1:生产者发送过来的数据,Leader收到数据后应答。
可能会丢数,leader同步,但副本没同步,leader挂了(丢数几率变小)(一般用于传输普通日志,允许丢个别数据)
- -1:生产者发送过来的数据,Leader和ISR队列里面的所有节点收齐数据后应答。(可靠性高,效率低)(用于传输对可靠性要求较高的场景,比如和钱相关的数据)
- 会有一个问题:所有Follower开始同步数据,但有一个故障了无法同步,怎么解决?
维护一个动态的ISR,和Leader保持同步的Follower+Leader集合,如果Follower长时间未向Leader发送通信请求或同步数据,则该Follower将被踢出ISR。时间阈值默认30s。这样就不用等长期联系不上或者已经故障的节点。
数据完全可靠条件 = ACK级别设置为-1 + 分区副本大于等于2 + ISR里应答的最小副本数量大于等于2
数据重复
在数据完全可靠条件下,follower同步完leader后的数据后,leader挂掉,升级为leader的follower已经有了数据又会再次接收数据造成重复。
- 至少一次:数据完全可靠条件 至少一次可以保证数据不丢失,但是不能保证数据不重复;
- 最多一次:ACK级别设置为0 最多一次可以保证数据不重复,不能保证数据不丢失。
- 精确一次:对于一些非常重要的信息,比如和钱相关的数据,要求数据既不能重复也不丢失。
引入幂等性和事务来解决。
- 幂等性:指Producer不论向Broker发送多少次重复数据,Broker端都只会持久化一条,保证不重复。
判断标准:具有<PID,Partition,SeqNumber>相同主键的消息提交时,broker只会持久化一条。其中,PID是Kafka每次重启会分配一个新的;partition是分区号;SeqNumber序列号是单调递增的
- 只能保证单分区单会话内不重复
- 开启参数enable.idempotence,默认开启
精确一次 = 幂等性 + 至少一次
- 事务(重启也能保证)
- 开启事务必须开启幂等性
Progucer在使用事务功能前,必须先自定义一个唯一的transactional.id,有了事务id,即使客户端挂掉了,重启后也能继续处理未完成的事务。
数据乱序
单分区内有序,分区与分区之间无序.(一个分区对应一个缓存队列。在生产者的双端缓存队列里一边进,一边出)
通过幂等性,Kafka服务端会缓存producer发来的最近5个request的元数据,无论如何,保证最近5个有序
kafka如何保证消息有序
1、可以设置topic 有且只有一个partition 2、根据业务需要,需要顺序的 指定为同一个partition
3、根据业务需要,比如同一个订单,使用同一个key,可以保证分配到同一个partition上
Broker
zookeeper
服务端存储kafka的相关信息:
1.记录了有哪些服务器 broker.ids
2.记录谁是Leader,isr信息(可以同步和已经同步的Follower+Leader集合)
3.辅助选举Leader的controller的信息
Broker总体工作流程
副本
**作用:**提高数据可靠性,默认副本1个, 生产环境一般配置2个,太多副本会增加磁盘存储空间,增加网络上数据传输,降低效率。
生产者只会把数据发给Leader,Follower找Leader同步数据
AR:Kafka分区中的所有副本统称为AR
AR = ISR + OSR
ISR:和Leader保持同步的Follower集合 + Leader
OSR:Follower与Leader同步时,延迟过多的副本(超过30s)
副本同步
LEO: 日志末端偏移量,表示下一次数据写入的offset位移值。
HW: 高水位,一个partition分区中ISR中最小的LEO。
对于HW, leader副本和follower副本只保存自身的HW,
对于LEO,follower副本只保存自身的,但是leader副本除了保存自身的,还会保存follower副本的
无论是leader副本所在的节点,还是follower副本所在的节点,分区都会保存所有的副本对象,但是只有本地副本才会有对应的日志文件。
consumer最多只能消费到HW所在的位置。另外每个replica都有HW,leader和follower各自负责更新自己的HW的状态。对于leader新写入的消息,consumer不能立刻消费,leader会等待该消息被所有ISR中的replicas同步后更新HW, 此时消息才能被consumer消费。这样就保证了如果leader所在的broker失效,该消息仍然可以从新选举的leader中获取。
Follower故障处理
Leader故障处理细节
Leader Partition 负载平衡
正常情况下,Kafka本身会自动把Leader partition均匀分散在各个机器上,保证每台机器的读写吞吐量都是均匀的。但是如果某些broker宕机,会导致Leader Partition过于集中在其他少部分几台broker上,造成集群负载不均衡。
通过设置auto.leader.rebalance.enable为true,让leader partition自动均衡,生产中建议关闭,因为会占用大量时间影响效率。
分区数只能增加不能减少。
只能手动增加副本因子,不能通过命令行
文件底层存储
Topic是逻辑上的概念,而partition是物理上的概念,每个分区对应于一个log文件。
Kafka采取分片和索引机制,将每个partition分为多个segment。每个segment包括.index文件,.log文件和.timeindex文件,位于同一个文件夹下,命名规则:topic名称+分区号
文件清理策略
Kafka默认日志保存七天,一般设置2-3天
提供的日志清理策略有两种:delete和compact
delect日志删除:将过期数据删除,默认
compact日志压缩:对于相同key的不同value值,只保留最后一个版本。(只适合特殊场景)
删除数据
高效读写数据
1.Kafka本身是分布式集群,可以采用分区技术,并行度高。
2.读数据采用稀疏索引,可以快速定位要消费的数据
3.顺序写磁盘
4.页缓存+零拷贝技术
消费者
消费方式:pull拉模式,从broker中主动拉取数据。不采用push的模式,因为考虑到消费者消费速度可能不一样,由broker决定消息发送速率,很难适应所有的消费者的消费速率。
pull模式不足是:如果Kafka没有数据,消费者可能会陷入循环中,一直返回空数据。
消费者总体工作流程
消费者组CG:由多个consumer组成。形成一个消费者组的条件:所有消费者的groupid相同。
- 消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费。一个消费者可以消费多个分区
- 消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
消费者组
初始化流程
详细消费流程
消费者分区的分配以及再平衡
- Range:range是对每个topic而言的。
首先对同一个topic里面的分区按照序号进行排序,并对消费者按照字母顺序进行排序。通过partition数/consumer数来决定每个消费者应该消费几个,如果除不尽,前面的几个消费者会多消费一个。
注意:如果有多个topic,针对每一个topic,前面的消费者都会多消费一个分区,如果topic过多,容易产生数据倾斜!
- RoundRobin:针对集群中所有topic而言。
- 轮询分区策略,把所有的partition和所有的consumer都列出来,按照hashcode排序,最后通过轮询算法来分配partition给到每个消费者。
- Sticky:黏性分配,在执行一次新的分配之前,考虑上一次的分配结果,尽量少的调整分配的变动,可以节省大量开销。
offset位移
__consumer_offsets 主题里面采用 key 和 value 的方式存储数据。
key 是group.id+topic+分区号,value 就是当前 offset 的值。
每隔一段时间,kafka 内部会对这个 topic 进行compact,也就是每个 group.id+topic+分区号就保留最新数据。
自动提交 offset:
消费者事务
重复消费:已经消费了数据,但是offset没提交
漏消费:先提交offset后消费
事务:
要保证原子性,从生产者到集群,集群到消费者,消费者到框架,任何一个环节都不能出问题。
数据积压(消费者如何提高吞吐量)
1.如果kafka消费能力不足,可以考虑增加Topic的分区数,并且同时提升消费组的消费者数量,消费者数 = 分区数。
2.如果是下游的数据处理不及时:提高每批次拉取的数量。
如果批次拉取数据过少,使处理的数据小于生产的数据,也会造成数据挤压。
如果消息已经挤压很多了
- 如果消息来不及消费,可以先存在数据库中,然后逐条消费
- 再用一个消息队列,外部或内部的,比如链表,把消息放到新的队列里,消费者从两个队列同时消费。(类似多线程)
- 提高消费的速度,对于分区数一定的情况下,为了避免单线程的消费一个分区的数据,我们可以多线程去消费分区的数据,这样从而提高kafka的消费者的消费性能,但是对于多线程,此时就需要手动去维护每个分区的offset这个偏移量了
总体
如何提升吞吐量?
- 提高生产者吞吐量
- 增加分区
- 消费者提高吞吐量
- 增加下游消费者处理能力
数据精准一次
1.生产者角度
- acks设置为-1
- 幂等性+事务
2.broker服务端角度
- 分区副本大于等于2
- ISR里应答的最小副本数大于等于2
3.消费者
- 事务+手动提交offset
- 消费者输出的目的地必须支持事务(框架,MySQL)
Kafka很难保证数据的强一致,如果下游不支持事务,下游需要支持幂等性,来保证数据不重复。但是很难保证完全不会丢失数据。如果强一致的场景需要换用别的消息队列组件。
rabbitmq在金融场景中经常使用,具有较高的严谨性,数据丢失的可能性更小,同时具备更高的实时性。
消息丢失解决方案
从kafka读取了相关的消息,并且就是实时写到数据库中,但是因为是线上的环境连接数据库可能会由于网络的问题造成连接失败,消息会丢失。
1.如果kafka数据源和数据库mysql是同一个数据源,可以考虑事务解决。一但数据库失败回滚就可以了,相应消费的信息也就回滚。
2.考虑到kafka消费的时候可以手动提交offset,那么每次我们可以先插入数据再提交offset,一旦插入数据失败的话我们不提交offset,默认不去消费,等到下一次重启broker就可以继续消费未消费的消息。
3.以利用Redis去将每次失败的的消息存起来,然后每天定时任务将其写到数据库,保证消息不丢失,或者就是手动实现消息重试(要用到redis比较麻烦)。
合理设置分区
服务器挂了处理办法
在生产环境中,如果某个Kafka节点挂掉。
正常处理办法:
(1)先尝试重新启动一下,如果能启动正常,直接解决。
(2)如果重启不行,考虑增加内存、增加CPU、网络带宽。
(3)如果将Kafka整个节点误删除,如果副本数大于等于2,可以按照服役新节点的方式重新服役一个新节点,并执行负载均衡。
单条日志大于1m
一条日志一般是0.5k-2k大小,如果单条日志大于1m,找到最大的日志,调整前三个参数的值。
Kafka-Eagle监控
Kafka-Eagle框架可以监控Kafka集群的整体运行情况,在生产环境中经常使用。可视化监控
Kafka-Eagle的安装依赖于MySQL
自定义:
在生产端和消费端的拦截器处,可以把信息暴露出去通过可视化页面达到类似的效果。
Kafka-Kraft模式
Kafka-Kraft架构
左图为Kafka现有架构,元数据在zookeeper中,运行时动态选举controller,由controller进行Kafka集群管理。
右图为kraft模式架构(实验性),不再依赖zookeeper集群,而是用三台controller节点代替zookeeper,元数据保存在controller中,由controller直接进行集群管理。
未来会大量流行
Spring整合kafka
- 引入依赖
-spring-kafka
- 配置Kafka
- 配置server、consumer
- 访问Kafka
- 生产者- - kafkaTemplate.send(topic, data);- 消费者- - @KafkaListener(topics = {"test"})
public void handleMessage(ConsumerRecord record){ }
生产者发消息是主动去调的,而消费者处理消息是自动去处理,通过一个kafka的监听器,监听到topics中的主题就会处理该主题下的消息。
版权归原作者 未172 所有, 如有侵权,请联系我们删除。