基于Neety的高性能中间件Mom

原创 2015年09月26日 09:51:39

前言

今年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;
import com.alibaba.middleware.race.mom.broker.SendHelper;
import com.alibaba.middleware.race.mom.model.InvokeFuture;
import com.alibaba.middleware.race.mom.model.MomRequest;
import com.alibaba.middleware.race.mom.model.MomResponse;
import com.alibaba.middleware.race.mom.model.RequestResponseFromType;
import com.alibaba.middleware.race.mom.model.RequestType;
import com.alibaba.middleware.race.mom.model.ResponseType;
import com.alibaba.middleware.race.mom.model.SubscriptRequestInfo;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelHandler.Sharable;
/**
 * 在我们的系统中服务端收到的数据只能是MomRequest  客户端收到的数据只能是MomResponse
 * broker 对于网络通讯收到的消息的处理函数  
 * 
 * 
 * @author zz
 *
 */
@Sharable
public class BrokerHandler  extends ChannelInboundHandlerAdapter{

    //对producer发送的消息进行处理,只有一种消息就是Message
    private MessageListener producerListener;

    //对consumer发送的消息进行处理,有两种消息,第一种是订阅信息,第二种是消费信息
    private MessageListener consumerRequestListener;

    public void setProducerListener(MessageListener producerListener) {
        this.producerListener = producerListener;
    }

    public void setConsumerRequestListener(MessageListener consumerRequestListener) {
        this.consumerRequestListener = consumerRequestListener;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // TODO Auto-generated method stub
        super.channelActive(ctx);
        System.out.println("connect from :"+ctx.channel().remoteAddress());
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        // TODO 这里对broker收到的所有消息进行dispatch

        MomRequest request=(MomRequest)msg;

        //构建响应消息
        MomResponse response=new MomResponse();
        response.setRequestId(request.getRequestId());
        response.setFromType(RequestResponseFromType.Broker);
        switch (request.getRequestType()) 
        {
        case ConsumeResult:
            //收到客户端消费结果信息
            ConsumeResult result=(ConsumeResult) request.getParameters();

            String key = response.getRequestId();
            if(SendHelper.containsFuture(key))
            {
                InvokeFuture<Object> future = SendHelper.removeFuture(key);

                if (future==null)
                {
                    return;
                }
                else
                {
                    future.setResult(request);
                }
            }
            //TODO应该在收到这个消息的时候就得到下一个需要发送的消息
            //BUT 为了做负载均衡不能再这里面发送消息
            consumerRequestListener.onConsumeResultReceived(result);
            consumerRequestListener.onRequest(request);

            break;
        case Message:
            //收到来自producer的信息
            Message message=(Message)request.getParameters();
            //当有多个生产者事同时刷入磁盘的数据量根据生产者上深
            if(request.getFromType()==RequestResponseFromType.Producer)
            {
                Conf.Increase(message.getTopic());
            }
            producerListener.onProducerMessageReceived(message,request.getRequestId(),ctx.channel());
            //返回给producer消息发送状态,由单独的线程返回数据给发送者
            //System.out.println("send message ack");
            break;
        case Subscript:
            //收到的是来自consumer的订阅信息
            SubscriptRequestInfo subcript=(SubscriptRequestInfo)request.getParameters();

            //构建一个channelinfo   clientid => groupid : topic + 随机数
            //String clientId=subcript.getGroupId()+":"+subcript.getTopic()+(new Random()).nextInt(100);
            String clientKey=subcript.getClientKey();
            ClientChannelInfo channel=new ClientChannelInfo(ctx.channel(), clientKey);
            consumerRequestListener.onConsumeSubcriptReceived(subcript,channel);
            consumerRequestListener.onRequest(request);

            response.setResponseType(ResponseType.AckSubscript);

            ctx.writeAndFlush(response);
            break;
        case Stop:
            String clientId=(String)request.getParameters();
            ConsumerManager.stopConsumer(clientId);
            break;
        default:
            System.out.println("type invalid");
            break;
        }


    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // TODO Auto-generated method stub
        super.channelReadComplete(ctx);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        // TODO Auto-generated method stub
        //super.exceptionCaught(ctx, cause);
//      consumerRequestListener.onError(cause);
//      producerListener.onError(cause);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        // TODO Auto-generated method stub
        super.channelInactive(ctx);
        System.out.println("disconnected");
    }

Producer消息的处理类

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

import io.netty.channel.Channel;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import com.alibaba.middleware.race.mom.Message;
import com.alibaba.middleware.race.mom.SendResult;
import com.alibaba.middleware.race.mom.SendStatus;
import com.alibaba.middleware.race.mom.broker.AckManager;
import com.alibaba.middleware.race.mom.broker.ConsumerGroupInfo;
import com.alibaba.middleware.race.mom.broker.ConsumerManager;
import com.alibaba.middleware.race.mom.broker.SemaphoreManager;
import com.alibaba.middleware.race.mom.broker.SendHelper;
import com.alibaba.middleware.race.mom.broker.TaskManager;
import com.alibaba.middleware.race.mom.file.LogTask;
import com.alibaba.middleware.race.mom.model.SendTask;
import com.alibaba.middleware.race.mom.tool.MemoryTool;
import com.alibaba.middleware.race.mom.tool.Tool;


//broker收到producer的消息的时候监听类
public class ProducerMessageListener  extends MessageListener{

