我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 Redis-IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

不知道大家有没有留意过,在使用一些app注册的时候,提示你用户名已经被占用了,需要更换一个,这是如何实现的呢?你可能想这不是很简单吗,去数据库里查一下有没有不就行了吗,那么假如用户数量很多,达到数亿级别呢,这又该如何是好?

数据库方案

第一种方案就是查数据库的方案,大家都能够想到,代码如下:

public class UsernameUniquenessChecker {
    private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database";
    private static final String DB_USER = "your_username";
    private static final String DB_PASSWORD = "your_password";

    public static boolean isUsernameUnique(String username) {
        try (Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD)) {
            String sql = "SELECT COUNT(*) FROM users WHERE username = ?";
            try (PreparedStatement stmt = conn.prepareStatement(sql)) {
                stmt.setString(1, username);
                try (ResultSet rs = stmt.executeQuery()) {
                    if (rs.next()) {
                        int count = rs.getInt(1);
                        return count == 0; // If count is 0, username is unique
                    }
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return false; // In case of an error, consider the username as non-unique
    }

    public static void main(String[] args) {
        String desiredUsername = "new_user";
        boolean isUnique = isUsernameUnique(desiredUsername);
        if (isUnique) {
            System.out.println("Username '" + desiredUsername + "' is unique. Proceed with registration.");
        } else {
            System.out.println("Username '" + desiredUsername + "' is already in use. Choose a different one.");
        }
    }
}

这种方法会带来如下问题:

  1. 性能问题,延迟高 如果数据量很大,查询速度慢。另外,数据库查询涉及应用程序服务器和数据库服务器之间的网络通信。建立连接、发送查询和接收响应所需的时间也会导致延迟。

  2. 数据库负载过高。频繁执行 SELECT 查询来检查用户名唯一性,每个查询需要数据库资源,包括CPU和I/O。

  1. 可扩展性差。数据库对并发连接和资源有限制。如果注册率继续增长,数据库服务器可能难以处理数量增加的传入请求。垂直扩展数据库(向单个服务器添加更多资源)可能成本高昂并且可能有限制。

缓存方案

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

public class UsernameCache {

    private static final String REDIS_HOST = "localhost";
    private static final int REDIS_PORT = 6379; 
    private static final int CACHE_EXPIRATION_SECONDS = 3600; 

    private static JedisPool jedisPool;

    // Initialize the Redis connection pool
    static {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        jedisPool = new JedisPool(poolConfig, REDIS_HOST, REDIS_PORT);
    }

    // Method to check if a username is unique using the Redis cache
    public static boolean isUsernameUnique(String username) {
        try (Jedis jedis = jedisPool.getResource()) {
            // Check if the username exists in the Redis cache
            if (jedis.sismember("usernames", username)) {
                return false; // Username is not unique
            }
        } catch (Exception e) {
            e.printStackTrace();
            // Handle exceptions or fallback to database query if Redis is unavailable
        }
        return true; // Username is unique (not found in cache)
    }

    // Method to add a username to the Redis cache
    public static void addToCache(String username) {
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.sadd("usernames", username); // Add the username to the cache set
            jedis.expire("usernames", CACHE_EXPIRATION_SECONDS); // Set expiration time for the cache
        } catch (Exception e) {
            e.printStackTrace();
            // Handle exceptions if Redis cache update fails
        }
    }

    // Cleanup and close the Redis connection pool
    public static void close() {
        jedisPool.close();
    }
}

这个方案最大的问题就是内存占用过大,假如每个用户名需要大约 20 字节的内存。你想要存储10亿个用户名的话,就需要20G的内存。

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

布隆过滤器方案

直接缓存判断内存占用过大,有没有什么更好的办法呢?布隆过滤器就是很好的一个选择。

那究竟什么布隆过滤器呢?

布隆过滤器Bloom Filter)是一种数据结构,用于快速检查一个元素是否存在于一个大型数据集中,通常用于在某些情况下快速过滤掉不可能存在的元素,以减少后续更昂贵的查询操作。布隆过滤器的主要优点是它可以提供快速的查找和插入操作,并且在内存占用方面非常高效。

