Java基于内存的消息队列实现
Java基于内存的消息队列实现
有需要源码的同学可以去Gitee上下载:
https://gitee.com/hyxl-520/memory_message_queue
先看测试情况
需求背景
需求来源于我写的一个单体应用方便使用的动态任务调度框架(详细内容见Gitee地址:https://gitee.com/hyxl-520/autojob.git),该框架需要实现对运行的任务的日志记录。我采用的方案是Log4j2的自定义Appender和Filter,但是如何有序、高效的采集Appender获得的日志是一个问题,因此我拟采用基于内存的消息队列。本博客只讨论消息队列实现方式,不讨论动态任务调度框架的使用方式。
实现的功能
- 支持可阻塞的消息生产和消费。
- 支持TTL(即消息可设置过期时长)。
- 支持单播和多播。
拟采用的数据结构
根据需求和功能,拟采用Java的LinkedBlockingQueue
,具体原因如下:
LinkedBlockingQueue
具有可阻塞、线程安全的优点。LinkedBlockingQueue
具有链式结构,由于要实现TTL,即会经常的删除消息队列中的已过期消息,相比ArrayBlockingQueue
,链式结构删除复杂度更低
完整数据结构如下:
Map<String, MessageQueue<MessageEntry<M>>> messageQueues;
public static class MessageQueue<M> {
private final BlockingQueue<M> messageQueue;
public MessageQueue() {
messageQueue = new LinkedBlockingQueue<>();
}
public MessageQueue(int maxLength) {
if (maxLength <= 0) {
throw new IllegalArgumentException("最大容量应该为非负数");
}
messageQueue = new LinkedBlockingDeque<>(maxLength);
}
public M takeMessageSync() throws InterruptedException {
return messageQueue.take();
}
public M takeMessage() {
return messageQueue.poll();
}
public M readMessage() {
return messageQueue.peek();
}
public M tryReadMessage() {
return messageQueue.element();
}
public boolean removeIf(Predicate<? super M> predicate) {
if (predicate == null) {
return false;
}
return messageQueue.removeIf(predicate);
}
public int length() {
return messageQueue.size();
}
public boolean remove(M message) {
if (message == null) {
return false;
}
return messageQueue.remove(message);
}
public Iterator<M> iterator() {
return messageQueue.iterator();
}
private boolean publishMessageSync(M message) throws InterruptedException {
if (message == null) {
return false;
}
try {
messageQueue.put(message);
return true;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
private boolean publishMessage(M message) {
if (message == null) {
return false;
}
try {
return messageQueue.offer(message);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
public static class MessageEntry<M> {
long messageId;
M message;
long expiringTime;
public MessageEntry(long messageId, M message, long expiringTime) {
this.messageId = messageId;
this.message = message;
this.expiringTime = expiringTime;
}
public MessageEntry() {
}
}
Map采用HashTable,确保线程安全。其中MessageQueue
封装了LinkedBlockingQueue
,作为容器的内部类,其中生产者方法为私有,保证容器可以访问其生产者方法,但是消费者在外部订阅后只能访问消费者的方法。
MessageEntry
是消息条目的封装,除了消息内容,还包含消息ID以及过期时间。
模块设计
IMessageQueueContext
:IMessageQueueContext
是消息容器,所有的消息都将放在这个类里面,其实现了IProducer
和ICounsumer
接口,即提供了生产者和消费者的所有功能。并且通过创建守护线程实现了TTL功能。过期监听策略分为两种(ExpirationListenerPolicy
):
- 单线程遍历所有topic的所有消息
- 每个topic并发遍历
前者适合主题少、总消息数少的情况,后者适合主题较多、消息总数较多的情况。
使用工厂模式,主要配置属性有:
/**
* 默认过期时间:ms,对所有消息设置,-1则表示消息均为永久性消息,除非消费者取出,否则将会一直存在。谨慎使用!
*/
private long defaultExpiringTime = -1;
/**
* 是否允许设置单个消息的过期时间
*/
private boolean isAllowSetEntryExpired = false;
/**
* 允许的最大主题数
*/
private int allowMaxTopicCount = 255;
/**
* 允许每个队列的最大消息数
*/
private int allowMaxMessageCountPerQueue;
/**
* 过期监听器的策略
*/
private ExpirationListenerPolicy listenerPolicy;
通过修改属性值可以构建出单播和多播的生产者消费者。
一个消息容器可供多个生产者和消费者使用。
提供topic过期功能,当消费者订阅后会统计一个订阅数目,当消费者取消订阅时如果当前topic的订阅数和消息堆积数为0,若指定时间内没有生产者生产消息将会移除该主题。
MessageProducer
:MessageProducer
为生产者,实现了IProducer
接口(其实最终实现来自于IMessageQueueContext
类),其提供如图上的方法。构建时需要指定所使用的消息容器。
MessageConsumer
:MessageConsumer
为消费者,实现了IConsumer
接口(其实最终实现来自于IMessageQueueContext
类)。对于消息消费强烈建议不要使用如下参数列表String topic
的方法,这个方法将不能使用消息容器提供的topic过期功能,建议先订阅指定队列,再从该队列获取消息,生产者对于队列订阅也提供了阻塞功能。
拓展
消息容器的队列可采用多种实现,如采用DelayQueue
可以实现死信队列。
完整源代码
IMessageQueueContext
package com.example.autojob.skeleton.mq;
/**
* @Description
* @Auther Huang Yongxiang
* @Date 2022/03/20 9:57
*/
public interface IMessageQueueContext<M> {
int length(String topic);
/**
* 使得消息立即过期
*
* @param topic 主题
* @return void
* @throws ErrorExpiredException 过期时发生异常抛出
* @author Huang Yongxiang
* @date 2022/3/20 11:31
*/
void expire(String topic, MessageQueueContext.MessageEntry<M> messageEntry) throws ErrorExpiredException;
/**
* 摧毁消息容器并启动垃圾清理
*
* @return void
* @author Huang Yongxiang
* @date 2022/3/22 14:33
*/
void destroy();
}
IConsumer
package com.example.autojob.skeleton.mq;
import java.util.concurrent.TimeUnit;
/**
* @Description
* @Auther Huang Yongxiang
* @Date 2022/03/21 14:20
*/
public interface IConsumer<M> {
/**
* 阻塞的获取一条消息,可以决定是否将该消息取出,即移出队列
*
* @param topic 主题
* @param isTakeout 是否移出队列,当为false时该方法将退化成非阻塞的
* @return M
* @author Huang Yongxiang
* @date 2022/3/20 10:55
*/
M takeMessageBlock(final String topic, final boolean isTakeout);
M takeMessageNoBlock(final String topic, final boolean isTakeout);
MessageQueueContext.MessageQueue<MessageQueueContext.MessageEntry<M>> subscriptionMessage(String topic);
/**
* 阻塞的尝试订阅指定消息队列,如果存在则立即返回,否则将会等待指定时长,若期间创建则会立即返回,否则等
* 到结束返回null
*
* @param topic 主题
* @param wait 要阻塞获取的时长
* @param unit wait的时间单位
* @return com.example.autojob.skeleton.mq.MessageQueueContext.MessageQueue<com.example.autojob.skeleton.mq.MessageQueueContext.MessageEntry < M>>
* @author Huang Yongxiang
* @date 2022/3/22 14:32
*/
MessageQueueContext.MessageQueue<MessageQueueContext.MessageEntry<M>> subscriptionMessage(String topic, long wait, TimeUnit unit);
void unsubscribe(String topic);
void unsubscribe(String topic,long wait,TimeUnit unit);
}
IProducer
package com.example.autojob.skeleton.mq;
import java.util.concurrent.TimeUnit;
/**
* @Description
* @Auther Huang Yongxiang
* @Date 2022/03/21 14:19
*/
public interface IProducer<M> {
/**
* 注册一个消息队列
*
* @param topic 主题名
* @return boolean
* @author Huang Yongxiang
* @date 2022/3/20 9:59
*/
boolean registerMessageQueue(String topic);
boolean hasTopic(String topic);
boolean removeMessageQueue(String topic);
/**
* 非阻塞的发布一条消息,当容量已满时立即返回
*
* @param message 消息内容
* @param topic 队列主题
* @return boolean
* @author Huang Yongxiang
* @date 2022/3/20 9:59
*/
boolean publishMessageNoBlock(final M message, final String topic);
/**
* 非阻塞的发布一条消息,同时指定其过期时间,当容量已满时立即返回
*
* @param message 消息内容
* @param topic 主题
* @param expiringTime 过期时长
* @param unit 时间单位
* @return boolean
* @author Huang Yongxiang
* @date 2022/3/20 10:09
*/
boolean publishMessageNoBlock(final M message, final String topic, final long expiringTime, final TimeUnit unit);
/**
* 阻塞的发布一条消息,当容量已满时等待空出
*
* @param message 消息内容
* @param topic 队列主题
* @return boolean
* @author Huang Yongxiang
* @date 2022/3/20 10:03
*/
boolean publishMessageBlock(final M message, final String topic);
boolean publishMessageBlock(final M message, final String topic, final long expiringTime, final TimeUnit unit);
}
ErrorExpiredException
package com.example.autojob.skeleton.mq;
/**
* @Description 消息过期异常类
* @Auther Huang Yongxiang
* @Date 2022/03/20 11:32
*/
public class ErrorExpiredException extends RuntimeException{
public ErrorExpiredException() {
super();
}
public ErrorExpiredException(String message) {
super(message);
}
public ErrorExpiredException(String message, Throwable cause) {
super(message, cause);
}
public ErrorExpiredException(Throwable cause) {
super(cause);
}
protected ErrorExpiredException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
ExpirationListenerPolicy
package com.example.autojob.skeleton.mq;
/**
* @Description 过期策略
* @Auther Huang Yongxiang
* @Date 2022/03/21 9:30
*/
public enum ExpirationListenerPolicy {
/**
* 单线程监听过期
*/
SINGLE_THREAD,
/**
* 按照主题并发监听过期,总消息数目过多时采取该方式可以使得效率更高,注意topic过多会占用大量线程资源
*/
TOPIC_CONCURRENCY
}
MessageConsumer
package com.example.autojob.skeleton.mq;
import java.util.concurrent.TimeUnit;
/**
* @Description 消费者
* @Auther Huang Yongxiang
* @Date 2022/03/21 12:53
*/
public class MessageConsumer<M> implements IConsumer<M> {
private final MessageQueueContext<M> messageQueueContext;
public MessageConsumer(MessageQueueContext<M> messageQueueContext) {
this.messageQueueContext = messageQueueContext;
}
@Override
public M takeMessageBlock(String topic, boolean isTakeout) {
if (messageQueueContext == null) {
throw new NullPointerException("消息容器为空");
}
return messageQueueContext.takeMessageBlock(topic, isTakeout);
}
@Override
public M takeMessageNoBlock(String topic, boolean isTakeout) {
if (messageQueueContext == null) {
throw new NullPointerException("消息容器为空");
}
return messageQueueContext.takeMessageNoBlock(topic, isTakeout);
}
@Override
public MessageQueueContext.MessageQueue<MessageQueueContext.MessageEntry<M>> subscriptionMessage(String topic) {
return messageQueueContext.subscriptionMessage(topic);
}
@Override
public MessageQueueContext.MessageQueue<MessageQueueContext.MessageEntry<M>> subscriptionMessage(String topic, long wait, TimeUnit unit) {
return messageQueueContext.subscriptionMessage(topic, wait, unit);
}
@Override
public void unsubscribe(String topic) {
messageQueueContext.unsubscribe(topic);
}
@Override
public void unsubscribe(String topic, long wait, TimeUnit unit) {
messageQueueContext.unsubscribe(topic, wait, unit);
}
}
MessageProducer
package com.example.autojob.skeleton.mq;
import lombok.Data;
import java.util.concurrent.TimeUnit;
/**
* @Description 生产者
* @Auther Huang Yongxiang
* @Date 2022/03/21 12:54
*/
@Data
public class MessageProducer<M> implements IProducer<M> {
private MessageQueueContext<M> queueContext;
private static final int maxMessageCount = 1000;
private static final long expiringTime = -1;
public MessageProducer(final MessageQueueContext<M> messageQueueContext) {
this.queueContext = messageQueueContext;
}
public MessageProducer() {
queueContextBuild();
}
public MessageQueueContext<M> getMessageQueueContext() {
if (queueContext == null) {
queueContextBuild();
}
return queueContext;
}
private void queueContextBuild() {
queueContext = MessageQueueContext.builder().setAllowMaxMessageCountPerQueue(maxMessageCount).setDefaultExpiringTime(expiringTime).setAllowMaxTopicCount(1).setListenerPolicy(ExpirationListenerPolicy.SINGLE_THREAD).setAllowSetEntryExpired(true).build();
}
@Override
public boolean registerMessageQueue(String topic) {
return queueContext.registerMessageQueue(topic);
}
@Override
public boolean hasTopic(String topic) {
return queueContext.hasTopic(topic);
}
@Override
public boolean removeMessageQueue(String topic) {
return queueContext.removeMessageQueue(topic);
}
@Override
public boolean publishMessageNoBlock(M message, String topic) {
return queueContext.publishMessageNoBlock(message, topic);
}
@Override
public boolean publishMessageNoBlock(M message, String topic, long expiringTime, TimeUnit unit) {
return queueContext.publishMessageNoBlock(message, topic, expiringTime, unit);
}
@Override
public boolean publishMessageBlock(M message, String topic) {
return queueContext.publishMessageBlock(message, topic);
}
@Override
public boolean publishMessageBlock(M message, String topic, long expiringTime, TimeUnit unit) {
return queueContext.publishMessageBlock(message, topic, expiringTime, unit);
}
}
MessageQueueContext
package com.example.autojob.skeleton.mq;
import com.example.autojob.util.convert.StringUtils;
import com.example.autojob.util.id.IdGenerator;
import com.example.autojob.util.id.SystemClock;
import com.example.autojob.util.thread.ScheduleTaskUtil;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.PostConstruct;
import javax.swing.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Predicate;
/**
* @Description 内存消息队列的context
* @Auther Huang Yongxiang
* @Date 2022/03/18 17:14
*/
@Slf4j
public class MessageQueueContext<M> implements IMessageQueueContext<M>, IProducer<M>, IConsumer<M> {
/**
* 默认过期时间:ms,对所有消息设置,-1则表示消息均为永久性消息,除非消费者取出,否则将会一直存在。谨慎使用!
*/
private long defaultExpiringTime = -1;
/**
* 是否允许设置单个消息的过期时间
*/
private boolean isAllowSetEntryExpired = false;
/**
* 允许的最大主题数
*/
private int allowMaxTopicCount = 255;
/**
* 允许每个队列的最大消息数
*/
private int allowMaxMessageCountPerQueue;
/**
* 过期监听器的策略
*/
private ExpirationListenerPolicy listenerPolicy;
//存储消息的数据结构
private Map<String, MessageQueue<MessageEntry<M>>> messageQueues;
private boolean isOpenListener = false;
/**
* 守护线程
*/
private ScheduledExecutorService executorService;
/**
* 各个主题的订阅数
*/
private Map<String, AtomicLong> subscriptionCount;
public static Builder<Object> builder() {
return new Builder<>();
}
private MessageQueueContext() {
}
@PostConstruct
public void init() {
}
@Override
public boolean registerMessageQueue(String topic) {
if (StringUtils.isEmpty(topic)) {
log.error("创建队列失败,主题为空");
return false;
}
if (messageQueues.containsKey(topic)) {
return false;
}
if (messageQueues.size() >= allowMaxTopicCount) {
log.error("当前消息容器最大支持{}个主题", allowMaxTopicCount);
return false;
}
try {
MessageQueue<MessageEntry<M>> messageQueue = new MessageQueue<>(allowMaxMessageCountPerQueue);
messageQueues.put(topic, messageQueue);
if (!isOpenListener) {
if (listenerPolicy == ExpirationListenerPolicy.SINGLE_THREAD) {
scheduleExpiringCheckSingleThread();
} else {
scheduleExpiringCheckTopicConcurrency();
}
isOpenListener = true;
}
if (subscriptionCount == null) {
subscriptionCount = new Hashtable<>();
}
subscriptionCount.put(topic, new AtomicLong(0));
return true;
} catch (Exception e) {
log.error("创建队列发生异常:{}", e.getMessage());
}
return false;
}
@Override
public boolean hasTopic(String topic) {
return messageQueues.containsKey(topic);
}
@Override
public boolean removeMessageQueue(String topic) {
try {
messageQueues.remove(topic);
System.gc();
return true;
} catch (Exception e) {
log.error("移除消息队列时发生异常:{}", e.getMessage());
}
return false;
}
@Override
public boolean publishMessageNoBlock(M message, String topic, long expiringTime, TimeUnit unit) {
if (!isAllowSetEntryExpired) {
log.error("不允许设置单个消息的过期时间");
return false;
}
if (!messageQueues.containsKey(topic)) {
log.error("发布非阻塞消息失败,所要发布到的队列:{}不存在", topic);
return false;
}
if (expiringTime <= 0 || unit == null) {
log.error("非法过期时间");
return false;
}
if (message == null) {
log.error("禁止发布空消息");
return false;
}
MessageEntry<M> messageEntry = new MessageEntry<>();
messageEntry.setMessageId(IdGenerator.getNextIdAsLong());
messageEntry.setMessage(message);
messageEntry.setExpiringTime(unit.toMillis(expiringTime) + SystemClock.now());
return messageQueues.get(topic).publishMessage(messageEntry);
}
@Override
public boolean publishMessageBlock(M message, String topic, long expiringTime, TimeUnit unit) {
if (!isAllowSetEntryExpired) {
log.error("不允许设置单个消息的过期时间");
return false;
}
if (!messageQueues.containsKey(topic)) {
log.error("发布非阻塞消息失败,所要发布到的队列:{}不存在", topic);
return false;
}
if (expiringTime <= 0 || unit == null) {
log.error("非法过期时间");
return false;
}
if (message == null) {
log.error("禁止发布空消息");
return false;
}
MessageEntry<M> messageEntry = new MessageEntry<>();
messageEntry.setMessageId(IdGenerator.getNextIdAsLong());
messageEntry.setMessage(message);
messageEntry.setExpiringTime(unit.toMillis(expiringTime) + SystemClock.now());
try {
return messageQueues.get(topic).publishMessageSync(messageEntry);
} catch (InterruptedException e) {
log.warn("发布可阻塞消息发生异常,等待时被异常占用:{}", e.getMessage());
}
return false;
}
/**
* 阻塞的获取一条消息,可以决定是否将该消息取出,即移出队列,当多播时最好不要移出队列
*
* @param topic 主题
* @param isTakeout 是否移出队列,当为false时该方法将退化成非阻塞的
* @return M
* @author Huang Yongxiang
* @date 2022/3/20 10:55
*/
@Override
public M takeMessageBlock(String topic, boolean isTakeout) {
if (!messageQueues.containsKey(topic)) {
return null;
}
if (isTakeout) {
try {
return messageQueues.get(topic).takeMessageSync().message;
} catch (InterruptedException e) {
log.warn("可阻塞获取消息发生异常,等待时被异常占用:{}", e.getMessage());
}
return null;
}
return messageQueues.get(topic).readMessage().message;
}
@Override
public M takeMessageNoBlock(String topic, boolean isTakeout) {
if (!messageQueues.containsKey(topic)) {
return null;
}
if (isTakeout) {
return messageQueues.get(topic).takeMessage().message;
}
return messageQueues.get(topic).readMessage().message;
}
@Override
public MessageQueue<MessageEntry<M>> subscriptionMessage(String topic) {
MessageQueue<MessageEntry<M>> messageQueue = messageQueues.get(topic);
if (messageQueue != null && subscriptionCount != null) {
AtomicLong atomicLong = subscriptionCount.get(topic);
atomicLong.incrementAndGet();
}
return messageQueue;
}
@Override
@SuppressWarnings("unchecked")
public MessageQueue<MessageEntry<M>> subscriptionMessage(String topic, long wait, TimeUnit unit) {
MessageQueue<MessageEntry<M>> messageQueue = subscriptionMessage(topic);
if (messageQueue != null) {
return messageQueue;
}
//进行阻塞获取
ScheduledFuture<Object> future = ScheduleTaskUtil.build(false, "subscriptionBlock").EOneTimeTask(() -> {
long blockTime = unit.toMillis(wait);
int i = 0;
try {
do {
if (hasTopic(topic)) {
return messageQueues.get(topic);
}
Thread.sleep(1);
} while (i++ <= blockTime);
return null;
} catch (Exception e) {
log.error("阻塞订阅时发生异常:{}", e.getMessage());
}
return null;
}, 1, TimeUnit.MILLISECONDS);
try {
return (MessageQueue<MessageEntry<M>>) future.get();
} catch (InterruptedException | ExecutionException e) {
log.error("阻塞订阅时发生异常:{}", e.getMessage());
}
return null;
}
@Override
public void unsubscribe(String topic) {
unsubscribe(topic, 5, TimeUnit.SECONDS);
}
@Override
public void unsubscribe(String topic, long wait, TimeUnit unit) {
if (subscriptionCount != null) {
AtomicLong atomicLong = subscriptionCount.get(topic);
atomicLong.decrementAndGet();
if (atomicLong.get() < 0) {
atomicLong.set(0);
}
//当有队列取消订阅,且目前消息数为0,则对指定队列监视5秒,5秒后依然没有生产者发布消息则直接移除主题
if (atomicLong.get() == 0 && length(topic) == 0) {
long w = unit.toMillis(wait);
log.debug("主题为{}的消息队列目前订阅数为0且积压消息为0,当{}ms后若无生产者发布消息将自动删除该主题队列", topic, w);
ScheduleTaskUtil.build(true, "temporaryProducerListener").EOneTimeTask(() -> {
int i = 0;
boolean flag = true;
do {
if (length(topic) > 0) {
flag = false;
break;
}
Thread.sleep(1);
} while (i++ <= w);
if (flag) {
log.debug("主题:{}自动删除完成", topic);
removeMessageQueue(topic);
}
return flag;
}, 1, TimeUnit.MILLISECONDS);
}
}
}
@Override
public int length(String topic) {
if (messageQueues.containsKey(topic)) {
return messageQueues.get(topic).length();
}
return 0;
}
@Override
public void expire(String topic, MessageQueueContext.MessageEntry<M> messageEntry) throws ErrorExpiredException {
if (messageEntry == null || !messageQueues.containsKey(topic)) {
throw new IllegalArgumentException("参数有误,ID非法或主题不存在");
}
try {
boolean flag = messageQueues.get(topic).remove(messageEntry);
if (!flag) {
throw new ErrorExpiredException("移出失败");
}
} catch (Exception e) {
log.error("过期时发生异常");
throw new ErrorExpiredException(e.getMessage());
}
}
@Override
public void destroy() {
messageQueues = null;
if (isOpenListener) {
try {
executorService.shutdown();
isOpenListener = false;
} catch (Exception e) {
log.error("关闭守护线程发生异常:{}", e.getMessage());
}
}
System.gc();
}
/**
* <p>根据迭代器位置来使得一个元素过期,由于迭代器的弱一致性,多线程环境下可能会出现使用迭代器时
* 发生插入\删除操作,由于该消息队列对于操作严格从队尾执行,因此对于插入修改能检测到,但是由于
* 删除从队首进行,可能发生当迭代器获取下一个元素时为空,这时应该立即停止遍历,等待下一次</p>
*
* @param iterator 迭代器
* @return void
* @author Huang Yongxiang
* @date 2022/3/21 11:42
*/
public void expire(Iterator<MessageEntry<M>> iterator) throws ErrorExpiredException {
if (iterator == null) {
throw new ErrorExpiredException("过期失败,迭代器为空");
}
try {
iterator.remove();
} catch (UnsupportedOperationException e) {
throw new ErrorExpiredException("过期失败,该迭代器不支持移除操作");
} catch (IllegalStateException e) {
throw new ErrorExpiredException("过期失败,可能发生删除操作");
}
}
@Override
public boolean publishMessageNoBlock(M message, String topic) {
if (!messageQueues.containsKey(topic)) {
log.error("发布非阻塞消息失败,所要发布到的队列主题:{}不存在", topic);
return false;
}
if (message == null) {
log.error("禁止发布空消息");
return false;
}
MessageEntry<M> messageEntry = new MessageEntry<>();
messageEntry.setMessageId(IdGenerator.getNextIdAsLong());
messageEntry.setMessage(message);
messageEntry.setExpiringTime(defaultExpiringTime > 0 ? defaultExpiringTime + SystemClock.now() : -1);
return messageQueues.get(topic).publishMessage(messageEntry);
}
public boolean publishMessageBlock(M message, String topic) {
if (!messageQueues.containsKey(topic)) {
log.error("发布阻塞消息失败,所要发布到的队列主题:{}不存在", topic);
return false;
}
if (message == null) {
log.error("禁止发布空消息");
return false;
}
try {
MessageEntry<M> messageEntry = new MessageEntry<>();
messageEntry.setMessageId(IdGenerator.getNextIdAsLong());
messageEntry.setMessage(message);
messageEntry.setExpiringTime(defaultExpiringTime > 0 ? defaultExpiringTime + SystemClock.now() : -1);
return messageQueues.get(topic).publishMessageSync(messageEntry);
} catch (InterruptedException e) {
log.warn("发布可阻塞消息发生异常,等待时被异常占用:{}", e.getMessage());
}
return false;
}
private void scheduleExpiringCheckSingleThread() {
executorService = Executors.newSingleThreadScheduledExecutor(runnable -> {
Thread thread = new Thread(runnable, "ExpiringCheckSingleThread");
thread.setDaemon(true);
return thread;
});
executorService.scheduleAtFixedRate(() -> {
for (Map.Entry<String, MessageQueue<MessageEntry<M>>> entry : messageQueues.entrySet()) {
for (Iterator<MessageEntry<M>> it = entry.getValue().iterator(); it.hasNext(); ) {
MessageEntry<M> message = it.next();
//如果达到过期时间则通知其过期
if (message.expiringTime >= 0 && message.expiringTime <= SystemClock.now()) {
try {
//log.info("messageId:{}已过期", message.messageId);
expire(it);
} catch (ErrorExpiredException e) {
log.error("主题:{},消息ID:{}过期失败:{}", entry.getKey(), message.getMessageId(), e.getMessage());
}
}
}
}
}, 1, 1, TimeUnit.MILLISECONDS);
}
private void scheduleExpiringCheckTopicConcurrency() {
executorService = Executors.newSingleThreadScheduledExecutor(runnable -> {
Thread thread = new Thread(runnable, "ExpiringCheckTopicConcurrencyThread");
thread.setDaemon(true);
return thread;
});
executorService.scheduleAtFixedRate(() -> {
for (Map.Entry<String, MessageQueue<MessageEntry<M>>> entry : messageQueues.entrySet()) {
ScheduledExecutorService queueScheduler = Executors.newSingleThreadScheduledExecutor(runnable -> {
Thread thread = new Thread(runnable, String.format("%sQueueListener", entry.getKey()));
thread.setDaemon(true);
return thread;
});
synchronized (MessageEntry.class) {
queueScheduler.schedule(() -> {
for (Iterator<MessageEntry<M>> it = entry.getValue().iterator(); it.hasNext(); ) {
MessageEntry<M> message = it.next();
//如果达到过期时间则通知其过期
if (message.expiringTime >= 0 && message.expiringTime <= SystemClock.now()) {
try {
log.info("messageId:{}已过期", message.messageId);
expire(it);
} catch (ErrorExpiredException e) {
log.error("主题:{},消息ID:{}过期失败:{}", entry.getKey(), message.getMessageId(), e.getMessage());
}
}
}
}, 1, TimeUnit.MILLISECONDS);
}
}
}, 1000, 1000, TimeUnit.MILLISECONDS);
}
@Setter
@Accessors(chain = true)
public static class Builder<M> {
/**
* 默认过期时间,对所有消息设置,-1则表示消息均为永久性消息,除非消费者取出,否则将会一直存在。谨慎使用!
*/
private long defaultExpiringTime = -1;
/**
* 是否允许设置单个消息的过期时间
*/
private boolean isAllowSetEntryExpired = false;
/**
* 允许的最大主题数
*/
private int allowMaxTopicCount = 255;
/**
* 允许每个队列的最大消息数
*/
private int allowMaxMessageCountPerQueue = 1000;
/**
* 过期监听器的策略
*/
private ExpirationListenerPolicy listenerPolicy = ExpirationListenerPolicy.SINGLE_THREAD;
public Builder<M> setDefaultExpiringTime(long defaultExpiringTime, TimeUnit unit) {
if (unit == TimeUnit.MICROSECONDS) {
log.error("最小支持毫秒级");
throw new IllegalArgumentException("最小支持毫秒级");
}
this.defaultExpiringTime = unit.toMillis(defaultExpiringTime);
return this;
}
public <M1 extends M> MessageQueueContext<M1> build() {
MessageQueueContext<M1> messageQueueContext = new MessageQueueContext<>();
messageQueueContext.messageQueues = new Hashtable<>(this.allowMaxTopicCount);
messageQueueContext.isAllowSetEntryExpired = this.isAllowSetEntryExpired;
messageQueueContext.allowMaxMessageCountPerQueue = this.allowMaxMessageCountPerQueue;
messageQueueContext.defaultExpiringTime = this.defaultExpiringTime;
messageQueueContext.allowMaxTopicCount = this.allowMaxTopicCount;
messageQueueContext.listenerPolicy = this.listenerPolicy;
return messageQueueContext;
}
}
public static class MessageQueue<M> {
private final BlockingQueue<M> messageQueue;
public MessageQueue() {
messageQueue = new LinkedBlockingQueue<>();
}
public MessageQueue(int maxLength) {
if (maxLength <= 0) {
throw new IllegalArgumentException("最大容量应该为非负数");
}
messageQueue = new LinkedBlockingDeque<>(maxLength);
}
public M takeMessageSync() throws InterruptedException {
return messageQueue.take();
}
public M takeMessage() {
return messageQueue.poll();
}
public M readMessage() {
return messageQueue.peek();
}
public M tryReadMessage() {
return messageQueue.element();
}
public boolean removeIf(Predicate<? super M> predicate) {
if (predicate == null) {
return false;
}
return messageQueue.removeIf(predicate);
}
public int length() {
return messageQueue.size();
}
public boolean remove(M message) {
if (message == null) {
return false;
}
return messageQueue.remove(message);
}
public Iterator<M> iterator() {
return messageQueue.iterator();
}
private boolean publishMessageSync(M message) throws InterruptedException {
if (message == null) {
return false;
}
try {
messageQueue.put(message);
return true;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
private boolean publishMessage(M message) {
if (message == null) {
return false;
}
try {
return messageQueue.offer(message);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
@Getter
@Setter(AccessLevel.PRIVATE)
public static class MessageEntry<M> {
long messageId;
M message;
long expiringTime;
public MessageEntry(long messageId, M message, long expiringTime) {
this.messageId = messageId;
this.message = message;
this.expiringTime = expiringTime;
}
public MessageEntry() {
}
}
public static void main(String[] args) {
int producerCount = 50;
int consumerCount = 50;
long maxMessageCount = 10000000;
AtomicLong getMessageAccount = new AtomicLong(0);
AtomicBoolean isFinished = new AtomicBoolean(false);
int perProducerCreate = (int) (maxMessageCount / producerCount);
MessageQueueContext<String> messageQueueContext = MessageQueueContext.builder().setAllowMaxTopicCount(producerCount).setListenerPolicy(ExpirationListenerPolicy.TOPIC_CONCURRENCY).setDefaultExpiringTime(30, TimeUnit.SECONDS).setAllowSetEntryExpired(true).build();
for (int i = 0; i < producerCount; i++) {
messageQueueContext.registerMessageQueue(i + "");
}
Runnable create = () -> {
MessageProducer<String> producer = new MessageProducer<>(messageQueueContext);
String topicName = Thread.currentThread().getName();
for (int i = 0; i < perProducerCreate; i++) {
producer.publishMessageBlock(StringUtils.getRandomStr(10), topicName);
}
};
Runnable get = () -> {
MessageConsumer<String> consumer = new MessageConsumer<>(messageQueueContext);
String topicName = Thread.currentThread().getName();
while (true) {
if (getMessageAccount.get() % 10000 == 0) {
log.info("已消费消息:{}条", getMessageAccount.get());
}
consumer.takeMessageBlock(topicName, true);
getMessageAccount.incrementAndGet();
if (getMessageAccount.get() == maxMessageCount) {
isFinished.set(true);
break;
}
}
};
long start = System.currentTimeMillis();
for (int i = 0; i < producerCount; i++) {
Thread thread = new Thread(create);
thread.setName("" + i);
thread.start();
}
for (int i = 0; i < consumerCount; i++) {
Thread thread = new Thread(get);
thread.setName("" + i);
thread.start();
}
Thread thread = new Thread(() -> {
while (true) {
if (isFinished.get()) {
break;
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long end = System.currentTimeMillis();
log.info("测试完成!消费者数目:{},生产者数目:{},消息总数:{},总计耗时:{}s", consumerCount, producerCount, maxMessageCount, (end - start) / 1000);
});
thread.start();
}
}
Java基于内存的消息队列实现的更多相关文章
- HQueue:基于HBase的消息队列
HQueue:基于HBase的消息队列 凌柏 1. HQueue简介 HQueue是一淘搜索网页抓取离线系统团队基于HBase开发的一套分布式.持久化消息队列.它利用HTable存储消息数据 ...
- php中对共享内存,消息队列的操作
http://www.cnblogs.com/fengwei/archive/2012/09/12/2682646.html php作为脚本程序,通常生命周期都很短,如在web应用中,一次请求就是ph ...
- 进程间通信机制(管道、信号、共享内存/信号量/消息队列)、线程间通信机制(互斥锁、条件变量、posix匿名信号量)
注:本分类下文章大多整理自<深入分析linux内核源代码>一书,另有参考其他一些资料如<linux内核完全剖析>.<linux c 编程一站式学习>等,只是为了更好 ...
- 基于硬件的消息队列中间件 Solace 简介之二
前言...... 前面简单介绍了Solace来自于哪家公司, 主要能做哪些事情. 本篇主要进一步介绍Solace作为消息传递的中间件如何工作的. 传统意义上来讲, 每当我们谈到消息中间件时, 首先想到 ...
- 使用java实现阿里云消息队列简单封装
一.前言 最近公司有使用阿里云消息队列的需求,为了更加方便使用,本人用了几天时间将消息队列封装成api调用方式以方便内部系统的调用,现在已经完成,特此记录其中过程和使用到的相关技术,与君共勉. 现在阿 ...
- linux后台查看共享内存和消息队列的命令
ipcs ipcs -q : 显示所有的消息队列 ipcs -qt : 显示消息队列的创建时间,发送和接收最后一条消息的时间 ipcs -qp: 显示往消息队列中放消息和从消息队列中取消息的进程ID ...
- 《linux程序设计》--读书笔记--第十四章信号量、共享内存和消息队列
信号量:用于管理对资源的访问: 共享内存:用于在程序之间高效的共享数据: 消息队列:在程序之间传递数据的一种简单方法: 一.信号量 临界代码:需要确保只有一个进程或者一个执行线程可以进入这个临界代码并 ...
- Java利用Redis实现消息队列
应用场景 为什么要用redis?二进制存储.java序列化传输.IO连接数高.连接频繁 一.序列化 这里编写了一个java序列化的工具,主要是将对象转化为byte数组,和根据byte数组反序列化成ja ...
- 基于Redis的消息队列php-resque
转载:http://netstu.5iunix.net/archives/201305-835/ 最近的做一个短信群发的项目,需要用到消息队列.因此开始了我对消息队列选型的漫长路. 为什么选型会纠结呢 ...
- Linux 进程间通信(管道、共享内存、消息队列、信号量)
进程通信 : 不同进程之间传播或交换信息 为什么要进程通信呢? 协同运行,项目模块化 通信原理 : 给多个进程提供一个都能访问到的缓冲区. 根据使用场景,我们能划分为以下几种通信 ...
随机推荐
- Docker | 使用dockerfile生成镜像,清理docker空间
用dockerfile生成镜像并挂载数据卷 编写dockerfile文件 创建dockerfile01 文件 # 基础镜像 FROM centos VOLUME ["volume01&quo ...
- 齐博x1钩子自动添加频道参数变量
频道或插件,增加功能的时候,可能要在后台增加开关参数.这个时候只需要增强对应的接口文件即可,比如创建这样一个文件\application\shop\ext\setting_get\give_jifen ...
- 9.为url添加可选的后缀
为url添加可选的后缀 在drf的机制中,响应数据的格式不再与单一内容类型连接,可以同时享有json格式或html格式,我们可以为api路径添加格式后缀的支持,使用格式后缀给我们明确指定了给定格式的u ...
- 部署redis-cluster
1.环境准备 ☆ 每个Redis 节点采用相同的相同的Redis版本.相同的密码.硬件配置 ☆ 所有Redis服务器必须没有任何数据 #所有主从节点执行: [root@ubuntu2004 ~]#ba ...
- Linux系统安装python
1. 安装python3 1.1 下载python3安装包及其依赖包(该步骤可忽略,步骤1.2 提供应用包链接) ① 在python官网下载所需的python3,或者用外网centos机器的wget命 ...
- pinpoint部署
pinpoint是一个分析大型分布式系统的平台,提供解决方案来处理海量跟踪数据,主要面向基于tomcat的Java 应用. pinpoint使用HBASE储存数据. 下面介绍pinpoint部署及应用 ...
- 重新整理 .net core 实践篇 ———— dotnet-dump [外篇]
前言 本文的上一篇为: https://www.cnblogs.com/aoximin/p/16861797.html 该文为dotnet-dump 和 procdump 的实战介绍一下. 正文 现在 ...
- 利用nginx自带的反向代理以及轮询功能实现应用的负载均衡
针对中间件部署的应用(war包),可使用nginx自带的反向代理以及轮询功能,实现应用的负载均衡. 一.架构图 二.环境准备 准备2套环境,如19.1.0.18:7001,19.1.0.16:7001 ...
- java基础篇—基础语法
一.关键字和保留字 1.什么是关键字? 通俗来说就是带有特殊含义的字符,有自己专门用途的单词 2.特点? 关键字全部由小写构成,以下是java官方列举出的关键字 注意: 保留关键字:指的是现有版 ...
- 广州2022CCPC补题
I Infection 知识点: 树上背包 第一次写树上背包的题目,没想到就是在区域赛中 神奇的是树上背包的复杂度,看起来是\(O(n^3)\),但是实际计算只有\(O(n^2)\) 学会树上背包后可 ...