基于Neety的高性能中间件Mom

本文介绍了参加阿里巴巴高性能中间件挑战赛的经历,重点阐述了如何设计和实现一个基于发布-订阅模型的MOM消息中间件(Mom)。文章详细讨论了Broker、Consumer和Producer的实现策略,强调了数据同步落盘的重要性,以及在文件系统上实现消息持久化的挑战。同时,提出了通过组提交和多线程发送来优化性能的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

今年7月份左右报名参加了阿里巴巴组织的高性能中间件挑战赛,这次比赛不像以往的比赛,是从一个工程的视角来比赛的。
这个比赛有两个赛题,第一题是实现一个RPC框架,第二道题是实现一个Mom消息中间件。

MOM题目如下

实现一个基于发布-订阅模型的消息中间件(broker+client)
必选特性:
提供可靠消息服务,broker要保证数据同步落盘才能向生产者返回发送成功的ack,并保证投递给所有的消费者,直至所有的消费者都消费成功(消费者消费成功或者失败都会返回对应的ack)。一旦消费者对一条消息发生订阅后,那么该消费者消费失败,消费超时(如果消息推送给消费者后,10秒没有返回响应,那么认为消费超时),或者不在线,消息不能丢失,需要尽快重投,让消费者在恢复后可以尽快消费完堆积的消息。
采用实时推送模型(只能push,push完后等待消费者ack,不能使用长轮询),消息一旦到达broker,要立马推送给消费者,消息延迟不能高于50ms。
消息支持自定义属性,能够支持简单的消息属性过滤订阅,broker只能投递符合属性条件的消息给订阅者。例如订阅者发起topic为trade,filter为area=hz的订阅,那么只有topic为trade,并带有area属性值为hz的消息才会投递给订阅者。client要实现提供的api,发送接口必须消息持久化成功才能返回成功。
消息存储必须基于文件系统自己实现,不能使用现成的存储系统,数据存储的根目录为$userhome/store/。系统运行过程中如果突然宕机或者断电(一定要保障消息数据落盘后才能向发送者响应发送成功),重启后消息数据不能丢失。(数据丢失的定义:生产者消息发送返回成功ack后,如果broker出现宕机或者断电后重启,消息丢失了,无法投递给所有的订阅者,直至所有订阅组都消费成功)
消费者和生产者启动的时候可以指定需要连接的broker ip,也就是实现broker单机的模式,前期跑分主要看broker的单机能力。
支持消费者集群,消费负载均衡。比如消费者A是一个集群,订阅了topicA。broker收到topicA的某条消息后,只投递给消费者A集群的某台机器,消费者集群的每台机器每秒消息消费量是均衡的。
加分特性(如果最后实现了必选特性,性能脱颖而出的几个团队,则还会综合考虑系统设计是否能支持以下的特性):
服务高可用,broker可以集群化部署,统一对外提供服务。broker集群中部分机器当机,不会导致消息发送失败,或者无法消费,对消息服务的影响越小越好。
数据高可用,消息存储多份,单一数据存储损坏,不会导致消息丢失。
具备良好的在线横向扩容能力。
支持大量的消息堆积,在大量消费失败或者超时的场景下,broker的性能和稳定不受影响,有良好的削峰填谷能力。
高性能、低成本。
考核方式
从系统设计角度和运行功能测试用例来评判必选特性,不满足必选特性,直接淘汰。
服务高可用、数据高可用、在线横向扩容能力从系统设计角度来评判
性能指标包括:每秒消息接收量,每秒消息投递量,消息投递延迟,消息发送的rt,消息堆积能力,削峰填谷能力。
性能压测场景
4k消息,一个发布者发布topicA,一个订阅者订阅这个topicA的所有消息,订阅者健康消费每条消息,无堆积
4k消息,一个发布者发布topicA,20个订阅者分别订阅topicA不同属性的消息,消费者健康消费,无堆积
4k消息,一个发布者发布topicA,一个订阅者订阅这个topicA的所有消息,订阅者消费超时,大量堆积
4k消息,一个发布者发布topicA,20个订阅者分别订阅topicA不同属性的消息,20个订阅者只有一个订阅者消费成功,其他订阅者消费超时、失败以及不在线,消息出现大量堆积。
4k消息,20个发布者发布20个不同的topic,每个topic都有20个订阅者,他们分别订阅不同属性值的消息,消费健康,无堆积
4k消息,20个发布者发布20个不同的topic,每个topic都有20个订阅者,他们分别订阅不同属性值的消息,所有消费均超时,大量堆积。堆积持续一段时间后,减少90%的发送量,并让消费者恢复正常,broker需要尽可能快的投递堆积的消息。

其实刚读了题目的时候内心是崩溃的,什么是消息发布者什么是消息订阅者。
但是仔细看看调理还是很清楚的,最主要的是实现里面的Broker,因为需要保证每条数据都不能丢失所以需要对数据进行持久化,而且题目要求不能使用数据库,所以只能选择文件系统了,这里面有个要求就是

broker要保证数据同步落盘才能向生产者返回发送成功的ack

实现方案以及注意点

Broker注意点

