欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺析python實(shí)現(xiàn)布隆過濾器及Redis中的緩存穿透原理

 更新時(shí)間:2021年09月14日 11:07:40   作者:somenzz  
本文帶你了解了位圖的實(shí)現(xiàn),布隆過濾器的原理及 Python 中的使用,以及布隆過濾器如何應(yīng)對(duì) Redis 中的緩存穿透,相信你對(duì)布隆過濾器已經(jīng)有了一定的認(rèn)識(shí)

在開發(fā)軟件時(shí),我們經(jīng)常需要判斷一個(gè)元素是否在一個(gè)集合中,比如,如何判斷單詞的拼寫是否錯(cuò)誤(判斷單詞是否在已知的字典中);在網(wǎng)絡(luò)爬蟲里,如何確認(rèn)一個(gè)網(wǎng)址是否已經(jīng)爬取過;反垃圾郵件系統(tǒng)中,如何判斷一個(gè)郵件地址是否為垃圾郵件地址等等。

如果這些作為面試題那就很有區(qū)分度了,初級(jí)工程師就會(huì)說,把全部的元素都存在 hash 表中,當(dāng)需要判斷元素是否在集合中時(shí),可以直接判斷,時(shí)間復(fù)雜度是 O(1),比如 Python 的集合:

>>> all_elements = {'a','b','c','d','e'}
>>> 'a' in all_elements
True
>>> 'f' in all_elements
False
>>>

這樣回答顯然沒有考慮到數(shù)量級(jí)的問題,就拿爬蟲中的 URL 去重來說,假設(shè)一個(gè) URL 的平均長(zhǎng)度是 64 字節(jié),那單純存儲(chǔ)這 10 億個(gè) URL,需要大約 60GB 的內(nèi)存空間。因?yàn)楣1肀仨毦S持較小的裝載因子,才能保證不會(huì)出現(xiàn)過多的哈希沖突,導(dǎo)致操作的性能下降。而且,用鏈表法解決沖突的哈希表,還會(huì)存儲(chǔ)鏈表指針,因此哈希表的存儲(chǔ)效率一般只有 50%。所以,如果將這 10 億個(gè) URL 構(gòu)建成哈希表,那需要的內(nèi)存空間會(huì)超過 100GB。這樣的話一般的機(jī)器,內(nèi)存就不夠用了,更別提哈希查找了。

因此考慮到這一點(diǎn),中級(jí)一點(diǎn)的工程師會(huì)說數(shù)據(jù)量大的時(shí)候可以采用分治的思想,用多臺(tái)機(jī)器(比如 20 臺(tái)內(nèi)存是 8GB 的機(jī)器)來存儲(chǔ)這 10 億網(wǎng)頁(yè)鏈接。這種分治的處理思路,也是一種解決辦法,本質(zhì)上還是增加硬件資源來解決問題,還不夠高級(jí)。

更高級(jí)的工程師會(huì)提到位圖、布隆過濾器,可以使用很小的內(nèi)存來實(shí)現(xiàn)大數(shù)據(jù)量的查詢。如果你提到了這兩個(gè)概念,那離 offer 也就不遠(yuǎn)了。想回答好這個(gè)問題,看本文就夠了,保證你搞懂布隆過濾器,要是搞不懂,你加我微信,我會(huì)對(duì)你負(fù)責(zé)的????。

在講布隆過濾器前,要先講一下另一種存儲(chǔ)結(jié)構(gòu),位圖(BitMap)。因?yàn)?,布隆過濾器本身就是基于位圖的,是對(duì)位圖的一種改進(jìn)。

同樣地,為了方便你理解,我們來簡(jiǎn)化問題,現(xiàn)在有 1 千萬個(gè)整數(shù),整數(shù)的范圍在 1 到 1 億之間。如何快速查找某個(gè)整數(shù)是否在這 1 千萬個(gè)整數(shù)中呢?

