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

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

前言

  • 不知道大家有没有留意过,在使用一些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.");
            }
        }
    }
    
    
  • 这种方法会带来如下问题:

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

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

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

缓存方案

  • 为了解决数据库调用用户名唯一性检查的性能问题,引入了高效的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 布隆过滤器的方案为大数据量下唯一性验证提供了一种基于内存的高效解决方案,它需要在内存消耗和错误率之间取得一个平衡点。当然布隆过滤器还有更多应用场景,比如防止缓存穿透、防止恶意访问等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/319832.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java获取已经发送谷歌邮件的打开状态

1.前言 现在网上的方案都是在邮件里面插入一张图片的地址,当收件人打开之后,就会发送请求到指定路径的服务器上,然后在请求的controller里面处理邮件的状态,这个方案确实是行得通的,本文章只是给大家避个坑&#xff0…

从传统训练到预训练和微调的训练策略

目录 前言1 使用基础模型训练手段的传统训练策略1.1 随机初始化为模型提供初始点1.2 目标函数设定是优化性能的关键 2 BERT微调策略: 适应具体任务的精妙调整2.1 利用不同的representation和分类器进行微调2.2 通过fine-tuning适应具体任务 3 T5预训练策略: 统一任务形式以提高…

Vue学习计划-Vue3--核心语法(七)pinia

pinia案例gitee地址 1. pinia 准备一个效果 【搭建 pinia 环境】 安装pinia: npm install pinia/yarn add pinia第二步:操作src/main.ts import { createApp } from vue import App from ./App.vue/* 引入createPinia,用于创建pinia */ import { crea…

阿里云优惠券介绍、种类、领取入口及使用教程

阿里云优惠券是阿里云提供的一种优惠活动,旨在帮助用户节省购买云服务产品的费用。本文将为大家详细介绍阿里云优惠券的相关信息,包括优惠券的介绍、种类、领取入口以及使用教程。 一、阿里云优惠券介绍 阿里云优惠券是阿里云提供给用户的一种优惠凭证&…

vue前端开发自学,异步加载组件,提升用户端的客户体验度

vue前端开发自学,异步加载组件,提升用户端的客户体验度!现实项目开发时,组件的数量非常庞大,如果都是一口气加载完,对手机用户来说,体验度会很差。因此,非常有必要使用异步加载。 那就是,用到了…

Neo4j知识图谱(2)创建与删除

Neo4j - CQL简介_w3cschoolhttps://www.w3cschool.cn/neo4j/neo4j_cql_introduction.html一、创建节点 create(n:Person{name:何仙鸟,age:21}) create就是创建,无论是点还是边都是用create来创建 n相当于一个别名,比如创建一个Person,而Pe…

React16源码: React中的schedule调度整体流程

schedule调度的整体流程 React Fiber Scheduler 是 react16 最核心的一部分,这块在 react-reconciler 这个包中这个包的核心是 fiber reconciler,也即是 fiber 结构fiber 的结构帮助我们把react整个树的应用,更新的流程,能够拆成…

JVM基础(10)——老年代调优

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO 联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬 学习必须往深处挖&…

[ACM算法学习] 诱导排序与 SA-IS算法

学习自诱导排序与SA-IS算法 - riteme.site 为了简化一些操作,定义 # 是字典序最小的字符,其字典序小于字母集里任意字符,并且将其默认作为每个字符串的最后一个字符,即作 S[|S|] SA-IS 算法 SA-IS 算法是基于诱导排序这种思想。…

Python基础知识:整理14 利用pyecharts生成地图

1 地图可视化的基本使用 from pyecharts.charts import Map from pyecharts.options import VisualMapOpts # 准备地图对象 map Map()# 准备数据 data [("北京市", 8), ("上海市", 99), ("广州省", 199), ("重庆市", 400), ("…

JavaScript学习笔记——变量、作用域、var、const和let

JavaScript学习笔记——变量、作用域、var、const和let 学习链接(原链接)变量变量声明的三种方式 作用域作用域介绍作用域分类全局作用域局部作用域(函数作用域)块级作用域块级作用域和局部(函数)作用域区别 varvar的作用域(全局函…

在数据中查找峰值

使用 findpeaks 函数求出一组数据中局部最大值的值和位置。 文件 spots_num 包含从 1749 年到 2012 年每年观测到的太阳黑子的平均数量。求出最大值及其出现的年份。将它们与数据一起绘制出来。 load("spots_num")[pks,locs] findpeaks(avSpots);plot(year,avSpots…

ssm基于vue的儿童教育网站的设计与实现论文

摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理,然而,随着近些年信息技术的迅猛发展,让许多比较老套的信息管理模式进行了更新迭代,视频信息因为其管理内容繁杂,管理数量繁多导致手工进行处理不能满足广大…

【26 预处理详解】

目录 预定义符号#define定义常量#define定义宏带有副作用的宏参数宏替换的规则宏函数的对比#和##命名约定#undef命令行定义条件编译头文件的包含其他预处理指令 1. 预定义符号 c语言设置了一些预定义符号,可以直接使用,预定义符号也是在预处理期间处理…

响应式编程初探-自定义实现Reactive Streams规范

最近在学响应式编程,这里先记录下,响应式编程的一些基础内容 1.名词解释 Reactive Streams、Reactor、WebFlux以及响应式编程之间存在密切的关系,它们共同构成了在Java生态系统中处理异步和响应式编程的一系列工具和框架。 Reactive Streams…

ruoyi后台管理系统部署-1-安装JDK

CentOS 7 安装JDK 1. 最简单方法 部署JDK最简单的方法是使用:yum 首先查看原服务器有没有安装 jdk # 没有输出 java -version yum list installed | grep javayum 安装 查看可供安装的jdk: yum search java | grep jdk yum -y list java*应该输出一堆…

C#无标题栏窗体拖动代码

文章目录 一、概念二、案例三、常见问题四、链接 一、概念 C#(C Sharp)是由微软公司开发的一种面向对象的编程语言。它是从C和C语言演化而来的,并结合了Java和其他编程语言的特性。C#是微软.NET平台的一部分,允许开发人员创建各种…

超强站群系统v9.0:最新蜘蛛池优化技术,一键安装,内容无缓存刷新,高效安全

安全、高效,化的优化利用php性能,使得运行流畅稳定 独创内容无缓存刷新不变,节省硬盘。防止搜索引擎识别蜘蛛池 蜘蛛池算法,轻松构建站点(电影、资讯、图片、论坛等等) 可以个性化每个网站的风格、内容、…

YOLOv7涨点改进:多层次特征融合(SDI),小目标涨点明显,| UNet v2,比UNet显存占用更少、参数更少

💡💡💡本文全网独家改进:多层次特征融合(SDI),能够显著提升不同尺度和小目标的识别率 💡💡💡在YOLOv7中如何使用 1)iAFF加入Neck替代Concat; 收录: YOLOv7高阶自研专栏介绍: http://t.csdnimg.cn/tYI0c ✨✨✨前沿最新计算机顶会复现 🚀🚀🚀YOL…

6.2 声音编辑工具GoldWave5简介(7)

6.2.5其它常用功能 1.高低通 把录制的语音和背景音乐融合在一起时,可能会出现背景音乐音量过大,语音音量过小的现象,这时可以选择“低通”将背景音乐的音量降低一些。 (1)选择【效果】|【波波器】|【低通/高通】命令&#xff0…