首先这意味着每个生产者是同步发送的,这也就是一意味着Broker每次写文件一定要保证刷到磁盘上去后再告知生产者发送下一个,使用普通的机械硬盘没刷一次磁盘大概是30ms左右,这也就意味着 每秒每次最多只能写30多次磁盘,所以我们为了提高生产者发送的效率,需要组提交,就是收到多个生产者的消息后再统一刷磁盘,这样可以尽可能的提高生产者的发送速率。
Broker在内存中和文件中都保存了消息,Broker开多个线程从消息队列中取出消息并发送(注意:每个线程发送了消息后等待收到consumer的ack消息,发送后等待超时就放到队列尾部)

Consumer注意点

Consumer在实现的时候主要需要注意的地方是,第一只能收到自己感兴趣的Topic的消息,并且消费成功后返回一个消息告诉Broker这个消息已经消费成功

Producer注意点

Producer主要是负责生产消息的,发送到Broker后就等待Broker的确认消息,收到确认消息后才可以进行下一次消息的发送。

Broker的代码实现

package com.alibaba.middleware.race.mom.broker.netty;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

import com.alibaba.middleware.race.mom.broker.ConsumerManager;
import com.alibaba.middleware.race.mom.broker.SemaphoreManager;
import com.alibaba.middleware.race.mom.broker.TaskManager;
import com.alibaba.middleware.race.mom.model.MomRequest;
import com.alibaba.middleware.race.mom.model.MomResponse;
import com.alibaba.middleware.race.mom.serializer.RpcDecoder;
import com.alibaba.middleware.race.mom.serializer.RpcEncoder;


/**
 * 在我们的系统中服务端收到的数据只能是MomRequest  客户端收到的数据只能是MomResponse
 * producer 是客户端  consumer 也是客户端   broker是服务器
 * 
 * 所以 producer->broker 是request
 * 所以 consumer->broker 是request
 * broker->consumer 是response broker->producer 是respose
 * 通过以上模型,我们的对数据编解码就可以不变
 * @author zz
 *
 */
//broker 服务器实现类
public class BrokerServerImpl implements BrokerServer {
   
   

    //订阅关系管理器
    //private ConsumerManager cmanager;

    private ServerBootstrap bootstrap;

    public BrokerServerImpl() {
        init();
    }

    @Override
    public void init() {
        // TODO Auto-generated method stub

        //需要做成保存成为文件的功能,broker重启的时刻可以从文件中恢复
        //cmanager=new ConsumerManager();
        final BrokerHandler handler=new BrokerHandler();

        //设置两个监听器
        handler.setConsumerRequestListener(new ConsumerMessageListener());
        handler.setProducerListener(new ProducerMessageListener());
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        //处理事件的线程池
        EventLoopGroup workerGroup = new NioEventLoopGroup(30);
        try 
        {
            bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
           .handler(new LoggingHandler(LogLevel.INFO))
           .childHandler(new ChannelInitializer<SocketChannel>() {
                           @Override
                           public void initChannel(SocketChannel ch)
                                    throws Exception {
                            ch.pipeline().addLast(new RpcEncoder(MomResponse.class));
                            ch.pipeline().addLast(new RpcDecoder(MomRequest.class));
                            ch.pipeline().addLast(handler);
                          }
                     }).option(ChannelOption.SO_KEEPALIVE , true );
       }
       catch (Exception e)
       {
           //TODO 异常处理
       }
    }

    @Override
    public void start() {
        // TODO Auto-generated method stub
        try
        {
            ChannelFuture cfuture = bootstrap.bind(8888).sync();

            //创建一个写文件的锁
            SemaphoreManager.createSemaphore("SendTask");
            //创建一个发送ack消息的信号量
            SemaphoreManager.createSemaphore("Ack");
            //恢复之前的发送任务到队列
            TaskManager.RecoverySendTask();         

            //启动发送线程
            ExecutorService executorService=Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*2+2);
            for (int i = 0; i < Runtime.getRuntime().availableProcessors(); i++) {
                executorService.execute(new SendThread());
                System.out.println("start sendThread:"+(i+1));
            }
            //启动ack发送线程
            for (int i = 0; i < Runtime.getRuntime().availableProcessors(); i++) {
                executorService.execute(new AckSendThread());
                System.out.println("start ack sendThread:"+(i+1));
            }

            //启动一个记录发送tps的线程
            executorService.execute(new RecordThread());
            //启动刷磁盘线程
            executorService.execute(new FlushThread());

            cfuture.channel().closeFuture().sync();
        }
        catch (Exception e) 
        {
            e.printStackTrace();
            // TODO: handle exception
            System.out.println("Broker start error!");
        }


    }
    public static void main(String[] args) {
        BrokerServer broker=new BrokerServerImpl();
        broker.start();
    }

}
package com.alibaba.middleware.race.mom.broker.netty;

import java.util.Random;

import com.alibaba.middleware.race.mom.ConsumeResult;
import com.alibaba.middleware.race.mom.Message;
import com.alibaba.middleware.race.mom.SendResult;
import com.alibaba.middleware.race.mom.broker.ClientChannelInfo;
import com.alibaba.middleware.race.mom.broker.ConsumerGroupInfo;
import com.alibaba.middleware.race.mom.broker.ConsumerManager;
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值