當(dāng)然,這個(gè)問題還是可以用哈希表來解決。不過,我們可以使用一種比較“特殊”的哈希表,那就是位圖。我們申請(qǐng)一個(gè)大小為 1 億、數(shù)據(jù)類型為布爾類型(true 或者 false)的數(shù)組。我們將這 1 千萬個(gè)整數(shù)作為數(shù)組下標(biāo),將對(duì)應(yīng)的數(shù)組值設(shè)置成 true。比如,整數(shù) 5 對(duì)應(yīng)下標(biāo)為 5 的數(shù)組值設(shè)置為 true,也就是 array[5]=true 。

當(dāng)我們查詢某個(gè)整數(shù) K 是否在這 1 千萬個(gè)整數(shù)中的時(shí)候,我們只需要將對(duì)應(yīng)的數(shù)組值 array[K]取出來,看是否等于 true。如果等于 true,那說明 1 千萬整數(shù)中包含這個(gè)整數(shù) K;相反,就表示不包含這個(gè)整數(shù) K。不過,很多語言中提供的布爾類型,大小是 1 個(gè)字節(jié)的,并不能節(jié)省太多內(nèi)存空間。實(shí)際上,表示 true 和 false 兩個(gè)值,我們只需要用一個(gè)二進(jìn)制位(bit)就可以了。那如何通過編程語言,來表示一個(gè)二進(jìn)制位呢?

這個(gè)用語言很難表達(dá),還是給出代碼吧,一碼勝千言啊。

這里先給出 Java 的,我覺得 Java 的更容易看懂,再給出 Python 的,你可以對(duì)比著代碼看下,應(yīng)該很快就理解了。

public class BitMap { // Java中char類型占16bit,也即是2個(gè)字節(jié)
  private char[] bytes;
  private int nbits;  
  public BitMap(int nbits) {
    this.nbits = nbits;
    this.bytes = new char[nbits/16+1];
  } 
  public void set(int k) {
    if (k > nbits) return;
    int byteIndex = k / 16;
    int bitIndex = k % 16;
    bytes[byteIndex] |= (1 << bitIndex);
  } 
  public boolean get(int k) {
    if (k > nbits) return false;
    int byteIndex = k / 16;
    int bitIndex = k % 16;
    return (bytes[byteIndex] & (1 << bitIndex)) != 0;
  }
}

Python 的實(shí)現(xiàn):

class BitMap(object):
    def __init__(self, max_value):
        """
        使用多個(gè)整型元素來儲(chǔ)存數(shù)據(jù),每個(gè)元素4個(gè)字節(jié)(32位)
        """
        self._size = int((max_value + 31 - 1) / 31)  # 計(jì)算需要的字節(jié)數(shù),字節(jié)數(shù)也是數(shù)組的大小
        self.array = [0 for i in range(self._size)]  # 數(shù)組的元素都初始化為0,每個(gè)元素有32位
    @staticmethod
    def get_element_index(num):
        """
        獲取該數(shù)即將儲(chǔ)存的字節(jié)在數(shù)組中下標(biāo)
        """
        return num // 31 
    @staticmethod
    def get_bit_index(num):
        """
        獲取該數(shù)在元素中的位下標(biāo)
        """
        return num % 31
 
    def set(self, num):
        """
        將該數(shù)存在對(duì)應(yīng)的元素的對(duì)應(yīng)位置
        """
        element_index = self.get_element_index(num)
        bit_index = self.get_bit_index(num)
        self.array[element_index] = self.array[element_index] | (1 << bit_index)
    def get(self, num):
        """
        查找該數(shù)是否存在與bitmap中
        """
        element_index = self.get_element_index(num)
        bit_index = self.get_bit_index(num)
        if self.array[element_index] & (1 << bit_index):
            return True
        return False

從上面位圖實(shí)現(xiàn)的代碼中,你應(yīng)該可以發(fā)現(xiàn),位圖通過數(shù)組下標(biāo)來定位數(shù)據(jù),所以,訪問效率非常高。而且,每個(gè)數(shù)字用一個(gè)二進(jìn)制位來表示,在數(shù)字范圍不大的情況下,所需要的內(nèi)存空間非常小。

