1. Kafka工业级异常处理

1)自定义异常体系 

底层模块应该把自己的异常抛出来,最最核心的上层流程控制逻辑来捕获所有的异常,根据异常进行对应的处理;

严格、成熟的系统里,一定有一套异常体系,就是针对系统可能出现的一些问题,定义好一些预定义的异常类,看到一个异常类就知道在代码的那个地方大概发生了什么样的异常,写好异常信息。

2)底层模块把自定义异常往上抛

3)核心控制流程中对各种异常进行处理

处理技巧1:直接抛出异常到最上层的调用地方

处理技巧2:封装成一种错误码的状态,400

处理技巧3:对异常的情况做一些统计

处理技巧4:在特殊的异常记录里,记录下发生的异常

 

2.  Kafka并发多线程下内存缓冲进行消息存放

2.1 大致流程

KafkaProducer设计理念是多线程并发安全的,可以有多个线程并发来调用他的send()方法。

Kafka会从内存缓冲区里获取一个分区对应的Deque,Deque这个队列放了很多Batch,batches是一个kafka自己实现的一个CopyOnWriteMap<topic, deque>。

如果此时还没有创建对应的Batch,会给予BufferPool给这个Batch分配一块内存出来,这个Batch对应的内存空间是可以复用的,避免频繁的垃圾回收。

*Tips:kafka自己实现的CopyOnWrite系列是适合读多写少的场景,每次更新的时候,都是copy一个副本做更新,把副本通过volatile写的方式赋给变量。写的时候不会阻塞读,减少读和写操作的互斥时间;坏处是会占用很大的内存。Kafka是一个分区创建一个Deque,是很低频的写操作。

 

2.2 如何基于CopyOnWriteMap实现线程安全的分区队列构建

Kafka自己实现了CopyOnWriteMap,CopyOnWriteMap中定义了一个普通的Map,并定一个为volatile,保证只要有人更新这个map对象的就可以立即看到;

读的时候是不用加锁的,写的时候是针对副本进行kv设置,把副本通过volatile写的方式赋给对应的变量(this.map = Collections.unmodifiableMap(copy));

putIfAbsent和put方法是有Synchronized。

 

2.3 基于NIO BufferPool分配内存

BufferPool里有一个Deque作为队列,缓存了一些ByteBuffer,默认每个ByteBuffer(即Batch)的大小是16kb。availableMemory默认32mb,每次分配一块ByteBuffer。

多线程并发请求都获取到了一块ByteBuffer内存,当后面的线程放入消息时发现已有一个batch,会把自己申请的ByteBuffer放回BufferPool池子里。

如果消息超过ByteBuffer,会直接申请对应大小的ByteBuffer,用完后是直接释放,让gc掉。

 

如果已经申请不了新的ByteBuffer去开辟一个Batch,会阻塞一段时间,如果还是不行,就会抛异常。

 

3. 工业级网络通信框架封装如何使用NIO

1)需要设置socket的发送和接收的缓冲区的大小;

2)TcpNoDelay,默认设置是false时,就开启Nagle算法,把网络通信中的一些小的数据包收集起来,组成一个大的数据包然后一次性发送,大量小包会导致网络拥塞;

3)如果设置是true,就是关闭Nagle,让你发送的数据包立马通过网络传输。

 

4. KafkaProducer源码中的精华

1)缓冲机制:数据结构,CopyOnWriteMap + Dequeu,Batch + Request

2)内存管理:内存块缓冲池,有很多空的内存块,可以循环的利用,大幅度减轻JVM GC的弊端,避免频繁的回收大量的内存块

3)网络通信:NIO封装自己的网络通信框架,KafkaSelector、KafkaChannel,一个客户端对多个Broker服务器建立长连接,缓存维护,IO多路复用,一个主线程完成跟多个客户端的网络通信,读写请求中的粘包和拆包的处理