URL重複排除の6つのソリューション!(詳細な実装コード付き)

URLの削除は、私たちの日常の仕事やインタビューなどで頻繁に発生します
mj.png
。Alibaba、NetEase Cloud、Youku、Homework Gangなどの有名なインターネット企業はすべて、同様のインタビューの質問とURLの削除を行っていることがわかります。 IPブラック/ホワイトリストの判定など、より類似したものがしばしば私たちの作業に現れるため、この記事では、重複するURLを削除する問題に焦点を当てます。

URL重複排除のアイデア

ビジネスシナリオとデータの量に関係なく、次のスキームを使用してURLの繰り返し判断を行うことができます。

  1. JavaのSetコレクションを使用して、追加の結果に従ってURLが複製されているかどうかを判別します(追加が成功すると、URLは複製されません)。
  2. 追加時の結果に応じて、RedisのSetコレクションを使用して、URLが重複しているかどうかを判断します。
  3. URLをデータベースに保存し、SQLステートメントを使用して、URLが重複しているかどうかを判断します。
  4. データベースのURL列を一意のインデックスとして設定し、追加時の結果に従ってURLが重複しているかどうかを判断します。
  5. GuavaのBloomフィルターを使用して、URLの判断を実現します。
  6. RedisのBloomフィルターを使用して、URLの判断を実現します。

上記スキームの具体的な実現は以下の通りである。

URL重複排除実装スキーム

1. JavaのSetコレクションを使用して重いと判断する

Setコレクションは本質的に繰り返し不可です。異なる値を持つ要素を格納するためにのみ使用できます。値が同じである場合、追加は失敗します。したがって、Setコレクションの結果を追加することにより、URLが重複しているかどうかを判断できます。実装コードは次のとおりです。

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<String> set = new HashSet();
        for (int i = 0; i < URLS.length; i++) {
            String url = URLS[i];
            boolean result = set.add(url);
            if (!result) {
                // 重复的 URL
                System.out.println("URL 已存在了:" + url);
            }
        }
    }
}

プログラムの実行結果は次のとおりです。

URLは既に存在します:www.apigo.cn

以上の結果から、セットセットを利用することで、URL判定機能を実現できることがわかる。

2.Redis Setコレクションの重複除外

Redis Setコレクションを使用するという実現の考え方は、JavaのSetコレクションの考え方と同じです。どちらも、Setの非再現性を使用することで実現されます。まず、Redisクライアントredis-cliを使用して、URL判断の例を実装しましょう。
image.png
上記の結果から、追加が成功した場合はURLが繰り返されないことを意味しますが、追加が失敗した場合(結果が0)は、URLが既に存在することを意味します。

コードを使用してRedis Set重複排除を実装してみましょう。実装コードは次のとおりです。

// 待去重 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 < URLS.length; i++) {
        String url = URLS[i];
        Long result = redisTemplate.opsForSet().add("urlrepeat", url);
        if (result == 0) {
            // 重复的 URL
            System.out.println("URL 已存在了:" + url);
        }
    }
}

上記のプログラムの実行結果は次のとおりです。

URLは既に存在します:www.apigo.cn

上記のコードではRedisTemplate 、Spring Dataの  実装を使用していRedisTemplate ます。SpringBootプロジェクトでオブジェクトを使用するには、  最初にspring-boot-starter-data-redis フレームワークを導入する必要があり  ます。構成情報は次のとおりです。

<!-- 添加操作 RedisTemplate 引用 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

次に、プロジェクトでRedis接続情報を構成し、application.propertiesで以下を構成する必要があります。

spring.redis.host=127.0.0.1
spring.redis.port=6379
#spring.redis.password=123456 # Redis 服务器密码,有密码的话需要配置此项

上記の2つのステップの後、Spring BootプロジェクトでRedisTemplate オブジェクトを使用してRedisを通常どおり操作できます 

image.png

3.データベースの重複排除

データベースを使用して、URLの繰り返し判断を実現することもできます。最初に、次の図に示すように、まずURLストレージテーブルを設計します。
image.png
このテーブルに対応する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  フィールドはインデックスとして設定されます。インデックスを設定すると、クエリを高速化できます。

次の図に示すように、最初に2つのテストデータをデータベースに追加します。

image.png

次の図に示すように、SQLステートメントを使用してクエリを実行します。
image.png
結果が0より大きい場合は、URLが重複していることを意味します。それ以外の場合は、URLが重複していないことを意味します。

4.一意のインデックス重複除外

また、データベースの一意のインデックスを使用してURLの重複を防ぐこともでき、その実装は以前のSetコレクションと非常によく似ています。

まず、フィールドURLに一意のインデックスを設定してから、URLデータを追加します。正常に追加できた場合、URLは繰り返されません。それ以外の場合は、繰り返されます。

一意のインデックスを作成するためのSQL実装は次のとおりです。

create unique index Index_url on urlinfo
(
   url
);

5.重いものを取り除くグアバブルームフィルター

