一、问题背景
关于自适应的问题和细节可以参考 文章 :Kafka消息消费之性能提升实践
多级并发含义是指针对批量数据的处理,不能完全并发,但是有条件可以分组进行并发处理,简单举例来说,针对数据集A、B、C、D、E、F、G,其中由于业务关系,其中ABC和DE和FG三组,组与为组间无法并发,但组内数据可以并发处理。
所以针对上述应用场景,需要开发相对应的技术框架,分离业务和技术关注点,降低业务复杂度。
要完成上述框架功能,分析需要解决的技术难点有:
- 首先需要一个自适应的消息采集框架,可以根据生产者的压力灵活调整批消息组中消息数量。(后序加入AI后,可以将批消息组中消息数量、处理时间、队列中存量消息数量进行学习)
- 其次批消息组间的处理要分级,需要一个分级处理的框架策略
- 最后,批消息组内的消息要可以并发(最好不产生饥饿情况)
一、框架原理图
二、自适应的消息采集框架
目前自适应框架相对来说已经够用,后序考虑加上AI的功能,将关键的自适应batchSize参数,进行更智能的调整。
详细参考文章:Kafka消息消费之性能提升实践
三、分级处理框架
分级处理框架中首先要抽象出分级策略,如下:
public interface MlcStrategy<T> {
Function<T, Integer> shardKey();
default Function<T, Boolean> shouldProcess() {
return t -> true;
}
Consumer<T> dataProcess();
}
- T:泛型参数对应待处理的消息类型
- shardKey:表示消息组织分片键,一般指定成消息的唯一标识;该标识仅在组内处理时生效
- shouldProcess:这里理解成在该组策略中如何判定若干消息归为一组
- dataProcess:数据的真正处理逻辑
使用多组策略,完成消息处理,核心逻辑如下:
/**
* @param datas 待处理的数据集
* @param level 分级处理标识
*/
private void datasProcess(List<T> datas, int level) {
//当未配置该分级策略时,直接不进行处理,分级策略要对于数据封闭
if (mlcStrategies.size() < (level + 1)) {
throw new RuntimeException("Make sure mlc strategy is data sealed.");
}
final MlcStrategy<T> tMlcStrategy = mlcStrategies.get(level);
//将该组数据中属于分级策略指定的消息分离出
final List<T> needProcessDatas = datas.stream().filter(p -> tMlcStrategy.shouldProcess().apply(p))
.collect(Collectors.toList());
//并发处理,同时增加栅栏
CountDownLatch latch = new CountDownLatch(needProcessDatas.size());
SERIAL_THREAD_EXECUTOR.setLatch(latch);
for (T data : needProcessDatas) {
final Integer shardKey = tMlcStrategy.shardKey().apply(data);
System.out.println("shard key:" + shardKey);
SERIAL_THREAD_EXECUTOR.execute(String.valueOf(shardKey), () -> {
tMlcStrategy.dataProcess().accept(data);
});
}
//保证上组消息全部完(剩余消息串行进行下一轮分组处理)
SERIAL_THREAD_EXECUTOR.await();
datas.removeAll(needProcessDatas);
if (!datas.isEmpty()) {
level += 1;
datasProcess(datas, level);
}
}
如上代码,很明显用到了递归,支持无限分级。
四、保序并发处理
针对批量消息的单纯并发处理,使用线程程即可。但如上的每组消息中并发依旧也是有条件的,举个实际例子,如一组链路消息,要求同一条正反链路是严格串行的,所以不能单独地针对这组消息进行并发。这里可以采用hash的方法,将链路的源宿端口求和后进行hashcode,这样保证正反链路进到同一个队列,背后使用单线程,则可以保序。如下红框标识的示意图:
但上述实现,需要自己创建线程,并进行线程的管理,能否复用现有的JDK线程池框架?从上面的实现中看到了SERIAL_THREAD_EXECUTOR变量,类型BalanceSerialThreadExecutor
final SerialThreadExecutor<String> SERIAL_THREAD_EXECUTOR = buildSerialThreadExecutor();
private SerialThreadExecutor<String> buildSerialThreadExecutor() {
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("xlevel-%d"