BloomFilter如何快速檢查用戶(hù)名重復(fù)
背景
在我們的項(xiàng)目中,用戶(hù)名是不能重復(fù)的,因?yàn)樵跀?shù)藏項(xiàng)目中,用戶(hù)名的唯一性很重要,因?yàn)椴仄芬鏊菰?,要確保整個(gè)鏈路上的參與者的唯一性,雖然用戶(hù) id 也是我唯一的,但是頁(yè)面上展示的時(shí)候不能用 id 呀。
有些網(wǎng)站,比如淘寶網(wǎng),也是要求用戶(hù)名唯一的。
所以為了實(shí)現(xiàn)這個(gè)用戶(hù)在注冊(cè),改名的時(shí)候的用戶(hù)名的唯一性,我們一般是先從數(shù)據(jù)庫(kù)查詢(xún)是否存在,不存在則讓用戶(hù)注冊(cè)。
但是為了考慮到性能,我們會(huì)把用戶(hù)名存儲(chǔ)到緩存中,一般使用 redis 緩存。
那既然都用了緩存了,那還不如干脆直接就用布隆過(guò)濾器來(lái)存儲(chǔ),既可以做重復(fù)校驗(yàn),又能節(jié)省空間。所以,我們?cè)谟脩?hù)名重復(fù)檢驗(yàn)這里就用到了布隆過(guò)濾器。
簡(jiǎn)介
布隆過(guò)濾器是一種數(shù)據(jù)結(jié)構(gòu),用于快速檢索一個(gè)元素是否可能存在于一個(gè)集合(bit 數(shù)組)中。
它的基本原理是利用多個(gè)哈希函數(shù),將一個(gè)元素映射成多個(gè)位,然后將這些位設(shè)置為 1。當(dāng)查詢(xún)一個(gè)元素時(shí),如果這些位都被設(shè)置為 1,則認(rèn)為元素可能存在于集合中,否則肯定不存在。
所以,布隆過(guò)濾器可以準(zhǔn)確的判斷一個(gè)元素是否一定不存在,但是因?yàn)楣_突的存在,所以他沒(méi)辦法判斷一個(gè)元素一定存在。只能判斷可能存在。
代碼實(shí)現(xiàn)
所以,我們定義了兩個(gè)方法,nickNameExist用于判斷是否存在,addNickName用于向布隆過(guò)濾器中添加已注冊(cè)的用戶(hù)名。
public boolean nickNameExist(String nickName) { //如果布隆過(guò)濾器中存在,再進(jìn)行數(shù)據(jù)庫(kù)二次判斷 if (this.bloomFilter.contains(nickName)) { return userMapper.findByNickname(nickName) != null; } return false; } private boolean addNickName(String nickName) { return this.bloomFilter.add(nickName); }
這里nickNameExist方法,先從布隆過(guò)濾器中查詢(xún),如果如果查到了,再去數(shù)據(jù)庫(kù)中查了一下,為啥呢?
因?yàn)椴悸∵^(guò)濾器有誤判的,存在一定的誤判率,他會(huì)把不存在的用戶(hù)判斷為存在,所以當(dāng)檢查結(jié)果是存在的時(shí)候,需要再次判斷一次。
因?yàn)橛脩?hù)注冊(cè)的時(shí)候,大多數(shù)情況下都是不重復(fù)的,所以我們可以快速的用布隆過(guò)濾器進(jìn)行不存在的判斷。
這里的bloomFilter是這樣被初始化出來(lái)的:
private RBloomFilter<String> bloomFilter; @Override public void afterPropertiesSet() throws Exception { this.bloomFilter = redissonClient.getBloomFilter("nickName"); if (!bloomFilter.isExists()) { this.bloomFilter.tryInit(10000000L, 0.01); } }
我們?cè)O(shè)置了10000000的容量,誤判率是0.01。
然后在修改用戶(hù)名這里,用這樣的方式進(jìn)行調(diào)用的。
如果用戶(hù)名修改了怎么辦?
如果原來(lái)用戶(hù)名是Aizer,被放到布隆過(guò)濾器了,但是后面我改成 Aizer666了,那么意味著 Aizer已經(jīng)沒(méi)有了,那么如何從布隆過(guò)濾器刪除呢?
很遺憾,不支持!
那怎么辦呢?
其實(shí)問(wèn)題也不大,因?yàn)椴悸∵^(guò)濾器本身就存在誤判率,但我們檢查布隆過(guò)濾器發(fā)現(xiàn)存在的時(shí)候,還是會(huì)去數(shù)據(jù)庫(kù)再確認(rèn)一遍的。
只要我們定期的重建一下布隆過(guò)濾器就行了。重建就是都刪了,然后重新構(gòu)建。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Java如何優(yōu)雅地書(shū)寫(xiě)if-else
在日常開(kāi)發(fā)中我們常常遇到有多個(gè)if?else的情況,之間書(shū)寫(xiě)顯得代碼冗余難看,對(duì)于追求更高質(zhì)量代碼的同學(xué),就會(huì)思考如何優(yōu)雅地處理這種代碼。本文我們就來(lái)探討下幾種優(yōu)化if?else的方法2022-08-08Java中ScheduledExecutorService介紹和使用案例(推薦)
ScheduledExecutorService是Java并發(fā)包中的接口,用于安排任務(wù)在給定延遲后運(yùn)行或定期執(zhí)行,它繼承自ExecutorService,具有線(xiàn)程池特性,可復(fù)用線(xiàn)程,提高效率,本文主要介紹java中的ScheduledExecutorService介紹和使用案例,感興趣的朋友一起看看吧2024-10-10spring batch 讀取多個(gè)文件數(shù)據(jù)導(dǎo)入數(shù)據(jù)庫(kù)示例
本篇文章主要介紹了spring batch 讀取多個(gè)文件數(shù)據(jù)導(dǎo)入數(shù)據(jù)庫(kù)示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-03-03java 設(shè)計(jì)模式之State(狀態(tài)模式)
這篇文章主要介紹了java 設(shè)計(jì)模式之State(狀態(tài)模式)的相關(guān)資料,一個(gè)類(lèi)的行為基于它的狀態(tài)的改變而改變。狀態(tài)模式歸屬于行為型模式,需要的朋友可以參考下2017-08-08教你開(kāi)發(fā)腳手架集成Spring?Boot?Actuator監(jiān)控的詳細(xì)過(guò)程
這篇文章主要介紹了開(kāi)發(fā)腳手架集成Spring?Boot?Actuator監(jiān)控的詳細(xì)過(guò)程,集成包括引入依賴(lài)配置文件及訪(fǎng)問(wèn)驗(yàn)證的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05解決異常處理問(wèn)題:getReader()?has?already?been?called?for?this
這篇文章主要介紹了解決異常處理:getReader()?has?already?been?called?for?this問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01微信公眾號(hào)開(kāi)發(fā)之回復(fù)圖文消息java代碼
這篇文章主要為大家詳細(xì)介紹了微信公眾號(hào)開(kāi)發(fā)之回復(fù)圖文消息java代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03Java實(shí)現(xiàn)批量查找與替換Excel文本的思路詳解
在 Java 中,可以通過(guò)find和replace的方法來(lái)查找和替換單元格的數(shù)據(jù),下面小編將以Excel文件為例為大家介紹如何實(shí)現(xiàn)Excel文件內(nèi)容的批量替換,感興趣的朋友跟隨小編一起看看吧2023-10-10java如何實(shí)現(xiàn)項(xiàng)目啟動(dòng)時(shí)執(zhí)行指定方法
這篇文章主要為大家詳細(xì)介紹了java項(xiàng)目如何啟動(dòng)時(shí)執(zhí)行指定方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07