    @Override
    void onProducerMessageReceived(Message msg,String requestId,Channel channel) {

        //放入一个requestId对应的channel 在后面发送ack后删除
        AckManager.pushRequest(requestId, channel);

        // TODO Auto-generated method stub
        //标识是否序列化成功
        boolean isError=false;
        String mapstr="";
        for(Map.Entry<String, String> entry:msg.getProperties().entrySet()){    
            mapstr+=entry.getKey()+"="+entry.getValue();
        }   
        //System.out.println("receive producer message msgid:"+msg.getMsgId()+" topic:"+msg.getTopic()+" filter:"+mapstr);
        String topic=msg.getTopic();
        //找到订阅这个消息的组信息   最好有订阅过滤条件
        List<ConsumerGroupInfo> allgroups= ConsumerManager.findGroupByTopic(topic);
        //符合这个消息过滤消息的组
        List<ConsumerGroupInfo> groups=new ArrayList<ConsumerGroupInfo>();
        for (ConsumerGroupInfo groupinfo : allgroups) 
        {
            //groupinfo.findSubscriptionData(topic);
            String filterName=groupinfo.findSubscriptionData(topic).getFitlerName();
            String filterValue=groupinfo.findSubscriptionData(topic).getFitlerValue();
            if(filterName==null)
            {
                groups.add(groupinfo);
            }
            else
            {
                //判断消息是否有组需要的字段,且字段的值和消息一致
                if(msg.getProperty(filterName)!=null&&msg.getProperty(filterName).equals(filterValue))
                {
                    groups.add(groupinfo);
                }
            }
        }
        if(groups.size()==0)//没有订阅这个消息的组,需要把消息存储在默认队列里面?
        {
            //TODO 丢失信息?
            //查找有没有专属于存储这个topic的队列
            //QueueFile queue=QueueManager.findQueue(topic);
            System.out.println("don't have match group");
            //返回这个ack
            SendResult ack=new SendResult();
            ack.setMsgId(msg.getMsgId());//message id
            ack.setInfo(requestId);//request id
            ack.setStatus(SendStatus.SUCCESS);
            AckManager.pushAck(ack);
            SemaphoreManager.increase("Ack");
        }
        //遍历所有的订阅该消息的组
        List<byte[]> logList=new ArrayList<byte[]>();
        List<SendTask> taskList=new ArrayList<SendTask>();
        for (ConsumerGroupInfo consumerGroupInfo : groups) {
            SendTask task=new SendTask();
            task.setGroupId(consumerGroupInfo.getGroupId());
            task.setTopic(topic);
            task.setMessage(msg);

            taskList.add(task);
            LogTask log=new LogTask(task, 0);
            byte[] data=Tool.serialize(log);
            logList.add(data);
        }
        try 
        {
            //生成一个requestID和messageID组成的键值
            String key=requestId+"@"+msg.getMsgId();
            //先把这些任务写到缓冲区,这时候ack消息还没有生成,producer还在等待ack消息
            //等到一定时机生成ACK消息加入ack消息队列等待ack发送线程发送ack消息
            FlushTool.writeToCache(logList,key);
        }
        catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        //添加一个发送任务
        if(MemoryTool.moreThan(1024*1024*100*8))//100MB可用内存
        {
            TaskManager.pushTask(taskList);//把这些任务放到内存中的任务队列

            for (int i=0;i<taskList.size();i++) {
                SemaphoreManager.increase("SendTask");
            }
        }
        else
        {
            //不添加到发送队列了
        }

    }

}

消费者ACK消息的处理类

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


import com.alibaba.middleware.race.mom.ConsumeResult;
import com.alibaba.middleware.race.mom.ConsumeStatus;
import com.alibaba.middleware.race.mom.Message;
import com.alibaba.middleware.race.mom.broker.ClientChannelInfo;
import com.alibaba.middleware.race.mom.broker.ConsumerManager;
import com.alibaba.middleware.race.mom.broker.SemaphoreManager;
import com.alibaba.middleware.race.mom.broker.SendHelper;
import com.alibaba.middleware.race.mom.broker.SubscriptionInfo;
import com.alibaba.middleware.race.mom.broker.TaskManager;
import com.alibaba.middleware.race.mom.file.LogTask;
import com.alibaba.middleware.race.mom.model.SendTask;
import com.alibaba.middleware.race.mom.model.SubscriptRequestInfo;
import com.alibaba.middleware.race.mom.tool.Tool;

public class ConsumerMessageListener extends MessageListener {

