1s生成130w个短链怎么做到的?

1s生成130w个短链怎么做到的?

写在前面:

基于非加密型MurmurHash算法,BLOOM过滤器,数据库唯一索引确保短链的生成是唯一的,且综合考虑使用强随机的UUIDv4版本(默认)使得hash分布更加均匀。实测本机单线程Java环境下可支持1s内生成130w左右的短链数量。

大概流程:

首先我们会使用UUID默认的uuid.randomuuid.tostring,生成一个长度128位的字符串。例如:

Generated UUID: 7a8a3461-34bf-426a-ba76-1c0541e03fdf

然后我们把这个UUID和我们的原始链接例如"https://www.baidu.com"拼接:

"7a8a3461-34bf-426a-ba76-1c0541e03fdfhttps://www.baidu.com"

对这一串进行murmurhash32()得出一个32位的计算值:

1789923922

然后对这个值进行base62之后:

1x8Kyg

兜底:

极小概率下我们生成的短链会重复,我们使用布隆过滤器来兜底,布隆过滤器返回不存在那就说明这个链接一定不存在,存在hash的冲突假设在1%,那么我们的568亿也还剩下568*0.99=562.32亿可用。

一定能生成568亿个短链吗?如果不能如何解决?

虽然 6 位 Base62 的编码空间为 568 亿,但由于 MurmurHash32 只能生成 42 亿个唯一的哈希值,因此 实际存储的短链数量上限为 42 亿。

采用MurmurHash128或者64,生成之后截取前6位,都有可能发生冲突,需判断(布隆过滤器)。

存储1亿条短链:

假设一条短链数据133字节,包含短链的各种描述信息。

133 字节/条 × 100,000,000 条 = 13,300,000,000 字节 ≈ 12.38 GB

布隆过滤器的配置

hashIterations(哈希函数数量) :10

expectedInsertions(预期插入元素数量) : 100000000

falseProbability(误判率): 0.001

总大小: 171.39mb 

参考计算网站:https://hur.st/bloomfilter/

发散思考:腾讯存储40亿qq号,限制1GB,能用布隆过滤器吗?

不能。存储40亿个元素简单的bitmap需要:476.84 MB

复杂的布隆过滤器需要4.79 GB

原因:布隆过滤器需要增加位数组大小来避免哈希冲突,底层是通过多个哈希函数来实现的,因为用了多个哈希函数,比如

str1计算得出槽位在1,2,3;

str2计算得出槽位在2,3,4;

只要有一位不同就可以说明两个str不同。

鉴于这个原理我们就要扩大数组大小,这样才能更加均匀的分布。

130w怎么测出来的?

一下数据均基于本机环境测试。R5-5600H 6核心12线程 4GHZ

1s内murmurhash32可计算大约5000万

1s内uuid.randomuuid可生成大约166万

综合之后:

第一次测试:
单线程测试中...
单线程 1 秒内 MurmurHash32 + Base62 操作次数: 1299483

多线程测试中...
多线程 1 秒内 MurmurHash32 + Base62 操作次数: 1527258

第二次测试:
单线程测试中...
单线程 1 秒内 MurmurHash32 + Base62 操作次数: 1422073

多线程测试中...
多线程 1 秒内 MurmurHash32 + Base62 操作次数: 1543755

第三次测试:
单线程测试中...
单线程 1 秒内 MurmurHash32 + Base62 操作次数: 1418979

多线程测试中...
多线程 1 秒内 MurmurHash32 + Base62 操作次数: 1558930

综合来看单线程下在130w-140w

这里给出测试代码:

public class MurmurHashBenchmark {

    private static final char[] CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
    private static final int SIZE = CHARS.length;
    private static final long TEST_DURATION_NANOS = 1_000_000_000L; // 1 秒 = 1_000_000_000 纳秒

    public static void main(String[] args) {
        System.out.println("单线程测试中...");
        int singleThreadOps = testSingleThread();
        System.out.println("单线程 1 秒内 MurmurHash32 + Base62 操作次数: " + singleThreadOps + "\n");

        System.out.println("多线程测试中...");
        int multiThreadOps = testMultiThread();
        System.out.println("多线程 1 秒内 MurmurHash32 + Base62 操作次数: " + multiThreadOps);
    }

    /**
     * 单线程测试:计算 1 秒内能执行多少次 MurmurHash32 + Base62
     */
    private static int testSingleThread() {
        long startTime = System.nanoTime();
        int count = 0;

        while (System.nanoTime() - startTime < TEST_DURATION_NANOS) {
            String input = UUID.randomUUID().toString();
            String base62Hash = hashToBase62(input);
            count++;
        }

        return count;
    }

    /**
     * 多线程测试:计算 1 秒内能执行多少次 MurmurHash32 + Base62
     */
    private static int testMultiThread() {
        AtomicInteger count = new AtomicInteger(0);
        long startTime = System.nanoTime();

        ForkJoinPool.commonPool().submit(() ->
                IntStream.range(0, Runtime.getRuntime().availableProcessors())
                        .parallel()
                        .forEach(i -> {
                            while (System.nanoTime() - startTime < TEST_DURATION_NANOS) {
                                String input = UUID.randomUUID().toString();
                                String base62Hash = hashToBase62(input);
                                count.incrementAndGet();
                            }
                        })
        ).join();

        return count.get();
    }

    /**
     * MurmurHash32 计算 + Base62 编码
     */
    private static String hashToBase62(String str) {
        int i = MurmurHash.hash32(str);
        long num = i < 0 ? Integer.MAX_VALUE - (long) i : i;
        return convertDecToBase62(num);
    }

    /**
     * 10 进制转 Base62
     */
    private static String convertDecToBase62(long num) {
        StringBuilder sb = new StringBuilder();
        while (num > 0) {
            sb.append(CHARS[(int) (num % SIZE)]);
            num /= SIZE;
        }
        return sb.reverse().toString();
    }
}

LICENSED UNDER CC BY-NC-SA 4.0
Comment