Skip to main content

sz_orm_core/
bloom.rs

1//! 布隆过滤器(公共实现)
2//!
3//! v4.7.0 架构债清零:合并 `cache_warmup_protection::BloomFilter`(自研)与
4//! `dist_cache::BloomFilterGuard`(bloomfilter crate)双实现为单一公共模块。
5//! 设计取舍:
6//!   - 并发安全:内部 `RwLock`,`add`/`might_contain` 均为 `&self`
7//!   - 容量拒绝:超过 `capacity` 的 `add` 返回 `CapacityExceeded`(不漏判语义:
8//!     拒绝写入而非静默丢位,避免"存在但查不到")
9//!   - 误判率:`might_contain` 假阳性 ≤ fpp,不存在一定返回 false
10//!
11//! 并发正确性:常规并发测试验证"add 后 might_contain 必命中"(不漏判不变量)。
12//! 注:loom 模型检查曾尝试引入,但 RUSTFLAGS=--cfg loom 会污染依赖树
13//! (crossbeam-queue → concurrent-queue 等无 loom 适配,2026-08-14 评估不可行),
14//! 并发验证采用常规多线程测试 + chaos/stress 组合。
15
16use std::sync::atomic::{AtomicUsize, Ordering};
17use std::sync::RwLock;
18
19/// 布隆过滤器错误
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum BloomError {
22    /// 超出容量(拒绝写入,保持不漏判)
23    CapacityExceeded {
24        /// 配置容量
25        capacity: usize,
26        /// 请求写入时的元素数(= 容量 + 1)
27        requested: usize,
28    },
29}
30
31/// 并发安全布隆过滤器
32///
33/// ```
34/// use sz_orm_core::bloom::BloomFilter;
35///
36/// let mut filter = BloomFilter::new(100, 0.01);
37/// filter.add("key-1").unwrap();
38/// assert!(filter.might_contain("key-1"));
39/// assert!(!filter.might_contain("key-2")); // 不存在一定返回 false(不漏判)
40/// ```
41pub struct BloomFilter {
42    bits: RwLock<Vec<u64>>,
43    num_bits: usize,
44    num_hashes: usize,
45    capacity: usize,
46    count: AtomicUsize,
47}
48
49impl BloomFilter {
50    /// 创建布隆过滤器
51    ///
52    /// `capacity` 预期元素数量,`fpp` 误判率(0~1,内部收敛到 0.0001~0.5)。
53    pub fn new(capacity: usize, fpp: f64) -> Self {
54        let capacity = capacity.max(1);
55        let fpp = fpp.clamp(0.0001, 0.5);
56        let ln2 = std::f64::consts::LN_2;
57        let m = (-(capacity as f64) * fpp.ln() / (ln2 * ln2)).ceil() as usize;
58        let m = m.max(8);
59        let k = ((m as f64 / capacity as f64) * ln2).ceil() as usize;
60        let k = k.max(1);
61        let num_words = m.div_ceil(64);
62        Self {
63            bits: RwLock::new(vec![0u64; num_words]),
64            num_bits: m,
65            num_hashes: k,
66            capacity,
67            count: AtomicUsize::new(0),
68        }
69    }
70
71    /// 添加键(容量满时返回 `CapacityExceeded`,拒绝写入保持不漏判)
72    pub fn add(&self, key: &str) -> Result<(), BloomError> {
73        let count = self.count.load(Ordering::Relaxed);
74        if count >= self.capacity {
75            return Err(BloomError::CapacityExceeded {
76                capacity: self.capacity,
77                requested: count + 1,
78            });
79        }
80        let (h1, h2) = self.hash(key);
81        let mut bits = self.bits.write().unwrap();
82        for i in 0..self.num_hashes {
83            let combined = h1.wrapping_add((i as u64).wrapping_mul(h2));
84            let idx = (combined as usize) % self.num_bits;
85            bits[idx / 64] |= 1u64 << (idx % 64);
86        }
87        self.count.fetch_add(1, Ordering::Relaxed);
88        Ok(())
89    }
90
91    /// 检查键可能存在(不漏判:不存在一定返回 false;假阳性 ≤ fpp)
92    pub fn might_contain(&self, key: &str) -> bool {
93        let (h1, h2) = self.hash(key);
94        let bits = self.bits.read().unwrap();
95        for i in 0..self.num_hashes {
96            let combined = h1.wrapping_add((i as u64).wrapping_mul(h2));
97            let idx = (combined as usize) % self.num_bits;
98            if bits[idx / 64] & (1u64 << (idx % 64)) == 0 {
99                return false;
100            }
101        }
102        true
103    }
104
105    /// 当前元素计数
106    pub fn count(&self) -> usize {
107        self.count.load(Ordering::Relaxed)
108    }
109
110    /// 是否为空
111    pub fn is_empty(&self) -> bool {
112        self.count() == 0
113    }
114
115    /// 容量
116    pub fn capacity(&self) -> usize {
117        self.capacity
118    }
119
120    /// 清空(保留容量/误判率配置)
121    pub fn clear(&self) {
122        let mut bits = self.bits.write().unwrap();
123        bits.iter_mut().for_each(|w| *w = 0);
124        self.count.store(0, Ordering::Relaxed);
125    }
126
127    /// 双哈希(xxhash 风格混合:基于 FNV-1a 的 h1 + 二次扰动 h2)
128    fn hash(&self, key: &str) -> (u64, u64) {
129        let mut h1: u64 = 0xcbf29ce484222325;
130        let mut h2: u64 = 0x9e3779b97f4a7c15;
131        for b in key.bytes() {
132            h1 ^= b as u64;
133            h1 = h1.wrapping_mul(0x100000001b3);
134            h2 = h2.wrapping_add(b as u64);
135            h2 = h2.wrapping_mul(0x100000001b3);
136        }
137        h2 ^= h2 >> 33;
138        h2 = h2.wrapping_mul(0xff51afd7ed558ccd);
139        (h1, h2)
140    }
141}
142
143// ============================================================================
144// 并发测试:并发 add 后 might_contain 必命中(不漏判的并发不变量)
145// ============================================================================
146#[cfg(test)]
147mod concurrent_tests {
148    use super::*;
149
150    /// 多线程并发写入不同 key,全部完成后 must_contain 必命中。
151    /// 验证写锁内原子完成(add 返回即可见)。
152    #[test]
153    fn bloom_concurrent_add_no_false_negative() {
154        use std::sync::Arc;
155        use std::thread;
156
157        let filter = Arc::new(BloomFilter::new(4096, 0.01));
158        let mut handles = vec![];
159        for t in 0..8 {
160            let f = Arc::clone(&filter);
161            handles.push(thread::spawn(move || {
162                for i in 0..200 {
163                    f.add(&format!("thread-{t}-key-{i}")).unwrap();
164                }
165            }));
166        }
167        for h in handles {
168            h.join().unwrap();
169        }
170        // 并发不变量:写入完成后必须命中(不存在才允许 false)
171        for t in 0..8 {
172            for i in 0..200 {
173                assert!(
174                    filter.might_contain(&format!("thread-{t}-key-{i}")),
175                    "add 后 must_contain 必须命中(并发不漏判): thread-{t}-key-{i}"
176                );
177            }
178        }
179        assert_eq!(filter.count(), 1600);
180    }
181
182    /// 并发读写混合:might_contain 不得 panic(RwLock 正确性冒烟)
183    #[test]
184    fn bloom_concurrent_read_write_smoke() {
185        use std::sync::Arc;
186        use std::thread;
187
188        let filter = Arc::new(BloomFilter::new(2048, 0.01));
189        let mut handles = vec![];
190        for t in 0..4 {
191            let f = Arc::clone(&filter);
192            handles.push(thread::spawn(move || {
193                for i in 0..100 {
194                    let key = format!("t{t}-k{i}");
195                    let _ = f.add(&key);
196                    let _ = f.might_contain(&key);
197                    let _ = f.count();
198                }
199            }));
200        }
201        for h in handles {
202            h.join().unwrap();
203        }
204        assert!(filter.count() > 0);
205    }
206}