### Kafka
> *昨天阿里网商银行实习内推一面,被惨虐。以为一面也就问问一些基础的东西,结果上来就怼项目,问Kafka,从头问到脚指尖。不过也怨不得别人,只能怪自己学艺不精,有很多该回答上的问题也没有回答上……所以特此再来深入理解一下Kafka。*
------------
#### 1. 为什么选择消息队列?
>1)**解耦**:可以独立的扩展或修改数据写入和处理的过程。
2)**冗余**:把数据进行持久化直到它们已经被完全处理,通过这一方式规避数据丢失的风险。
3)**扩展性**:由于解耦了处理过程,所以增大消息入队和处理的频率变得很容易,只要另外增加处理过程即可。
4)**可恢复性:**:系统的一部分组件失效时,不会影响到整个系统。即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
5)**缓冲**:有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。
6)**异步通信**:消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。
------------
#### 2. 为什么选择Kafka?
>(与其他消息队列相比,从哪些方面进行比较)
对比Kafka、RabbitMQ和ACtiveMQ(**吞吐量、可靠性、模型架构、可用性、负载均衡方面**)
>**1. 吞吐量**:Kafka > RabbitMQ > ActiveMQ
kafka具有较高的吞吐量,**内部采用消息的批量处理,zero-copy机制,数据的存储和获取是本地磁盘顺序批量操作,具有O(1)的复杂度,消息处理的效率很高**。
>**2. 可靠性**:kafka < RabbitMQ。
**RabbitMQ支持对消息的可靠的传递,支持事务,不支持批量的操作;基于存储的可靠性的要求存储可以采用内存或者硬盘**。
>**3. 模型架构方面**:
RabbitMQ遵循AMQP协议,RabbitMQ的broker由Exchange,Binding,queue组成,其中exchange和binding组成了消息的路由键;客户端Producer通过连接channel和server进行通信,Consumer从queue获取消息进行消费(长连接,queue有消息会推送到consumer端,consumer循环从输入流读取数据)。**rabbitMQ以broker为中心;有消息的确认机制**。
kafka遵从一般的MQ结构,producer,broker,consumer,**以consumer为中心,消息的消费信息保存的客户端consumer上**,consumer根据消费的点,从broker上批量pull数据;**无消息确认机制**。
>**4. 可用性方面**:
rabbitMQ支持miror的queue,主queue失效,miror queue接管。kafka的broker支持主备模式。activeMq也支持主备模式。
>**5. 负载均衡方面**:
kafka采用zookeeper对集群中的broker、consumer进行管理,producer可以基于语义指定分片,消息发送到broker的某个分片上。
rabbitMQ的负载均衡需要单独的loadbalancer进行支持。
------------
#### 3. Kafka为什么快?
>在写/读数据的时候通过哪些方法来实现?
(写数据:顺序写入,MMFile。读数据:sendfile,数据压缩)
>*数据压缩:Producer端可以通过GZIP或Snappy格式对消息集合进行压缩,减少数据传输量*
>**1.写数据(生产者):**
**1).顺序写入**
因为硬盘是机械结构,每次读写都会先寻址再写入,其中寻址是一个很耗时的操作。磁盘随机访问I/O的耗时大大地高于顺序I/O。因此,为了提高读写硬盘的速度,Kafka使用顺序I/O的方式。
**2).MMFile**
Memory Mapped Files(内存映射文件)(mmap)。
只是顺序写入硬盘,硬盘的访问速度还是不可能追上内存。所以Kafka的数据并不是实时的写入硬盘 ,它充分利用了现代操作系统**分页存储**来利用内存提高I/O效率。
它的工作原理是直接利用操作系统的Page来**实现文件到物理内存的直接映射**。映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映用户空间。
使用这种方式可以获取很大的I/O提升, 省去了用户空间到内核空间 复制的开销(调用文件的read会把数据先放到内核空间的内存中,然后再复制到用户空间的内存中。)也有一个很明显的缺陷——不可靠, 写到mmap中的数据并没有被真正的写到硬盘,操作系统会在程序主动调用flush的时候才把数据真正的写到硬盘。 Kafka提供了一个参数——producer.type来控制是不是主动flush,如果Kafka写入到mmap之后就立即flush然后再返回Producer叫 同步 (sync);写入mmap之后立即返回Producer不调用flush叫 异步 (async)。
>注意:
>采用内存映射文件,文件的读写是由操作系统来负责的,所以即使Java程序在写入内存后就挂了,操作系统仍然会对内存中的文件写入文件系统。
>但是,如果电源故障或者主机瘫痪,有可能内存映射文件还没有写入磁盘,因此会丢失数据。
>**2.读数据(消费者):**
**Zero Copy + sendfile(优化web Server静态文件的速度)**
  一般的Web Server发送静态文件的过程:先将文件复制到内核空间(read调用)->复制到用户空间->再复制到内核空间(socket调用)->复制到网卡空间进行发送。
  Kafka对此进行优化,采用Zero Copy的机制(页缓存,内存空间映射)减少复制的次数,提高速度。然后,Kafka把所有的消息都存放在一个一个的文件中, 当消费者需要数据的时候Kafka直接把“文件”发送给消费者。(近乎于带宽的速度)
