(1) 经典组合:Flume + Kafka + Strom + HDFS/HBase
在流式计算中,Flume做分布式采集数据,Kafka是分布式队列系统,对采集数据做分布式缓存,Storm通过消费kafka的数据进行计算,计算结果存储到HBase中。
(2) Storm是在线流式计算分布式框架,Mapreduce/spark是离线流式计算分布式框架,离线实时性得不到保障,在线可以。
(3) Kafka是Linkedin于2010年12月开源的消息系统,一种分布式的基于发布/订阅(对外服务)的消息系统。
(4) 例如订阅号、邮箱等第三方信息提供方,存储信息,用户异步的方式去获取。
(5) Kafka数据单位:message
(6) Kafka的目标:成为一个队列平台,不仅支持离线,还要支持在线。
(7) Kafka对消息保存时根据Topic进行归类,发送消息者称为producer,消息接收者称为consumer,此外kafka集群有多个kafka实例组成,每个实例(server)成为broker。
(1) 消息持久化:通过O(1)的磁盘数据结构提供数据的持久化(高六千多倍)
数据直接往磁盘写(如果磁盘优化得好,性能比内存好)
操作系统两个功能:预读、后写
特点:对磁盘的顺序读写比对内存的随机读写速度要快
(2) 高吞吐量:每秒百万级的消息读写
(3) 分布式:扩展能力强
(4) 客户端支持:java、php、python、c++
(5) 实时性:生产者生产的message立即被消费者可见。
(1) Broker:每一台机器叫个一个Broker;
(2) Producer:日志消息生产者,用来写数据,这里日志是message;
(3) Consumer:消息的消费者,用来读数据;
(4) Topic:是一个虚拟概念,类似一个话题,不同消费者去指定的Topic中读,不同的生产者往不同的Topic中写;
(5) Partition:是一个实例的概念,以文件夹形式存在,在Topic基础上做了进一步分层;
(6) Offset:kafka的存储文件都是按照offset.kafka来命名,用offset做名字的好处是方便查询。例如你想找2018的位置,只要找2018.kafkad 的文件即可。
(7) Consumer Group (GG):这是kafka用来实现一个topic消息的广播(发给所有的consumer)和单播(发给任意一个consumer)的手段。一个topic可以有多个CG。topic的消息会复制-给consumer。如果需要实现广播,只要每个consumer有一个独立的CG就可以了。要实现单播只要所有的consumer在同一个CG。用CG还可以将consumer进行自由的分组而不需要多次发送消息到不同的topic。
4 架构
(1) 中间小信封是message,topic中有三个管道partition,topic中的信息存在key和value,根据key分配到后面的partition。
(2) Partition是以一个文件夹的形式存在的,内部的message传输是严格有序的,保证消息的顺序性。
(3) Kafka内部是分布式的、一个Kafka集群通常包括多个Broker。
(4) 负载均衡:将Topic分成多个分区(partition),每个Broker存储一个或多个Partition。
(5) 多个Producer和Consumer同时生产和消费消息。
(6) 如果consumerA正在读取patition1的数据,其他consumer是不能同步读取partition1数据的。
(7) 无论是kafka集群,还是producer和consumer都依赖zookeeper集群保存一些meta信息,来保证系统可用性。
(8) Produce可能指定往哪个partition写数据,和broker之间不存在负载均衡;
Consumer和broker存在负载均衡,通过zookeeper协调;
(9) Producer和broker之间:push模式(推)
Consumer和broker之间:pull模式(拉)
(10)Consumer中还存在负载均衡
Broker---》consumer grop----》consumer
负载均衡的思想,出现个别节点不稳定,会体现rebalance机制
(11) Partition由两部分组成:
index log:定位存储索引信息
message log:真实数据
(1) 一个Topic是一个用于发布消息的分类或feed名,kafka集群使用分区的日志,每个分区都是有顺序且不变的消息序列;
(2) commit的log可以不断追加。消息在每个分区中都分配了一个叫offset的id序列来唯一识别分区中的消息;
(3) 在每个消费者都持久化这个offset在日志中。通常消费者读消息时会使offset直线性的增长,但是kafka是把更多的主导权交给消费者,由消费者保存各自的offset偏移量,它可以按任意顺序来消费消息,比如复位到老的offset来重新处理;
(4) 可以把topic近似想象成一个队列queue,发送一个消息,或者消费一个消息,必须明确指定topic;
(5) 为了实现扩展性,提高kafka吞吐率,一个topic可以分为多个partition,每个partition是一个有序的队列。Partition中的每条信息都会被分配一个有序的id(offset)。Kafka只保证按一个partition中的顺序将消息发送给consumer,不保证一个topic的整体(多个partition间)的顺序。
(6) 消息的发布者和消息的接收者是通过Topic建立关系的;
(7) 不同的Topic是代表不同的消息的一个类别或者是一些不同的类型;
(8) 物理上,不同的topic的消息是分开存储的
(9) Topic中的偏移量offset,可以通过制定偏移量来定位到数据读取的位置
不同partition上的offset是不一样的,所以读数据的时候,不仅仅要指定读那个位置,还要指定不同的partition。
(10) Partition上的offset是有编号的,而且还是递增的,Kafka是顺序写的,写入partition中是顺序追加的,写的速度非常快。
(11) Kafka是无状态的,不会保存客户或者消费者的偏移量,把更多的主导权交给消费者(client来保存各自的offset)。
(12) Offset是先由zookeeper维护的,变由topic维护,架构复杂建议客户端维护。
Message是kafka通信的基本单位,每个producer可以向一个topic发布一些消息。如果consumer订阅了这个主题,那么新发布的消息就会广播给consumers。
传输最大消息message的size不能超过1M,可以通过配置参数控制。
为了数据流的高效性,message需要经过格式化处理转为二进制,格式如下:
– message length : 4 bytes (value: 1+4+n)
– "magic" value : 1 byte
– crc : 4 bytes
– payload : n bytes
生产者可以发布数据到它指定的topic中,并可以指定在topic里哪些消息分配到哪些分区(比如简单的轮流分发各个分区或通过指定分区语义分配key到对应分区)
producer有两种模式,由produer.type控制:
produer.type=sync是同步模式(实时)
produer.type=async是异步模式(达到设定发送条件:时间、数据量)
异步模式:批处理发送,当message积累到一定数量或等待一定时间后进行发送。
同步模式:生产者直接把消息发送给对应分区的broker,而不需要任何路由层。
(1) 一种更抽象的消费方式:消费组(consumer group)。
(2) 该方式包含了传统的queue和发布订阅方式。
–首先消费者标记自己一个消费组名。消息将投递到每个消费组中的某一个消费者实例上。
–如果所有的消费者实例都有相同的消费组,这样就像传统的queue方式。
–如果所有的消费者实例都有不同的消费组,这样就像传统的发布订阅方式。
–消费组就好比是个逻辑的订阅者,每个订阅者由许多消费者实例构成(用于扩展或容错)。
(3) 相对于传统的消息系统,Kafka拥有更强壮的顺序保证。
(4) 由于topic采用了分区,可在多Consumer进程操作时保证顺序性和负载均衡。
这里配置了组A和组B,两个broker,即server1和server2,一个broker有两个partition,每一个partition只能同一时间服务于一个同一个consumer group里的Consumer。
(1) 无论发布的消息是否被消费,消息会一直持久化,消息会被删除掉:达到持久化时间(7天)和最大持久化数据量。(两者可配置)
(2) Kafka存储布局简单:Topic的每个partition对应一个逻辑日志,这个逻辑日志就是一个或多个segment文件。
(3) 消息顺写:每次生产者发布消息到一个分区,代理就将消息追加到最后一段文件中。
(4) 与传统的消息系统不同,kafka系统中存储的消息没有明确的消息ID。
(5) 消息通过日志中的逻辑偏移量来公开。偏移量:offset,partition里面的一个位置。
(6) partition内部是有一个真正落地的数据,这个数据叫segment;一个partition它会对应着多个segment文件,而且每一个segment文件相当于是你的头部有一个索引号,尾部有一个消息号。
给定一个顺序数字序列,如何快速查找到其中某一个值的位置?
查找算法:二分法+顺序遍历
如下图,在一个partition中有多个segment,每一个segment都是一个区间,区间数字就是offset,这时要查找350这个数据,先用二分法找出301-400这个segment,然后再从头到尾遍历这个segment,得到350.
10 传输效率
(1) 生产者一次提交一批消息作为一个请求。消费者虽然利用api遍历历史消息是一个一个的,但背后也是一次请求获取一批数据,从而减少网络请求数量。
(2) Kafka层采用无缓存设计,而是依赖于底层的文件系统页缓存。这有助于避免双重缓存,即消息只缓存了一份在页缓存中。同时这在kafka重启后保持缓存warm也有额外的优势。因kafka根本不缓存消息在进程中,故gc开销也就很小。
(3) zero-copy:kafka为了减少字节拷贝,采用了大多数系统都会提供的sendfile系统调用。
(4) 0拷贝:减少kernel和user模式上下文的切换,直接disk上data传输给socket,而不是通过应用程序传输。
(5) 传统传输是四次拷贝,zero-copy传输两次拷贝。
Kafka代理是无状态的:意味着消费者必须维护已消费的状态信息。这些信息由消费者自己维护,代理完全不管(通过offset定位)。
Time-based消息保留策略:从代理删除消息变得很棘手,因为代理并不知道消费者是否已经使用了该消息。Kafka创新性地解决了这个问题,它将一个简单的基于时间的SLA应用于保留策略。当消息在代理中超过一定时间(7天)后,将会被自动删除。
这种创新设计有很大的好处,消费者可以故意倒回到老的偏移量再次消费数据。这违反了队列的常见约定,但被证明是许多消费者的基本特征。
(1) Kafka默认采用at least once的消息投递策略。即在消费者端的处理顺序是获得消息->处理消息->保存位置。这可能导致一旦客户端挂掉,新的客户端接管时处理前面客户端已处理过的消息。
(2) 三种保证策略:
At most once:最多发送一次,不会重复传输,消息可能会丢;
At least once:消息至少发送一次,如果消息未能接受成功,会出现重发的可能,保证消息不丢失;
Exactly once:每条消息肯定会被传输一次且仅传输一次。
(1) Kafka将日志(数据)复制到指定多个服务器上。
(2) 副本的单元是partition。在正常情况下,每个分区有一个leader和0到多个follower。
(3) leader处理对应分区上所有的读写请求,follower不与外界关联,分区可以多于broker数,leader也是分布式的。
(4) follower的日志和leader的日志是相同的,follower被动的复制leader。如果leader挂了,其中一个follower会自动变成新的leader。
(5) 副本数目可以设置,不超过broker个数。
(6) 针对partition副本管理,如果一共有f+1个broker的话,允许挂掉f个,一个broker就可以存数据。
(7) Partition可以存储在不同的broker上,一个broker可以有多个partition,一个topic可以有多个partition。
(8) kafka在zookeeper动态维护了一个集合,叫ISR(同步副本)集合(里面表示的所有副本,即都有可能当上leader的副本)。
(9) Follower数据是被动从leader中复制的,所以它的数据永远是小于leader上的,如果落后太多会被删除。
(10)ISR里副本需要删除的两个情况:消息延迟问题和数据落后太多。
(11)kafka内部它会有一个配置,这个配置叫做replica.lag.max.messages,这个配置如果设为4的话就表明你的从follower不能落后与主Leader超过3个消息,否则的话从ISR中删除,那一旦从ISR中删除的话,就是说你以后也没有当Leader主的机会了,那我之后也不会给你去同步数据了,那我就判定你这个broker就是挂掉了的状态。
(12)和其他分布式系统一样,节点“活着”定义在于能否处理一些失败情况,kafka需要两个条件保证是“活着”;
节点在zookeeper注册的session还在且可维护(基于zookeeper心跳机制)
如果是slave则能够紧随leader的更新不至于落得太远。
(13)kafka采用in sync来代替“活着”,如果follower挂掉或者卡住或者落后很远,则leader会移除同步列表中的in sync。
关于数据落置:replica.lag.max.messages;
关于消息延迟配置:replica.lag.time.max.ms;
replica.lag.max.messages如果设为4,表明follower不能落后于leader超过3个消息,否则从ISR中删除。
(14)所谓一条消息是“提交”的,意味着所有in sync的副本也持久化到他们的log中。这意味着消费者无需担心leader挂掉导致数据丢失。另一方面,生产者可以选择是否等待消息“提交”。
(15)kafka动态的维护了一组in-sync(ISR)的副本,表示已追上了leader,只有处于该状态的成员组才是能被选择为leader。这些ISR组会在发生变化时被持久化到zookeeper中。通过ISR模型和f+1副本,可以让kafka的topic支持最多f个节点挂掉而不会导致提交的数据丢失;
(16)副本数据写成功:message真正存储还是要存储到磁盘上的,数据写入到leader后,从节点以异步的方式主动到leader拉取数据,数据到达从服务器后并不是立马写磁盘的,立即往磁盘写的话会消耗很多时间,时效性不高,而是先存储在内存中,写完内存后就返回ack给leader,说明副本数据写成功,之后再自己内部落地数据到磁盘,这样既高效也保证了数据可靠性。
(17)参考HDFS的多副本的方式来完成数据高可用:备份副本不在同一个broker(机器)。
如果设置一个Topic,这个topic有5个partition,3个replication,该如何分配?
Kafka分配replicatioin的算法:(哈希取模)
目的:将不同的partition均衡的分配给broker。
假设:将第i个partition分配到(i % N)个broker上,那第i个partition的第j个replication分配到((i+j) % N)个broker上。
虽然partition里面有多个replication,如果里面有M个replication,其中有1个leader,其他(M-1)个是follower。
(1) 由于kafka中一个topic中的不同分区(partition)只能被消费组中的一个消费者消费,就避免了多个消费者消费相同的分区时会导致额外的开销(如要协调哪个消费者消费哪个消息,还有锁及状态的开销)。
(2) kafka中消费进程只需要在代理和同组消费者有变化时时进行一次协调(这种协调不是经常性的,故可以忽略开销)。
例如:下图1-9代表数据信息,一个partition同一时间只能服务一个consumer,这时consumer1就全部读取了1-9,c2就处于空闲状态,导致额外开销;
可以动态调节partiton数目,多个partition可以提升并发,每个consumer消费的数据不能重复。
Zookeeper保证系统可用性,zk中会保存着一些meta信息(topic)
kafka使用zookeeper做以下事情:
探测broker和consumer的添加或移除;
当1发生时触发每个消费者进程的重新负载;
维护消费关系和追踪消费者在分区消费的消息的offset。
(1)Broker Node Registry
• /brokers/ids/[0...N] --> host:port(ephemeral node)
– broker启动时在/brokers/ids下创建一个znode,把broker id写进去。
– 因为broker把自己注册到zookeeper中实用的是瞬时节点,所以这个注册是动态的,如果broker宕机或者没有响应该节点就会被删除。
(2)Broker Topic Registry
• /brokers/topics/[topic]/[0...N] -->nPartions (ephemeral node)
– 每个broker把自己存储和维护的partion信息注册到该路径下。
(3) Consumersand Consumer Groups
– consumers也把它们自己注册到zookeeper上,用以保持消费负载平衡和offset记录。
– group id相同的多个consumer构成一个消费租,共同消费一个topic,同一个组的consumer会尽量均匀的消费,其中的一个consumer只会消费一个partion的数据。
(4) Consumer Id Registry
•/consumers/[group_id]/ids/[consumer_id]--> {"topic1": #streams, ...,"topicN": #streams}(ephemeral node)
– 每个consumer在/consumers/[group_id]/ids下创建一个瞬时的唯一的consumer_id,用来描述当前该group下有哪些consumer是alive的,如果消费进程挂掉对应的consumer_id就会从该节点删除
(5) Consumer Offset Tracking
•/consumers/[group_id]/offsets/[topic]/[partition_id]-->offset_counter_value((persistent node)
– consumer把每个partition的消费offset记录保存在该节点下。
(6) Partition Owner registry
•/consumers/[group_id]/owners/[topic]/[broker_id-partition_id]-->
consumer_node_id (ephemeral node)
– 该节点维护着partion与consumer之间的对应关系。
(1) 解耦:
允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
(2) 冗余:
消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的"插入-获取-删除"范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。
(3) 扩展性:
因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。
(4) 灵活性 & 峰值处理能力:
在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
(5) 可恢复性:
系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
(6) 顺序保证:
在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。(Kafka保证一个Partition内的消息的有序性)
(7) 缓冲:
有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。
(8) 异步通信:
很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。