use std::collections::BTreeMap;
use std::sync::Arc;
use bytes::Buf;
use bytes::Bytes;
use rocketmq_common::MessageDecoder::MESSAGE_MAGIC_CODE_POSITION;
use rocketmq_common::MessageDecoder::MESSAGE_MAGIC_CODE_V2;
use rocketmq_common::MessageDecoder::SYSFLAG_POSITION;
use tracing::info;
use crate::base::dispatch_request::DispatchRequest;
use crate::base::store_checkpoint::StoreCheckpoint;
use crate::config::message_store_config::MessageStoreConfig;
use crate::log_file::commit_log::check_message_and_return_size;
use crate::log_file::commit_log::BLANK_MAGIC_CODE;
use crate::log_file::commit_log::MESSAGE_MAGIC_CODE;
use crate::log_file::mapped_file::default_mapped_file_impl::DefaultMappedFile;
use crate::log_file::mapped_file::MappedFile;
const PARSE_BATCH_SIZE: usize = 64 * 1024; const MIN_MESSAGE_SIZE: usize = 4 + 4;
#[derive(Debug, Default, Clone)]
pub struct RecoveryStatistics {
pub files_processed: usize,
pub messages_recovered: u64,
pub bytes_processed: u64,
pub invalid_messages: u64,
pub recovery_time_ms: u128,
}
impl RecoveryStatistics {
pub fn log_summary(&self, recovery_type: &str) {
info!(
"{} recovery completed: {} files, {} messages, {:.2} MB, {} invalid, {}ms",
recovery_type,
self.files_processed,
self.messages_recovered,
self.bytes_processed as f64 / 1024.0 / 1024.0,
self.invalid_messages,
self.recovery_time_ms
);
}
}
pub struct BatchMessageIterator<'a> {
mapped_file: &'a Arc<DefaultMappedFile>,
current_offset: usize,
file_size: u64,
buffer: Bytes,
buffer_start_offset: usize,
}
impl<'a> BatchMessageIterator<'a> {
pub fn new(mapped_file: &'a Arc<DefaultMappedFile>) -> Self {
let file_size = mapped_file.get_file_size();
Self {
mapped_file,
current_offset: 0,
file_size,
buffer: Bytes::new(),
buffer_start_offset: 0,
}
}
fn refill_buffer(&mut self) -> bool {
if self.current_offset >= self.file_size as usize {
return false;
}
let remaining = self.file_size as usize - self.current_offset;
let fetch_size = remaining.min(PARSE_BATCH_SIZE);
if let Some(bytes) = self.mapped_file.get_bytes(self.current_offset, fetch_size) {
self.buffer = bytes;
self.buffer_start_offset = self.current_offset;
true
} else {
false
}
}
pub fn next_message(&mut self) -> Option<(Bytes, usize, usize)> {
loop {
if self.buffer.remaining() < MIN_MESSAGE_SIZE && !self.refill_buffer() {
return None;
}
let offset_in_buffer = self.current_offset - self.buffer_start_offset;
if self.buffer.remaining() < 4 {
return None;
}
let total_size = {
let mut temp = self.buffer.clone();
temp.get_i32()
};
if total_size <= 0 {
return None;
}
let msg_size = total_size as usize;
let absolute_offset = self.current_offset;
if self.buffer.remaining() < msg_size {
if msg_size > PARSE_BATCH_SIZE {
let msg_bytes = self.mapped_file.get_bytes(self.current_offset, msg_size)?;
self.current_offset += msg_size;
self.buffer = Bytes::new();
return Some((msg_bytes, absolute_offset, msg_size));
} else {
if !self.refill_buffer() {
return None;
}
continue;
}
}
let msg_bytes = self.buffer.copy_to_bytes(msg_size);
self.current_offset += msg_size;
return Some((msg_bytes, absolute_offset, msg_size));
}
}
pub fn current_offset(&self) -> usize {
self.current_offset
}
}
pub struct RecoveryContext {
pub check_crc: bool,
pub check_dup_info: bool,
pub message_store_config: Arc<MessageStoreConfig>,
pub max_delay_level: i32,
pub delay_level_table: BTreeMap<i32, i64>,
pub stats: RecoveryStatistics,
}
impl RecoveryContext {
pub fn new(
check_crc: bool,
check_dup_info: bool,
message_store_config: Arc<MessageStoreConfig>,
max_delay_level: i32,
delay_level_table: BTreeMap<i32, i64>,
) -> Self {
Self {
check_crc,
check_dup_info,
message_store_config,
max_delay_level,
delay_level_table,
stats: RecoveryStatistics::default(),
}
}
#[inline]
pub fn process_message(&mut self, msg_bytes: &mut Bytes, absolute_offset: usize) -> DispatchRequest {
let dispatch_request = check_message_and_return_size(
msg_bytes,
self.check_crc,
self.check_dup_info,
true,
&self.message_store_config,
self.max_delay_level,
&self.delay_level_table,
);
if dispatch_request.success {
if dispatch_request.msg_size > 0 {
self.stats.messages_recovered += 1;
self.stats.bytes_processed += dispatch_request.msg_size as u64;
}
} else {
self.stats.invalid_messages += 1;
}
dispatch_request
}
}
pub fn find_recovery_start_index(
mapped_files: &[Arc<DefaultMappedFile>],
message_store_config: &Arc<MessageStoreConfig>,
store_checkpoint: &StoreCheckpoint,
) -> usize {
let mut index = (mapped_files.len() as i32) - 1;
while index >= 0 {
let mapped_file = &mapped_files[index as usize];
if is_mapped_file_matched_recover(message_store_config, mapped_file, store_checkpoint) {
break;
}
index -= 1;
}
if index <= 0 {
0
} else {
index as usize
}
}
fn is_mapped_file_matched_recover(
message_store_config: &Arc<MessageStoreConfig>,
mapped_file: &Arc<DefaultMappedFile>,
store_checkpoint: &StoreCheckpoint,
) -> bool {
use std::mem;
use rocketmq_common::common::sys_flag::message_sys_flag::MessageSysFlag;
use rocketmq_common::UtilAll::time_millis_to_human_string;
let magic_code = mapped_file
.get_bytes(MESSAGE_MAGIC_CODE_POSITION, mem::size_of::<i32>())
.unwrap_or(Bytes::from([0u8; mem::size_of::<i32>()].as_ref()))
.get_i32();
if magic_code != MESSAGE_MAGIC_CODE && magic_code != MESSAGE_MAGIC_CODE_V2 {
return false;
}
let sys_flag = mapped_file
.get_bytes(SYSFLAG_POSITION, mem::size_of::<i32>())
.unwrap_or(Bytes::from([0u8; mem::size_of::<i32>()].as_ref()))
.get_i32();
let born_host_length = if sys_flag & MessageSysFlag::BORNHOST_V6_FLAG == 0 {
8
} else {
20
};
let msg_store_time_pos = 4 + 4 + 4 + 4 + 4 + 8 + 8 + 4 + 8 + born_host_length;
let store_timestamp = mapped_file
.get_bytes(msg_store_time_pos, mem::size_of::<i64>())
.unwrap_or(Bytes::from([0u8; mem::size_of::<i64>()].as_ref()))
.get_i64();
if store_timestamp == 0 {
return false;
}
if message_store_config.message_index_enable && message_store_config.message_index_safe {
if store_timestamp <= store_checkpoint.get_min_timestamp_index() as i64 {
info!(
"find check timestamp, {} {}",
store_timestamp,
time_millis_to_human_string(store_timestamp)
);
return true;
}
} else if store_timestamp <= store_checkpoint.get_min_timestamp() as i64 {
info!(
"find check timestamp, {} {}",
store_timestamp,
time_millis_to_human_string(store_timestamp)
);
return true;
}
false
}
#[inline]
pub fn is_blank_message(msg_bytes: &Bytes) -> bool {
if msg_bytes.len() < 8 {
return false;
}
let mut temp = msg_bytes.clone();
let _total_size = temp.get_i32();
let magic_code = temp.get_i32();
magic_code == BLANK_MAGIC_CODE
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_batch_iterator_empty() {
}
#[test]
fn test_recovery_statistics() {
let stats = RecoveryStatistics {
messages_recovered: 1000,
bytes_processed: 1024 * 1024,
files_processed: 5,
..Default::default()
};
assert_eq!(stats.messages_recovered, 1000);
assert_eq!(stats.bytes_processed, 1024 * 1024);
}
}