    @Override
    void onConsumeResultReceived(ConsumeResult msg) {
        // TODO Auto-generated method stub
        if(msg.getStatus()==ConsumeStatus.SUCCESS)//消费成功的话,找到对应的队列删除那个消息,然后继续发送下个消息
        {
            //String key=msg.getGroupID()+msg.getTopic()+msg.getMsgId();
            //System.out.println("consume ok");

            if(TaskManager.findInResend(msg.getGroupID(), msg.getTopic(), msg.getMsgId()))
            {
                //表示这个消息时已经超时的 这时候发过来的消费消息无效
                return;
            }
            //TODO 记录一个发送成功的日志
            SendTask task=new SendTask();
            task.setGroupId(msg.getGroupID());
            task.setTopic(msg.getTopic());
            Message message=new Message();
            message.setMsgId(msg.getMsgId());
            task.setMessage(message);
            LogTask logtask=new LogTask(task, 1);
            byte[] data=Tool.serialize(logtask);
            //消费情况直接写入文件
            FlushTool.writeConsumeResult(data);
        }
    }
    //收到订阅消息后的操作
    @Override
    void onConsumeSubcriptReceived(SubscriptRequestInfo msg,ClientChannelInfo channel) {
        // TODO Auto-generated method stub

        System.out.println("receive subcript info groupid:"+msg.getGroupId()+" topic:"+msg.getTopic()+" filterName:"+msg.getPropertieName()+" filterValue:"+msg.getPropertieValue()+" clientId:"+channel.getClientId());

        SubscriptionInfo subscript=new SubscriptionInfo();
        subscript.setTopic(msg.getTopic());
        subscript.setFitlerName(msg.getPropertieName());
        subscript.setFitlerValue(msg.getPropertieValue());
        channel.setSubcript(subscript);//设置订阅信息

        //加入某个组
        //相同的组和相同的topic,更新订阅条件就好
        ConsumerManager.addGroupInfo(msg.getGroupId(), channel);

        //TODO nothing todo

    }

}

Broker消息刷盘的类

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

import java.util.concurrent.TimeUnit;


/**
 * 刷磁盘线程  当收到多个生产者消息的时候缓存满 的时候刷磁盘,然后唤醒等待刷磁盘的线程  也就是等待发送ACK的线程将他们唤醒
 * @author sei.zz
 *
 */
public class FlushThread implements Runnable {

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(true)
        {
            try 
            {
                long start =System.currentTimeMillis();
                //等待一段时间,是否收集齐一定数量的消息
                if (!FlushTool.semp.tryAcquire(1000, TimeUnit.MILLISECONDS)) {
                    synchronized (FlushTool.syncObj) 
                    {
                        //TODO 把cacheList里面的数据刷入磁盘
                        //FlushTool.logWriter.log("time out");
                        FlushTool.flush();
                    }
                    continue;
                }
                else
                {
                    long end=System.currentTimeMillis();
                    FlushTool.logWriter.log("collect all:use time"+(end-start)+" num:"+FlushTool.cacheList.size());
                    synchronized (FlushTool.syncObj) 
                    {
                        //TODO 把cacheList里面的数据刷入磁盘
                        FlushTool.flush();
                    }
                }
            }
            catch (InterruptedException e)
            {
                throw new RuntimeException();
            }
        }
    }
}

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

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Semaphore;