比如剛剛那個(gè)例子,如果用哈希表存儲(chǔ)這 1 千萬的數(shù)據(jù),數(shù)據(jù)是 32 位的整型數(shù),每個(gè)整數(shù) 4 個(gè)字節(jié),那總共至少需要 1 千萬 * 4  = 40MB 的存儲(chǔ)空間。如果我們通過位圖的話,數(shù)字范圍在 1 到 1 億之間,只需要 1 億個(gè)二進(jìn)制位,也就是 12MB 左右的存儲(chǔ)空間就夠了。

位圖就是用一個(gè)二進(jìn)制位的 1 來代表一個(gè)元素存在,是不是挺簡(jiǎn)單的?不過,這里我們有個(gè)假設(shè),就是數(shù)字所在的范圍不是很大。如果數(shù)字的范圍很大,比如剛剛那個(gè)問題,數(shù)字范圍不是 1 到 1 億,而是 1 到 10 億,那位圖的大小就需要 10 億個(gè)二進(jìn)制位,也就是 120MB 的大小,消耗的內(nèi)存空間,增加了 10 倍,如何不增加內(nèi)存空間來解決問題呢?請(qǐng)繼續(xù)往下看。

布隆過濾器的原理

這個(gè)時(shí)候,布隆過濾器就要出場(chǎng)了。布隆過濾器就是為了解決剛剛這個(gè)問題,對(duì)位圖這種數(shù)據(jù)結(jié)構(gòu)的一種改進(jìn)。

布隆過濾器是由伯頓·布隆于 1970 年提出的,為了簡(jiǎn)化說明布隆過濾器的原理,我們降低數(shù)據(jù)量級(jí):假如數(shù)字范圍是從 1 到 100 提升到 200,為了不占用太多內(nèi)存,我們依然使用 100 個(gè)二進(jìn)制位,如果數(shù)據(jù)個(gè)數(shù)超過 100 個(gè),就必然存在哈希沖突,怎么辦?

因?yàn)槲覀兪褂?1 位代表一個(gè)元素,因此 100 個(gè)二進(jìn)制位,最多代表 100 個(gè)元素,但是假如使用 2 位來代表一個(gè)元素呢?那就是組合 C(100,2) = 100*99/2 = 4950 個(gè)數(shù),是不是可以代表更多?當(dāng)然了,你還可以使用 3 位代表一個(gè)元素,這樣可以代表 161700 個(gè)數(shù)。

我們以使用 2 位二進(jìn)制位來代表一個(gè)元素為例,設(shè)計(jì)兩個(gè) HASH 函數(shù),bit1 = HASH1(num),bit2 = HASH2(num),存入 num 時(shí)就把 bit1 和 bit2 都置為 1;判斷時(shí)就判斷 bit1 和 bit2,當(dāng) bit1 和 bit2 都為 1 時(shí),就表示 num 存在集合中。

這樣會(huì)有個(gè)問題:兩個(gè)數(shù) num1 和 num2 經(jīng)過兩個(gè) HASH 函數(shù)之后,結(jié)果一樣,也就是存在 HASH 沖突,這樣就可能誤判。

實(shí)際上,只要讓誤判的概率足夠低,結(jié)果就是可信的。假設(shè)哈希函數(shù)的個(gè)數(shù)為 k,二進(jìn)制的位數(shù)為 m,元素個(gè)數(shù) n,可以從數(shù)學(xué)上計(jì)算出他們與誤判率的關(guān)系[1](原麥迪遜威斯康星大學(xué)曹培教授提供):

可以看出,當(dāng) m/n = 16,n = 8,時(shí),誤判率為萬分之五,這在大多數(shù)應(yīng)用中都是可以接受的,而且這種誤判是這樣的:如果這個(gè)元素在集合中,那么布隆過濾器絕不會(huì)漏掉,如果不在集合中,則有可能判定為在集合中,比如說對(duì)應(yīng)垃圾郵件,布隆過濾器絕不會(huì)漏掉黑名單中的任何一個(gè)可疑地址,但是它有一定極小的概率將一個(gè)不在黑名單上的電子郵件判定為在黑名單中。

到這里我相信你已經(jīng)明白了個(gè)中原理。

在 Python 中使用布隆過濾器

pypi 搜了了 Python 中的布隆過濾器,有 3 個(gè):

pip install bloom-filter2
pip install pybloom-live
pip install bloompy

