Java面试核心知识点笔记
其中囊括了JVM、锁、并发、Java反射、Spring原理、微服务、Zookeeper、数据库、数据结构等大量知识点。
Java中高级面试高频考点整理
最后分享Java进阶学习及面试必备的视频教学
// 设置NameServer的地址
consumer.setNamesrvAddr(“localhost:9876”);
// 订阅一个或者多个Topic,以及Tag来过滤需要消费的消息
consumer.subscribe(“OneMoreTopic”, “*”);
// 注册回调实现类来处理从broker拉取回来的消息
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
System.out.printf(“%s %s Receive New Messages:%n”
, sdf.format(new Date())
, Thread.currentThread().getName());
for (MessageExt msg : msgs) {
System.out.printf(“\tMsg Id: %s%n”, msg.getMsgId());
System.out.printf(“\tBody: %s%n”, new String(msg.getBody()));
}
// 标记该消息已经被成功消费
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
// 启动消费者实例
consumer.start();
System.out.println(“Consumer Started.”);
}
}
再写一个延迟消息的生产者,用于发送延迟消息:
public class DelayProducer {
public static void main(String[] args) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat(“HH:mm:ss.SSS”);
// 实例化消息生产者Producer
DefaultMQProducer producer = new DefaultMQProducer(“OneMoreGroup”);
// 设置NameServer的地址
producer.setNamesrvAddr(“localhost:9876”);
// 启动Producer实例
producer.start();
Message msg = new Message(“OneMoreTopic”
, “DelayMessage”, “This is a delay message.”.getBytes());
//“1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”
//设置消息延迟级别为3,也就是延迟10s。
msg.setDelayTimeLevel(3);
// 发送消息到一个Broker
SendResult sendResult = producer.send(msg);
// 通过sendResult返回消息是否成功送达
System.out.printf(“%s Send Status: %s, Msg Id: %s %n”
, sdf.format(new Date())
, sendResult.getSendStatus()
, sendResult.getMsgId());
// 如果不再发送消息,关闭Producer实例。
producer.shutdown();
}
}
运行生产者以后,就会发送一条延迟消息:
10:37:14.992 Send Status: SEND_OK, Msg Id: C0A8006D5AB018B4AAC216E0DB690000
10秒钟后,消费者收到的这条延迟消息:
10:37:25.026 ConsumeMessageThread_1 Receive New Messages:
Msg Id: C0A8006D5AB018B4AAC216E0DB690000
Body: This is a delay message.
延迟消息的原理分析
以下分析的RocketMQ源码的版本号是4.7.1,版本不同源码略有差别。
CommitLog
在org.apache.rocketmq.store.CommitLog中,针对延迟消息做了一些处理:
// 延迟级别大于0,就是延时消息
if (msg.getDelayTimeLevel() > 0) {
// 判断当前延迟级别,如果大于最大延迟级别,
// 就设置当前延迟级别为最大延迟级别。
if (msg.getDelayTimeLevel() > this.defaultMessageStore
.getScheduleMessageService().getMaxDelayLevel()) {
msg.setDelayTimeLevel(this.defaultMessageStore
.getScheduleMessageService().getMaxDelayLevel());
}
// 获取延迟消息的主题,
// 其中RMQ_SYS_SCHEDULE_TOPIC的值为SCHEDULE_TOPIC_XXXX
topic = TopicValidator.RMQ_SYS_SCHEDULE_TOPIC;
// 根据延迟级别获取延迟消息的队列Id,
// 队列Id其实就是延迟级别减1
queueId = ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel());
// 备份真正的主题和队列Id
MessageAccessor.putProperty(msg
, MessageConst.PROPERTY_REAL_TOPIC, msg.getTopic());
MessageAccessor.putProperty(msg
, MessageConst.PROPERTY_REAL_QUEUE_ID, String.valueOf(msg.getQueueId()));
msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties()));
// 设置延时消息的主题和队列Id
msg.setTopic(topic);
msg.setQueueId(queueId);
}
可以看到,每一个延迟消息的主题都被暂时更改为SCHEDULE_TOPIC_XXXX,并且根据延迟级别延迟消息变更了新的队列Id。接下来,处理延迟消息的就是org.apache.rocketmq.store.schedule.ScheduleMessageService。
ScheduleMessageService
ScheduleMessageService是由org.apache.rocketmq.store.DefaultMessageStore进行初始化的,初始化包括构造对象和调用load
方法。最后,再执行ScheduleMessageService的start
方法:
public void start() {
// 使用AtomicBoolean确保start方法仅有效执行一次
if (started.compareAndSet(false, true)) {
this.timer = new Timer(“ScheduleMessageTimerThread”, true);
// 遍历所有延迟级别
for (Map.Entry<Integer, Long> entry : this.delayLevelTable.entrySet()) {
// key为延迟级别
Integer level = entry.getKey();
// value为延迟级别对应的毫秒数
Long timeDelay = entry.getValue();
// 根据延迟级别获得对应队列的偏移量
Long offset = this.offsetTable.get(level);
// 如果偏移量为null,则设置为0
if (null == offset) {
offset = 0L;
}
if (timeDelay != null) {
// 为每个延迟级别创建定时任务,
// 第一次启动任务延迟为FIRST_DELAY_TIME,也就是1秒
this.timer.schedule(
new DeliverDelayedMessageTimerTask(level, offset), FIRST_DELAY_TIME);
}
}
// 延迟10秒后每隔flushDelayOffsetInterval执行一次任务,
// 其中,flushDelayOffsetInterval默认配置也为10秒
this.timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
try {
// 持久化每个队列消费的偏移量
if (started.get()) ScheduleMessageService.this.persist();
} catch (Throwable e) {
log.error(“scheduleAtFixedRate flush exception”, e);
}
}
}, 10000, this.defaultMessageStore
.getMessageStoreConfig().getFlushDelayOffsetInterval());
}
}
遍历所有延迟级别,根据延迟级别获得对应队列的偏移量,如果偏移量不存在,则设置为0。然后为每个延迟级别创建定时任务,第一次启动任务延迟为1秒,第二次及以后的启动任务延迟才是延迟级别相应的延迟时间。
然后,又创建了一个定时任务,用于持久化每个队列消费的偏移量。持久化的频率由flushDelayOffsetInterval属性进行配置,默认为10秒。
定时任务
最后
这份清华大牛整理的进大厂必备的redis视频、面试题和技术文档
祝大家早日进入大厂,拿到满意的薪资和职级~~~加油!!
感谢大家的支持!!
定时任务
最后
这份清华大牛整理的进大厂必备的redis视频、面试题和技术文档
祝大家早日进入大厂,拿到满意的薪资和职级~~~加油!!
感谢大家的支持!!
[外链图片转存中…(img-XfBegU6g-1715005582973)]