Sur la base du code source, simuler et implémenter RabbitMQ - gestion des données mémoire (4)

Table des matières

1. Gestion des données mémoire

1.1. Analyse de la demande

1.2. Implémenter la classe MemoryDataCenter

1.2.1. Gestion des données ConcurrentHashMap

1.2.2. Fonctionnement du commutateur d'encapsulation

1.2.3. Encapsulation des opérations de file d'attente

1.2.4. Opération de liaison d'encapsulation

1.2.5. Fonctionnement du message d'encapsulation

1.2.6. Encapsulation de l'opération de message non confirmé

1.2.7. Opération d'encapsulation et de récupération des données


1. Gestion des données mémoire


1.1. Analyse de la demande

Actuellement, la base de données est utilisée pour gérer les commutateurs, les liaisons et les files d'attente, et les fichiers de données sont utilisés pour gérer les messages.

Enfin, une classe est utilisée pour intégrer les deux parties ci-dessus afin de fournir un ensemble unifié d'interfaces à la couche supérieure.

Mais pour MQ, les données sont principalement stockées en mémoire et les données sont stockées sur le disque dur en complément (les données du disque dur sont principalement destinées au stockage persistant et les données ne seront pas perdues après le redémarrage).

Ensuite, vous devez utiliser la mémoire pour gérer les données ci-dessus~~

Ici, nous utilisons principalement ConcurrentHashMap pour la gestion des données (principalement en raison de problèmes de sécurité des threads).

Exchange : utilisez ConcurrentHashMap, la clé est le nom, la valeur est l'objet Exchange.

File d'attente : utilisez ConcurrentHashMap, la clé est le nom, la valeur est l'objet MSGQueue.

Liaison : utilisez ConcurrentHashMap imbriqué, la clé est ExchangeName, la valeur est un ConcurrentHashMap (la clé est queueName, la valeur est un objet Binding).

Message : utilisez ConcurrentHashMap, la clé est messageId, la valeur est l'objet Message.

La relation entre les files d'attente et les messages : utilisez ConcurrentHashMap imbriqué, la clé est queueName et la valeur est une LinkedList (chaque élément est un objet Message).

Messages qui représentent "non confirmés" : utilisez ConcurrentHashMap imbriqué, la clé est queueName, la valeur est ConcurrentHashMap (la clé est messageId, la valeur est l'objet Message, l' implémentation ultérieure de la logique de confirmation de message doit fournir une confirmation basée sur le contenu de la réponse d'accusé de réception messageId, find et supprimez l'objet Message dans la structure ci-dessus en fonction de ce messageId ).

Ps : Le MQ implémenté ici prend en charge ACK dans deux modes de réponse

  1. Réponse automatique : lorsque le consommateur prend l'élément, l'intégralité du message est considérée comme ayant reçu une réponse et l'intégralité du message peut être supprimée à ce moment-là.
  2. Réponse manuelle : lorsque le consommateur prend l'élément, le message n'est pas considéré comme une réponse. Le consommateur doit appeler activement une méthode basicAck. Ce n'est qu'alors qu'il sera considéré comme une réponse réelle et le message pourra être supprimé.

1.2. Implémenter la classe MemoryDataCenter

1.2.1. Gestion des données ConcurrentHashMap

Ici, ConcurrentHashMap est utilisé pour effectuer une gestion unifiée de la mémoire sur les données ci-dessus.

    //key 是 exchangeName, value 是 Exchange 对象
    private ConcurrentHashMap<String, Exchange> exchangeMap = new ConcurrentHashMap<>();
    //key 是 queueName, value 是 MSGQueue 对象
    private ConcurrentHashMap<String, MSGQueue> queueMap = new ConcurrentHashMap<>();
    //第一个 key 是 exchangeName,第二个 key 是 queueName
    private ConcurrentHashMap<String, ConcurrentHashMap<String, Binding>> bindingsMap = new ConcurrentHashMap<>();
    //key 是 messageId ,value 是 Message 对象
    private ConcurrentHashMap<String, Message> messageMap = new ConcurrentHashMap<>();
    //key 是 queueName , value 是 Message 的链表
    private ConcurrentHashMap<String, LinkedList<Message>> queueMessageMap = new ConcurrentHashMap<>();
    // 第一个 key 是 queueName, 第二个 key 是 messageId
    private ConcurrentHashMap<String, ConcurrentHashMap<String, Message>> queueMessageWaitAckMap = new ConcurrentHashMap<>();