import com.alibaba.middleware.race.mom.SendResult;
import com.alibaba.middleware.race.mom.SendStatus;
import com.alibaba.middleware.race.mom.broker.AckManager;
import com.alibaba.middleware.race.mom.broker.SemaphoreManager;
import com.alibaba.middleware.race.mom.file.MessageLog;
import com.alibaba.middleware.race.mom.tool.LogWriter;


/**
 * 
 * @author sei.zz
 *
 */
public class FlushTool {


    public static MessageLog log=null;
    public static LogWriter logWriter=null;
    static
    {
        try 
        {
            log=new MessageLog("message");//初始化持久化文件的实例
            logWriter=LogWriter.getLogWriter();
        }
        catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static Object syncObj=new Object();//用来刷磁盘的时候同步的

    //阻塞每个线程是否可以返回了
    public static Semaphore canReturn =new Semaphore(0);
    //一段时间内收到的消息缓存在这里,由一个单独的线程来刷磁盘
    public static List<byte[]> cacheList=new ArrayList<byte[]>();
    //存储正在等待ack消息的requestID和message ID
    public static List<String> requestCacheList=new ArrayList<String>();

    public static Semaphore semp=null;//一个标识收到多少个数据后就开始刷盘的信号量。
    public static int threadNum=Conf.connNum;
    static
    {
        semp=new Semaphore(-threadNum);//与发送线程数量相同
    }

    public static void writeToCache(byte[] data,String requestId)
    {
        //System.out.println("write in cache");
        synchronized (cacheList) {
            cacheList.add(data);
            requestCacheList.add(requestId);
            semp.release();
        }
    }
    public static void writeToCache(List<byte[]> list,String requestId)
    {
        //System.out.println("write in cache");
        synchronized (cacheList) {
            cacheList.addAll(list);
            requestCacheList.add(requestId);
            semp.release();//收到一个数据,释放一下,当释放到足够多的时候 线程会刷盘
        }
    }
    public static void reset()
    {
        semp=new Semaphore(-threadNum);
    }

    //将缓冲区的数据写入硬盘,唤醒在等待刷盘操作的线程
    public static void flush()
    {
        List<byte[]> temp=null;
        List<String> requestTemp=null;
        synchronized (cacheList) {
            temp=new ArrayList<byte[]>();
            requestTemp=new ArrayList<String>();
            temp.addAll(cacheList);
            requestTemp.addAll(requestCacheList);
            cacheList.clear();
            requestCacheList.clear();
            reset();
        }

        if(temp!=null&&temp.size()>0)//刷已经写好的数据,此时其他线程可以把数据写入cacheList
        {
            long start=System.currentTimeMillis();
            boolean error=false;
            //同步的把数据存储到磁盘
            if(!log.SynSave(temp))
                error=true;
            long end=System.currentTimeMillis();

            logWriter.log("save use time:"+(end-start)+" number:"+temp.size()+" cachelist:"+cacheList.size());
            //存储结束后,把刷盘成功的消息,生成对应的ack消息,设置消息id
            for (int i = 0; i < requestTemp.size(); i++) {
                SendResult ack=new SendResult();
                String[] arr=requestTemp.get(i).split("@");
                ack.setMsgId(arr[1]);//message id
                ack.setInfo(arr[0]);//request id
                if(error)
                    ack.setStatus(SendStatus.FAIL);
                else
                    ack.setStatus(SendStatus.SUCCESS);

                AckManager.pushAck(ack);//往ack队列里面放入一个ack消息
                SemaphoreManager.increase("Ack");
            }
        }
        //System.out.println("flush");
    }

    public static boolean writeConsumeResult(byte[] data)
    {
        if(log!=null)
            return log.AsynSave(data);
        else
            return false;
    }
}

Consumer的实现类

package com.alibaba.middleware.race.mom;

import java.util.UUID;

import com.alibaba.middleware.race.mom.cunsumer.netty.MomConsumerConnection;
import com.alibaba.middleware.race.mom.cunsumer.netty.MomConsumerNettyConnection;
import com.alibaba.middleware.race.mom.cunsumer.netty.MomConsumertHandler;
import com.alibaba.middleware.race.mom.cunsumer.netty.ResponseCallbackListener;
import com.alibaba.middleware.race.mom.model.MomRequest;
import com.alibaba.middleware.race.mom.model.MomResponse;
import com.alibaba.middleware.race.mom.model.RequestType;
import com.alibaba.middleware.race.mom.model.SubscriptRequestInfo;

public class DefaultConsumer implements Consumer{
    //broker服务器ip地址
    private String brokerIp;
    //连接broker服务器的连接
    private MomConsumerConnection consumerConn;
    //消费者组id
    private String groupId;
    //订阅的topic
    private String topic;
    //过滤的属性名
    private String propertieName;
    //过滤的值
    private String propertieValue;
    //监听器
    private MessageListener listener;
    //是否有属性过滤
    private boolean isFilter=false;