ブルームフィルター(ブルームフィルター)は1970年にブルームによって提案されました。これは実際には非常に長いバイナリベクトルであり、一連のランダムマッピング関数です。ブルームフィルターを使用して、要素がコレクション内にあるかどうかを取得できます。その利点は、スペース効率とクエリ時間が一般的なアルゴリズムをはるかに上回っていることですが、欠点は、特定の誤認識率と削除の難しさがあることです。

ブルームフィルターのコア実装は、非常に大きなビット配列といくつかのハッシュ関数です。ビット配列の長さをm、ハッシュ関数の数をkと想定します。

上の図を例として、特定の操作フローを考えます。セットに3つの要素{x、y、z}があり、ハッシュ関数の数が3であるとします。最初に、ビット配列を初期化し、その中の各ビットにビット0を設定します。セット内の各要素について、要素は3つのハッシュ関数によって順番にマッピングされ、各マッピングはビット配列上のポイントに対応するハッシュ値を生成し、ビット配列に対応する位置は1としてマークされます、W要素がセットに存在するかどうかを照会する場合、同じメソッドがWをハッシュによってビット配列の3つのポイントにマップします。3点のうち1点が1でない場合、その要素は集合に存在してはならない、と判断できます。逆に、3つのポイントがすべて1の場合、要素はセットに存在する可能性があります。注:要素がセット内に存在する必要があるかどうかをここで判断することはできません。特定の誤判断率がある可能性があります。それは図から見ることができます:特定の要素がマッピングを通じて3つの点4、5、および6に対応するとします。これらの3点はすべて1ですが、これらの3点は異なる要素をハッシュすることによって取得された位置であることは明らかです。したがって、この状況は、要素がセットに含まれていなくても、すべて1に対応する可能性があることを示しています。これは誤判定率です。存在の理由。

Googleが提供するGuavaフレームワークを使用してBloomフィルターを操作できるため、最初にpom.xmlにGuava参照を追加できます。構成は次のとおりです。

<!-- 添加 Guava 框架 -->
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>28.2-jre</version>
</dependency>

URL判定の実装コード:

public class URLRepeat {
    // 待去重 URL
    public static final String[] URLS = {
            "www.apigo.cn",
            "www.baidu.com",
            "www.apigo.cn"
    };

    public static void main(String[] args) {
        // 创建一个布隆过滤器
        BloomFilter<String> filter = BloomFilter.create(
                Funnels.stringFunnel(Charset.defaultCharset()),
                10, // 期望处理的元素数量
                0.01); // 期望的误报概率
        for (int i = 0; i < URLS.length; i++) {
            String url = URLS[i];
            if (filter.mightContain(url)) {
                // 用重复的 URL
                System.out.println("URL 已存在了:" + url);
            } else {
                // 将 URL 存储在布隆过滤器中
                filter.put(url);
            }
        }
    }
}

上記のプログラムの実行結果は次のとおりです。

URLは既に存在します:www.apigo.cn

6.Redis Bloomフィルターの重複除外

GuavaのBloomフィルターに加えて、RedisのBloomフィルターを使用してURL判断を実装することもできます。これを使用する前に、Redisサーバーのバージョンが4.0以降であること(このバージョンではブルームフィルターのみがサポートされていること)、および通常の使用ではRedisブルームフィルター機能が有効になっていることを確認する必要があります。

Dockerを例に、Redisブルームフィルターのインストールとアクティブ化をデモンストレーションします。次の図に示すように、最初にRedisブルームフィルターをダウンロードし、Redisサービスを再起動するときにブルームフィルターをオンにします。
image.png

ブルームフィルターの使用
ブルームフィルターが通常オンになった後、最初にRedisクライアントredis-cliを使用してブルームフィルターのURL判定を実装します。実装コマンドは次のとおりです。
image.png

Redisでは、ブルームフィルターの操作コマンドは多くありません。主に次のコマンドが含まれます。

  • bf.add add要素;
  • bf.existsは、要素が存在するかどうかを判断します。
  • bf.maddは複数の要素を追加します。
  • bf.mexistsは、複数の要素が存在するかどうかを判断します。
  • bf.reserveは、ブルームフィルターの精度率を設定します。

次に、コードを使用してRedis Bloomフィルターの使用方法を示します。

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 < URLS.length; i++) {
            String url = URLS[i];
            boolean exists = bfExists(jedis, _KEY, url);
            if (exists) {
                // 重复的 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;
    }
}

上記のプログラムの実行結果は次のとおりです。

URLは既に存在します:www.apigo.cn

総括する

この記事では、6つのURL重複排除ソリューションを紹介します。このうち、Redis Set、Redis Bloomフィルター、データベース、一意のインデックスは分散システムに適しています。大規模な分散システムの場合は、Redis Bloomフィルターの使用をお勧めします。 URL重複排除を実現するには、それが大量のデータを持つ単一のマシンである場合、GuavaのBloomerを使用してURL重複排除を実現することをお勧めします

おすすめ

転載: blog.csdn.net/qq_46388795/article/details/108511808