1.2.2. Fonctionnement du commutateur d'encapsulation

L'objectif principal est d'insérer, d'obtenir et de supprimer des commutateurs dans ExchangeMap.


    public void insertExchange(Exchange exchange) {
        exchangeMap.put(exchange.getName(), exchange);
        System.out.println("[MemoryDataCenter] 新交换机添加成功! exchangeName=" + exchange.getName());
    }

    public Exchange getExchange(String exchangeName) {
        return exchangeMap.get(exchangeName);
    }

    public void deleteExchange(String exchangeName) {
        exchangeMap.remove(exchangeName);
        System.out.println("[MemoryDataCenter] 交换机删除成功! exchangeName=" + exchangeName);
    }

1.2.3. Encapsulation des opérations de file d'attente

L'objectif principal est d'insérer, d'obtenir et de supprimer des files d'attente de queueMap.

    public void insertQueue(MSGQueue queue) {
        queueMap.put(queue.getName(), queue);
        System.out.println("[MemoryDataCenter] 新队列添加成功! queueName=" + queue.getName());
    }

    public MSGQueue getQueue(String queueName) {
        return queueMap.get(queueName);
    }

    public void deleteQueue(String queueName) {
        queueMap.remove(queueName);
        System.out.println("[MemoryDataCenter] 队列删除成功! queueName=" + queueName);
    }

1.2.4. Opération de liaison d'encapsulation

Il convient de noter ici que la logique de verrouillage ne signifie pas nécessairement qu'il est sûr s'il est verrouillé, ni qu'il n'est pas nécessairement dangereux s'il n'est pas verrouillé. Si la logique de ce code est très forte et doit être conditionnée dans un code atomique opération, alors le verrouillage peut être effectué, mais si la cause et l'effet ne sont pas si forts, ce n'est pas nécessaire, car le verrouillage nécessite également une surcharge, et la compétition de verrouillage après le verrouillage prend encore plus de temps.

    public void insertBinding(Binding binding) throws MqException {
//        ConcurrentHashMap<String, Binding> bindingMap = bindingsMap.get(binding.getExchangeName());
//        if(bindingMap == null) {
//            bindingMap = new ConcurrentHashMap<>();
//            bindingsMap.put(binding.getExchangeName(), bindingMap);
//        }
        //上面这段逻辑可以用以下代码来替换
        ConcurrentHashMap<String, Binding> bindingMap = bindingsMap.computeIfAbsent(binding.getExchangeName(),
                k -> new ConcurrentHashMap<>());

        synchronized(bindingMap) {
            //再根据 queueName 查一下,只有不存在的时候才能插入,存在就抛出异常
            if(bindingMap.get(binding.getQueueName()) != null) {
                throw new MqException("[MemoryDataCenter] 绑定已经存在! exchangeName=" + binding.getExchangeName() +
                        ", queueName=" + binding.getQueueName());
            }
            bindingMap.put(binding.getQueueName(), binding);
        }
        System.out.println("[MemoryDataCenter] 新绑定添加成功!exchangeName=" + binding.getExchangeName() +
                ", queueName=" + binding.getQueueName());

    }

    /**
     * 获取绑定有两个版本
     * 1.根据 exchangeName 和 queueName 确定唯一一个 Binding
     * 2.根据 exchangeName 获取到所有的 Binding
     * @param exchangeName
     * @param queueName
     * @return
     */
    public Binding getBinding(String exchangeName, String queueName) throws MqException {
        ConcurrentHashMap<String, Binding> bindingMap = bindingsMap.get(exchangeName);
        if(bindingMap == null) {
            throw new MqException("[MemoryDataCenter] 绑定不存在!exchangeName=" + exchangeName +
                    ", queueName=" + queueName);
        }
        return bindingMap.get(queueName);
    }

    public ConcurrentHashMap<String, Binding> getBindings(String exchangName) {
        return bindingsMap.get(exchangName);
    }

    public void deleteBinding(Binding binding) throws MqException {
        ConcurrentHashMap<String, Binding>  bindingMap = bindingsMap.get(binding.getExchangeName());
        //这里操作不是很关键,因此可以不用加锁(加锁不一定就安全,也不是说不加锁就一定不安全,要结合实际场景)
        //如果这段代码前后逻辑性很强,需要打包成一个原子性的操作,那就可以进行加锁,如果不是那么强的因果,就没必要,因为加锁也是需要开销的,加锁之后的锁竞争更是一个时间消耗
        if(bindingMap == null) {
            throw new MqException("[MemoryDataCenter] 绑定不存在!exchangeName=" + binding.getExchangeName() +
                    ", queueName=" + binding.getQueueName());
        }
        bindingMap.remove(binding.getQueueName());
        System.out.println("[MemoryDataCenter] 绑定删除成功!exchangeName=" + binding.getExchangeName() +
                ", queueName=" + binding.getQueueName());
    }