    //是否输入订阅信息
    private boolean isSubcript=false;

    private boolean isRunning=false;

    private String clientKey="";

    public DefaultConsumer() {
        //this.brokerIp=System.getProperty("SIP");
        brokerIp="127.0.0.1";
        consumerConn=new MomConsumerNettyConnection(brokerIp,8888);
    }

    @Override
    public void start() {
        // TODO Auto-generated method stub
        if(isSubcript)
        {
            //设置处理器 和结果回调函数
            consumerConn.setHandle(new MomConsumertHandler(consumerConn,new ResponseCallbackListener() {
                @Override
                public void onTimeout() {
                    // TODO Auto-generated method stub

                }
                @Override
                public Object onResponse(Object response) {
                    // TODO Auto-generated method stub
                    MomResponse mr=(MomResponse)response;
                    //调用上层设置的回调函数
                    ConsumeResult result=listener.onMessage((Message)mr.getResponse());
                    result.setGroupID(groupId);
                    result.setTopic(topic);
                    result.setMsgId(((Message)mr.getResponse()).getMsgId());
                    return result;
                }
                @Override
                public void onException(Throwable e) {
                    // TODO Auto-generated method stub
                    if(e instanceof java.net.ConnectException)
                    {
                        System.out.println("connect error");
                        if(isRunning)
                            restartConnect();
                    }
                }
                @Override
                public void onDisconnect(String msg) {
                    // TODO Auto-generated method stub
                    if(isRunning)
                        restartConnect();

                }
            }));
            //连接服务器
            consumerConn.connect();
            clientKey=groupId+topic+UUID.randomUUID().toString();
            //clientKey=groupId+topic+"0000007";
            //TODO 发送一个自己的订阅信息给服务器
            isRunning=true;
            MomRequest request=new MomRequest();
            request.setRequestType(RequestType.Subscript);
            request.setRequestId(UUID.randomUUID().toString());

            //构造订阅信息发送给
            SubscriptRequestInfo subscript=new SubscriptRequestInfo();
            subscript.setGroupId(groupId);
            subscript.setTopic(topic);
            subscript.setPropertieName(propertieName);
            subscript.setPropertieValue(propertieValue);
            subscript.setClientKey(clientKey);
            request.setParameters(subscript);
            consumerConn.Send(request);
        }
    }

