Sur la base du code source, simulez et implémentez RabbitMQ - obtenez la persistance des messages et unifiez les opérations sur le disque dur (3)

Table des matières

1. Implémenter la persistance des messages

1.1. Paramètres de stockage des messages

1.1.1. Méthode de stockage

1.1.2. Accord sur le format de stockage

1.1.3. Contenu du fichier Queue_data.txt

 1.1.4. Contenu du fichier Queue_stat.txt

1.2. Implémenter la classe MessageFileManager

1.2.1. Concevoir la structure des répertoires et le format de fichier

1.2.2. Implémenter la rédaction du message

1.2.3. Implémenter la suppression des messages (accès aléatoire aux fichiers)

1.2.4. Récupérer tous les messages valides dans le fichier de file d'attente

1.2.5. Mécanisme GC

1.2.6, extension du GC

2. Fonctionnement unifié du disque dur


1. Implémenter la persistance des messages


1.1. Paramètres de stockage des messages

1.1.1. Méthode de stockage

Comment les messages Message transmis doivent-ils être stockés sur le disque dur ? Nous devrions considérer quelques points :

  1. Les opérations de message n'impliquent pas d'ajouts, de suppressions, de modifications et de recherches complexes.
  2. Le nombre de messages peut être très important et l'efficacité de l'accès à la base de données n'est pas très élevée.

Par conséquent, la base de données n'est pas utilisée ici pour le stockage, mais les messages sont stockés dans des fichiers ~

1.1.2. Accord sur le format de stockage

Les messages sont attachés aux files d'attente. Ainsi, une fois stockés, les messages sont développés en fonction de la dimension de la file d'attente.

D'après le chapitre précédent, nous avons parlé du stockage de la base de données, nous avons donc déjà le répertoire de données (meta.db est dans ce répertoire). Ici, nous sommes d'accord - une file d'attente est un répertoire de fichiers, et il y a deux fichiers pour stocker les messages, comme indiqué ci-dessous:

  1. Le premier fichier queue_data.txt : permet de sauvegarder le contenu du message ;
  2. Le deuxième fichier queue_stat.txt : utilisé pour sauvegarder les informations statistiques des messages ;

1.1.3. Contenu du fichier Queue_data.txt

