URL 去重的 6 種方案!(附詳細(xì)代碼)

作者 | 王磊
來源 | Java中文社群(ID:javacn666)
URL 去重在我們?nèi)粘9ぷ髦泻兔嬖囍泻艹S龅剑热邕@些:

可以看出,包括阿里,網(wǎng)易云、優(yōu)酷、作業(yè)幫等知名互聯(lián)網(wǎng)公司都出現(xiàn)過類似的面試題,而且和 URL 去重比較類似的,如 IP 黑/白名單判斷等也經(jīng)常出現(xiàn)在我們的工作中,所以我們本文就來“盤一盤”URL 去重的問題。
URL 去重思路
在不考慮業(yè)務(wù)場景和數(shù)據(jù)量的情況下,我們可以使用以下方案來實現(xiàn) URL 的重復(fù)判斷:
使用 Java 的 Set 集合,根據(jù)添加時的結(jié)果來判斷 URL 是否重復(fù)(添加成功表示 URL 不重復(fù)); 使用 Redis 中的 Set 集合,根據(jù)添加時的結(jié)果來判斷 URL 是否重復(fù); 將 URL 都存儲在數(shù)據(jù)庫中,再通過 SQL 語句判斷是否有重復(fù)的 URL; 把數(shù)據(jù)庫中的 URL 一列設(shè)置為唯一索引,根據(jù)添加時的結(jié)果來判斷 URL 是否重復(fù); 使用 Guava 的布隆過濾器來實現(xiàn) URL 判重; 使用 Redis 的布隆過濾器來實現(xiàn) URL 判重。
以上方案的具體實現(xiàn)如下。
URL 去重實現(xiàn)方案
1.使用 Java 的 Set 集合判重
Set 集合天生具備不可重復(fù)性,使用它只能存儲值不相同的元素,如果值相同添加就會失敗,因此我們可以通過添加 Set 集合時的結(jié)果來判定 URL 是否重復(fù),實現(xiàn)代碼如下:
public?class?URLRepeat?{
????//?待去重?URL
????public?static?final?String[]?URLS?=?{
????????????"www.apigo.cn",
????????????"www.baidu.com",
????????????"www.apigo.cn"
????};
????public?static?void?main(String[]?args)?{
????????Set?set?=?new?HashSet();
????????for?(int?i?=?0;?i?????????????String?url?=?URLS[i];
????????????boolean?result?=?set.add(url);
????????????if?(!result)?{
????????????????//?重復(fù)的?URL
????????????????System.out.println("URL 已存在了:"?+?url);
????????????}
????????}
????}
}
程序的執(zhí)行結(jié)果為:
URL 已存在了:www.apigo.cn
從上述結(jié)果可以看出,使用 Set 集合可以實現(xiàn) URL 的判重功能。
2.Redis Set 集合去重
使用 Redis 的 Set 集合的實現(xiàn)思路和 Java 中的 Set 集合思想思路是一致的,都是利用 Set 的不可重復(fù)性實現(xiàn)的,我們先使用 Redis 的客戶端 redis-cli 來實現(xiàn)一下 URL 判重的示例:

從上述結(jié)果可以看出,當(dāng)添加成功時表示 URL 沒有重復(fù),但添加失敗時(結(jié)果為 0)表示此 URL 已經(jīng)存在了。
我們再用代碼的方式來實現(xiàn)一下 Redis 的 Set 去重,實現(xiàn)代碼如下:
//?待去重?URL
public?static?final?String[]?URLS?=?{
????"www.apigo.cn",
????"www.baidu.com",
????"www.apigo.cn"
};
@Autowired
RedisTemplate?redisTemplate;
@RequestMapping("/url")
public?void?urlRepeat()?{
????for?(int?i?=?0;?i?????????String?url?=?URLS[i];
????????Long?result?=?redisTemplate.opsForSet().add("urlrepeat",?url);
????????if?(result?==?0)?{
????????????//?重復(fù)的?URL
????????????System.out.println("URL 已存在了:"?+?url);
????????}
????}
}
以上程序的執(zhí)行結(jié)果為:
URL 已存在了:www.apigo.cn
以上代碼中我們借助了 Spring Data 中的 RedisTemplate?實現(xiàn)的,在 Spring Boot 項目中要使用 RedisTemplate 對象我們需要先引入 spring-boot-starter-data-redis?框架,配置信息如下:
<dependency>
????<groupId>org.springframework.bootgroupId>
????<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
然后需要再項目中配置 Redis 的連接信息,在 application.properties 中配置如下內(nèi)容:
spring.redis.host=127.0.0.1
spring.redis.port=6379
#spring.redis.password=123456?#?Redis?服務(wù)器密碼,有密碼的話需要配置此項
經(jīng)過以上兩個步驟之后,我們就可以在 Spring Boot 的項目中正常的使用 RedisTemplate 對象來操作 Redis 了。