    //重新连接broker服务器
    public void restartConnect()
    {
        boolean isConnected=false;
        System.out.println("restart");
        //TODO 获取到新的brokerIp地址 从zookeeper获取
        //brokerIp=System.getProperty("SIP");
        brokerIp="127.0.0.1";
        consumerConn=new MomConsumerNettyConnection(brokerIp,8888);
        //重新设置处理器
        //设置处理器 和结果回调函数
        consumerConn.setHandle(new MomConsumertHandler(consumerConn,new ResponseCallbackListener() {
            @Override
            public void onTimeout() {
                // TODO Auto-generated method stub

            }
            @Override
            public Object onResponse(Object response) {
                // TODO Auto-generated method stub
                MomResponse mr=(MomResponse)response;
                //调用上层设置的回调函数
                ConsumeResult result=listener.onMessage((Message)mr.getResponse());
                result.setGroupID(groupId);
                result.setTopic(topic);
                result.setMsgId(((Message)mr.getResponse()).getMsgId());
                return result;
            }
            @Override
            public void onException(Throwable e) {
                // TODO Auto-generated method stub
                //System.out.println("DefaultConsumer error");
                if(e instanceof java.net.ConnectException)
                {
                    System.out.println("connect error");
                    if(isRunning)
                        restartConnect();
                }
            }
            @Override
            public void onDisconnect(String msg) {
                // TODO Auto-generated method stub
                if(isRunning)
                    restartConnect();

            }
        }));
        //连接服务器
        try 
        {
            consumerConn.connect();
            isConnected=true;
        }
        catch (Exception e)
        {
            // 重新连接服务器失败,过3秒重新连接
            try 
            {
                Thread.sleep(3000);
            }
            catch (InterruptedException ie) 
            {
                //TODO nothing todo
            }
            isConnected=false;
            restartConnect();
        }

        //TODO 发送一个自己的订阅信息给服务器
        if(isConnected)
        {
            MomRequest request=new MomRequest();
            request.setRequestType(RequestType.Subscript);
            request.setRequestId(UUID.randomUUID().toString());

            //构造订阅信息发送给
            SubscriptRequestInfo subscript=new SubscriptRequestInfo();
            subscript.setGroupId(groupId);
            subscript.setTopic(topic);
            subscript.setPropertieName(propertieName);
            subscript.setPropertieValue(propertieValue);
            subscript.setClientKey(clientKey);
            request.setParameters(subscript);
            consumerConn.Send(request);
        }
    }

    @Override
    public void subscribe(String topic, String filter, MessageListener listener) {
        // TODO Auto-generated method stub
        this.topic=topic;
        if(filter.trim().length()>0&&filter.contains("="))
        {
            this.propertieName=filter.split("=")[0];
            this.propertieValue=filter.split("=")[1];
            this.isFilter=true;
        }
        this.listener=listener;//设置监听
        isSubcript=true;
    }

