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

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

基本场景图

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

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

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


消息实例

        基本的消息体如上图所示,业务进程根据租户业务拆分成一个(或多个)设备的多个配置项,每台设备的配置项之间有配置顺序要求: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任务。

Task

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

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

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

Iterator-remove

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

dispatchThread

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


模拟消息消费

        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生活,有不到位的地方欢迎大家指正,同时有更好的解决方式希望大佬们不吝赐教,多谢!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • 编写优质的并发代码是一件难度极高的事情。Java语言从第一版本开始内置了对多线程的支持,这一点在当年是非常了不起的...
    哥哥是欧巴Vitory阅读 367评论 0 0
  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    胜浩_ae28阅读 5,035评论 0 23
  • 线程对象是可以产生线程的对象。比如在 Java 平台中 Thread 对象 、Runnable 对象。线程,是指正...
    Alei_Android阅读 369评论 0 0
  • 黑色的海岛上悬着一轮又大又圆的明月,毫不嫌弃地把温柔的月色照在这寸草不生的小岛上。一个少年白衣白发,悠闲自如地倚坐...
    小水Vivian阅读 3,066评论 1 5
  • 渐变的面目拼图要我怎么拼? 我是疲乏了还是投降了? 不是不允许自己坠落, 我没有滴水不进的保护膜。 就是害怕变得面...
    闷热当乘凉阅读 4,190评论 0 13