首页
开发技巧正文内容

如何检查一个用户名是否存在于十亿用户中

2024年08月31日
阅读时长 2 分钟
阅读量 3
如何检查一个用户名是否存在于十亿用户中

前言

我想知道大家是否注意到,在注册一些应用时,会提示您用户名已被占用,需要更改。

现在让我们逐个查看每种方案的优缺点。

数据库方案

这种方法带来以下问题:

  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 哈希映射名称,用于存储用户信息  
    private static final String USER_HASH_NAME = "users";  
  
    public static void main(String[] args) {  
        // 创建一个 Redisson 客户端  
        RedissonClient redisson = createRedissonClient();  
  
        // 检索用于存储用户信息的哈希映射  
        RMap<String, String> users = redisson.getMap(USER_HASH_NAME);  
  
        // 向哈希映射中添加用户  
        users.put("user123", "someUserInfo"); // 这里的 "someUserInfo" 可以是 JSON 字符串、UUID 等  
  
        // 检查用户是否存在  
        boolean exists = users.containsKey("user123");  
        System.out.println("用户 'user123' 是否存在?" + exists);  
  
        // 检查一个不存在的用户  
        exists = users.containsKey("user456");  
        System.out.println("用户 'user456' 是否存在?" + exists);  
  
        // 关闭 Redisson 客户端  
        redisson.shutdown();  
    }  
  
    // 创建 Redisson 客户端的辅助方法  
    private static RedissonClient createRedissonClient() {  
        Config config = new Config();  
        config.useSingleServer()  
                .setAddress("redis://127.0.0.1:6379") // 根据您的 Redis 地址进行调整  
                .setPassword("yourpassword"); // 如果有密码,请提供您的 Redis 密码  
  
        return Redisson.create(config);  
    }  
}

这种解决方案的最大问题是内存使用过多。假设每个用户名需要大约 15 字节的内存。如果要存储十亿个用户名,您将需要 15GB 的内存。

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

布隆过滤器方案

如果直接缓存判断结果导致内存使用过多,是否有更好的方法?布隆过滤器是一个非常好的选择。

什么是布隆过滤器?

布隆过滤器是一种高度空间效率的随机数据结构。它使用位数组来简洁地表示一个集合,并可以确定一个元素是否属于这个集合。布隆过滤器的高效性是以一定的代价为代价的:在确定一个元素是否属于某个集合时,有可能错误地将一个不属于该集合的元素视为属于该集合(误报)。因此,布隆过滤器不适用于“零错误”的应用场景。然而,在可以容忍低错误率的应用场景中,布隆过滤器通过极少的错误实现了存储空间的显著节省。

结构

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

位数组,每个位最初为 0

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

搜索过程类似于插入过程。同样,使用 k 个哈希函数对要搜索的值进行哈希。只有当哈希得到的每个位的值都为 1 时,才表明该值“可能”真实存在;反之,如果任何位的值为 0,则表明该值必定不存在。例如,y1 必定不存在;而 y2 可能存在。

Redis 本身支持布隆过滤器的数据结构。让我们简单地使用 Redisson 客户端实现代码:

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

public class UserExistenceChecker {

    // Redis 中布隆过滤器的名称
    private static final String BLOOM_FILTER_NAME = "user_existence_filter";

    public static void main(String[] args) {
        // 创建一个 Redisson 客户端
        RedissonClient redisson = createRedissonClient();

        // 检索或创建一个布隆过滤器实例
        // 预期元素数量和误报概率是参数
        RBloomFilter<String> bloomFilter = redisson.getBloomFilter(BLOOM_FILTER_NAME);
        bloomFilter.tryInit(100000L, 0.001); // 使用预期元素和误报率初始化布隆过滤器

        // 向布隆过滤器中添加用户
        bloomFilter.add("user123");

        // 检查用户是否存在
        boolean exists = bloomFilter.contains("user123"); // 应返回 true
        System.out.println("用户 'user123' 是否存在?" + exists);

        // 检查一个不存在的用户(由于布隆过滤器的特性,可能错误地报告为 true)
        exists = bloomFilter.contains("user456"); // 假设未添加,理想情况下应返回 false,但可能是误报
        System.out.println("用户 'user456' 是否存在?" + exists);

        // 关闭 Redisson 客户端
        redisson.shutdown();
    }

    // 创建 Redisson 客户端的辅助方法
    private static RedissonClient createRedissonClient() {
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://127.0.0.1:6379"); // 根据您的 Redis 地址进行调整
//                .setPassword("yourpassword"); // 如果有密码,请提供您的 Redis 密码

        return Redisson.create(config);
    }
}

优点:

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

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

缺点:

  • 存在误报率: 当布隆过滤器确定元素是否存在时,存在一定的误报率。这意味着在某些情况下,它可能错误地报告某个元素存在,但不会错误地报告某个元素不存在。然而,这通常影响不大。

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

总结

Redis 布隆过滤器方案为大数据量下的唯一性验证提供了高效的基于内存的解决方案,但需要消耗内存。

免责声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

相关文章

探索多种软件架构模式及其实用应用
2024年11月22日19:06
本文深入探讨了多种软件架构模式,包括有界上下文、边车模式、发布-订阅模式、应用网关、微服务、命令职责分离(CQRS)等,介绍了它们的优点、使用场景以及具体应用实例。文章强调根据具体项目需求和团队能力选择最合适的架构,以构建高效和可维护的解决方案,同时展示了各架构模式间的综合应用,提供了丰富的案例和技术细节。
15个高级Python快捷键助您更快编程
2024年11月21日07:02
本文分享了 15 个高级的 Python 编程快捷键,包括上下文管理器、行内字典合并、函数参数解包、链式比较、dataclasses、海象运算符、反转列表、备忘录缓存、splitlines、enumerate、字典推导、zip 用于并行迭代、itertools.chain 扁平化列表、functools.partial 部分函数和 os.path 文件路径管理等,帮助开发者提高编程效率和代码简洁性。
揭示网页开发的 11 个迷思:停止相信这些误区
2024年11月19日22:05
网页开发充满误解,这篇博文针对11个常见迷思进行揭秘。包括网站开发后不需更新、需掌握所有技术、AI会取代开发者等。强调持续学习、专业化、用户体验的重要性,澄清误区如多任务处理的必要性和最新技术的必需性。文章提醒开发者注重实用而非追求完美代码,以务实态度面对开发工作。
你知道 CSS 的四种 Focus 样式吗?
2024年11月18日21:41
本文介绍了四种 CSS focus 样式::focus、:focus-visible、:focus-within 以及自定义的 :focus-visible-within,帮助提升网站用户体验。:focus 样式应用于被选中元素;:focus-visible 仅在键盘导航时显示;:focus-within 用于父元素;自定义 :focus-visible-within 结合两者效果。合理运用这些样式能使网站更方便键盘用户导航。
利用 Python 实现自动化图像裁剪:简单高效的工作流程
2024年11月11日20:49
使用 Python 和 OpenCV 自动裁剪图像,轻松实现 16:9 的完美构图。这个指南介绍了如何通过代码进行灰度化、模糊处理和边缘检测,最终识别出最重要的部分进行裁剪。特别适合需要批量处理图像的情况,节省大量时间。
每位资深前端开发人员都应了解的 TypeScript 高级概念
2024年11月11日02:07
资深前端开发者应了解 TypeScript 的高级概念,如联合类型、交叉类型、类型保护、条件类型、映射类型、模板字面量类型和递归类型。这些特性可提升代码的可维护性和可扩展性,确保在开发复杂应用时实现更高的类型安全性和效率。