具体的实现原理和数据结构如下图所示:

布隆过滤器的核心思想是使用一个位数组(bit array)和一组哈希函数。

  • 位数组(Bit Array) :布隆过滤器使用一个包含大量位的数组,通常初始化为全0。每个位可以存储两个值,通常是0或1。这些位被用来表示元素的存在或可能的存在。

  • 哈希函数(Hash Functions) :布隆过滤器使用多个哈希函数,每个哈希函数可以将输入元素映射到位数组的一个或多个位置。这些哈希函数必须是独立且具有均匀分布特性。

那么具体是怎么做的呢?

  • 添加元素:如上图所示,当将字符串“xuyang”,“alvin”插入布隆过滤器时,通过多个哈希函数将元素映射到位数组的多个位置,然后将这些位置的位设置为1。

  • 查询元素:当要检查一个元素是否存在于布隆过滤器中时,通过相同的哈希函数将元素映射到位数组的相应位置,然后检查这些位置的位是否都为1。如果有任何一个位为0,那么可以确定元素不存在于数据集中。但如果所有位都是1,元素可能存在于数据集中,但也可能是误判。

本身redis支持布隆过滤器的数据结构,我们用代码简单实现了解一下:

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class BloomFilterExample {
    public static void main(String[] args) {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        JedisPool jedisPool = new JedisPool(poolConfig, "localhost", 6379);

        try (Jedis jedis = jedisPool.getResource()) {
            // 创建一个名为 "usernameFilter" 的布隆过滤器,需要指定预计的元素数量和期望的误差率
            jedis.bfCreate("usernameFilter", 10000000, 0.01);
            
            // 将用户名添加到布隆过滤器
            jedis.bfAdd("usernameFilter", "alvin");
            
            // 检查用户名是否已经存在
            boolean exists = jedis.bfExists("usernameFilter", "alvin");
            System.out.println("Username exists: " + exists);
        }
    }
}

在上述示例中,我们首先创建一个名为 "usernameFilter" 的布隆过滤器,然后使用 bfAdd 将用户名添加到布隆过滤器中。最后,使用 bfExists 检查用户名是否已经存在。

优点:

  • 节约内存空间,相比使用哈希表等数据结构,布隆过滤器通常需要更少的内存空间,因为它不存储实际元素,而只存储元素的哈希值。如果以 0.001 误差率存储 10 亿条记录,只需要 1.67 GB 内存,对比原来的20G,大大的减少了。

  • 高效的查找, 布隆过滤器可以在常数时间内(O(1))快速查找一个元素是否存在于集合中,无需遍历整个集合。

缺点:

  • 误判率存在:布隆过滤器在判断元素是否存在时,有一定的误判率。这意味着在某些情况下,它可能会错误地报告元素存在,但不会错误地报告元素不存在。

  • 不能删除元素:布隆过滤器通常不支持从集合中删除元素,因为删除一个元素会影响其他元素的哈希值,增加了误判率。

总结

Redis 布隆过滤器的方案为大数据量下唯一性验证提供了一种基于内存的高效解决方案,它需要在内存消耗和错误率之间取得一个平衡点。当然布隆过滤器还有更多应用场景,比如防止缓存穿透、防止恶意访问等。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

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

不知道大家有没有留意过,在使用一些app注册的时候,提示你用户名已经被占用了,需要更换一个,这是如何实现的呢?你可能想这不是很简单吗,去数据库里查一下有没有不就行了吗,那么假如用户数量很多,达到数亿级别呢,这又该如何是好?数据库方案第一种方案就是查数据库的方案,大家都能够想到,代码如下:public clas
面试官问,如何在十亿级别用户中检查用户名是否存在?
2024-04-02

