生产者(有回调)批量发送消息总是异常
结论
生产者配置本地缓存批量发送消息(有回调流程)时,需要确保回调流程的迅速处理(如用线程池处理),不然会影响批量发送的效率,在本地缓存满后,影响尤为严重,导致很多消息都没有成功发送到kafka。与消费流程无关
解释
kafka producer 配置 batch.size=120000(约117kb),linger.ms=10,max.block.ms=3000,request.timeout.ms=3000
send 方法将消息保存到本地缓存(默认32m)后就返回。然后由另一个线程(sender线程)不断循环地一批一批地将数据发送到kafka。发送并受到kafka的响应后开始处理回调消息,处理结束后才会释放本地缓存中的空间。所以回调操作耗时较长的话,会影响本地缓存的及时释放。如果消息存入本地缓存的速度比消息发送到kafka的速度快,那本地缓存很快就会满。如果本地缓存满了,新发送消息的线程将一直被阻塞。直到到期或者有空间空出来。这里就存在丢消息的可能
在各种阻塞的过程中可能还有其他的一些机制,会剔除一些因超时而过期的消息,然后再报个错
异常表现
- Exception:Failed to allocate memory within the configured max blocking time 3000 ms
- Exception:Expiring 55 record(s) for redbag_product_from_mission_success_notify-0: 941205 ms has passed since batch creation plus linger time
- Exception:Expiring 600 record(s) for redbag_product_from_mission_success_notify-1: 340666 ms has passed since last append
- Exception:Expiring 290 record(s) for redbag_product_from_mission_success_notify-4: 664132 ms has passed since last attempt plus backoff time
原理说明
在新版本的kafka中(从0.9开始),其实只有异步方式一种,是批量发送的方式
在producer端,存在2个线程,一个是producer主线程,用户端调用send消息时,是在主线程执行的,数据被缓存到RecordAccumulato中,send方法即刻返回,也就是说此时并不能确定消息是否真正的发送到broker。另外一个是sender IO线程,其不断轮询RecordAccumulator,满足一定条件后,就进行真正的网络IO发送,使用的是异步非阻塞的NIO。主线程的send方法提供了一个用于回调的参数,当sender线程发送完后,回调函数将被调用,可以用来处理成功,失败或异常的逻辑
如果在发送时指定了partition,则消息将被保存到指定的tp分区队列,如果没有指定分区,将对key散列后来计算分区,相同key的消息将被写到同一个分区队列中,如果key是null,且使用默认的分区器,则分区器将用轮询的方法(Round Robin)将序列化后的消息均衡分布到不同的队列中,sender线程从Accumulator中取出批量数据组成一个batch发送
如果想使用同步方式,其实是通过异步方式间接实现,因为异步方式返回的是一个future对象,在这对象上调用get方法,将被阻塞直到返回结果
如下图所示:在record被加入到accumulator时,会根据record所在的tp找到RecordBatch队列,如果不存在,就新建一个队列,在队列中取出最后一个RecordBatch,如果这个batch还有空间,就把record新追加到缓存后面,这样1个batch可能会有多个record,如果batch空间不够,就新创建一个batch,重新分配一个Max(16k, recordsize)的buffer,如果这个record超过16k,则这个batch中只会保存这1个record
下图详细描述了数据发送到accumulator及sender线程从accumulator取出并发送到broker的过程,红色的粗虚线表示数据流向,红色粗实线表示sender线程不断的循环过程,在虚线旁带有数字标号的是具体的执行步骤