    @Override
    public void setGroupId(String groupId) {
        // TODO Auto-generated method stub
        this.groupId=groupId;
    }

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

        if(isRunning)
        {
            isRunning=false;
            //发送一个退订消息,把自己从订阅关系里面移除
            MomRequest request=new MomRequest();
            request.setRequestType(RequestType.Stop);
            request.setRequestId(UUID.randomUUID().toString());
            request.setParameters(new String(clientKey));
            consumerConn.SendSync(request);
            consumerConn.close();
        }

    }

}

由于这个项目的代码太多,没能全部贴出里面的代码,其中的通讯方式主要就是RPC的实现方案。
其中有很多地方是需要注意的。

原创声明

本文作者是 小竹zz 原地址是 http://blog.csdn.net/zhujunxxxxx/article/details/48742529 如需转载请注明出处

第一次FullGC优化实战

关于FullGC,博主只是在一些书中或者博客中,看别人调优过,今天兴起,亲自在本地调了一把本地的项目:         第一步:我首先打开了jvisualvm.exe,在Visual GC 里面发现了...

基于hadoop的分布式分词程序(庖丁分词)

一、使用的分词包——庖丁分词器介绍 1.1、简介: 庖丁系统是个完全基于lucene的中文分词系统,它就是重新建了一个analyzer,叫做PaodingAnalyzer,这个anal...

面向消息的中间件(MOM)的代表JMS

当前,CORBA、DCOM、RMI等RPC中间件技术已广泛应用于各个领域。但是面对规模和复杂度都越来越高的分布式系统,这些技术也显示出其局限性:(1)同步通信:客户发出调用后,必须等待服务对象完成处理...
  • heicm
  • heicm
  • 2011年05月10日 15:12
  • 506

面向消息的中间件MOM

MOM( Message Oriented Middleware)指的是利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统 的集成。简介MOM通过提供消息传递和消息排队...
  • heicm
  • heicm
  • 2011年05月10日 15:08
  • 901

ActiveMQ In Action 第二章 理解面向对象的中间件(MOM)和JMS 前言及2.1 介绍企业消息传递

这章主要内容有: 企业消息传递机制和面向消息的中间件 理解Java消息服务(JMS) 使用JMS API来发送和接收消息 消息驱动bean的一个例子...

RPC、ORB、MOM三类中间件比较

网上漫无目的的爬文档看,发现Oracle一篇《面向消息的中间件 (Message-Oriented Middleware, MOM) 》讲得不错,摘部分内容出来,大家分享,我也留个备份。    ...
  • vsddvsd
  • vsddvsd
  • 2017年01月20日 17:37
  • 174

Java中间件:淘宝网系统高性能利器

【TechTarget中国原创】淘宝网是亚太最大的网络零售商圈,其知名度毋庸置疑,吸引着越来越多的消费者从街头移步这里,成为其忠实粉丝。如此多的用户和交易量,也意味着海量的信息处理,其背后的IT架构的...

java架构之高并发,分布式,集群,高性能,中间件合集高级学习

视频课程内容包含: 高级Java架构师包含:Spring boot、Spring  cloud、Dubbo、Redis、ActiveMQ、Nginx、Mycat、Spring、MongoDB、Zer...

高性能分布式应用开发中间件ICE简介

http://lanhy2000.blog.163.com/blog/static/436786082012327449775/

高性能可伸缩的分布式消息中间件设计

消息中间件基本上是每一个大型互联网公司的标准基础技术组件配置,虽然有很多的开源消息中间件,功能也很强大,但是今天我还是想介绍一下怎样自主架构与设计并实现一套完整的分布式消息中间件。开源的消息中间件或多...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:基于Neety的高性能中间件Mom
举报原因:
原因补充:

(最多只允许输入30个字)