1.2.5. Fonctionnement du message d'encapsulation

Il convient de noter ici que LinkedList n'est pas sécurisé pour les threads et nécessite un traitement spécial.

    /**
     * 添加消息
     * @param message
     */
    public void addMessage(Message message) {
        messageMap.put(message.getMessageId(), message);
        System.out.println("[MemoryDataCenter] 新消息添加成功!messageId=" + message.getMessageId());
    }

    /**
     * 根据 id 查询消息
     * @param messageId
     */
    public Message selectMessage(String messageId) {
        return messageMap.get(messageId);
    }

    /**
     * 根据 id 删除消息
     * @param messageId
     */
    public void removeMessage(String messageId) {
        messageMap.remove(messageId);
        System.out.println("[MemoryDataCenter] 消息被移除!messageId=" + messageId);
    }

    /**
     * 发送消息到指定队列
     * @param message
     */
    public void sendMessage(MSGQueue queue, Message message) {
        //先根据队列名字找到指定的链表
        LinkedList<Message> messages = queueMessageMap.computeIfAbsent(queue.getName(), k -> new LinkedList<>());
        //LinkedList 是线程不安全的
        synchronized (messages) {
            messages.add(message);
        }
        //这里把消息在消息中心也插入一下。即使 message 在消息中心存在也没关系
        //因为相同的 messageId 对应的 message 的内容一定是一样的(服务器不会修改 Message 的内容)
        addMessage(message);
        System.out.println("[MemoryDataCenter] 消息被投递到队列当中!messageId=" + message.getMessageId());
    }

    /**
     * 从队列中取消息
     * @param queueName
     * @return
     */
    public Message pollMessage(String queueName) {
        LinkedList<Message> messages = queueMessageMap.get(queueName);
        if(messages == null) {
            return null;
        }
        synchronized (messages) {
            if(messages.size() == 0) {
                return null;
            }
            //链表中有消息就进行头删
            Message currentMessage = messages.remove(0);
            System.out.println("[MemoryDataCenter] 消息从队列中取出! messageId=" + currentMessage.getMessageId());
            return currentMessage;
        }
    }

    /**
     * 获取指定队列的消息个数
     * @param queueName
     * @return
     */
    public int getMessageCount(String queueName) {
        LinkedList<Message> messages = queueMessageMap.get(queueName);
        if(messages == null) {
            return 0;
        }
        synchronized (messages) {
            return messages.size();
        }
    }

1.2.6. Encapsulation de l'opération de message non confirmé