编程热搜

  • mongo入门-基本使用-安装和crud
    在理解MongoDB基础概念后,本文将介绍MongoDB的安装和最基本的CURD操作。Mongo入门 - 基本使用:安装和CRUDMongoDB安装一些参考文档以Linux为例安装连接和建库CRUD操作InsertQueryUpdateDeleteBulkWrite参考文档# MongoDB安装MongoDB的安装比较
    mongo入门-基本使用-安装和crud
  • mongo入门-基本使用-java-api
    本文为低优先级,只是向你介绍下MongoDB提供的原生的JavaAPI;而大多数公司使用Spring框架,会使用Spring Data对MongoDB原生API的封装,比如JPA,MongoTemplate等。Mongo入门 - 基本使用:Java APIMongoDB Driver代码测试# MongoDB Driv
    mongo入门-基本使用-java-api
  • mongo进阶-db核心-索引实现
    为什么需要索引?当你抱怨MongoDB集合查询效率低的时候,可能你就需要考虑使用索引了,为了方便后续介绍,先科普下MongoDB里的索引机制(同样适用于其他的数据库比如mysql)。mongo-9552:PRIMARY> db.person.find(){ "_id"&nb
    mongo进阶-db核心-索引实现
  • mongo进阶-wt引擎-checkpoint原理
    Checkpoint主要有两个目的: 一是将内存里面发生修改的数据写到数据文件进行持久化保存,确保数据一致性;二是实现数据库在某个时刻意外发生故障,再次启动时,缩短数据库的恢复时间,WiredTiger存储引擎中的Checkpoint模块就是来实现这个功能的。Mongo进阶 - WT引擎:checkpoint原理为什么
    mongo进阶-wt引擎-checkpoint原理
  • mongo进阶-db核心-分片sharding
    分片(sharding)是MongoDB通过水平扩展将数据集分布在不同的服务器上来提高自己的存储容量和吞吐量。和MySQL分区方案相比,MongoDB的最大区别在于它几乎能自动完成所有事情,只要告诉MongoDB要分配数据,它就能自动维护数据在不同服务器之间的均衡。Mongo进阶 - DB核心:分片Sharding分片
    mongo进阶-db核心-分片sharding
  • mongo入门-mongodb整体生态
    很多人在学习Mongo时仅仅围绕着数据库功能,围绕着CRUD和聚合操作,但是MongoDB其实已经基本形成了它自身的生态了。我们在学习一项技能时一定要跳出使用的本身,要从高一点的格局上了解整个生态,这样会对你构筑知识体系有很大的帮助。Mongo入门 - MongoDB整体生态整体生态MongoDB ServerMong
    mongo入门-mongodb整体生态
  • mongo入门-基本使用-spring集成
    本文为主要介绍Spring Data对MongoDB原生API的封装,比如Spring-data-mongo,MongoTemplate等。以及原生API和Spring data系列之间的关系。Mongo入门 - 基本使用:Spring集成Spring Data 与 MongoDBSpring Data的层次结构spr
    mongo入门-基本使用-spring集成
  • mongo入门-mongodb基础概念
    在学习MongoDB之前先简单了解相关概念。Mongo入门 - MongoDB基础概念什么是NoSQL?为什么使用NoSQL?NoSQL数据库的简要历史NoSQL的功能什么是MongoDBMongoDB功能为什么使用MongoDBMongoDB常用术语MongoDB与RDBMS区别# 什么是NoSQL?NoSQL是一种
    mongo入门-mongodb基础概念
  • mongo入门-基本使用-效率工具
    本文将主要介绍常用的MongoDB的工具,这些工具可以极大程度的提升你的效率。Mongo入门 - 基本使用:效率工具官方MongoDB CompassNoSQLBoosterRobot3TVs Code plugin其它# 官方MongoDB Compass推荐使用MongoDB Compass,所以详细截几个图给大家
    mongo入门-基本使用-效率工具
  • mongo进阶-db核心-复制集
    在实际的生产环境中,我们需要考虑数据冗余和高可靠性,即通过在不同的机器上保存副本来保证数据的不会因为单点损坏而丢失;能够随时应对数据丢失、机器损坏带来的风险。MongoDB的复制集就是用来解决这个问题的,一组复制集就是一组mongod实例掌管同一个数据集,实例可以在不同的机器上面。实例中包含一个主导,接受客户端所有的写
    mongo进阶-db核心-复制集

目录