基于Neety的高性能中间件Mom
来源:互联网 发布:js对象参数 编辑:程序博客网 时间:2024/05/16 14:04
前言
今年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 * */@Sharablepublic 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 如需转载请注明出处
- 基于Neety的高性能中间件Mom
- 面向消息的中间件MOM
- 阿里巴巴中间件性能挑战赛(MOM篇)
- Neety+SpringBoot写一个基于Http协议的文件服务器
- MOM:消息中间件
- 面向消息的中间件(MOM)的代表JMS
- 面向消息的中间件(MOM)的代表JMS
- 面向消息的中间件 (Message-Oriented Middleware, MOM)
- 中间件 —— 消息中间件(MOM)
- 高性能可伸缩的分布式消息中间件设计
- 浅谈jms之中间件(mom)activeMQ的安装和部署
- 消息中间件Spring.Mom v1设计
- RPC、ORB、MOM三类中间件比较
- Hprose高性能远程动态通讯中间件
- NRedis-Proxy 高性能Redis 中间件服务
- NRedis-Proxy 高性能Redis 中间件服务
- NRedis-Proxy 高性能Redis 中间件服务
- Netty开发中间件:高并发性能优化
- 2522:A simple problem
- 【设计原则】面向对象编程的六大原则
- 查找(一)二分查找
- 测试用例模板
- MySQL快速复制数据库的方法
- 基于Neety的高性能中间件Mom
- 《数据结构》——排序
- 测试杯子、电梯
- ThreadPool用法与优势
- 【html】The RadioButton control
- Handler
- FSWD_3_JavaScriptAdvance
- 单元测试、集成测试、系统测试和验收测试
- 三角形测试