Il est convenu ici que le fichier queue_data.txt contient plusieurs messages, chaque message est stocké sous forme binaire et chaque message se compose de deux parties.

  1. La première partie est censée occuper 4 octets et est utilisée pour enregistrer la longueur du message (pour éviter les problèmes de collage).
  2. La deuxième partie concerne les données binaires spécifiques du message (données après sérialisation de l'objet Message).

Comme indiqué ci-dessous:

 1.1.4. Contenu du fichier Queue_stat.txt

Utilisez ce fichier pour enregistrer les statistiques des messages.

Il n'y a qu'une seule ligne de données au format texte et seulement deux colonnes :

  1. La première colonne correspond au nombre total de messages dans queue_data.txt.
  2. La deuxième colonne est le nombre de messages valides dans queue_data.txt.

Les deux sont séparés par \t, sous la forme : 2000\t1500

1.2. Implémenter la classe MessageFileManager

1.2.1. Concevoir la structure des répertoires et le format de fichier

Définissez une classe interne pour représenter les informations statistiques de la file d'attente (statique est préférée et découplée de la classe externe).

    static public class Stat {
        //对于这样的简单类定义成 public 就不用 get set 方法了,类似于 C 的结构体
        public int totalCount;
        public int validCount;
    }

Obtenez le chemin du fichier de messages correspondant à la file d'attente et le chemin du fichier de données/statistiques de la file d'attente via les méthodes suivantes.

    /**
     * 用来获取指定队列对应的消息文件所在路径
     * @param queueName
     * @return
     */
    private String getQueueDir(String queueName) {
        return "./data/" + queueName;
    }

    /**
     * 用来获取该队列的消息数据文件路径
     * 此处使用 txt 文件,存储二进制数据,实际上不太合适,但也先这样吧~
     * 跟适合使用 .bin / .dat
     * @param queueName
     * @return
     */
    private String getQueueDataPath(String queueName) {
        return getQueueDir(queueName) + "/queue_data.txt";
    }

    /**
     * 用来获取该队列的消息统计文件路径
     * @param queueName
     * @return
     */
    private String getQueueStatPath(String queueName) {
        return getQueueDir(queueName) + "/queue_stat.txt";
    }

Lisez et écrivez le fichier de statistiques de file d'attente via les méthodes suivantes (pour faciliter l'initialisation du fichier de statistiques lors de la création ultérieure du fichier).

    /**
     * 从文件中读取队列消息统计信息
     * @param queueName
     * @return
     */
    private Stat readStat(String queueName) {
        Stat stat = new Stat();
        try(InputStream inputStream = new FileInputStream(getQueueStatPath(queueName))) {
            Scanner scanner = new Scanner(inputStream);
            stat.totalCount = scanner.nextInt();
            stat.validCount = scanner.nextInt();
            return stat;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 将队列消息统计信息写入文件
     * @param queueName
     * @param stat
     */
    private void writeStat(String queueName, Stat stat) {
        try(OutputStream outputStream = new FileOutputStream(getQueueStatPath(queueName))) {
            PrintWriter printWriter = new PrintWriter(outputStream);
            printWriter.write(stat.totalCount + "\t" + stat.validCount);
            printWriter.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

Créez et détruisez des fichiers et des répertoires via

    /**
     * 创建队列对应的文件和目录
     * @param queueName
     */
    public void createQueueFiles(String queueName) throws IOException {
        //1.创建队列对应的消息目录
        File baseDir = new File(getQueueDir(queueName));
        if(!baseDir.exists()) {
            //不存在,就创建这个目录
            boolean ok = baseDir.mkdirs();
            if (!ok) {
                throw new IOException("创建目录失败!baseDir=" + baseDir.getAbsolutePath());
            }
        }
        //2.创建队列数据文件
        File queueDataFile = new File(getQueueDataPath(queueName));
        if(!queueDataFile.exists()) {
            boolean ok = queueDataFile.createNewFile();
            if(!ok) {
                throw new IOException("创建文件失败! queueDataFile=" + queueDataFile.getAbsolutePath());
            }
        }
        //3.创建消息统计文件
        File queueStatFile = new File(getQueueStatPath(queueName));
        if(!queueStatFile.exists()) {
            boolean ok = queueStatFile.createNewFile();
            if(!ok) {
                throw new IOException("创建文件失败! queueStatFile=" + queueStatFile.getAbsolutePath());
            }
        }
        //4.给消息统计文件,设定初始值. 0\t0
        Stat stat = new Stat();
        stat.totalCount = 0;
        stat.validCount = 0;
        writeStat(queueName, stat);
    }

    /**
     * 删除队列的目录和文件
     * 此方法的用处:队列也是可以被删除的,队列删除之后,就需要调用此方法,删除对应的消息文件之类的
     * @param queueName
     * @throws IOException
     */
    public void destroyQueueFiles(String queueName) throws IOException {
        //先删除里面的文件,再删除目录
        File queueDataFile = new File(getQueueDataPath(queueName));
        boolean ok1 = queueDataFile.delete();
        File queueStatFile = new File(getQueueStatPath(queueName));
        boolean ok2 = queueStatFile.delete();
        File baseDir = new File(getQueueDir(queueName));
        boolean ok3 = baseDir.delete();
        if(!ok1 || !ok2 || !ok3) {
            //任意一个失败,都算整体删除失败
            throw new IOException("删除队列目录和文件失败! baseDir=" + baseDir.getAbsolutePath());
        }
    }

1.2.2. Implémenter la rédaction du message

La rédaction du message est principalement divisée en quatre étapes suivantes :

  1. Vérifiez d'abord si le fichier actuel existe
  2. Sérialisez l'objet Message dans un tableau d'octets binaires
  3. Calculez offsetBeg et offsetEnd de l’objet Message en fonction de la longueur actuelle du fichier de file d’attente.
  4. Ajouter les données du message à la fin du fichier
  5. Mettre à jour le contenu du fichier de statistiques des messages
    /**
     * 检查队列的目录和文件是否存在
     * 如果后续有生产者 broker server 生产消息了,这个消息就需要被记录到文件上(持久化的前提是文件必须要存在)
     * @param queueName
     * @return
     */
    public boolean checkFilesExits(String queueName) {
        //数据文件和统计文件都判断存在
        File queueDataFile = new File(getQueueDataPath(queueName));
        if(!queueDataFile.exists()) {
            return false;
        }
        File queueStatFile = new File(getQueueStatPath(queueName));
        if(!queueStatFile.exists()) {
            return false;
        }
        return true;
    }


    /**
     * 将一个新的消息(message)放到队列文件中(queue)
     * @param queue
     * @param message
     */
    public void sendMessage(MSGQueue queue, Message message) throws MqException, IOException {
        //1.先检查当前文件是否存在
        if(!checkFilesExits(queue.getName())) {
            throw new MqException("[MessageFileManager] 队列对应的文件不存在! queueName=" + queue.getName());
        }
        //2.把 Message 对象进行序列化,转化成 二进制 字节数组
        byte[] messageBinary = BinaryTool.toBytes(message);
        //3.根据当前队列文件长度,计算出 Message 对象的 offsetBeg 和 offsetEnd
        //将新的 Message 数据,写入到文件的末尾,那么此时 offsetBeg = 4 + 当前文件总长度 (4 个字节是我们约定好用来表示信息长度的)
        // offsetEnd = 当前文件总长度 + 4 + message 长度

        //这里为了避免写操作引发线程安全问题
        synchronized(queue) {
            File queueDataFile = new File(getQueueDataPath(queue.getName()));
            message.setOffsetBeg(queueDataFile.length() + 4);
            message.setOffsetEnd(queueDataFile.length() + 4 + messageBinary.length);
            //4.将 message 数据追加到文件末尾
            try(OutputStream outputStream = new FileOutputStream(queueDataFile, true)) { //这里 true 表示追加到文件末尾
                try(DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) {
                    //这里用 writeInt 来写 message 长度是为了保证占 4 个字节(直接用 write 只会写一个字节)
                    dataOutputStream.writeInt(messageBinary.length);
                    //写入消息体
                    dataOutputStream.write(messageBinary);
                    dataOutputStream.flush();
                }
            }
            //5.更新消息统计文件内容
            Stat stat = readStat(queue.getName());
            stat.validCount += 1;
            stat.totalCount += 1;
            writeStat(queue.getName(), stat);
        }
    }

1.2.3. Implémenter la suppression des messages (accès aléatoire aux fichiers)

La logique de suppression ici consiste en fait à définir l'attribut isValid dans les données stockées sur le disque dur sur 0, puis à l'écrire sur le disque dur.

  1. Lisez d’abord les données du fichier et restaurez-les dans l’objet Message.
  2. Remplacer isValid par 0
  3. Écrivez les données ci-dessus dans le fichier
  4. Mettre à jour le fichier de statistiques

Pourquoi cette méthode de suppression est-elle adoptée ici ?

Lors de l'ajout d'un message, vous pouvez directement ajouter le message à la fin du fichier, mais la suppression du message est difficile ~ car le fichier peut être considéré comme une structure de "liste de séquences", donc si vous supprimez directement les éléments du milieu, vous avez besoin concevoir un "transfert de liste de séquences". Une telle opération est très inefficace.

Par conséquent, il est plus approprié d'utiliser la suppression logique ici~~

  • Lorsque isValid vaut 1, cela indique un message valide.
  • Lorsque isValid vaut 0, cela indique un message invalide

Au fil du temps, le fichier peut devenir de plus en plus volumineux et il peut y avoir un grand nombre de messages invalides. Dans ce cas, il est nécessaire de mettre en œuvre un mécanisme de récupération de place sur le fichier de données de message actuel (ce sera discuté plus tard).

    public void deleteMessage(MSGQueue queue, Message message) throws IOException, ClassNotFoundException {
        //读写文件注意线程安全问题
        synchronized(queue) {
            try (RandomAccessFile randomAccessFile = new RandomAccessFile(getQueueDataPath(queue.getName()), "rw")) {
                //1.先从文件中读出对应的 Message 数据
                byte[] bufferSrc = new byte[(int) (message.getOffsetEnd() - message.getOffsetBeg())];
                randomAccessFile.seek(message.getOffsetBeg());
                randomAccessFile.read(bufferSrc); //类似于食堂打饭
                //2.把当前读出来的二进制数据,反序列化成 Message 对象
                Message diskMessage = (Message) BinaryTool.fromBytes(bufferSrc);
                //3.把 isValid 设置成无效
                diskMessage.setIsValid((byte) 0x0);
                //此处不用把形参中的 message 的 isValid 设为 0,因为这个参数代表内存中管理的 Message 对象
                //这个对象马上就会被从内存中删除
                //4.重新写入文件
                byte[] bufferDest = BinaryTool.toBytes(diskMessage);
                //这里还需要将光标移动到最初这个消息的位置,因为 read 操作也会挪动光标
                randomAccessFile.seek(message.getOffsetBeg());
                randomAccessFile.write(bufferDest);
                // 通过上述折腾,对于文件来说,只有一个字节发生改变了而已
            }
            //更新统计文件,消息无效了,消息个数就需要 -1
            Stat stat = readStat(queue.getName());
            if(stat.validCount > 0) {
                stat.validCount -= 1;
            }
            writeStat(queue.getName(), stat);
        }
    }

Ps : L'objet message dans ce paramètre ici doit contenir offsetBeg et offsetEnd valides

1.2.4. Récupérer tous les messages valides dans le fichier de file d'attente

Lisez le contenu du message valide (isValid = 1) dans le fichier et chargez-le en mémoire (cette méthode est prête à être appelée au démarrage du programme, donc aucun verrouillage n'est requis)

Ps :
 Un seul paramètre de queueName suffit ici, aucun objet MSGQueue n'est nécessaire.
LinkedList est principalement utilisé pour les opérations de suppression de tête ultérieures.

    public LinkedList<Message> loadAllMessageFromQueue(String queueName) throws IOException, MqException, ClassNotFoundException {
        LinkedList<Message> messages = new LinkedList<>();
        try (InputStream inputStream = new FileInputStream(getQueueDataPath(queueName))) {
            try (DataInputStream dataInputStream = new DataInputStream(inputStream)) {
                //记录当前光标位置
                long currentOffset = 0;
                while(true) {
                    //1.读取当前消息的长度
                    int messageSize = dataInputStream.readInt();
                    //2.按照长度获取消息内容
                    byte[] buffer = new byte[messageSize];
                    int actualSize = inputStream.read(buffer);
                    //比较理论和实际消息长度
                    if(messageSize != actualSize) {
                        //如果不匹配说明文件出问题了
                        throw new MqException("[MessageFileManager] 文件格式错误! queueName=" + queueName);
                    }
                    //3.把读到的二进制数据反序列化成 Message 对象
                    Message message = (Message) BinaryTool.fromBytes(buffer);
                    //4.判断这个消息是否是无效对象
                    if(message.getIsValid() != 0x1) {
                        //无效消息直接跳过
                        //虽然是无效数据,但是 offset 不要忘记更新
                        currentOffset += (4 + messageSize);
                        continue;
                    }
                    //5.有效数据就加入到链表中,加入前计算一下 offsetBeg 和 offsetEnd
                    //这个位置需要知道当前文件光标的位置,由于当下使用的 DataInputStream 不方便直接获取文件光标位置, 因此需要使用 currentOffset 手动记录一下
                    message.setOffsetBeg(currentOffset + 4);
                    message.setOffsetEnd(currentOffset + 4 + messageSize);
                    currentOffset += (4 + messageSize);
                    //6.最后加入到链表当中
                    messages.add(message);
                }
            } catch (EOFException e) {
                //这个 catch 并非真的用来处理 ”异常“ ,而是 ”正常“ 业务逻辑,这是为了当消息读完了能得到一个反馈(有点顺水推舟的感觉)
                //因为,当消息读取到文件末尾,readInt 就会引发异常(EOF异常)
                System.out.println("[MessageFileManager] 恢复 Message 数据完成");
            }
        }
        return messages;
    }

1.2.5. Mécanisme GC

Ici, nous utilisons un algorithme de copie pour collecter les déchets dans le fichier de données du message.

Plus précisément, nous parcourons directement le fichier de données de message d'origine, copions toutes les données valides dans un nouveau fichier, supprimons l'intégralité de l'ancien fichier, puis changeons le nom du nouveau fichier par le nom de l'ancien fichier.

Quand un GC est-il déclenché ?

Le principe selon lequel l'algorithme de réplication est approprié est qu'il n'y a pas beaucoup de données valides dans l'espace actuel et que la plupart sont des données invalides (pour réduire le coût de déplacement des données)

On est donc ici d'accord : lorsque le nombre total de messages dépasse 2000, et que le nombre de messages valides est inférieur à 50% du nombre total de messages, un GC sera déclenché (pour éviter que les GC soient trop fréquents, par exemple, là soit 4 messages au total, dont 2 invalides, cela déclenche GC).

Ps : Les deux chiffres ici sont tous deux personnalisés. L'accent doit être mis sur les stratégies, les idées et les méthodes, plutôt que sur des chiffres spécifiques.

    /**
     * 检查是否要针对队列的消息数据文件进行 GC
     * @param queueName
     * @return
     */
    public boolean checkGC(String queueName) {
        Stat stat = readStat(queueName);
        if(stat.totalCount > 2000 && (double)stat.validCount / (double)stat.totalCount < 0.5) {
            return true;
        }
        return false;
    }

    /**
     * 获取新文件
     * @param queueName
     * @return
     */
    public String getQueueDataNewPath(String queueName) {
        return getQueueDir(queueName) + "/queue_data_new.txt";
    }

    /**
     * 执行真正的 gc 操作
     * 使用复制算法完成
     * 创建一个新的文件,名字叫做 queue_data_new.txt
     * 把之前消息数据文件中的有效消息都读出来,写道新的文件中
     * 删除旧的文件,再把新的文件改名回 queue_data.txt
     * 同时要记得更新消息统计文件
     * @param queue
     */
    public void gc(MSGQueue queue) throws MqException, IOException, ClassNotFoundException {
        //gc 意味着 "大洗牌" ,这个过程中其他线程不得干预
        synchronized(queue) {
            //由于 gc 操作可能回比较耗时,此处统计一下执行耗时的时间
            long gcBeg = System.currentTimeMillis();

            //1.创建一个新文件
            File queueDataNewFile = new File(getQueueDataNewPath(queue.getName()));
            if(queueDataNewFile.exists()) {
                //正常情况下,这个文件是不存在的,如果存在就是以外,说明上次 gc 了一半,中途发生了以外
                throw new MqException("[MessageFileManager] gc 时发现该队列的 queue_data_new 已经存在! " +
                        "queueName=" + queue.getName());
            }
            boolean ok = queueDataNewFile.createNewFile();
            if(!ok) {
                throw new MqException("[MessageFileManager] 创建文件失败! queueDataNewFile=" +
                        queueDataNewFile.getName());
            }
            //2.从旧文件中读出所有的有效消息
            LinkedList<Message> messages = loadAllMessageFromQueue(queue.getName());
            //3.把有效消息写入新的文件
            try(OutputStream outputStream = new FileOutputStream(queueDataNewFile)) {
                try(DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) {
                    for(Message message : messages) {
                        byte[] buffer = BinaryTool.toBytes(message);
                        //先写消息长度
                        dataOutputStream.writeInt(buffer.length);
                        //再写消息内容
                        dataOutputStream.write(buffer);
                    }
                }
            }
            //4.删除旧文件
            File queueDataOldFile = new File(getQueueDataPath(queue.getName()));
            ok = queueDataOldFile.delete();
            if(!ok) {
                throw new MqException("[MessageFileManager] 删除旧的文件失败! queueDataOldFile=" + queueDataOldFile.getName());
            }
            //把 queue_data_new.txt 重命名成 queue_data.txt
            ok = queueDataNewFile.renameTo(queueDataOldFile);
            if(!ok) {
                throw new MqException("[MessageFileManager] 文件重命名失败! queueDataNewFile=" + queueDataNewFile.getAbsolutePath() +
                        ", queueDataOldFile=" + queueDataOldFile.getAbsolutePath());
            }
            //5.跟新统计文件
            Stat stat = readStat(queue.getName());
            stat.totalCount = messages.size();
            stat.validCount = messages.size();
            writeStat(queue.getName(), stat);

            long gcEnd = System.currentTimeMillis();
            System.out.println("[MessageFileManager] gc 执行完毕!queueName=" +
                    queue.getName() + "time=" + (gcEnd - gcBeg) + "ms");
        }
    }

1.2.6, extension du GC

Lorsqu'il y a beaucoup de messages dans une certaine file d'attente, et que beaucoup d'entre eux sont des messages valides, le coût des opérations ultérieures sur ce fichier augmentera considérablement. Par exemple, si la taille du fichier est de 10 Go, si un GC est déclenché à ce moment-là. temps, le temps global sera très élevé.

Pour RabbitMQ, la solution consiste à diviser un gros fichier en plusieurs petits fichiers.

  1. Fractionnement de fichiers : lorsque la longueur d'un seul fichier atteint un certain seuil, il sera divisé en deux fichiers (séparé, il devient plusieurs fichiers).
  2. Fusion de fichiers : chaque fichier individuel sera soumis à une GC. Si le fichier s'avère beaucoup plus petit après la GC, il peut être fusionné avec d'autres fichiers adjacents.

Idées de mise en œuvre spécifiques :

  1. Une structure de données spéciale est nécessaire pour stocker le nombre de fichiers de données dans la file d'attente actuelle, la taille de chaque fichier, le nombre de messages et le nombre de messages non valides.
  2. Concevez des stratégies pour savoir quand déclencher le fractionnement des messages et quand déclencher la fusion de fichiers.

Ps : Vous n'avez pas besoin de donner ici l'implémentation précise, si vous en avez besoin, vous pouvez m'envoyer un message privé (à condition de noter l'identifiant WeChat).

2. Fonctionnement unifié du disque dur


Utilisez cette classe pour gérer toutes les données sur le disque dur

  1. Base de données : commutateurs, liaisons, files d'attente
  2. Fichier de données : message

La logique de la couche supérieure doit faire fonctionner le disque dur et est exploitée uniformément via cette classe (le code de la couche supérieure ne se soucie pas de savoir si les données actuelles sont stockées dans une base de données ou un fichier), ce qui améliore la cohésion et la maintenabilité du code.

public class DiskDataCenter {

    //这个实例用来管理数据库中的数据
    private DataBaseManager dataBaseManager = new DataBaseManager();
    //这个实例用来管理数据文件中的数据
    private MessageFileManager messageFileManager = new MessageFileManager();

    /**
     * 针对上面两个实例进行初始化
     */
    public void init() {
        dataBaseManager.init();
        // messageFileManager 中 init 是一个空方法,只是先列在这里,一旦后续需要扩展,就在这里进行初始化即可
        messageFileManager.init();
    }

    //封装交换机操作
    public void insertExchange(Exchange exchange) {
        dataBaseManager.insertExchange(exchange);
    }

    public void deleteExchange(String exchangeName) {
        dataBaseManager.deleteExchange(exchangeName);
    }

    public List<Exchange> selectAllExchanges() {
        return dataBaseManager.selectAllExchanges();
    }

    //封装队列操作
    public void insertQueue(MSGQueue queue) throws IOException {
        dataBaseManager.insertQueue(queue);
        //创建队列的同时,不仅需要把队列写入到数据库中,还需要创建出对应的目录和文件
        messageFileManager.createQueueFiles(queue.getName());
    }

    public void deleteQueue(String queueName) throws IOException {
        dataBaseManager.deleteQueue(queueName);
        //删除队列的同时,不仅需要把队列从数据库中删除,还需要把对应的文件和目录删除
        messageFileManager.destroyQueueFiles(queueName);
    }

    public List<MSGQueue> selectAllQueue() {
        return dataBaseManager.selectAllQueues();
    }

    //封装绑定操作
    public void insertBinding(Binding binding) {
        dataBaseManager.insertBinding(binding);
    }

    public void deleteBinding(Binding binding) {
        dataBaseManager.deleteBinding(binding);
    }

    public List<Binding> selectAllBindings() {
        return dataBaseManager.selectAllBindings();
    }

    //封装消息操作
    public void sendMessage(MSGQueue queue, Message message) throws IOException, MqException {
        messageFileManager.sendMessage(queue, message);
    }

    public void deleteMessage(MSGQueue queue, Message message) throws IOException, ClassNotFoundException, MqException {
        messageFileManager.deleteMessage(queue, message);
        //这里删除消息以后还需要看以下文件中是否有太多的无效文件需要进行清除
        if(messageFileManager.checkGC(queue.getName())) {
            messageFileManager.gc(queue);
        }
    }

    public List<Message> selectAllMessagesFromQueue(String queueName) throws IOException, MqException, ClassNotFoundException {
        return messageFileManager.loadAllMessageFromQueue(queueName);
    }


}

Ps : Il y a une certaine logique dans l'encapsulation des files d'attente et des messages ici !

file d'attente:

  1. Lors de la création d'une file d'attente, vous devez non seulement écrire la file d'attente dans la base de données, mais également créer les répertoires et fichiers correspondants.
  2. Lors de la suppression d'une file d'attente, vous devez non seulement supprimer la file d'attente de la base de données, mais également supprimer les fichiers et répertoires correspondants.

 information:

  1. Après avoir supprimé le message, vous devez vérifier s'il y a trop de fichiers invalides dans les fichiers suivants qui doivent être effacés (GC)

Je suppose que tu aimes

Origine blog.csdn.net/CYK_byte/article/details/132380597
conseillé
Classement