解决消息队列的消息乱序问题(模拟)

1,368

明确需求

我现在要解决的是mq在消费过程中消息乱序处理问题。

image.png 简单介绍一下上图。消息通过消息解析器解析出其自带的报文序号。如果该报文序号和解析器中的前置报文序号相差等于1(消息正常)更新前置报文。并将消息的functionID得到其具体的适配处理器,得到具体的handler,进行处理。序号小于0时表示此时发生消息重复消费。其它就是消息乱序。乱序的消息加入延迟队列并按照消息的序号进行排序。并使用定时器定时的取出到消息解析器中校验。

代码实现

消息体

package com.fire.plan.mq;

import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 * @author zwd
 * @date 2021/8/28
 * @email zwd@hhh.com
 */
public class MessageContent implements Delayed {
    Integer sourceId;//不同下流的消息ID
    Integer businessId;//业务ID
    String msg;//待发送的message
    String functionId;//具体需要处理的方法
    long createTimeMillis;//消息的创建时间
    int count; //加入队列的次数

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public long getCreateTimeMillis() {
        return createTimeMillis;
    }

    public void setCreateTimeMillis(long createTimeMillis) {
        this.createTimeMillis = createTimeMillis;
    }

    public static Builder builder(){
        return new Builder();
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return 0;
    }

    @Override
    public int compareTo(Delayed o) {
        MessageContent messageContent = (MessageContent) o;
        if (this.businessId > messageContent.businessId) {
            return 1;
        }else if (this.businessId < messageContent.businessId){
            return -1;
        }else{
            return 0;
        }
    }

    static class Builder{
        private Integer sourceId;//不同下流的消息ID
        private Integer businessId;//业务ID
        private String msg;//待发送的message
        private String functionId;

        public Builder() {
        }

        public Builder sourceId(Integer sourceId) {
            this.sourceId = sourceId;
            return this;
        }

        public Builder businessId(Integer businessId) {
            this.businessId = businessId;
            return this;
        }


        public Builder msg(String msg) {
            this.msg = msg;
            return this;
        }

        public Builder functionId(String functionId) {
            this.functionId = functionId;
            return this;
        }

        public MessageContent build(){
            MessageContent messageContent = new MessageContent();
            messageContent.sourceId = this.sourceId;
            messageContent.businessId = this.businessId;
            messageContent.msg = this.msg;
            messageContent.functionId = this.functionId;
            return messageContent;
        }
    }
}

消息处理器

package com.fire.plan.mq;

/**
 * @author zwd
 * @date 2021/8/28
 * @email zwd@hhh.com
 */
public interface MessageHandler {
    void processMsg(MessageContent messageContent);
}
/**
 * @author zwd
 * @date 2021/8/28
 * @email zwd@hhh.com
 */
public class MessageHandlerOne implements MessageHandler{
    @Override
    public void processMsg(MessageContent messageContent) {
        System.out.println(messageContent.businessId + " is being process");
    }
}

/**
 * @author zwd
 * @date 2021/8/28
 * @email zwd@hhh.com
 */
public class MessageHandlerTwo implements MessageHandler{
    @Override
    public void processMsg(MessageContent messageContent) {
        System.out.println(messageContent.businessId + " is being process");
    }
}
/**
 * @author zwd
 * @date 2021/8/28
 * @email zwd@hhh.com
 */
public class MessageHandlerThree implements MessageHandler{
    @Override
    public void processMsg(MessageContent messageContent) {
        System.out.println(messageContent.businessId + " is being process");
    }
}

消息适配器

/**
 * @author zwd
 * @date 2021/8/28
 * @email zwd@hhh.com
 */
public interface MessageAdaptive {
    MessageHandler adapter(MessageContent messageContent);
}
package com.fire.plan.mq;

import java.util.HashMap;

/**
 * @author zwd
 * @date 2021/8/28
 * @email zwd@hhh.com
 */
public class MessageAdaptiveMq implements MessageAdaptive{
    private HashMap<String, MessageHandler> handlerMapping = new HashMap<>();