第三個(gè) bloompy[2] 的文檔比較詳細(xì),推薦使用(如果有興趣,你可以自己實(shí)現(xiàn)一個(gè)):

bloompy 提供了四種布隆過濾器:

1、標(biāo)準(zhǔn)布隆過濾器。

標(biāo)準(zhǔn)布隆過濾器只能進(jìn)行數(shù)據(jù)的查詢和插入,是其他過濾器的基類,可以進(jìn)行過濾器的存儲(chǔ)和恢復(fù),代碼示例:

>>> import bloompy
>>> bf = bloompy.BloomFilter(error_rate=0.001,element_num=10**3)
# 查詢?cè)厥欠裨谶^濾器里返回狀態(tài)標(biāo)識(shí)
# 如果不在里面則插入,返回False表示元素不在過濾器里
>>> bf.add(1) 
False
>>> bf.add(1)
True
>>> 1 in bf
True
>>> bf.exists(1)
True
>>> bf.add([1,2,3])
False
>>> bf.add([1,2,3])
True
>>> [1,2,3] in bf
True
>>> bf.exists([1,2,3])
True
# 將過濾器存儲(chǔ)在一個(gè)文件里
>>> bf.tofile('filename.suffix') 
# 從一個(gè)文件里恢復(fù)過濾器。自動(dòng)識(shí)別過濾器的種類。
>>> recovered_bf = bloompy.get_filter_fromfile('filename.suffix') 
# 或者使用過濾器類的類方法 'fromfile' 來進(jìn)行過濾器的復(fù)原。對(duì)應(yīng)的類只能恢復(fù)對(duì)應(yīng)的過濾器
>>> recovered_bf = bloompy.BloomFilter.fromfile('filename.suffix') 
# 返回已經(jīng)插入的元素個(gè)數(shù)
>>> bf.count
2 
# 過濾器的容量
>>> bf.capacity
1000
# 過濾器的位向量
>>> bf.bit_array
bitarray('00....')
# 過濾器位數(shù)組長(zhǎng)度
>>> bf.bit_num
14400
# 過濾器的哈希種子,默認(rèn)是素?cái)?shù),可修改
>>> bf.seeds
[2, 3, 5, 7, 11,...]
# 過濾器哈希函數(shù)個(gè)數(shù)
>>> bf.hash_num
10
 

2、計(jì)數(shù)布隆過濾器。

它是標(biāo)準(zhǔn)布隆過濾器的子類,但是可以執(zhí)行刪除操作。內(nèi)置默認(rèn)使用 4 位二進(jìn)制位來表示標(biāo)準(zhǔn)布隆過濾器的 1 個(gè)位,從而實(shí)現(xiàn)可以增減。

>>> import  bloompy
>>> cbf  = bloompy.CountingBloomFilter(error_rate=0.001,element_num=10**3)
# 與標(biāo)準(zhǔn)布隆過濾器一樣
>>> cbf.add(12)
False
>>> cbf.add(12)
True
>>> 12 in cbf
True
>>> cbf.count
1 
# 查詢?cè)貭顟B(tài)返回標(biāo)識(shí),如果元素存在過濾器里則刪除
>>> cbf.delete(12)
True
>>> cbf.delete(12)
False
>>> 12 in cbf
False
>>> cbf.count
0
# 從文件中恢復(fù)過濾器
>>> recovered_cbf = bloompy.CountingBloomFilter.fromfile('filename.suffix')
 

3、標(biāo)準(zhǔn)擴(kuò)容布隆過濾器。

當(dāng)插入的元素個(gè)數(shù)超過當(dāng)前過濾器的容量時(shí),自動(dòng)增加過濾器的容量,默認(rèn)內(nèi)置一次擴(kuò)容 2 倍。支持查詢和插入功能。

