分布式消息中间件
基于Netty手写MQ分布式消息中间件
田超凡
20191212
转载请注明原作者
1 实现思路
(1) 定义Netty序列化编解码器工具类-MarshallingCoderFactory,基于工厂方法模式创建编码解码器
(2) 定义常量类,区分Netty客户端是生产者还是消费者、MQ服务器地址和端口
(3) 实现MQ服务器端-MQBroker,基于Netty搭建服务器,绑定编码解码器和事件驱动处理器
(4) 实现MQ服务器端事件驱动处理器,使用全局容器存放队列名称和队列的映射关系,队列和订阅的消费者之间的映射关系,
在接收到客户端发送的请求数据之后,根据客户端类型不同分开处理:
如果是生产者角色,那么就根据队列名称获取队列(不存在就创建对应的队列),然后需要把发布的消息放入队列,同时基于观察者模式通知所有订阅该消息队列的消费者进行消费,消费成功则做出队列操作
如果是消费者角色,那么根据队列名称获取MQ服务器缓存的队列消息,同时在消费者和订阅的消息队列之间建立映射关系并存入MQ服务器,最后把获取到的队列消息返回响应给消费者
(5) 实现生产者Netty客户端,发送队列名称、队列消息、客户端类型参数到MQ消息中间件服务器
(6) 实现消费者Netty客户端,发送队列名称、客户端类型参数到MQ消息中间件容器,绑定事件驱动处理器
(7) 实现消费者事件驱动处理器,在监听到MQ消息中间件服务器返回的响应消息之后,对获取到的消息进行进一步业务处理。
2 手写MQ消息中间件代码实现
package com.tcf.mq.source.util;
/***
* TODO TCF 常量类
* @author Hasee
*
*/
public class Const {
//TODO TCF MQ消息中间件-服务器运行主机和端口号
public static class MQBrokerInfo
{
//TODO TCF 主机ip
public static final String HOST="127.0.0.1";
//TODO TCF 主机端口号
public static final Integer PORT=8080;
}
//TODO TCF 生产者和消费者类型
public static class NettyClientType
{
//TODO TCF 生产者
public static final String PRODUCER="1";
//TODO TCF 消费者
public static final String CONSUMER="2";
}
//TODO TCF MQ队列名称
public static class MQQueueNames
{
//TODO TCF 队列1
public static final String QUEUE_FIRST="queue_first";
}
}
package com.tcf.mq.source.util;
import org.jboss.marshalling.MarshallerFactory;
import org.jboss.marshalling.Marshalling;
import org.jboss.marshalling.MarshallingConfiguration;
import io.netty.handler.codec.marshalling.DefaultMarshallerProvider;
import io.netty.handler.codec.marshalling.DefaultUnmarshallerProvider;
import io.netty.handler.codec.marshalling.MarshallerProvider;
import io.netty.handler.codec.marshalling.MarshallingDecoder;
import io.netty.handler.codec.marshalling.MarshallingEncoder;
import io.netty.handler.codec.marshalling.UnmarshallerProvider;
/***
* TODO TCF Netty内置序列化编码解码器
*
* @author Hasee
*
*/
public class MarshallingCoderFactory {
/**
* 创建Jboss Marshalling解码器MarshallingDecoder
*
* @return MarshallingDecoder
*/
public static MarshallingDecoder buildMarshallingDecoder()
{
// 首先通过Marshalling工具类的精通方法获取Marshalling实例对象 参数serial标识创建的是java序列化工厂对象。
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
// 创建了MarshallingConfiguration对象,配置了版本号为5
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
// 根据marshallerFactory和configuration创建provider
UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration);
// 构建Netty的MarshallingDecoder对象,俩个参数分别为provider和单个消息序列化后的最大长度
MarshallingDecoder decoder = new MarshallingDecoder(provider, 1024 * 1024 * 1);
return decoder;
}
/**
* 创建Jboss Marshalling编码器MarshallingEncoder
*
* @return MarshallingEncoder
*/
public static MarshallingEncoder buildMarshallingEncoder()
{
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration);
// 构建Netty的MarshallingEncoder对象,MarshallingEncoder用于实现序列化接口的POJO对象序列化为二进制数组
MarshallingEncoder encoder = new MarshallingEncoder(provider);
return encoder;
}
}
package com.tcf.mq.source.entity;
import java.io.Serializable;
/***
* TODO TCF 发送消息信息类
* @author Hasee
*
*/
public class DevilyEntity implements Serializable{
/**
*
*/
private static final long serialVersionUID = -4668828944906705282L;
//TODO TCF 队列名称
private String queueName;
//TODO TCF 发送的消息
private String message;
//TODO TCF 中间件类型:1 生产者 2 消费者
private String connectionType;
//TODO TCF 构造柱入
public DevilyEntity(String queueName,String message,String connectionType)
{
this.queueName=queueName;
this.message=message;
this.connectionType=connectionType;
}
//TODO TCF 默认无参构造
public DevilyEntity()
{
}
public String getQueueName() {
return queueName;
}
public void setQueueName(String queueName) {
this.queueName = queueName;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getConnectionType() {
return connectionType;
}
public void setConnectionType(String connectionType) {
this.connectionType = connectionType;
}
}
package com.tcf.mq.source.broker;
import java.net.InetSocketAddress;
import com.tcf.mq.source.handle.MQBrokerEventHandle;
import com.tcf.mq.source.util.Const;
import com.tcf.mq.source.util.MarshallingCoderFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/***
* TODO TCF 消息中间件服务器
* @author Hasee
*
*/
public class MQBroker {
public static void main(String[] args)
{
//TODO TCF Netty初始化服务器
NioEventLoopGroup bossGroup=new NioEventLoopGroup();
NioEventLoopGroup workGroup=new NioEventLoopGroup();
//TODO TCF 初始化Netty服务器
ServerBootstrap serverBootstrap=new ServerBootstrap();
serverBootstrap.group(bossGroup,workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel serverSocketChannel) throws Exception
{
//TODO TCF 绑定事件驱动处理器和编码解码器
serverSocketChannel.pipeline().addLast(MarshallingCoderFactory.buildMarshallingEncoder());
serverSocketChannel.pipeline().addLast(MarshallingCoderFactory.buildMarshallingDecoder());
//TODO TCF 绑定MQ消息中间件服务器事件驱动处理器
serverSocketChannel.pipeline().addLast(new MQBrokerEventHandle());
}
});
//TODO TCF 初始化通道,运行MQ中间件服务器
try
{
ChannelFuture channelFuture=serverBootstrap.bind(new InetSocketAddress(Const.MQBrokerInfo.HOST,Const.MQBrokerInfo.PORT));
System.out.println("====MQ消息中间件服务器启动成功====");
channelFuture.channel().closeFuture().sync();
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
//TODO TCF 关闭Netty线程组,释放线程资源
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
package com.tcf.mq.source.handle;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import org.apache.commons.lang3.StringUtils;
import com.tcf.mq.source.entity.DevilyEntity;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/***
* TODO TCF MQ服务器事件驱动处理器
* @author Hasee
*
*/
public class MQBrokerEventHandle extends SimpleChannelInboundHandler<DevilyEntity> {
//TODO TCF 生产者发布的消息
private static Map<String,Queue<DevilyEntity>> queueMap=new HashMap<String,Queue<DevilyEntity>>();
//TODO TCF 消息队列和订阅的消费者
private static Map<String,List<ChannelHandlerContext>> consumerMap=new HashMap<String,List<ChannelHandlerContext>>();
/***
* TODO TCF 接收到客户端发送的消息
* TODO TCF 生产者则读取消息存入缓冲区,通知消费者消费
* TODO TCF 消费者则直接从缓冲区读取数据
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx,DevilyEntity devilyEntity) throws Exception
{
//TODO TCF 消息队列名称
String queueName=devilyEntity.getQueueName();
if(StringUtils.isNotEmpty(queueName))
{
//TODO TCF 生产者则直接把投递的消息放入队列并通知消费者消费,如果是消费者则关联MQ中间件服务器上下文并直接消费
String connectionType=devilyEntity.getConnectionType();
if(StringUtils.isNotEmpty(connectionType))
{
if(connectionType.equals("1"))
{
//TODO TCF 生产者
//TODO TCF 根据队列名称获取队列,如果队列为空则创建队列放入集合,否则直接投递消息
Queue<DevilyEntity> queue=queueMap.get(queueName);
if(queue==null)
{
queue=new LinkedBlockingQueue<DevilyEntity>();
queueMap.put(queueName,queue);
}
//TODO TCF 消息投递到MQ队列,入队列
queue.offer(devilyEntity);
System.out.println("===>生产者投递的消息:"+devilyEntity.getMessage());
//TODO TCF 通知消费者消费,获取消费者绑定的MQ中间件上下文
List<ChannelHandlerContext> list=consumerMap.get(queueName);
if(list!=null && list.size()>0)
{
//TODO TCF 投递消息的时候消费者已存在,通知消费者消费,直接出队列
DevilyEntity data=queue.poll();
System.out.println("发现"+list.size()+"个订阅了当前队列的消费者,通知消费者消费");
for(ChannelHandlerContext context:list)
{
if(context!=null)
{
context.writeAndFlush(data);
}
}
}
}
else
{
//TODO TCF 消费者
//TODO TCF 根据队列名称获取需要消费的队列
Queue<DevilyEntity> queue=queueMap.get(queueName);
//TODO TCF 建立消费者和消费队列之间的关联关系
//TODO TCF 消费者订阅消息队列
if(consumerMap.containsKey(queueName))
{
consumerMap.get(queueName).add(ctx);
}
else
{
List<ChannelHandlerContext> list=new ArrayList<ChannelHandlerContext>();
list.add(ctx);
consumerMap.put(queueName,list);
}
if(queue!=null && queue.size()>0)
{
//TODO TCF 直接消费,出队列,返回获取到的消息队列到消费者,消费成功
devilyEntity=queue.poll();
if(devilyEntity!=null)
{
System.out.println("消费者获取到缓存中的消息===>"+devilyEntity.getMessage()+",队列剩余消息数量:"+queue.size());
//TODO TCF 把获取的到的队列消息返回给消费者
ctx.writeAndFlush(devilyEntity);
}
}
else
{
System.out.println("没有获取到队列名称为:"+queueName+"的消息,请先使用生产者发布消息");
}
}
}
}
}
}
package com.tcf.mq.source.producer;
import java.net.InetSocketAddress;
import com.tcf.mq.source.entity.DevilyEntity;
import com.tcf.mq.source.util.Const;
import com.tcf.mq.source.util.MarshallingCoderFactory;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/***
* TODO TCF MQ消息中间件-生产者客户端,投递消息到MQ
* @author Hasee
*
*/
public class MQProducer {
public static void main(String[] args)
{
//TODO TCF 初始化Netty客户端
NioEventLoopGroup workGroup=new NioEventLoopGroup();
Bootstrap bootstrap=new Bootstrap();
bootstrap.group(workGroup)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(Const.MQBrokerInfo.HOST,Const.MQBrokerInfo.PORT))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception
{
//TODO TCF 绑定编码解码器
socketChannel.pipeline().addLast(MarshallingCoderFactory.buildMarshallingEncoder());
socketChannel.pipeline().addLast(MarshallingCoderFactory.buildMarshallingDecoder());
}
});
//TODO TCF 启动Netty客户端,投递消息到MQ
try
{
ChannelFuture channelFuture=bootstrap.connect().sync();
System.out.println("生产者启动成功,已投递消息");
//TODO TCF 生产者需要投递的消息
String message="***测试消息***";
DevilyEntity devilyEntity=new DevilyEntity(Const.MQQueueNames.QUEUE_FIRST,message,Const.NettyClientType.PRODUCER);
//TODO TCF 生产者投递消息
channelFuture.channel().writeAndFlush(devilyEntity);
channelFuture.channel().closeFuture().sync();
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
//TODO TCF 关闭Netty线程组,释放占用的线程资源
workGroup.shutdownGracefully();
}
}
}
package com.tcf.mq.source.consumer;
import java.net.InetSocketAddress;
import com.tcf.mq.source.entity.DevilyEntity;
import com.tcf.mq.source.handle.MQConsumerEventHandle;
import com.tcf.mq.source.util.Const;
import com.tcf.mq.source.util.MarshallingCoderFactory;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/***
* TODO TCF MQ消费者,连接到MQ消息中间件,根据队列名称获取队列消息
* @author Hasee
*
*/
public class MQConsumer {
public static void main(String[] args)
{
//TODO TCF 初始化消费者Netty客户端
NioEventLoopGroup workGroup=new NioEventLoopGroup();
//TODO TCF 初始化Netty客户端信息,监听客户端初始化事件,绑定编解码器和事件驱动处理器
Bootstrap bootstrap=new Bootstrap();
bootstrap.group(workGroup)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(Const.MQBrokerInfo.HOST,Const.MQBrokerInfo.PORT))
.handler(new ChannelInitializer<SocketChannel>() {
//TODO TCF 初始化Netty客户端事件
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception
{
//TODO TCF 绑定编解码器和事件驱动处理器
socketChannel.pipeline().addLast(MarshallingCoderFactory.buildMarshallingEncoder());
socketChannel.pipeline().addLast(MarshallingCoderFactory.buildMarshallingDecoder());
//TODO TCF 绑定事件驱动处理器
socketChannel.pipeline().addLast(new MQConsumerEventHandle());
}
});
//TODO TCF 初始化Netty通道,连接到MQ消息中间件,指定需要消费的队列名称
try
{
ChannelFuture channelFuture=bootstrap.connect().sync();
System.out.println("消费者启动成功");
//TODO TCF 需要传输的对象信息,指定需要获取的消息队列名称
DevilyEntity devilyEntity=new DevilyEntity(Const.MQQueueNames.QUEUE_FIRST,null,Const.NettyClientType.CONSUMER);
//TODO TCF 连接到MQ消息中间件,发送消息
channelFuture.channel().writeAndFlush(devilyEntity);
channelFuture.channel().closeFuture().sync();
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
//TODO TCF 关闭Netty线程组,释放资源
workGroup.shutdownGracefully();
}
}
}
package com.tcf.mq.source.handle;
import com.tcf.mq.source.entity.DevilyEntity;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/***
* TODO TCF MQ消费者-Netty客户端事件驱动处理器
* @author Hasee
*
*/
public class MQConsumerEventHandle extends SimpleChannelInboundHandler<DevilyEntity> {
//TODO TCF 接收到MQ消息中间件返回的响应信息
@Override
protected void channelRead0(ChannelHandlerContext ctx,DevilyEntity devilyEntity) throws Exception
{
if(devilyEntity!=null)
{
//TODO TCF 获取到的消息中间件响应信息
String message=devilyEntity.getMessage();
System.out.println("消费者接收到MQ消息中间件中的消息:"+message);
}
}
}
转载请注明原作者