    public void registry(String functionId, MessageHandler messageHandler){
        handlerMapping.put(functionId, messageHandler);
    }

    @Override
    public MessageHandler adapter(MessageContent messageContent) {

        return handlerMapping.get(messageContent.functionId);
    }
}

消息解析器

package com.fire.plan.mq;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.DelayQueue;

/**
 * @author zwd
 * @date 2021/8/28
 * @email zwd@hhh.com
 */
public class MessageSolver {
    private static volatile MessageSolver messageSolver = null;
    Integer preBusinessId;
    DelayQueue<MessageContent> delayed;
    MessageAdaptive messageAdaptive;
    long timeout = 6;

    private MessageSolver(){
        this.delayed =  new DelayQueue<>();
        preBusinessId = 0;
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                if (!delayed.isEmpty()) {
                    MessageContent messageContent = delayed.poll();
                    long currentTimeMillis = System.currentTimeMillis() / 1000;
                    long createTimeMillis = messageContent.createTimeMillis;
                    if (currentTimeMillis - createTimeMillis > timeout) {
                        //将消息存储到db
                        System.out.println(messageContent.businessId + " will be store to db");
                    } else {
                        //重新检查前置报文
                        messageSolver.messageSolver(messageContent);
                    }
                }
            }
        };

        Timer timer = new Timer();
        //500ms检查一次前置报文
        timer.schedule(timerTask, 0 ,500);
    }

    public static MessageSolver getMessageSolver(){
        if (messageSolver == null) {
            synchronized (MessageSolver.class) {
                if (messageSolver == null) {
                    messageSolver = new MessageSolver();
                }
            }
        }
        return messageSolver;
    }

    public void messageSolver(MessageContent messageContent){
        int flag = messageContent.businessId - messageSolver.preBusinessId;
        if (flag == 1) {
            MessageHandler adapter = messageAdaptive.adapter(messageContent);
            adapter.processMsg(messageContent);
            //更新前置报文编号
            this.preBusinessId = messageContent.businessId;
        }else if (flag <= 0){
            System.out.println("this message has been consumed");
        }else {
            if (messageContent.getCount() == 0)  messageContent.setCreateTimeMillis(System.currentTimeMillis() / 1000);
            messageContent.setCount(messageContent.getCount() + 1);
            delayed.add(messageContent);
        }
    }


    public long getTimeout() {
        return timeout;
    }

    public void setTimeout(long timeout) {
        this.timeout = timeout;
    }

    public MessageAdaptive getMessageAdaptive() {
        return messageAdaptive;
    }

    public void setMessageAdaptive(MessageAdaptive messageAdaptive) {
        this.messageAdaptive = messageAdaptive;
    }
}

测试

package com.fire.plan.mq;

/**
 * @author zwd
 * @date 2021/8/28
 * @email zwd@hhh.com
 */
public class Main {
    public static void main(String[] args) throws InterruptedException {
        MessageContent message1 = MessageContent.builder()
                .sourceId(1).businessId(1).msg("first message").functionId("function01").build();
        MessageContent message2 = MessageContent.builder()
                .sourceId(1).businessId(2).msg("second message").functionId("function02").build();
        MessageContent message3 = MessageContent.builder()
                .sourceId(1).businessId(3).msg("third message").functionId("function03").build();

        MessageAdaptiveMq messageAdaptiveMq = new MessageAdaptiveMq();
        messageAdaptiveMq.registry("function01",new MessageHandlerOne());
        messageAdaptiveMq.registry("function02",new MessageHandlerTwo());
        messageAdaptiveMq.registry("function03",new MessageHandlerThree());

        MessageSolver messageSolver = MessageSolver.getMessageSolver();
        messageSolver.setMessageAdaptive(messageAdaptiveMq);
        messageSolver.messageSolver(message3);
        messageSolver.messageSolver(message1);
        messageSolver.messageSolver(message2);
    }
}

输出:
1 is being process
2 is being process
3 is being process