rocketmq_client_rust/latency/
mq_fault_strategy.rs1use std::sync::atomic::AtomicBool;
16use std::sync::atomic::Ordering;
17use std::sync::Arc;
18
19use cheetah_string::CheetahString;
20use rocketmq_model::common::message::message_queue::MessageQueue;
21use rocketmq_runtime::ChildServiceContext;
22
23use crate::base::client_config::ClientConfig;
24use crate::latency::latency_fault_tolerance::LatencyFaultTolerance;
25#[cfg(any(test, feature = "test-support"))]
26use crate::latency::latency_fault_tolerance_impl::FaultClock;
27use crate::latency::latency_fault_tolerance_impl::LatencyFaultToleranceImpl;
28use crate::producer::producer_impl::default_mq_producer_impl::DefaultResolver;
29use crate::producer::producer_impl::default_mq_producer_impl::DefaultServiceDetector;
30use crate::producer::producer_impl::queue_filter::QueueFilter;
31use crate::producer::producer_impl::topic_publish_info::TopicPublishInfo;
32
33#[derive(Clone)]
34pub struct MQFaultStrategy {
35 latency_fault_tolerance: Arc<LatencyFaultToleranceImpl<DefaultResolver, DefaultServiceDetector>>,
36 send_latency_fault_enable: Arc<AtomicBool>,
37 start_detector_enable: Arc<AtomicBool>,
38 latency_max: Vec<u64>,
39 not_available_duration: Vec<u64>,
40}
41
42impl MQFaultStrategy {
43 pub const DEFAULT_LATENCY_MAX: [u64; 7] = [50, 100, 550, 1800, 3000, 5000, 15000];
44 pub const DEFAULT_NOT_AVAILABLE_DURATION: [u64; 7] = [0, 0, 2000, 5000, 6000, 10000, 30000];
45 const ISOLATION_LATENCY_MS: u64 = 10_000;
47
48 pub fn new(service_context: ChildServiceContext, client_config: &ClientConfig) -> Self {
49 let tolerance_impl = LatencyFaultToleranceImpl::new(service_context);
50 Self::from_tolerance_impl(tolerance_impl, client_config)
51 }
52
53 #[cfg(any(test, feature = "test-support"))]
54 pub(crate) fn new_with_fault_clock(
55 service_context: ChildServiceContext,
56 client_config: &ClientConfig,
57 clock: Arc<dyn FaultClock>,
58 ) -> Self {
59 let tolerance_impl = LatencyFaultToleranceImpl::new_with_clock(service_context, clock);
60 Self::from_tolerance_impl(tolerance_impl, client_config)
61 }
62
63 fn from_tolerance_impl(
64 tolerance_impl: LatencyFaultToleranceImpl<DefaultResolver, DefaultServiceDetector>,
65 client_config: &ClientConfig,
66 ) -> Self {
67 tolerance_impl.set_detect_interval(client_config.detect_interval);
68 tolerance_impl.set_detect_timeout(client_config.detect_timeout);
69 tolerance_impl.set_start_detector_enable(client_config.start_detector_enable);
70 let latency_fault_tolerance = Arc::new(tolerance_impl);
71 Self {
72 latency_fault_tolerance: Arc::clone(&latency_fault_tolerance),
73 send_latency_fault_enable: Arc::new(AtomicBool::new(client_config.send_latency_enable)),
74 start_detector_enable: Arc::new(AtomicBool::new(client_config.start_detector_enable)),
75 latency_max: Self::DEFAULT_LATENCY_MAX.to_vec(),
76 not_available_duration: Self::DEFAULT_NOT_AVAILABLE_DURATION.to_vec(),
77 }
78 }
79
80 pub fn start_detector(&self) {
81 LatencyFaultTolerance::start_detector(self.latency_fault_tolerance.clone());
82 }
83
84 pub(crate) fn set_resolve(&self, resolver: DefaultResolver) {
85 self.latency_fault_tolerance.set_resolver(resolver);
86 }
87
88 pub(crate) fn set_service_detector(&self, service_detector: DefaultServiceDetector) {
89 self.latency_fault_tolerance.set_service_detector(service_detector);
90 }
91
92 pub fn shutdown(&self) {
93 self.latency_fault_tolerance.shutdown();
94 }
95
96 pub async fn shutdown_async(&self) -> bool {
97 self.latency_fault_tolerance.shutdown_detector().await
98 }
99
100 pub fn is_start_detector_enable(&self) -> bool {
101 self.start_detector_enable.load(Ordering::Relaxed)
102 }
103
104 pub fn set_start_detector_enable(&self, start_detector_enable: bool) {
105 self.start_detector_enable
106 .store(start_detector_enable, Ordering::Relaxed);
107 self.latency_fault_tolerance
108 .set_start_detector_enable(start_detector_enable);
109 }
110
111 pub fn is_send_latency_fault_enable(&self) -> bool {
112 self.send_latency_fault_enable.load(Ordering::Relaxed)
113 }
114
115 pub fn select_one_message_queue(
116 &self,
117 tp_info: &TopicPublishInfo,
118 last_broker_name: Option<&CheetahString>,
119 reset_index: bool,
120 ) -> Option<MessageQueue> {
121 let broker_filter = BrokerFilter::with_last_broker_name(last_broker_name.cloned());
122
123 if self.send_latency_fault_enable.load(Ordering::Relaxed) {
124 if reset_index {
125 tp_info.reset_index();
126 }
127
128 let available_filter = AvailableFilter {
129 latency_fault_tolerance: Arc::clone(&self.latency_fault_tolerance),
130 };
131 let filter = &[
132 &available_filter as &dyn QueueFilter,
133 &broker_filter as &dyn QueueFilter,
134 ];
135 if let Some(mq) = tp_info.select_one_message_queue_filters(filter) {
136 return Some(mq);
137 }
138
139 let reachable_filter = ReachableFilter {
140 latency_fault_tolerance: Arc::clone(&self.latency_fault_tolerance),
141 };
142 let filter = &[
143 &reachable_filter as &dyn QueueFilter,
144 &broker_filter as &dyn QueueFilter,
145 ];
146 if let Some(mq) = tp_info.select_one_message_queue_filters(filter) {
147 return Some(mq);
148 }
149
150 return tp_info.select_one_message_queue_filters(&[]);
151 }
152
153 if let Some(mq) = tp_info.select_one_message_queue_filters(&[&broker_filter]) {
154 return Some(mq);
155 }
156 tp_info.select_one_message_queue_filters(&[])
157 }
158
159 pub fn get_latency_max(&self) -> &[u64] {
160 &self.latency_max
161 }
162
163 pub fn set_latency_max(&mut self, latency_max: impl Into<Vec<u64>>) {
164 self.latency_max = latency_max.into();
165 }
166
167 pub fn get_not_available_duration(&self) -> &[u64] {
168 &self.not_available_duration
169 }
170
171 pub fn set_not_available_duration(&mut self, not_available_duration: impl Into<Vec<u64>>) {
172 self.not_available_duration = not_available_duration.into();
173 }
174
175 pub async fn update_fault_item(
176 &self,
177 broker_name: CheetahString,
178 current_latency: u64,
179 isolation: bool,
180 reachable: bool,
181 ) {
182 if self.send_latency_fault_enable.load(Ordering::Relaxed) {
183 let effective_latency = if isolation {
184 Self::ISOLATION_LATENCY_MS
185 } else {
186 current_latency
187 };
188 let duration = self.compute_not_available_duration(effective_latency);
189 self.latency_fault_tolerance
190 .update_fault_item(broker_name, current_latency, duration, reachable)
191 .await;
192 }
193 }
194
195 fn compute_not_available_duration(&self, current_latency: u64) -> u64 {
196 let len = self.latency_max.len().min(self.not_available_duration.len());
197 for i in (0..len).rev() {
198 if current_latency >= self.latency_max[i] {
199 return self.not_available_duration[i];
200 }
201 }
202 0
203 }
204
205 #[cfg(any(test, feature = "test-support"))]
206 pub(crate) fn not_available_duration_for_test(&self, current_latency: u64, isolation: bool) -> u64 {
207 self.compute_not_available_duration(if isolation {
208 Self::ISOLATION_LATENCY_MS
209 } else {
210 current_latency
211 })
212 }
213
214 #[cfg(any(test, feature = "test-support"))]
215 pub(crate) fn fault_item_state_for_test(&self, broker_name: &CheetahString) -> Option<(bool, bool, u64, u64)> {
216 self.latency_fault_tolerance.fault_item_state_for_test(broker_name)
217 }
218
219 #[inline]
220 pub fn set_send_latency_fault_enable(&self, send_latency_fault_enable: bool) {
221 self.send_latency_fault_enable
222 .store(send_latency_fault_enable, Ordering::Relaxed);
223 }
224}
225
226#[derive(Clone, Debug, Default, Eq, PartialEq)]
227pub struct BrokerFilter {
228 last_broker_name: Option<CheetahString>,
229}
230
231impl BrokerFilter {
232 pub fn new() -> Self {
233 Self::default()
234 }
235
236 pub fn with_last_broker_name(last_broker_name: Option<CheetahString>) -> Self {
237 Self { last_broker_name }
238 }
239
240 pub fn last_broker_name(&self) -> Option<&CheetahString> {
241 self.last_broker_name.as_ref()
242 }
243
244 pub fn get_last_broker_name(&self) -> Option<&CheetahString> {
245 self.last_broker_name()
246 }
247
248 pub fn set_last_broker_name(&mut self, last_broker_name: impl Into<CheetahString>) {
249 self.last_broker_name = Some(last_broker_name.into());
250 }
251
252 pub fn clear_last_broker_name(&mut self) {
253 self.last_broker_name = None;
254 }
255}
256
257impl QueueFilter for BrokerFilter {
258 fn filter(&self, message_queue: &MessageQueue) -> bool {
259 if let Some(last_broker_name) = &self.last_broker_name {
260 message_queue.broker_name() != last_broker_name
261 } else {
262 true
263 }
264 }
265}
266
267struct ReachableFilter {
268 latency_fault_tolerance: Arc<LatencyFaultToleranceImpl<DefaultResolver, DefaultServiceDetector>>,
269}
270
271impl QueueFilter for ReachableFilter {
272 fn filter(&self, message_queue: &MessageQueue) -> bool {
273 self.latency_fault_tolerance.is_reachable(message_queue.broker_name())
274 }
275}
276
277struct AvailableFilter {
278 latency_fault_tolerance: Arc<LatencyFaultToleranceImpl<DefaultResolver, DefaultServiceDetector>>,
279}
280
281impl QueueFilter for AvailableFilter {
282 fn filter(&self, message_queue: &MessageQueue) -> bool {
283 self.latency_fault_tolerance.is_available(message_queue.broker_name())
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290
291 #[test]
292 fn broker_filter_matches_java_last_broker_name_rule() {
293 let mq = MessageQueue::from_parts("topic", "broker-a", 0);
294 let mut filter = BrokerFilter::new();
295
296 assert!(filter.filter(&mq));
297 assert!(filter.get_last_broker_name().is_none());
298
299 filter.set_last_broker_name("broker-a");
300 assert_eq!(
301 filter.get_last_broker_name(),
302 Some(&CheetahString::from_static_str("broker-a"))
303 );
304 assert!(!filter.filter(&mq));
305
306 filter.set_last_broker_name("broker-b");
307 assert!(filter.filter(&mq));
308
309 filter.clear_last_broker_name();
310 assert!(filter.filter(&mq));
311 }
312
313 #[test]
314 fn mq_fault_strategy_copies_detector_config_like_java_constructor() {
315 let mut client_config = ClientConfig::default();
316 client_config.set_detect_interval(12_345);
317 client_config.set_detect_timeout(321);
318 client_config.set_start_detector_enable(true);
319
320 let strategy = MQFaultStrategy::new(
321 crate::runtime::test_service_context("mq-fault-strategy-test"),
322 &client_config,
323 );
324 let (detect_interval, detect_timeout) = strategy.latency_fault_tolerance.detector_config_for_test();
325
326 assert_eq!(detect_interval, 12_345);
327 assert_eq!(detect_timeout, 321);
328 assert!(strategy.is_start_detector_enable());
329 assert!(strategy.latency_fault_tolerance.is_start_detector_enable());
330 }
331
332 #[test]
333 fn cloned_strategy_shares_runtime_switches_and_snapshots_thresholds() {
334 let mut strategy = MQFaultStrategy::new(
335 crate::runtime::test_service_context("mq-fault-strategy-clone-test"),
336 &ClientConfig::default(),
337 );
338 strategy.set_latency_max(vec![10, 20, 30]);
339 strategy.set_not_available_duration(vec![0, 100, 200]);
340 strategy.set_send_latency_fault_enable(true);
341 strategy.set_start_detector_enable(true);
342
343 let snapshot = strategy.clone();
344
345 strategy.set_latency_max(vec![40, 50, 60]);
346 strategy.set_not_available_duration(vec![300, 400, 500]);
347 strategy.set_send_latency_fault_enable(false);
348 strategy.set_start_detector_enable(false);
349
350 assert_eq!(snapshot.get_latency_max(), &[10, 20, 30]);
351 assert_eq!(snapshot.get_not_available_duration(), &[0, 100, 200]);
352 assert!(!snapshot.is_send_latency_fault_enable());
353 assert!(!snapshot.is_start_detector_enable());
354 }
355}