JAVA:多线程处理,实现MQ中同类型消息保序消费

最近项目中有一个关于MQ消息消费和java线程池的应用场景,如下:

JAVA:多线程处理,实现MQ中同类型消息保序消费_第1张图片
基本场景图

业务进程:实现基础业务功能,将租户业务拆分成设备配置,通过MQ与设备适配层进行消息通知。

MQ:使用主流MQ中间件实现。保存的消息为租户业务拆分后的设备配置信息。

设备适配层:消息消费者,消费消息(单线程消费),将消息中的设备配置信息下发到对应的设备上(多线程处理配置下发)。


JAVA:多线程处理,实现MQ中同类型消息保序消费_第2张图片
消息实例

        基本的消息体如上图所示,业务进程根据租户业务拆分成一个(或多个)设备的多个配置项,每台设备的配置项之间有配置顺序要求:config1->config2->config3->config4。业务进程按照配置顺序将配置信息写入MQ中,此时MQ中的消息是有序的。

        设备适配层作为消息消费者,读取MQ中的设备配置消息,使用线程池来进行设备配置处理,提高消息处理速度。此时,如何在多线程并行的情况下保证同一台设备的配置下发顺序,即在多线程并行处理时如何保证MQ中同类型消息(设备ip相同)的保序消费呢???


        因为消息可以根据设备ip进行区分,这时我想到如果可以把包含同一个设备ip的消息放入到一个线程中处理就可以解决该问题了。- _ -

        那么如何做呢???或者说如何设计Runnable的接口实现??(Thread执行的Task)

        上面写了一大堆文字,作为码农的我真心感觉有点累,下面先上代码吧:

package thread;

import java.util.ArrayList;

import java.util.LinkedList;

import java.util.List;

import java.util.concurrent.ScheduledThreadPoolExecutor;

import java.util.concurrent.TimeUnit;

public class T004_TestThreadRef {

public static void main(String[] args) {

//初始化Scheduled线程池,设置核心线程数为5

ScheduledThreadPoolExecutor executorService =new ScheduledThreadPoolExecutor(5);

//初始化5个task

OperationTask task1 =new OperationTask("taskList1");

OperationTask task2 =new OperationTask("taskList2");

OperationTask task3 =new OperationTask("taskList3");

OperationTask task4 =new OperationTask("taskList4");

OperationTask task5 =new OperationTask("taskList5");

//使用线程池执行task

executorService.scheduleAtFixedRate(task1,0,5000,TimeUnit.MILLISECONDS);

executorService.scheduleAtFixedRate(task2,0,5000,TimeUnit.MILLISECONDS);

executorService.scheduleAtFixedRate(task3,0,5000,TimeUnit.MILLISECONDS);

executorService.scheduleAtFixedRate(task4,0,5000,TimeUnit.MILLISECONDS);

executorService.scheduleAtFixedRate(task5,0,5000,TimeUnit.MILLISECONDS);

//构建消息list

ListipList =new ArrayList<>();

ipList.add("1.1.1.1");

ipList.add("2.2.2.2");

ipList.add("3.3.3.3");

ipList.add("4.4.4.4");

ipList.add("5.5.5.5");

ipList.add("6.6.6.6");

ipList.add("7.7.7.7");

ipList.add("1.1.1.1");

ipList.add("1.1.1.1");

ipList.add("1.1.1.1");

ipList.add("1.1.1.1");

ipList.add("1.1.1.1");

ipList.add("1.1.1.1");

ipList.add("1.1.1.1");

//将消息Hash到对应的task中

dispatchThread(task1,task2,task3,task4,task5,ipList);

try {

Thread.sleep(1000);

}catch (InterruptedException e) {

e.printStackTrace();

}

// 模拟第二次的消息消费

dispatchThread(task1,task2,task3,task4,task5,ipList);

//关闭线程池

//executorService.shutdown();

}

private static void dispatchThread(OperationTask task1,OperationTask task2,OperationTask task3,OperationTask task4,OperationTask task5,List ipList) {

for (String s : ipList) {

//使用设备ip的hash进行取模运算

int mod =Math.floorMod(s.hashCode(),5);

switch (mod) {

case 1:

task1.addTask(s);

break;

case 2:

task2.addTask(s);

break;

case 3:

task3.addTask(s);

break;

case 4:

task4.addTask(s);

break;

case 0:

task5.addTask(s);

break;

default:

task1.addTask(s);

break;

}

}

}

static class OperationTask implements Runnable {

public OperationTask(String name) {

this.name = name;

}

private final String name;

//配置下发任务队列

//        private final Queue tasksQueue = new LinkedList<>();

        private final LinkedListtasks =new LinkedList<>();

//添加配置下发任务

        public void addTask(String task) {

this.tasks.add(task);

}

@Override

