1use std::collections::HashMap;
16use std::collections::HashSet;
17use std::sync::Arc;
18
19use cheetah_string::CheetahString;
20use rocketmq_model::common::message::message_queue::MessageQueue;
21use rocketmq_model::common::mix_all;
22use rocketmq_protocol::protocol::header::namesrv::topic_operation_header::TopicRequestHeader;
23use rocketmq_protocol::protocol::header::query_consumer_offset_request_header::QueryConsumerOffsetRequestHeader;
24use rocketmq_protocol::protocol::header::update_consumer_offset_header::UpdateConsumerOffsetRequestHeader;
25use rocketmq_transport::api::RpcRequestHeader;
26use tokio::sync::Mutex;
27use tracing::error;
28use tracing::info;
29use tracing::warn;
30
31use crate::consumer::store::controllable_offset::ControllableOffset;
32use crate::consumer::store::offset_store::OffsetStoreTrait;
33use crate::consumer::store::read_offset_type::ReadOffsetType;
34use crate::factory::mq_client_instance::MQClientInstance;
35
36pub struct RemoteBrokerOffsetStore {
37 client_instance: Arc<MQClientInstance>,
38 group_name: CheetahString,
39 offset_table: Arc<Mutex<HashMap<MessageQueue, ControllableOffset>>>,
40 persisted_offset_table: Arc<Mutex<HashMap<MessageQueue, i64>>>,
41}
42
43impl RemoteBrokerOffsetStore {
44 pub fn new(client_instance: Arc<MQClientInstance>, group_name: CheetahString) -> Self {
45 Self {
46 client_instance,
47 group_name,
48 offset_table: Arc::new(Mutex::new(HashMap::with_capacity(64))),
49 persisted_offset_table: Arc::new(Mutex::new(HashMap::with_capacity(64))),
50 }
51 }
52
53 async fn offset_needs_persist(&self, mq: &MessageQueue, offset: i64) -> bool {
54 let persisted_offset_table = self.persisted_offset_table.lock().await;
55 match persisted_offset_table.get(mq) {
56 Some(&persisted_offset) => persisted_offset != offset,
57 None => true,
58 }
59 }
60
61 async fn mark_offset_persisted(&self, mq: &MessageQueue, offset: i64) {
62 let mut persisted_offset_table = self.persisted_offset_table.lock().await;
63 persisted_offset_table.insert(mq.clone(), offset);
64 }
65
66 async fn fetch_consume_offset_from_broker(&self, mq: &MessageQueue) -> crate::ClientResult<i64> {
67 let broker_name = self.client_instance.get_broker_name_from_message_queue(mq).await;
68 let mut find_broker_result = self
69 .client_instance
70 .find_broker_address_in_subscribe(&broker_name, mix_all::MASTER_ID, true)
71 .await;
72
73 if find_broker_result.is_none() {
74 self.client_instance
75 .update_topic_route_info_from_name_server_topic(mq.topic())
76 .await;
77 let broker_name = self.client_instance.get_broker_name_from_message_queue(mq).await;
78 find_broker_result = self
79 .client_instance
80 .find_broker_address_in_subscribe(&broker_name, mix_all::MASTER_ID, false)
81 .await;
82 }
83 if let Some(find_broker_result) = find_broker_result {
84 let request_header = QueryConsumerOffsetRequestHeader {
85 consumer_group: self.group_name.clone(),
86 topic: mq.topic().clone(),
87 queue_id: mq.queue_id(),
88 set_zero_if_not_found: None,
89 topic_request_header: Some(TopicRequestHeader {
90 lo: None,
91 rpc: Some(RpcRequestHeader {
92 namespace: None,
93 namespaced: None,
94 broker_name: Some(mq.broker_name().clone()),
95 oneway: None,
96 }),
97 }),
98 };
99 let Some(mq_client_api_impl) = self.client_instance.mq_client_api_impl.load_full() else {
100 return Err(crate::ClientError::not_initialized("MQClientAPIImpl"));
101 };
102 mq_client_api_impl
103 .query_consumer_offset(find_broker_result.broker_addr.as_str(), request_header, 5_000)
104 .await
105 } else {
106 Err(mq_client_err!(format!("broker not found, {}", mq.broker_name())))
107 }
108 }
109}
110
111impl OffsetStoreTrait for RemoteBrokerOffsetStore {
112 async fn load(&self) -> crate::ClientResult<()> {
113 Ok(())
114 }
115
116 async fn update_offset(&self, mq: &MessageQueue, offset: i64, increase_only: bool) {
117 let mut offset_table = self.offset_table.lock().await;
118 let offset_old = offset_table
119 .entry(mq.clone())
120 .or_insert_with(|| ControllableOffset::new(offset));
121 if increase_only {
122 offset_old.update(offset, true);
123 } else {
124 offset_old.update_unconditionally(offset);
125 }
126 }
127
128 async fn update_and_freeze_offset(&self, mq: &MessageQueue, offset: i64) {
129 let mut offset_table = self.offset_table.lock().await;
130 offset_table
131 .entry(mq.clone())
132 .or_insert_with(|| ControllableOffset::new(offset))
133 .update_and_freeze(offset);
134 }
135
136 async fn read_offset(&self, mq: &MessageQueue, type_: ReadOffsetType) -> i64 {
137 match type_ {
138 ReadOffsetType::ReadFromMemory => {
139 let offset_table = self.offset_table.lock().await;
140 if let Some(offset) = offset_table.get(mq) {
141 return offset.get_offset();
142 }
143 -1
144 }
145 ReadOffsetType::MemoryFirstThenStore => {
146 let memory_offset = {
147 let offset_table = self.offset_table.lock().await;
148 offset_table.get(mq).map(ControllableOffset::get_offset)
149 };
150 if let Some(offset) = memory_offset {
151 return offset;
152 }
153 match self.fetch_consume_offset_from_broker(mq).await {
154 Ok(value) => {
155 self.update_offset(mq, value, false).await;
156 value
157 }
158 Err(error)
159 if error.broker_response_code()
160 == Some(rocketmq_protocol::code::response_code::ResponseCode::QueryNotFound as i32) =>
161 {
162 -1
163 }
164 Err(_) => {
165 warn!("fetchConsumeOffsetFromBroker exception: {:?}", mq);
166 -2
167 }
168 }
169 }
170 ReadOffsetType::ReadFromStore => match self.fetch_consume_offset_from_broker(mq).await {
171 Ok(value) => {
172 self.update_offset(mq, value, false).await;
173 value
174 }
175 Err(error)
176 if error.broker_response_code()
177 == Some(rocketmq_protocol::code::response_code::ResponseCode::QueryNotFound as i32) =>
178 {
179 -1
180 }
181 Err(_) => {
182 warn!("fetchConsumeOffsetFromBroker exception: {:?}", mq);
183 -2
184 }
185 },
186 }
187 }
188
189 async fn persist_all(&self, mqs: &HashSet<MessageQueue>) {
190 if mqs.is_empty() {
191 return;
192 }
193 let mut unused_mq = HashSet::new();
194 let mut used_mq = Vec::new();
195 let mut offset_table = self.offset_table.lock().await;
196 for (mq, offset) in offset_table.iter_mut() {
197 if mqs.contains(mq) {
198 let offset = offset.get_offset();
199 used_mq.push((mq.clone(), offset));
200 } else {
201 unused_mq.insert(mq.clone());
202 }
203 }
204 for mq in &unused_mq {
205 offset_table.remove(mq);
206 info!("remove unused mq, {}, {}", mq, self.group_name);
207 }
208 drop(offset_table);
209 if !unused_mq.is_empty() {
210 let mut persisted_offset_table = self.persisted_offset_table.lock().await;
211 for mq in &unused_mq {
212 persisted_offset_table.remove(mq);
213 }
214 }
215
216 for (mq, offset) in used_mq {
217 if !self.offset_needs_persist(&mq, offset).await {
218 continue;
219 }
220 match self.update_consume_offset_to_broker(&mq, offset, true).await {
221 Ok(_) => {
222 self.mark_offset_persisted(&mq, offset).await;
223 info!(
224 "[persistAll] Group: {} ClientId: {} updateConsumeOffsetToBroker {} {}",
225 self.group_name, self.client_instance.client_id, mq, offset
226 );
227 }
228 Err(e) => {
229 error!("updateConsumeOffsetToBroker exception, {},{}", mq, e);
230 }
231 }
232 }
233 }
234
235 async fn persist(&self, mq: &MessageQueue) {
236 let offset_table = self.offset_table.lock().await;
237 let Some(offset) = offset_table.get(mq).map(ControllableOffset::get_offset) else {
238 return;
239 };
240 drop(offset_table);
241 if !self.offset_needs_persist(mq, offset).await {
242 return;
243 }
244 match self.update_consume_offset_to_broker(mq, offset, true).await {
245 Ok(_) => {
246 self.mark_offset_persisted(mq, offset).await;
247 info!(
248 "[persist] Group: {} ClientId: {} updateConsumeOffsetToBroker {} {}",
249 self.group_name, self.client_instance.client_id, mq, offset
250 );
251 }
252 Err(e) => {
253 error!("updateConsumeOffsetToBroker exception, {},{}", mq, e);
254 }
255 }
256 }
257
258 async fn remove_offset(&self, mq: &MessageQueue) {
259 let offset_table_size = {
260 let mut offset_table = self.offset_table.lock().await;
261 offset_table.remove(mq);
262 offset_table.len()
263 };
264 let mut persisted_offset_table = self.persisted_offset_table.lock().await;
265 persisted_offset_table.remove(mq);
266 info!(
267 "remove unnecessary messageQueue offset. group={}, mq={}, offsetTableSize={}",
268 mq, self.group_name, offset_table_size
269 );
270 }
271
272 async fn clone_offset_table(&self, topic: &str) -> HashMap<MessageQueue, i64> {
273 let offset_table = self.offset_table.lock().await;
274 offset_table
275 .iter()
276 .filter(|(mq, _)| mq.topic_str() == topic)
277 .map(|(mq, offset)| (mq.clone(), offset.get_offset()))
278 .collect()
279 }
280
281 async fn update_consume_offset_to_broker(
282 &self,
283 mq: &MessageQueue,
284 offset: i64,
285 is_oneway: bool,
286 ) -> crate::ClientResult<()> {
287 let broker_name = self.client_instance.get_broker_name_from_message_queue(mq).await;
288 let mut find_broker_result = self
289 .client_instance
290 .find_broker_address_in_subscribe(&broker_name, mix_all::MASTER_ID, false)
291 .await;
292
293 if find_broker_result.is_none() {
294 self.client_instance
295 .update_topic_route_info_from_name_server_topic(mq.topic())
296 .await;
297 let broker_name = self.client_instance.get_broker_name_from_message_queue(mq).await;
298 find_broker_result = self
299 .client_instance
300 .find_broker_address_in_subscribe(&broker_name, mix_all::MASTER_ID, false)
301 .await;
302 }
303
304 if let Some(find_broker_result) = find_broker_result {
305 let request_header = UpdateConsumerOffsetRequestHeader {
306 consumer_group: self.group_name.clone(),
307 topic: mq.topic().clone(),
308 queue_id: mq.queue_id(),
309 commit_offset: offset,
310 topic_request_header: Some(TopicRequestHeader {
311 lo: None,
312 rpc: Some(RpcRequestHeader {
313 namespace: None,
314 namespaced: None,
315 broker_name: Some(mq.broker_name().clone()),
316 oneway: None,
317 }),
318 }),
319 };
320 let Some(mq_client_api_impl) = self.client_instance.mq_client_api_impl.load_full() else {
321 return Err(crate::ClientError::not_initialized("MQClientAPIImpl"));
322 };
323 if is_oneway {
324 mq_client_api_impl
325 .update_consumer_offset_oneway(find_broker_result.broker_addr.as_str(), request_header, 5_000)
326 .await?;
327 } else {
328 mq_client_api_impl
329 .update_consumer_offset(&find_broker_result.broker_addr, request_header, 5_000)
330 .await?;
331 };
332 Ok(())
333 } else {
334 Err(mq_client_err!(format!("broker not found, {}", mq.broker_name())))
335 }
336 }
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342 use crate::base::client_config::ClientConfig;
343
344 fn new_store() -> RemoteBrokerOffsetStore {
345 let runtime = crate::runtime::test_client_runtime("remote-broker-offset-store-test");
346 let client_instance = MQClientInstance::new_arc(
347 ClientConfig::default(),
348 0,
349 "remote-offset-coalescing-test",
350 None,
351 runtime.component("instance"),
352 runtime.telemetry_handle().clone(),
353 runtime.pool().request_future_holder(),
354 );
355 RemoteBrokerOffsetStore::new(client_instance, CheetahString::from_static_str("group-a"))
356 }
357
358 fn message_queue() -> MessageQueue {
359 MessageQueue::from_parts("topic-a", "broker-a", 0)
360 }
361
362 #[tokio::test]
363 async fn persisted_offset_coalescing_skips_only_identical_offsets() {
364 let store = new_store();
365 let mq = message_queue();
366
367 assert!(store.offset_needs_persist(&mq, 10).await);
368
369 store.mark_offset_persisted(&mq, 10).await;
370
371 assert!(!store.offset_needs_persist(&mq, 10).await);
372 assert!(store.offset_needs_persist(&mq, 9).await);
373 assert!(store.offset_needs_persist(&mq, 11).await);
374 }
375
376 #[tokio::test]
377 async fn remove_offset_clears_persisted_coalescing_state() {
378 let store = new_store();
379 let mq = message_queue();
380
381 store.mark_offset_persisted(&mq, 10).await;
382 assert!(!store.offset_needs_persist(&mq, 10).await);
383
384 store.remove_offset(&mq).await;
385
386 assert!(store.offset_needs_persist(&mq, 10).await);
387 }
388
389 #[tokio::test]
390 async fn read_from_memory_distinguishes_absent_queue_from_stored_zero_offset() {
391 let store = new_store();
392 let absent_mq = message_queue();
393 let zero_mq = MessageQueue::from_parts("topic-a", "broker-a", 1);
394
395 assert_eq!(store.read_offset(&absent_mq, ReadOffsetType::ReadFromMemory).await, -1);
396
397 store.update_offset(&zero_mq, 0, false).await;
398 assert_eq!(store.read_offset(&zero_mq, ReadOffsetType::ReadFromMemory).await, 0);
399 }
400
401 #[tokio::test]
402 async fn memory_first_then_store_returns_cached_value_without_network_fallback() {
403 let store = new_store();
404 let mq = message_queue();
405 store.update_offset(&mq, 42, false).await;
406
407 let offset = store.read_offset(&mq, ReadOffsetType::MemoryFirstThenStore).await;
408
409 assert_eq!(offset, 42);
410 }
411
412 #[tokio::test]
413 async fn clone_offset_table_filters_by_exact_topic_with_overlapping_broker_and_queue_ids() {
414 let store = new_store();
415 let topic_a_q0 = MessageQueue::from_parts("topic-a", "broker-a", 0);
416 let topic_b_q0 = MessageQueue::from_parts("topic-b", "broker-a", 0);
417
418 store.update_offset(&topic_a_q0, 5, false).await;
419 store.update_offset(&topic_b_q0, 9, false).await;
420
421 let table = store.clone_offset_table("topic-a").await;
422
423 assert_eq!(table.len(), 1);
424 assert_eq!(table.get(&topic_a_q0), Some(&5));
425
426 let missing = store.clone_offset_table("topic-missing").await;
427 assert!(missing.is_empty());
428 }
429
430 #[tokio::test]
431 async fn clone_offset_table_snapshot_is_isolated_from_later_store_mutations() {
432 let store = new_store();
433 let mq = message_queue();
434 store.update_offset(&mq, 1, false).await;
435
436 let mut snapshot = store.clone_offset_table("topic-a").await;
437 snapshot.insert(mq.clone(), 999);
438 assert_eq!(store.read_offset(&mq, ReadOffsetType::ReadFromMemory).await, 1);
439
440 let snapshot_before_mutation = store.clone_offset_table("topic-a").await;
441 store.update_offset(&mq, 2, false).await;
442 store.remove_offset(&mq).await;
443
444 assert_eq!(snapshot_before_mutation.get(&mq), Some(&1));
445 }
446}