面试官问:如何在十亿级别用户中检查用户名是否存在

前言

不知道大家有没有注意到,大部分的APP在注册的时候,会提示用户名、邮件或手机号已经被占用,需要更换一个。

在这里插入图片描述

现在我们来逐一看一下各个实现方案的优缺点。

数据库方案

在这里插入图片描述

这种方法带来以下的问题:

  1. 存在延迟比较大的性能问题。如果数据量很大,查询速度会变慢,而且数据库查询涉及应用服务器与数据库服务器之间的网络通信,建立连接、发送查询、接收响应所需的时间会导致延迟。
  2. 数据库负载过大。频繁执行 SELECT 查询来检查用户名的唯一性,每个查询都会消耗数据库资源,包括 CPU 和系统 I/O 资源。
  3. 可扩展性差。数据库对并发连接和资源有限制。如果注册率持续上升,数据库服务器可能难以处理不断增加的传入请求。数据库的垂直扩展(向单个服务器添加更多资源)可能成本高昂,并且可能存在局限性。

缓存解决方案

为了解决检查用户名唯一性的数据库调用的性能问题,我们引入了高效的 Redis 缓存。

在这里插入图片描述

使用的代码如下:


import org.redisson.Redisson;  
import org.redisson.api.RedissonClient;  
import org.redisson.config.Config;  
import org.redisson.api.RMap;  

public class UserExistenceChecker {  

    // Redis hash map name to store user information  
    private static final String USER_HASH_NAME = "users";  

    public static void main(String[] args) {  
        // Create a Redisson client  
        RedissonClient redisson = createRedissonClient();  

        // Retrieve the hash map to store user information  
        RMap users = redisson.getMap(USER_HASH_NAME);  

        // Add a user to the hash map  
        users.put("user123", "someUserInfo"); // Here "someUserInfo" could be a JSON string, UUID, etc.  

        // Check if a user exists  
        boolean exists = users.containsKey("user123");  
        System.out.println("User 'user123' exists? " + exists);  

        // Check for a non-existent user  
        exists = users.containsKey("user456");  
        System.out.println("User 'user456' exists? " + exists);  

        // Shutdown the Redisson client  
        redisson.shutdown();  
    }  

    // Helper method to create a Redisson client  
    private static RedissonClient createRedissonClient() {  
        Config config = new Config();  
        config.useSingleServer()  
                .setAddress("redis://127.0.0.1:6379") // Adjust to your Redis address  
                .setPassword("yourpassword"); // Provide your Redis password if any  

        return Redisson.create(config);  
    }  
}

这个方案最大的问题是内存占用过大,假设每个用户名大约需要15字节的内存,如果要存储10亿个用户名,就需要15GB的内存。

总内存 = 每条记录的内存使用量 * 记录数 = 15 字节/记录 * 1,000,000,000 条记录 = 15,000,000,000
字节 ≈ 15,000,000 KB ≈ 15,000 MB ≈ 15 GB

布隆过滤器方案

如果直接缓存进行判断,会占用过多的内存,有没有更好的办法?布隆过滤器(Bloom Filter )是一个很不错的选择。

什么是布隆过滤器?

Bloom Filter 是一种空间利用率极高的随机数据结构,它用一个位数组简洁地表示一个集合,并能判断某个元素是否属于这个集合。Bloom Filter 的这种高效是有一定代价的:在判断某个元素是否属于某个集合时,有可能把不属于该集合的元素误认为属于该集合(false positive)。因此 Bloom Filter 并不适合“零错误”的应用场景。但在可以容忍较低错误率的应用场景中,Bloom Filter 却能通过极少的错误实现存储空间的大幅度节省。

结构

从上面的分析可以知道,布隆过滤器的核心思想是利用一个位数组(bit array)和一组哈希函数。

位数组,每个位最初为 0:
在这里插入图片描述

在插入值x时,分别使用k个哈希函数(图中3个)对x的值进行哈希运算,并将哈希值与布隆过滤器的容量取余数,并将结果所代表的相应位的值设置为1。

在这里插入图片描述

查找过程类似于插入过程,同样使用k个哈希函数对要查找的值进行哈希处理,只有哈希处理后得到的每个比特位的值为1时,才表示该值“可能”存在;反之,如果任意一个比特位的值为0,则表示该值一定不存在。例如,y1一定不存在,而y2则可能存在。

在这里插入图片描述

Redis本身是支持Bloom filter这种数据结构的,我们来使用Redisson客户端简单实现一下代码:


import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

public class UserExistenceChecker {

    // Name of the Bloom Filter in Redis
    private static final String BLOOM_FILTER_NAME = "user_existence_filter";

    public static void main(String[] args) {
        // Create a Redisson client
        RedissonClient redisson = createRedissonClient();

        // Retrieve or create a Bloom Filter instance
        // Expected number of elements and false positive probability are parameters
        RBloomFilter bloomFilter = redisson.getBloomFilter(BLOOM_FILTER_NAME);
        bloomFilter.tryInit(100000L, 0.001); // Initialize the Bloom Filter with expected elements and false positive rate

        // Add a user to the Bloom Filter
        bloomFilter.add("user123");

        // Check if a user exists
        boolean exists = bloomFilter.contains("user123"); // Should return true
        System.out.println("User 'user123' exists? " + exists);

        // Check for a non-existent user (might falsely report as true due to Bloom Filter's nature)
        exists = bloomFilter.contains("user456"); // Assuming not added, should ideally return false, but could be a false positive
        System.out.println("User 'user456' exists? " + exists);

        // Shutdown the Redisson client
        redisson.shutdown();
    }

    // Helper method to create a Redisson client
    private static RedissonClient createRedissonClient() {
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://127.0.0.1:6379"); // Adjust to your Redis address
//                .setPassword("yourpassword"); // Provide your Redis password if any

        return Redisson.create(config);
    }
}

优点:

  1. 节省内存空间:相比使用哈希表等数据结构,布隆过滤器通常需要更少的内存空间,因为它不存储实际的元素,而只存储元素的哈希值。如果存储10亿条记录,误差probability为0.001,则只需要 1.67 GB 的内存。相比原来的15G,是已经大大减少了。
  2. 高效查找:布隆过滤器可以在常数时间内快速判断某个元素是否存在于集合中(O(1)),而不需要遍历整个集合。

缺点:

  1. 存在误报率: Bloom filter 在判断某个元素是否存在的时候,存在一定的误报率。这意味着在某些情况下,它可能会误报某个元素存在,但不会误报某个元素不存在。不过一般影响不大。
  2. 不能删除元素:布隆过滤器通常不支持从集合中删除元素,因为删除一个元素会影响其他元素的哈希值,增加误报率。

Redis Bloom filter方案为大数据量下的唯一性验证提供了一种高效的基于内存的解决方案,并且需要消耗一定的内存。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们处理,核实后本网站将在24小时内删除侵权内容。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无休居士

感谢您的支持,我会继续努!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值