深入理解并发编程之手写消息中间件实战
一、逻辑分析
MQ(Message Queue)消息队列,是基础数据结构中“先进先出”的一种数据结构。一般用来解决应用解耦,异步消息,流量削峰等问题,实现高性能,高可用,可伸缩和最终一致性架构。
大多数人应该都知道MQ,消息中间件都有生产者、队列和消费者,如下图我们就使用线程手写一个类似于消息队列来加深对并发编程的理解。
这里我们使用Netty作为网络通讯工具。
根据分析我们需要三部分代码,一个是消息中间件服务,一个是生产者还有一个消费者:
- 写一个NettyServer端项目作为消息中间件服务,创建一个并发队列LinkedBlockingQueue缓存我们的消息。
- 创建一个NettyClient 作为生产者角色连接我们NettyServer端,缓存投递的消息内容存放到队列中。
- 创建一个NettyClient 作为消费者角色连接我们NettyServer端,从缓存队列中取出消息内容。
二、伪代码实现
1.MQ服务
首先写一个NettyServer服务,这个就不用看了固定代码:
public class MQNettyServer {
/**
* netty启动端口号
*/
private static int port = 9081;
public static void main(String[] args) {
/**
* 客户端创建两个线程池组分别为 boss线程组和工作线程组
*/
// 用于接受客户端连接的请求 (并没有处理请求)
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
// 用于处理客户端连接的读写操作
NioEventLoopGroup workGroup = new NioEventLoopGroup();
// 用于创建我们的ServerBootstrap
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new MQServerHandler());
}
});
try {
// 绑定端口号,同步等待成功
ChannelFuture future = serverBootstrap.bind(port).sync();
System.out.println("MQ服务器启动成功:" + port);
// 等待服务器监听端口
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 优雅的关闭连接
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
再写一个ServerHandler
public class MQServerHandler extends SimpleChannelInboundHandler {
private static ConcurrentHashMap<String, LinkedBlockingDeque> concurrentHashMap = new ConcurrentHashMap<String, LinkedBlockingDeque>();
private static ConcurrentHashMap<String, ChannelHandlerContext> channelhashMap =
new ConcurrentHashMap<String, ChannelHandlerContext>();
/**
* netty客户端向netty服务器端发送消息 就会走该方法
*
* @param ctx
* @param o
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object o) throws Exception {
// 1.获取消息并解析为JSON
ByteBuf byteBuf = (ByteBuf) o;
String request = byteBuf.toString(CharsetUtil.UTF_8);
System.out.println("request:" + request);
JSONObject msg = JSONObject.parseObject(request);
String type = msg.getString("type");
if (StringUtils.isEmpty(type)) {
return;
}
String dequeName = msg.getString("dequeName"); //消息的业务类型
if (StringUtils.isEmpty(dequeName)) {
return;
}
//判断是生产者还是消费者
if ("producer".equals(type)) {
//如果是生产者,将消息存放到队列
producerService(msg, dequeName);
// 通知消费者消费消息
notifyConsumer(dequeName);
return;
}
//如果是消费者
if ("consumer".equals(type)) {
// 消费者处理
consumerService(msg, ctx, dequeName);
// 缓存我们消费者连接
channelhashMap.put(dequeName, ctx);
return;
}
}
/**
* 消费者被动获取消息
* @param dequeName
*/
private void notifyConsumer(String dequeName) {
//获取当前业务的消费者
ChannelHandlerContext consumerCtx = channelhashMap.get(dequeName);
if (consumerCtx != null) {
//获取当前业务的消息队列
LinkedBlockingDeque<String> linkedBlockingDeque = concurrentHashMap.get(dequeName);
if (linkedBlockingDeque == null) {
return;
}
//如果有消息存在则一直消费到消息队列清空
while (linkedBlockingDeque.size() > 0) {
String poll = linkedBlockingDeque.poll();
if (StringUtils.isEmpty(poll)) {
return;
}
//将消息发送到消费者终端
consumerCtx.writeAndFlush(Unpooled.copiedBuffer(poll, CharsetUtil.UTF_8));
}
}
}
/**
* 消费者主动消费消息
* @param msg
* @param ctx
* @param dequeName
*/
private void consumerService(JSONObject msg, ChannelHandlerContext ctx, String dequeName) {
//获取对应的消息队列
LinkedBlockingDeque<String> linkedBlockingDeque = concurrentHashMap.get(dequeName);
if (linkedBlockingDeque == null) {
return;
}
// 从队列中取出该消息 响应给消费者
String poll = linkedBlockingDeque.poll();
if (StringUtils.isEmpty(poll)) {
return;
}
// 将消息发送到消费者终端
ctx.writeAndFlush(Unpooled.copiedBuffer(poll, CharsetUtil.UTF_8));
}
/**
* 将消息存放到对应的消息队列中
* @param msg
* @param dequeName
*/
private void producerService(JSONObject msg, String dequeName) {
// 获取消息内容
String data = msg.getString("data");
if (StringUtils.isEmpty(data)) {
return;
}
// 找到对应业务的消息队列
LinkedBlockingDeque<String> linkedBlockingDeque = concurrentHashMap.get(dequeName);
//如果消息队列为空则创建消息队列,注意这里有并发问题
if (linkedBlockingDeque == null) {
linkedBlockingDeque = new LinkedBlockingDeque<String>();
concurrentHashMap.put(dequeName, linkedBlockingDeque);
}
// 将该消息放入到该队列缓存起来
linkedBlockingDeque.offer(data);
}
}
2.生产者
生产者也使用Netty服务,消费者应该是客户端:
Netty生产者客户端,固定代码不看了
public class ProducerNettyClient {
public static void main(String[] args) {
//创建nioEventLoopGroup
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress("127.0.0.1", 9081))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ProducerHandler());
}
});
try {
// 发起同步连接
ChannelFuture sync = bootstrap.connect().sync();
sync.channel().closeFuture().sync();
} catch (Exception e) {
} finally {
group.shutdownGracefully();
}
}
}
生产者推送消息,这代码也不用看了,很简单的代码逻辑
public class ProducerHandler extends SimpleChannelInboundHandler<ByteBuf> {
/**
* 活跃通道可以发送消息
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 发送数据
JSONObject send = new JSONObject();
send.put("type", "producer");
send.put("dequeName", "mayiktMember");
JSONObject data = new JSONObject();
data.put("userId", "123456");
data.put("content", "蚂蚁课堂牛逼");
send.put("data", data);
ctx.writeAndFlush(Unpooled.copiedBuffer(send.toJSONString(), CharsetUtil.UTF_8));
}
/**
* 读取消息
* @param ctx
* @param msg
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
}
}
3.消费者
消费者应该和生产者一样,我们看生产者代码的时候有个读取消息和发送消息,消费者就使用读取消息
固定Netty代码不看了
public class ConsumerNettyClient {
public static void main(String[] args) {
//创建nioEventLoopGroup
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(
"127.0.0.1", 9081))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ConsumerHandler());
}
});
try {
// 发起同步连接
ChannelFuture sync = bootstrap.connect().sync();
sync.channel().closeFuture().sync();
} catch (Exception e) {
} finally {
group.shutdownGracefully();
}
}
}
消费者消费消息:
public class ConsumerHandler extends SimpleChannelInboundHandler<ByteBuf> {
/**
* 活跃通道可以发送消息
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
JSONObject send = new JSONObject();
send.put("type", "consumer");
send.put("dequeName", "mayiktMember");
ctx.writeAndFlush(Unpooled.copiedBuffer(send.toJSONString(), CharsetUtil.UTF_8));
}
/**
* 读取消息
*
* @param ctx
* @param msg
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println("resp:" + msg.toString(CharsetUtil.UTF_8));
}
}
执行结果:
本例子只是为了加深并发编程的应用理解,不代表实际代码。
内容来源: 蚂蚁课堂