#### 4. 怎样进行扩容?
**1)扩容**
新加入的broker可以向zookeeper注册,加入到集群中。(修改配置文件的zookeeper.connect配置项)
>**新加入的broker只对新的topic起作用**,对已有的topic如果不进行处理是不会承担任何任务的。
**2)topic迁移**
>已有的topic的数据不会自动迁移到新的broker上,需要利用kafka的重新分区分配工具手动操作。迁移是一个很费时的操作。
>步骤:创建json文件->指定要重新分配的topic->生成迁移分配规则文件->执行迁移分配->验证分配
>注意:在迁移过程中不能人为的结束或停止kafka服务,不然会有数据不一致的问题。
优化:1.在topic未被生产和消费时,进行重新分配,避免重复数据和出错;
   2.减少迁移的数据量(调整数据保持的默认时间,删除掉不必要的数据)。
#### 5. 容错机制?
主备模式。(partition有副本,但是broker没有副本,所以一旦broker挂掉,该broker的所有消息都不可用)
>每个分区都有一个leader节点,和多个follower节点。一个partition可以复制多份,放在不同的broker上,构成一个集群。
>**Leader副本:**
>负责直接响应client端的读写请求,即和生产者和消费者直接对接,生产者生产一条消息,直接进入Leader副本;
>**Follower副本:**
>作为特殊消费者,被动的接收leader副本中的数据。注意:follower副本不能响应client端的读写请求;
>**ISR集合:**
与leader保持同步的follower,属于ISR副本集合(同步的备份集合),反过来说,在某个时刻,还在被动接收,不是和leader完全一致的,不能属于ISR副本集合,同步完成后才属于ISR集合;
>**ISR集合作用:**
在当前Leader不可用时,Kafka集群会从ISR集合中选取一个Follower升级为新Leader;通过维护ISR集合,一个拥有(N+1)个备份的Topic可用容忍N个备份不可用。(即:**如果一个topic指定了replica factor为N,那么就允许有N-1个Broker出错**)
#### 6. Partition是怎样分配的?
> 1. 将所有Broker(假设共n个Broker)和待分配的Partition排序
> 2. 将第i个Partition分配到第(i mod n)个Broker上 (这个就是leader)
> 3. 将第i个Partition的第j个Replica分配到第((i + j) mode n)个Broker上
#### 7. Kafka是否存在数据丢失?
>可能存在数据丢失,是否丢失取决于采用哪一种Ack机制。
>a. 当Ack设置为0的时候,Producer不和Kafka集群进行确认,当网络发送故障的时候,数据会出现丢失的可能。
>b. 异步发送时,数据会先在Client端按一定规则缓存再批量发送,在这段时间内,如果Client端发送故障,将导致数据丢失。
>c. 异步发送时,Client端缓存的消息超出缓冲区的大小,也可能导致数据丢失。(*为防止缓冲区满,可以设置不限制阻塞超时时间,缓冲区满时就让他一直处于阻塞状态。*)
>d. 当Ack设置为1的时候,只返回leader的确认。当Leader副本接收成功返回确认后,此时Follower副本可能还在同步,这时如果Leader副本发生异常,重新选举出的Leader副本不能和原Leader副本保持一致,将出现数据丢失的情况。
>要想要完全不丢失,就设置为**同步模式**(写入mmap之后立即flush),并且**Ack设置为-1**(即数据写入leader和follower副本后再确认)。
但是这样会导致系统吞吐量大大降低。想要提高吞吐量就设置为异步模式,并且Ack设置为0。
#### 8.消费时的最优设计
>consumer group下的consumer thread的数量等于partition数量,这样效率是最高的。
>因为一个partition中的数据只能被一个consumer grop中的一个consumer线程消费。 一个consumer group下,无论有多少个consumer,这个consumer group一定回去把这个topic下所有的partition都消费了。
#### 9. 什么时候rebalance?
>a. 新consumer加入组、已有consumer主动离开组或已有consumer崩溃了
>b. 订阅topic数发生变更
>c. 订阅topic的分区数发生变更
#### 10. rebalance策略
>控制策略:
a. 在/consumers/[consumer-group]/下注册id
b. 设置对/consumers/[consumer-group] 的watcher
c. 设置对/brokers/ids的watcher
d. zk下设置watcher的路径节点更改,触发consumer rebalance
>算法:
a. 将目标 topic 下的所有 partirtion 排序,存于PT
b. 对某 consumer group 下所有 consumer 排序,存于 CG,第 i 个consumer 记为 Ci
c. N=size(PT)/size(CG),向上取整
d. 解除 Ci 对原来分配的 partition 的消费权(i从0开始)
e. 将第i*N到(i+1)*N-1个 partition 分配给 Ci