基于Netty手写一款高并发、高性能的消息队列Xy-MQ(一)

前言

在大学时期用过NIO手写消息队列,那时候还不会Netty,用原生的NIO出现了很多问题,例如自定义消息协议后socket无法正常关闭,需要手动修改缓冲区大小等问题。最近刚好学习了Netty,准备用Netty去重写这个消息队列。我将会带着大家从零开始,手写一款高性能、高安全性、持久化的消息队列。源码会同步到Github:https://github.com/Lyx0912/XY-MQ。感兴趣的可以点个Star!!!

介绍

在当时Nio编写完成后,就已经拥有超高性能了,每秒钟可以生产和消费消息20000+。在使用Netty完成基本架构后尝试发送100万消息也是几秒钟的事,而且还是经过本地磁盘存储的(要是早点知道Netty就好了)。这次用Netty重写,是想要写一款拥有高性能、高并发、高可靠性、轻量级的消息队列
而且,在手写消息队列的时候使用了很多的设计模式,例如策略模式、观察者模式、工厂模式等,在阅读源码的时候可以了解这些设计模式的实际场景!!!

技术选型

Netty+LevelDb+SpringBoot+FastJson。消息队列的吞吐量局限于持久化的方式,为了提高性能,就放弃了传统的数据库,选择了高性能的KV数据库:LevelDb。选型SpringBoot就是为了方便后期搭建可视化Web页面,同时还可以直接使用定时任务和异步任务。FastJson用来作为消息对象的解析和封装,后面可能会换成Protobuf协议。

系统架构

在这里插入图片描述

持久化模型

在这里插入图片描述

文件结构

服务端:
在这里插入图片描述
客户端:
在这里插入图片描述
公共模块:
在这里插入图片描述

部分代码

服务端核心类

    /**
     * 服务端初始化工作
     *
     * @return void
     * @author 黎勇炫
     * @create 2022/7/9
     * @email 1677685900@qq.com
     */
    public void init() {
        ServerBootstrap server = new ServerBootstrap();
        try {
            server.group(bossGroup, ioGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, backLog)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childHandler(new ServerInitializer());

            ChannelFuture sync = server.bind(port);
            // 数据恢复
            recoveryMessage();
            // 开始推送消息
            sendMessageToClients();
            sendDelayMessageToClient();
            sendMessageToSubscriber();
            sync.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException("netty服务端启动失败");
        } finally {
            // 关闭两个工作线程组
            bossGroup.shutdownGracefully();
            ioGroup.shutdownGracefully();
        }
    }

消息持久化

    public void putMessage(Long key, Message message) {
        byte[] messageByte = JSON.toJSONBytes(message, new SerializerFeature[]{SerializerFeature.DisableCircularReferenceDetect});
        try {
            db.put(String.valueOf(key).getBytes(charset), messageByte);
        } catch (UnsupportedEncodingException e) {
            logger.error("消息编号{}持久化失败",message.getMessageId());
            throw new XyException(ExceptionEnum.FAILED_TO_STORAGE);
        }
    }

推送消息(队列)

    public void sendMessageToClients() {
        // 异步执行,遍历队列消息容器
        CompletableFuture.runAsync(() -> {
            try {
                while (!bossGroup.isShutdown()) {
                    // 遍历整个队列容器
                    for (Map.Entry<String, LinkedBlockingDeque<Message>> entry : queueContainer.entrySet()) {
                        // key就是队列名
                        String key = entry.getKey();
                        LinkedBlockingDeque<Message> queue = entry.getValue();
                        // 当消息队列中有数据并且该队列存在消费者,就调用线程池,负责为该队列推送消息
                        while (queue.size() > 0 && consumerContainer.containsKey(key)) {
                            if (consumerContainer.get(key).size() != 0) {
                                Channel channel = getChannel(consumerContainer.get(key));
                                // 只有连接在活跃状态下才开始推送消息
                                if (channel.isActive()) {
                                    // 发送消息到未断开连接的消费者
                                    Message message = queue.poll();
                                    MessageUtils.message2Protocol(message);
                                    channel.writeAndFlush(MessageUtils.message2Protocol(message));
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                logger.error("消息推送失败");
            }
        }, taskExecutor);
    }

消息持久化接口

public interface StorageHelper {
    /**
     * 存储消息到队列消息容器
     * @param queueContainer 队列消息容器
     * @param message 消息对象
     * @return void
     * @author 黎勇炫
     * @create 2022/7/10
     * @email 1677685900@qq.com
     */
    public void storeQueueMessage(ConcurrentHashMap<String, LinkedBlockingDeque<Message>> queueContainer, Message message);

    /**
     *  存储消息到主题消息容器
     * @param topicContainer 主题消息容器
     * @param message 消息对象
     * @return void
     * @author 黎勇炫
     * @create 2022/7/10
     * @email 1677685900@qq.com
     */
    public void storeTopicMessage(ConcurrentHashMap<String, LinkedBlockingDeque<Message>> topicContainer, Message message);

    /**
     * 存储消息到延时队列容器
     * @param delayQueueMap 延时队列容器
     * @param message 消息对象
     * @return void
     * @author 黎勇炫
     * @create 2022/7/10
     * @email 1677685900@qq.com
     */
    public void storeDelayMessage(ConcurrentHashMap<String, DelayQueue<Message>> delayQueueMap, Message message);

    /**
     * 存储消息到延时主题消息容器
     * @param delayTopicMap 主题消息容器
     * @param message 消息对象
     * @return void
     * @author 黎勇炫
     * @create 2022/7/10
     * @email 1677685900@qq.com
     */
    public void storeDelayTopicMessage(ConcurrentHashMap<String, DelayQueue<Message>> delayTopicMap, Message message);
}

待优化

1.延迟消息BUG:延时消息基于jdk自带的delayQueue实现,系统宕机重启后服务端读取leveldb中的消息后将消息重新放回延时队列,会重新设置到期时间。例如:设置一条消息5分钟后推送,中途系统宕机,系统重启后会从当前时间开始重新计时5分钟。

2.异步推送下消息乱序:原先设想是每条队列来消息时,就会交给线程池专门用一条线程负责推送这条队列的消息,直到消息推送完毕。结果会打乱消息顺序。

3.可视化界面:在所有的功能开发完后会开发一个可视化界面。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值