        public void run() {

//执行配置下发操作

System.out.println(name + " start.");

            int count =0;

while (!tasks.isEmpty()) {

System.out.println("TaskOperator: " +name +" operate task : " +tasks.pop());

try {

Thread.sleep(500);

}catch (InterruptedException e) {

e.printStackTrace();

}

count++;

}

/*            Iterator iterable = tasks.iterator();

while (iterable.hasNext()) {

System.out.println("TaskOperator: " + name + " operate task : " + tasks.pop());

try {

Thread.sleep(500);

} catch (InterruptedException e) {

e.printStackTrace();

}

iterable.remove();

count++;

}*/

            System.out.println(name +" end with " + count +" completed");

}

}

}



        还是代码看着亲切啊!!上面的代码就是我模拟的设备适配层中消费消息的功能:使用ScheduledThreadPoolExecutor这个线程池来处理设备配置下发操作。以5s周期执行OperationTask任务。

JAVA:多线程处理,实现MQ中同类型消息保序消费_第3张图片
Task

        OperationTask实现了Runnable接口,它的run方法执行的就是配置下发操作,使用while循环判断tasks中是否存在配置下发任务,使用tasks.pop()方法获取配置下发消息。注意,这里使用了LinkedList来保存设备配置任务。LinkedListFIFO特性,可以保证配置任务的顺序。

        为什么使用while+pop来处理任务,不使用for/Iterator+remove来处理任务呢??

        消息消费使用的是线程池,由一个主线程将消息写入到对应的worker线程的队列中,worker线程又要从队列中取数据进行处理,此时使用for/Iterator+remove操作进行任务处理时会因为主线程写入数据到队列,导致List中length和index的变化,导致循环失败。

JAVA:多线程处理,实现MQ中同类型消息保序消费_第4张图片
Iterator-remove

        我这里使用Iterator进行循环,使用remove清除队列中的已完成消息,会导致线程池中线程挂掉。。。具体原因还没有找到,后面找到再更新到这里。

JAVA:多线程处理,实现MQ中同类型消息保序消费_第5张图片
dispatchThread

        将设备ip进行hash+mod计算,分配task到对应的OperationTask的任务队列中,调用task的addTask方法。


JAVA:多线程处理,实现MQ中同类型消息保序消费_第6张图片
模拟消息消费

        ipList模拟设备配置消息,包含设备ip。通过调用dispatchThread方法模拟消息消费,每次消费ipList.length个消息,然后进行运算写入到各task的队列中。Schedule线程池执行task任务,周期性消费task的队列(LinkedList)中的消息。

        结果:

taskList4 start.

taskList5 start.

taskList3 start.

taskList2 start.

TaskOperator: taskList2 operate task : 2.2.2.2

taskList1 start.

TaskOperator: taskList3 operate task : 6.6.6.6

TaskOperator: taskList5 operate task : 4.4.4.4

TaskOperator: taskList4 operate task : 1.1.1.1

TaskOperator: taskList1 operate task : 3.3.3.3

TaskOperator: taskList1 operate task : 7.7.7.7

taskList3 end with 1 completed

TaskOperator: taskList4 operate task : 5.5.5.5

taskList2 end with 1 completed

taskList5 end with 1 completed

TaskOperator: taskList1 operate task : 3.3.3.3

TaskOperator: taskList4 operate task : 1.1.1.1

TaskOperator: taskList1 operate task : 7.7.7.7

TaskOperator: taskList4 operate task : 1.1.1.1

taskList1 end with 4 completed

TaskOperator: taskList4 operate task : 1.1.1.1

TaskOperator: taskList4 operate task : 1.1.1.1

TaskOperator: taskList4 operate task : 1.1.1.1

TaskOperator: taskList4 operate task : 1.1.1.1

TaskOperator: taskList4 operate task : 1.1.1.1

TaskOperator: taskList4 operate task : 1.1.1.1

taskList1 start.

taskList1 end with 0 completed

taskList2 start.

taskList5 start.

TaskOperator: taskList5 operate task : 4.4.4.4

taskList3 start.

TaskOperator: taskList2 operate task : 2.2.2.2

TaskOperator: taskList3 operate task : 6.6.6.6

TaskOperator: taskList4 operate task : 5.5.5.5

taskList5 end with 1 completed

taskList2 end with 1 completed

taskList3 end with 1 completed

TaskOperator: taskList4 operate task : 1.1.1.1

TaskOperator: taskList4 operate task : 1.1.1.1

TaskOperator: taskList4 operate task : 1.1.1.1

TaskOperator: taskList4 operate task : 1.1.1.1

TaskOperator: taskList4 operate task : 1.1.1.1

TaskOperator: taskList4 operate task : 1.1.1.1

TaskOperator: taskList4 operate task : 1.1.1.1

taskList4 end with 18 completed

taskList4 start.

taskList4 end with 0 completed

taskList1 start.

taskList2 start.

taskList2 end with 0 completed

taskList1 end with 0 completed

taskList3 start.

taskList3 end with 0 completed

taskList4 start.

taskList4 end with 0 completed

taskList5 start.

taskList5 end with 0 completed

        通过结果看到,总共执行了28个task,与dispatchThread分配的task个数相同。

        第一次通过记录自己的coding生活,有不到位的地方欢迎大家指正,同时有更好的解决方式希望大佬们不吝赐教,多谢!

你可能感兴趣的:(JAVA:多线程处理,实现MQ中同类型消息保序消费)