Message queue (9) - implementation of consumer core class

foreword

In our last blog, we wrote about the implementation of the virtual host. In the virtual host, we need to use two unrealized classes, which are the verification binding keyword and the consumer class. Next, we implement the core code of the consumer class.

consumer design ideas

In this class, first we need to hold the virtualHost object to operate the data, then we designate a thread pool to be responsible for the specific callback function, and scan all the queues through a scanning queue to see if there is a new message in that queue. If there is, it will be placed in the blocking queue, and the consumer will take out a message from the blocking queue to respond each time. If multiple consumers subscribe to a message, then use the polling method to get the message
insert image description here

Core API

Attributes

Virtual host
thread pool
blocking queue
scanning thread

Method
①Add a message to the blocking queue

// 往阻塞队列中添加消息
    public void notifyConsume(String queueName) throws InterruptedException {
    
    
        tokenQueue.put(queueName);
    }

② Subscribe to news

Our idea is to find the corresponding queue first, then check whether there are messages in the queue, and consume the messages if there are

 //添加订阅者
    public void addConsumer(String consumerTag, String queueName, boolean autoAck, Consumer consumer) throws MqException {
    
    
        // 1 找到对应的队列
        MSGQueue queue = virtuaHost.getMemoryDataCenter().getQueue(queueName);

        if (queue == null){
    
    
            throw new MqException("[ConsumerManager] 队列不存在! queueName=" + queueName);
        }
        ConsumEnv consumEnv = new ConsumEnv(consumerTag,queueName,autoAck,consumer);
        synchronized (queue){
    
    
            queue.addConsumEnv(consumEnv);
            // 如果当前队列中已经有了一些消息了, 需要立即就消费掉.
            int n = virtuaHost.getMemoryDataCenter().getMessageCount(queueName);
            for (int i = 0; i < n; i++) {
    
    
                // 这个方法调用一次就消费一条消息.
                consumeMessage(queue);
            }
        }
    }

③Consumption news
Regarding the consumption news, we will consume them sequentially according to the polling method

 // 消费消息
    private void consumeMessage(MSGQueue queue) {
    
    
        // 1. 按照轮询的方式, 找个消费者出来.
        ConsumEnv luckyDog = queue.chooseConsumEnv();
        if (luckyDog == null){
    
    
            // 说明没有消费者
            return;
        }
        // 2. 从队列中取出一个消息
        Message message = virtuaHost.getMemoryDataCenter().pollMessage(queue.getName());
        if (message == null){
    
    
            // 说明没有消息,不能消费
            return;
        }
        // 3. 把消息带入到消费者的回调方法中, 丢给线程池执行.
        workPool.submit(()->{
    
    
            try {
    
    
                //1,将消息放到待确认的集合中, 这个操作在回调函数之前
                virtuaHost.getMemoryDataCenter().addMessageWaitAck(queue.getName(),message);
                //2. 执行回调函数
                luckyDog.getConsumer().handleDelivery(luckyDog.getConsumerTag(),message.getBasicProperties(),message.getBody());
                System.out.println("[ConsumerManager] 消息被成功消费, queueName = "+queue.getName());
                //3. 如果是自动应答, 就可以之间删除消息
                // 如果是手动应答,  就先什么也不做
                if (luckyDog.isAutoAck()){
    
    
                    // 1删除硬盘上的消息
                    if (message.getDeliverMode() == 2){
    
    
                        virtuaHost.getDiskDataCenter().deleteMessage(queue,message);
                    }
                    //2 删除待确认的消息
                    virtuaHost.getMemoryDataCenter().removeMessageWaitAck(queue.getName(), message.getMessageId());
                    // 3 删除内存中的消息
                    virtuaHost.getMemoryDataCenter().removeMessage(message.getMessageId());
                    System.out.println("[ConsumerManager] 消息被成功消费! queueName=" + queue.getName());
                }
            } catch (IOException | ClassNotFoundException | MqException e) {
    
    
                e.printStackTrace();
            }
        });
    }

overall code

package com.example.demo.mqServer.core;

import com.example.demo.Common.ConsumEnv;
import com.example.demo.Common.Consumer;
import com.example.demo.Common.MqException;
import com.example.demo.mqServer.VirtuaHost;

import java.io.IOException;
import java.util.concurrent.*;

