如何在十亿级别用户中检查用户名是否存在?

2023-10-30 16:21

本文主要是介绍如何在十亿级别用户中检查用户名是否存在?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

不知道大家有没有留意过,在使用一些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 poolstatic {JedisPoolConfig poolConfig = new JedisPoolConfig();jedisPool = new JedisPool(poolConfig, REDIS_HOST, REDIS_PORT);}// Method to check if a username is unique using the Redis cachepublic static boolean isUsernameUnique(String username) {try (Jedis jedis = jedisPool.getResource()) {// Check if the username exists in the Redis cacheif (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 cachepublic static void addToCache(String username) {try (Jedis jedis = jedisPool.getResource()) {jedis.sadd("usernames", username); // Add the username to the cache setjedis.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 poolpublic 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

布隆过滤器方案

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

那究竟什么布隆过滤器呢?
布隆过滤器的原理(二进制 + 哈希函数)
假设布隆过滤器由 20位二进制、 3个哈希函数组成,每个元素经过哈希函数处理都能生成一个索引位置。

布隆过滤器的基础操作有两个:添加、查询

添加元素: 将每一个哈希函数生成的索引位置都设为 1

查询元素是否存在:
如果有一个哈希函数生成的索引位置不为 1,就代表不存在(100%准确)
如果每一个哈希函数生成的索引位置都为 1,就代表存在(存在一定的误判率)

在这里插入图片描述
添加、查询的时间复杂度都是:O(k) ,k 是哈希函数的个数
空间复杂度是:O(m) ,m 是二进制位的个数
布隆过滤器的误判率(公式)
误判率 p 受 3 个因素影响:二进制位的个数 m、哈希函数的个数 k、数据规模 n。

误判率 p 的公式:
在这里插入图片描述
已知误判率 p、数据规模 n,求二进制位的个数 m、哈希函数的个数 k:

二进制位的个数 m:
在这里插入图片描述
哈希函数的个数 k:
在这里插入图片描述

总结:

布隆过滤器(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))快速查找一个元素是否存在于集合中,无需遍历整个集合。
缺点:

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

这篇关于如何在十亿级别用户中检查用户名是否存在?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/309146

相关文章

Python版本与package版本兼容性检查方法总结

《Python版本与package版本兼容性检查方法总结》:本文主要介绍Python版本与package版本兼容性检查方法的相关资料,文中提供四种检查方法,分别是pip查询、conda管理、PyP... 目录引言为什么会出现兼容性问题方法一:用 pip 官方命令查询可用版本方法二:conda 管理包环境方法

java中判断json key是否存在的几种方法

《java中判断jsonkey是否存在的几种方法》在使用Java处理JSON数据时,如何判断某一个key是否存在?本文就来介绍三种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的... 目http://www.chinasem.cn录第一种方法是使用 jsONObject 的 has 方法

Spring Boot分层架构详解之从Controller到Service再到Mapper的完整流程(用户管理系统为例)

《SpringBoot分层架构详解之从Controller到Service再到Mapper的完整流程(用户管理系统为例)》本文将以一个实际案例(用户管理系统)为例,详细解析SpringBoot中Co... 目录引言:为什么学习Spring Boot分层架构?第一部分:Spring Boot的整体架构1.1

k8s admin用户生成token方式

《k8sadmin用户生成token方式》用户使用Kubernetes1.28创建admin命名空间并部署,通过ClusterRoleBinding为jenkins用户授权集群级权限,生成并获取其t... 目录k8s admin用户生成token创建一个admin的命名空间查看k8s namespace 的

MySQL使用EXISTS检查记录是否存在的详细过程

《MySQL使用EXISTS检查记录是否存在的详细过程》EXISTS是SQL中用于检查子查询是否返回至少一条记录的运算符,它通常用于测试是否存在满足特定条件的记录,从而在主查询中进行相应操作,本文给大... 目录基本语法示例数据库和表结构1. 使用 EXISTS 在 SELECT 语句中2. 使用 EXIS

SpringBoot日志级别与日志分组详解

《SpringBoot日志级别与日志分组详解》文章介绍了日志级别(ALL至OFF)及其作用,说明SpringBoot默认日志级别为INFO,可通过application.properties调整全局或... 目录日志级别1、级别内容2、调整日志级别调整默认日志级别调整指定类的日志级别项目开发过程中,利用日志

JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法

《JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法》:本文主要介绍JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法,每种方法结合实例代码给大家介绍的非常... 目录引言:为什么"相等"判断如此重要?方法1:使用some()+includes()(适合小数组)方法2

如何通过try-catch判断数据库唯一键字段是否重复

《如何通过try-catch判断数据库唯一键字段是否重复》在MyBatis+MySQL中,通过try-catch捕获唯一约束异常可避免重复数据查询,优点是减少数据库交互、提升并发安全,缺点是异常处理开... 目录1、原理2、怎么理解“异常走的是数据库错误路径,开销比普通逻辑分支稍高”?1. 普通逻辑分支 v

Linux实现查看某一端口是否开放

《Linux实现查看某一端口是否开放》文章介绍了三种检查端口6379是否开放的方法:通过lsof查看进程占用,用netstat区分TCP/UDP监听状态,以及用telnet测试远程连接可达性... 目录1、使用lsof 命令来查看端口是否开放2、使用netstat 命令来查看端口是否开放3、使用telnet

SpringBoot集成EasyExcel实现百万级别的数据导入导出实践指南

《SpringBoot集成EasyExcel实现百万级别的数据导入导出实践指南》本文将基于开源项目springboot-easyexcel-batch进行解析与扩展,手把手教大家如何在SpringBo... 目录项目结构概览核心依赖百万级导出实战场景核心代码效果百万级导入实战场景监听器和Service(核心