2024.2.19 模拟实现 RabbitMQ —— 虚拟主机设计

目录

引言

实现 VirtualHost 类

属性

交换机相关操作

队列相关操作

绑定相关操作

消息相关操作

关于线程安全问题

针对 VirtualHost 单元测试


引言

  • 虚拟主机的概念类似于 MySQL 的 database,用于将 交换机、队列、绑定、消息 进行逻辑上的隔离
  • 虚拟主机不仅仅要管理数据,还需要提供一些 核心 API,供上层代码进行调用
  • 就是将之前写的 内存 和 硬盘 的数据管理给串起来
  • 即整个程序的核心业务逻辑

核心 API:

  1. 创建交换机 exchangeDeclare
  2. 删除交换机 exchangeDelete
  3. 创建队列 queueDeclare
  4. 删除队列 queueDelete
  5. 创建绑定 queueBind
  6. 删除绑定 queueUnbind
  7. 发送消息 basicPublish
  8. 订阅消息 basicConsume
  9. 确认消息 basicAck

注意点一:

  • 此处各 API 的取名,各 API 中设定的参数,均参考于 RabbitMQ

注意点二:

  • 此处我们仅实现单个虚拟主机,并不打算实现添加/删除虚拟主机的 API
  • 但是会在设计数据结构上留下这样的扩展空间

实例理解

  • 虚拟主机存在目的,就是为了保证隔离,即不同虚拟主机之间的内容互不影响
  • 当 虚拟主机1 中创建了一个名为 "testExchange" 的交换机
  • 而 虚拟主机2 中也创建了一个名为 "testExchange" 的交换机
  • 虽然这两个交换机的名字相同,但是却处于不同虚拟主机中,所以需要区分开来

问题:

  • 如何表示 交换机 与 虚拟主机 之间的从属关系?

可选方案:

  • 方案一:参考数据库设计 "一对多" 的方案,给交换机表添加个属性,虚拟主机 id/name
  • 方案二:重新约定交换机的名字,即  新交换机名字 = 虚拟主机名字 + 交换机真实名字
  • 方案三:给每个虚拟主机,分配一组不同的数据库和文件(比方案二麻烦,但更优雅)

回答:

  • 此处我们选择方案二!
  • 约定在 VirtualHost 中的核心 api 里,对 exchangeName 和 queueName 做一个转换
  • 在该层代码中进行转换后,后续代码 MemoryDataCenter、DiskDataCenter 无需调整

  • 按照这个方式,也可以去区分不同的队列

  • 绑定 与 交换机和队列 相关,通过上述操作,绑定自然也就被隔离开了!
  • 消息 与 队列 相关,因为队列名已经区分开了,消息自然也就被区分开了!

实现 VirtualHost 类

属性

@Getter
public class VirtualHost {
    private String virtualHostName;
    private MemoryDataCenter memoryDataCenter = new MemoryDataCenter();
    private DiskDataCenter diskDataCenter = new DiskDataCenter();
    private Router router = new Router();
    private ConsumerManager consumerManager = new ConsumerManager(this);

//    操作交换机的锁对象
    private final Object exchangeLocker = new Object();
//    操作队列的锁对象
    private final Object queueLocker = new Object();

    public VirtualHost(String name) {
        this.virtualHostName = name;

//        对于 MemoryDataCenter 来说,不需要额外的初始化操作的,只要对象 new 出来就行
//        但是,针对 DiskDataCenter 来说,则需要进行初始化操作,建库建表和初始化数据的设定
//        另外还需要针对硬盘的数据,进行恢复到内存中
        diskDataCenter.init();

        try {
            memoryDataCenter.recovery(diskDataCenter);
        } catch (IOException | MqException | ClassNotFoundException e) {
            e.printStackTrace();
            System.out.println("[VirtualHost] 恢复内存数据失败!");
        }
    }
}
  • Router 类用于实现交换机的转发规则,验证 bindingKey 和 routingKey 的合法性
  • ConsumerManager 类用于实现消费消息的核心逻辑

交换机相关操作

//    创建交换机
//    如果交换机不存在就创建,如果存在 则直接返回
//    返回值是 boolean,创建成功,返回 true,失败返回 false
    public boolean exchangeDeclare(String exchangeName, ExchangeType exchangeType, boolean durable,
                                   boolean autoDelete, Map<String,Object> arguments) {
//        把交换机的名字,加上虚拟主机作为前缀
        exchangeName = virtualHostName + exchangeName;
        try {
            synchronized (exchangeLocker) {
//            1、判定该交换机是否已经存在,直接通过内存查询
                Exchange existsExchange = memoryDataCenter.getExchange(exchangeName);
                if (existsExchange != null) {
//                该交换机已经存在
                    System.out.println("[VirtualHost] 交换机已经存在!exchangeName = " + exchangeName);
                    return true;
                }
//            2、真正创建交换机,先构造 Exchange 对象
                Exchange exchange = new Exchange();
                exchange.setName(exchangeName);
                exchange.setType(exchangeType);
                exchange.setDurable(durable);
                exchange.setAutoDelete(autoDelete);
                exchange.setArguments(arguments);
//            3、把交换机对象写入硬盘
                if(durable) {
                    diskDataCenter.insertExchange(exchange);
                }
//            4、把交换机对象写入内存
                memoryDataCenter.insertExchange(exchange);
                System.out.println("[VirtualHost] 交换机创建完成!exchangeName = " + exchangeName);
//            上述逻辑,先写硬盘,后写内存,目的就是因为硬盘更容易写失败,如果硬盘写失败了,内存就不写了
//            要是先写内存,内存写成功了,硬盘写失败了,还需要把内存的数据给再删掉,就比较麻烦了
            }
            return true;
        }catch (Exception e) {
            System.out.println("[VirtualHost] 交换机创建失败!exchangeName = " + exchangeName);
            e.printStackTrace();
            return false;
        }
    }

//    删除交换机
    public boolean exchangeDelete(String exchangeName) {
        exchangeName = virtualHostName + exchangeName;
        try {
            synchronized (exchangeLocker) {
//            1、先找到对应的交换机
                Exchange toDelete = memoryDataCenter.getExchange(exchangeName);
                if(toDelete == null) {
                    throw new MqException("[VirtualHost] 交换机不存在无法删除!");
                }
//            2、删除硬盘上的数据
                if(toDelete.isDurable()) {
                    diskDataCenter.deleteExchange(exchangeName);
                }
//            3、删除内存中的交换机数据
                memoryDataCenter.deleteExchange(exchangeName);
                System.out.println("[VirtualHost] 交换机删除成功!exchangeName = " + exchangeName);
            }
            return true;
        }catch (Exception e) {
            System.out.println("[VirtualHost] 交换机删除失败!exchangeName = " + exchangeName);
            e.printStackTrace();
            return false;
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

茂大师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值