/*
* 通过这个类, 来实现来实现消费者消费消息的核心功能
* */
public class ConsumerManager {
    
    
    // 持有上层对象 VirtualHost 调用 ,来操作数据
    private VirtuaHost virtuaHost;
    // 指定一个线程池, 负责执行具体的回调函数
    private ExecutorService workPool = Executors.newFixedThreadPool(4);
    //  存放令牌的队列  - 阻塞队列
    private BlockingDeque<String> tokenQueue = new LinkedBlockingDeque<>();
    //  扫描线程
    private Thread scannerThread = null;
    //
    public ConsumerManager(VirtuaHost virtuaHost) {
    
    
        this.virtuaHost = virtuaHost;
        scannerThread =new Thread(()->{
    
    
            while (true){
    
    
                try {
    
    
                    String queueName = tokenQueue.take();
                    MSGQueue queue = virtuaHost.getMemoryDataCenter().getQueue(queueName);
                    if (queue == null){
    
    
                        throw new MqException("[ConsumerManager] 取令牌后发现, 该队列名不存在! queueName=" + queueName);
                    }
                    synchronized (queue){
    
    
                        consumeMessage(queue);
                    }
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } catch (MqException e) {
    
    
                    e.printStackTrace();
                }
            }
        });
        scannerThread.setDaemon(true);
        scannerThread.start();
    }
    // 往阻塞队列中添加消息
    public void notifyConsume(String queueName) throws InterruptedException {
    
    
        tokenQueue.put(queueName);
    }
    // 增加订阅
    public void addConsumer(String consumerTag, String queueName, boolean autoAck, Consumer consumer) throws MqException {
    
    
       // 先找到对应的队列
        MSGQueue queue = virtuaHost.getMemoryDataCenter().getQueue(queueName);
        if (queue == null){
    
    
            throw new MqException("[ConsumerManager] 队列不存在! queueName=" + queueName);
        }
        ConsumEnv consumEnv = new ConsumEnv(consumerTag,queueName,autoAck,consumer);
        synchronized (queue){
    
    
            queue.addConsumEnv(consumEnv);
            // 如果当前队列中已经有了一些消息了, 需要立即就消费掉.
            int n = virtuaHost.getMemoryDataCenter().getMessageCount(queueName);
            for (int i = 0; i < n; i++) {
    
    
                // 这个方法调用一次就消费一条消息.
                consumeMessage(queue);
            }
        }
    }
    private void consumeMessage(MSGQueue queue) {
    
    
        // 1. 按照轮询的方式, 找个消费者出来.
        ConsumEnv luckyDog = queue.chooseConsumEnv();
        if (luckyDog == null){
    
    
            // 说明没有消费者
            return;
        }
        // 2. 从队列中取出一个消息
        Message message = virtuaHost.getMemoryDataCenter().pollMessage(queue.getName());
        if (message == null){
    
    
            // 说明没有消息,不能消费
            return;
        }
        // 3. 把消息带入到消费者的回调方法中, 丢给线程池执行.
        workPool.submit(()->{
    
    
            try {
    
    
                //1,将消息放到待确认的集合中, 这个操作在回调函数之前
                virtuaHost.getMemoryDataCenter().addMessageWaitAck(queue.getName(),message);
                //2. 执行回调函数
                luckyDog.getConsumer().handleDelivery(luckyDog.getConsumerTag(),message.getBasicProperties(),message.getBody());
                System.out.println("[ConsumerManager] 消息被成功消费, queueName = "+queue.getName());
                //3. 如果是自动应答, 就可以之间删除消息
                // 如果是手动应答,  就先什么也不做
                if (luckyDog.isAutoAck()){
    
    
                    // 1删除硬盘上的消息
                    if (message.getDeliverMode() == 2){
    
    
                        virtuaHost.getDiskDataCenter().deleteMessage(queue,message);
                    }
                    //2 删除待确认的消息
                    virtuaHost.getMemoryDataCenter().removeMessageWaitAck(queue.getName(), message.getMessageId());
                    // 3 删除内存中的消息
                    virtuaHost.getMemoryDataCenter().removeMessage(message.getMessageId());
                    System.out.println("[ConsumerManager] 消息被成功消费! queueName=" + queue.getName());
                }
            } catch (IOException | ClassNotFoundException | MqException e) {
    
    
                e.printStackTrace();
            }
        });
    }


}

Guess you like

Origin blog.csdn.net/qq_56454895/article/details/132182782