Message « non confirmé » : utilisez ConcurrentHashMap imbriqué, la clé est queueName, la valeur est ConcurrentHashMap (la clé est messageId, la valeur est l'objet Message, la mise en œuvre ultérieure de la logique de confirmation du message doit fournir une confirmation basée sur le contenu de la réponse d'accusé de réception messageId, rechercher et supprimer l'objet Message dans la structure ci-dessus basée sur ce messageId ).

    /**
     * 添加未确认的消息
     * @param queueName
     * @param message
     */
    public void addMessageWaitAck(String queueName, Message message) {
        ConcurrentHashMap<String, Message> messageHashMap = queueMessageWaitAckMap.computeIfAbsent(queueName,
                k -> new ConcurrentHashMap<>());
        messageHashMap.put(message.getMessageId(), message);
        System.out.println("[MemoryDataCenter] 消息进入待确认队列!messageId=" + message.getMessageId());
    }

    /**
     * 删除未确认的消息
     * @param messageId
     */
    public void removeMessageWaitAck(String queueName, String messageId) {
        ConcurrentHashMap<String, Message> messageHashMap = queueMessageWaitAckMap.get(queueName);
        if(messageHashMap == null) {
            return;
        }
        messageHashMap.remove(messageId);
        System.out.println("[MemoryDataCenter] 消息从待确认队列中删除!messageId=" + messageId);
    }

    public Message getMessageWaitAck(String queueName, String messageId) {
        ConcurrentHashMap<String, Message> messageHashMap = queueMessageWaitAckMap.get(queueName);
        if(messageHashMap == null) {
            return null;
        }
        return messageHashMap.get(messageId);
    }

1.2.7. Opération d'encapsulation et de récupération des données

Lisez les données du disque dur et restaurez dans la mémoire les données de toutes les dimensions précédemment stockées de manière persistante sur le disque dur.


    public void recovery(DiskDataCenter diskDataCenter) throws IOException, MqException, ClassNotFoundException {
        //1.先清空之前所有的数据
        exchangeMap.clear();
        queueMap.clear();
        bindingsMap.clear();
        messageMap.clear();
        queueMessageMap.clear();
        //2.恢复所有的交换机数据
        List<Exchange> exchanges = diskDataCenter.selectAllExchanges();
        for(Exchange exchange : exchanges) {
            exchangeMap.put(exchange.getName(), exchange);
        }
        //3.恢复所有的队列数据
        List<MSGQueue> queues = diskDataCenter.selectAllQueue();
        for(MSGQueue queue : queues) {
            queueMap.put(queue.getName(), queue);
        }
        //4.恢复所有绑定数据
        List<Binding> bindings = diskDataCenter.selectAllBindings();
        for(Binding binding : bindings) {
            ConcurrentHashMap<String, Binding> bindingMap = bindingsMap.computeIfAbsent(binding.getExchangeName(),
                    k -> new ConcurrentHashMap<>());
            bindingMap.put(binding.getQueueName(), binding);
        }
        //5.恢复所有的消息数据
        for(MSGQueue queue : queues) {
            LinkedList<Message> messages = diskDataCenter.loadAllMessagesFromQueue(queue.getName());
            queueMessageMap.put(queue.getName(), messages);
            //遍历所有的队列,根据每个队列名字。来恢复所有消息
            for(Message message : messages) {
                messageMap.put(message.getMessageId(), message);
            }
        }

    }

Ps : "Message non confirmé" Cette partie des données n'a pas besoin d'être restaurée à partir du disque dur, et le stockage sur disque dur n'a pas été pris en compte ici auparavant ~

Une fois le serveur redémarré en attente d'accusé de réception, ces "messages non reconnus" seront restaurés en "messages non récupérés". Lorsque ce message sera stocké sur le disque dur, il sera considéré comme "messages non récupérés".".

 

おすすめ

転載: blog.csdn.net/CYK_byte/article/details/132390842