3.數(shù)據(jù)庫去重
我們也可以借助數(shù)據(jù)庫實現(xiàn) URL 的重復(fù)判斷,首先我們先來設(shè)計一張 URL 的存儲表,如下圖所示:

此表對應(yīng)的 SQL 如下:
/*==============================================================*/
/*?Table:?urlinfo???????????????????????????????????????????????*/
/*==============================================================*/
create?table?urlinfo
(
???id???????????????????int?not?null?auto_increment,
???url??????????????????varchar(1000),
???ctime????????????????date,
???del??????????????????boolean,
???primary?key?(id)
);
/*==============================================================*/
/*?Index:?Index_url?????????????????????????????????????????????*/
/*==============================================================*/
create?index?Index_url?on?urlinfo
(
???url
);
其中 id 為自增的主鍵,而 url? 字段設(shè)置為索引,設(shè)置索引可以加快查詢的速度。
我們先在數(shù)據(jù)庫中添加兩條測試數(shù)據(jù),如下圖所示:

我們使用 SQL 語句查詢,如下圖所示:

如果結(jié)果大于 0 則表明已經(jīng)有重復(fù)的 URL 了,否則表示沒有重復(fù)的 URL。
4.唯一索引去重
我們也可以使用數(shù)據(jù)庫的唯一索引來防止 URL 重復(fù),它的實現(xiàn)思路和前面 Set 集合的思想思路非常像。
首先我們先為字段 URL 設(shè)置了唯一索引,然后再添加 URL 數(shù)據(jù),如果能添加成功則表明 URL 不重復(fù),反之則表示重復(fù)。
創(chuàng)建唯一索引的 SQL 實現(xiàn)如下:
create?unique?index?Index_url?on?urlinfo
(
???url
);
5.Guava 布隆過濾器去重
布隆過濾器(Bloom Filter)是1970年由布隆提出的。它實際上是一個很長的二進(jìn)制向量和一系列隨機映射函數(shù)。布隆過濾器可以用于檢索一個元素是否在一個集合中。它的優(yōu)點是空間效率和查詢時間都遠(yuǎn)遠(yuǎn)超過一般的算法,缺點是有一定的誤識別率和刪除困難。
布隆過濾器的核心實現(xiàn)是一個超大的位數(shù)組和幾個哈希函數(shù),假設(shè)位數(shù)組的長度為 m,哈希函數(shù)的個數(shù)為 k。

以上圖為例,具體的操作流程:假設(shè)集合里面有 3 個元素 {x, y, z},哈希函數(shù)的個數(shù)為 3。首先將位數(shù)組進(jìn)行初始化,將里面每個位都設(shè)置位 0。對于集合里面的每一個元素,將元素依次通過 3 個哈希函數(shù)進(jìn)行映射,每次映射都會產(chǎn)生一個哈希值,這個值對應(yīng)位數(shù)組上面的一個點,然后將位數(shù)組對應(yīng)的位置標(biāo)記為 1,查詢 W 元素是否存在集合中的時候,同樣的方法將 W 通過哈希映射到位數(shù)組上的 3 個點。如果 3 個點的其中有一個點不為 1,則可以判斷該元素一定不存在集合中。反之,如果 3 個點都為 1,則該元素可能存在集合中。注意:此處不能判斷該元素是否一定存在集合中,可能存在一定的誤判率。可以從圖中可以看到:假設(shè)某個元素通過映射對應(yīng)下標(biāo)為 4、5、6 這 3 個點。雖然這 3 個點都為 1,但是很明顯這 3 個點是不同元素經(jīng)過哈希得到的位置,因此這種情況說明元素雖然不在集合中,也可能對應(yīng)的都是 1,這是誤判率存在的原因。
我們可以借助 Google 提供的 Guava 框架來操作布隆過濾器,實現(xiàn)我們先在 pom.xml 中添加 Guava 的引用,配置如下:
<dependency>
????<groupId>com.google.guavagroupId>
????<artifactId>guavaartifactId>
????<version>28.2-jreversion>
dependency>
URL 判重的實現(xiàn)代碼:
public?class?URLRepeat?{
????//?待去重?URL
????public?static?final?String[]?URLS?=?{
????????????"www.apigo.cn",
????????????"www.baidu.com",
????????????"www.apigo.cn"
????};
????public?static?void?main(String[]?args)?{
????????//?創(chuàng)建一個布隆過濾器
????????BloomFilter?filter?=?BloomFilter.create(
????????????????Funnels.stringFunnel(Charset.defaultCharset()),
????????????????10,?//?期望處理的元素數(shù)量
????????????????0.01);?//?期望的誤報概率
????????for?(int?i?=?0;?i?????????????String?url?=?URLS[i];
????????????if?(filter.mightContain(url))?{
????????????????//?用重復(fù)的?URL
????????????????System.out.println("URL 已存在了:"?+?url);
????????????}?else?{
????????????????//?將?URL?存儲在布隆過濾器中
????????????????filter.put(url);
????????????}
????????}
????}
}
以上程序的執(zhí)行結(jié)果為:
URL 已存在了:www.apigo.cn
6.Redis 布隆過濾器去重
除了 Guava 的布隆過濾器,我們還可以使用 Redis 的布隆過濾器來實現(xiàn) URL 判重。在使用之前,我們先要確保 Redis 服務(wù)器版本大于 4.0(此版本以上才支持布隆過濾器),并且開啟了 Redis 布隆過濾器功能才能正常使用。
以 Docker 為例,我們來演示一下 Redis 布隆過濾器安裝和開啟,首先下載 Redis 的布隆過器,然后再在重啟 Redis 服務(wù)時開啟布隆過濾器,如下圖所示:

布隆過濾器使用布隆過濾器正常開啟之后,我們先用 Redis 的客戶端 redis-cli 來實現(xiàn)一下布隆過濾器 URL 判重了,實現(xiàn)命令如下:

在 Redis 中,布隆過濾器的操作命令不多,主要包含以下幾個:
bf.add 添加元素; bf.exists 判斷某個元素是否存在; bf.madd 添加多個元素; bf.mexists 判斷多個元素是否存在; bf.reserve 設(shè)置布隆過濾器的準(zhǔn)確率。
接下來我們使用代碼來演示一下 Redis 布隆過濾器的使用:
import?redis.clients.jedis.Jedis;
import?utils.JedisUtils;
import?java.util.Arrays;
public?class?BloomExample?{
????//?布隆過濾器?key
????private?static?final?String?_KEY?=?"URLREPEAT_KEY";
????
????//?待去重?URL
????public?static?final?String[]?URLS?=?{
????????????"www.apigo.cn",
????????????"www.baidu.com",
????????????"www.apigo.cn"
????};
????public?static?void?main(String[]?args)?{
????????Jedis?jedis?=?JedisUtils.getJedis();
?????????for?(int?i?=?0;?i?????????????String?url?=?URLS[i];
????????????boolean?exists?=?bfExists(jedis,?_KEY,?url);
????????????if?(exists)?{
????????????????//?重復(fù)的?URL
????????????????System.out.println("URL 已存在了:"?+?url);
????????????}?else?{
????????????????bfAdd(jedis,?_KEY,?url);
????????????}
????????}
????}
????/**
?????*?添加元素
?????*?@param?jedis?Redis?客戶端
?????*?@param?key???key
?????*?@param?value?value
?????*?@return?boolean
?????*/
????public?static?boolean?bfAdd(Jedis?jedis,?String?key,?String?value)?{
????????String?luaStr?=?"return?redis.call('bf.add',?KEYS[1],?KEYS[2])";
????????Object?result?=?jedis.eval(luaStr,?Arrays.asList(key,?value),
????????????????Arrays.asList());
????????if?(result.equals(1L))?{
????????????return?true;
????????}
????????return?false;
????}
????/**
?????*?查詢元素是否存在
?????*?@param?jedis?Redis?客戶端
?????*?@param?key???key
?????*?@param?value?value
?????*?@return?boolean
?????*/
????public?static?boolean?bfExists(Jedis?jedis,?String?key,?String?value)?{
????????String?luaStr?=?"return?redis.call('bf.exists',?KEYS[1],?KEYS[2])";
????????Object?result?=?jedis.eval(luaStr,?Arrays.asList(key,?value),
????????????????Arrays.asList());
????????if?(result.equals(1L))?{
????????????return?true;
????????}
????????return?false;
????}
}
以上程序的執(zhí)行結(jié)果為:
URL 已存在了:www.apigo.cn
總結(jié)
本文介紹了 6 種 URL 去重的方案,其中 Redis Set、Redis 布隆過濾器、數(shù)據(jù)庫和唯一索引這 4 種解決方案適用于分布式系統(tǒng),如果是海量的分布式系統(tǒng),建議使用 Redis 布隆過濾器來實現(xiàn) URL 去重,如果是單機海量數(shù)據(jù)推薦使用 Guava 的布隆器來實現(xiàn) URL 去重。
—?【 THE END 】— 本公眾號全部博文已整理成一個目錄,請在公眾號里回復(fù)「m」獲取! 3T技術(shù)資源大放送!包括但不限于:Java、C/C++,Linux,Python,大數(shù)據(jù),人工智能等等。在公眾號內(nèi)回復(fù)「1024」,即可免費獲取!!