>>> import bloompy
>>> sbf = bloompy.ScalableBloomFilter(error_rate=0.001,initial_capacity=10**3)
# 默認(rèn)初次可以設(shè)置容量1000
>>> len(sbf)
0
>>> 12 in sbf
False
>>> sbf.add(12)
False
>>> 12 in sbf 
True
>>> len(sbf)
1
>>> sbf.filters
[<bloompy.BloomFilter object at 0x000000000B6F5860>]
>>> sbf.capacity
1000
#當(dāng)過濾器的元素個(gè)數(shù)達(dá)到容量極限時(shí),過濾器會(huì)自動(dòng)增加內(nèi)置的標(biāo)準(zhǔn)過濾器,
#每次增加2倍容量,自動(dòng)實(shí)現(xiàn)擴(kuò)容
>>> for i in range(1000):
        sbf.add(i)
>>> 600 in sbf
True
>>> len(sbf)
2
>>> sbf.filters
[<bloompy.BloomFilter object at 0x000000000B6F5860>, <bloompy.BloomFilter object at 0x000000000B32F748>]
>>> sbf.capacity
3000 
# 從文件中恢復(fù)過濾器
>>> recovered_sbf = bloompy.ScalableBloomFilter.fromfile('filename.suffix')
 

4、計(jì)數(shù)擴(kuò)容布隆過濾器。

它是標(biāo)準(zhǔn)擴(kuò)容布隆過濾器的子類,但支持刪除元素的操作。

>>> import bloompy
>>> scbf = bloompy.SCBloomFilter(error_rate=0.001,initial_capacity=10**3)
>>> scbf.add(1)
False
>>> 1 in scbf
True
>>> scbf.delete(1)
True
>>> 1 in scbf
False
>>> len(scbf)
1
>>> scbf.filters
[<bloompy.CountingBloomFilter object at 0x000000000B6F5828>]
# 插入元素使其達(dá)到過濾器當(dāng)前容量極限值
>>> for i in range(1100):
        scbf.add(i)
>>> len(scbf)
2
>>> scbf.filters
[<bloompy.CountingBloomFilter object at 0x000000000B6F5828>, <bloompy.CountingBloomFilter object at 0x000000000B6F5898>]
# 從文件中恢復(fù)過濾器
>>> recovered_scbf = bloompy.SCBloomFilter.fromfile('filename.suffix')
 

Redis 中使用布隆過濾器

你可以手動(dòng)為 Redis 安裝 RedisBloom 插件,也可以直接使用官方[3]提供的 docker 版本:

docker run -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest

然后在 Redis 中就可以這樣使用:

127.0.0.1:6379> BF.ADD newFilter foo
(integer) 1
127.0.0.1:6379> BF.EXISTS newFilter foo
(integer) 1
127.0.0.1:6379> BF.EXISTS newFilter notpresent
(integer) 0
127.0.0.1:6379> BF.MADD myFilter foo bar baz
1) (integer) 1
2) (integer) 1
3) (integer) 1
127.0.0.1:6379> BF.MEXISTS myFilter foo nonexist bar
1) (integer) 1
2) (integer) 0
3) (integer) 1
 

其實(shí) Redis 中使用布隆過濾器還有一個(gè)很大的用處,就是處理緩存穿透。Redis 大部分情況都是通過 Key 查詢對(duì)應(yīng)的值,假如發(fā)送的請(qǐng)求傳進(jìn)來的 key 是不存在 Redis 中的,那么就查不到緩存,查不到緩存就會(huì)去數(shù)據(jù)庫(kù)查詢。假如有大量這樣的請(qǐng)求,這些請(qǐng)求像“穿透”了緩存一樣直接打在數(shù)據(jù)庫(kù)上,這種現(xiàn)象就叫做緩存穿透。

解決方案:

1、把無效的 Key 存進(jìn) Redis中。如果 Redis 查不到數(shù)據(jù),數(shù)據(jù)庫(kù)也查不到,我們把這個(gè) Key 值保存進(jìn)Redis,設(shè)置 value="null",當(dāng)下次再通過這個(gè) Key 查詢時(shí)就不需要再查詢數(shù)據(jù)庫(kù)。這種處理方式肯定是有問題的,假如傳進(jìn)來的這個(gè)不存在的 Key 值每次都是隨機(jī)的,那存進(jìn) Redis 也沒有意義。

2、使用布隆過濾器。布隆過濾器的作用是某個(gè) key 不存在,那么就一定不存在,它說某個(gè) key 存在,那么很大可能是存在(存在一定的誤判率)。于是我們可以在緩存之前再加一層布隆過濾器,在查詢的時(shí)候先去布隆過濾器查詢 key 是否存在,如果不存在就直接返回。

最后的話

布隆過濾器的數(shù)學(xué)原理在于兩個(gè)完全隨機(jī)的數(shù)字相沖突的概率很小,因此可以在很小的誤判率條件下用很少的空間存儲(chǔ)大量的信息,解決誤判的常見辦法,就是在建立一個(gè)小的白名單,存儲(chǔ)那些可能被誤判的信息。

參考資料

[1]

關(guān)系:

http://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html

[2]

bloompy:

https://github.com/01ly/bloompy/blob/master/zh-cn.md

[3]

官方:

https://oss.redis.com/redisbloom/Quick_Start/

相關(guān)文章

  • Python?yield?關(guān)鍵詞,

    Python?yield?關(guān)鍵詞,

    這篇文章主要介紹了Python?yield?關(guān)鍵詞,要理解yield的作用,你必須理解生成器是什么。在理解生成器之前,必須先理解迭代器。下面文章我們就先從
    迭代器開始展開yield關(guān)鍵詞的相關(guān)自資料 ,需要的朋友可以參考一下
    2021-12-12
  • Flask框架Jinjia模板常用語法總結(jié)

    Flask框架Jinjia模板常用語法總結(jié)

    這篇文章主要介紹了Flask框架Jinjia模板常用語法,結(jié)合實(shí)例形式總結(jié)分析了Jinjia模板的變量、賦值、流程控制、函數(shù)、塊、宏等基本使用方法,需要的朋友可以參考下
    2018-07-07
  • python中內(nèi)置庫(kù)csv的使用及說明

    python中內(nèi)置庫(kù)csv的使用及說明

    這篇文章主要介紹了python中內(nèi)置庫(kù)csv的使用及說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • 非遞歸的輸出1-N的全排列實(shí)例(推薦)

    非遞歸的輸出1-N的全排列實(shí)例(推薦)

    下面小編就為大家?guī)硪黄沁f歸的輸出1-N的全排列實(shí)例(推薦)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-04-04
  • Python Datetime模塊和Calendar模塊用法實(shí)例分析

    Python Datetime模塊和Calendar模塊用法實(shí)例分析

    這篇文章主要介紹了Python Datetime模塊和Calendar模塊用法,結(jié)合實(shí)例形式分析了Python日期時(shí)間及日歷相關(guān)的Datetime模塊和Calendar模塊原理、用法及操作注意事項(xiàng),需要的朋友可以參考下
    2019-04-04
  • python簡(jiǎn)單文本處理的方法

    python簡(jiǎn)單文本處理的方法

    這篇文章主要介紹了python簡(jiǎn)單文本處理的方法,涉及Python針對(duì)文本文件及字符串操作的相關(guān)技巧,需要的朋友可以參考下
    2015-07-07
  • Python 解決logging功能使用過程中遇到的一個(gè)問題

    Python 解決logging功能使用過程中遇到的一個(gè)問題

    這篇文章主要介紹了Python 解決logging功能使用過程中遇到的一個(gè)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • Pytest測(cè)試報(bào)告工具Allure用法介紹

    Pytest測(cè)試報(bào)告工具Allure用法介紹

    這篇文章介紹了Pytest測(cè)試報(bào)告工具Allure的用法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • Python常用庫(kù)推薦

    Python常用庫(kù)推薦

    本文給大家推薦的是在Python學(xué)習(xí)使用中經(jīng)常需要用到的第三方庫(kù)和工具,非常的實(shí)用,有需要的小伙伴可以參考下
    2016-12-12
  • Python利用scikit-learn實(shí)現(xiàn)近鄰算法分類的示例詳解

    Python利用scikit-learn實(shí)現(xiàn)近鄰算法分類的示例詳解

    scikit-learn已經(jīng)封裝好很多數(shù)據(jù)挖掘的算法,這篇文章就來用scikit-learn實(shí)現(xiàn)近鄰算法分類,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下
    2023-02-02

最新評(píng)論