随着我们的应用日益流行,不断增长的用户群带来了越来越多的签到。我们决定对此进行一些限制,只允许每个用户对每个位置给出特定星级评分一次。例如,如果用户 100 在位置 73 签到并评了 3 星,我们将拒绝他们在该位置进行的任何进一步的 3 星签到。
为了实现这一点,我们需要一种方法来记住每次签到,并快速确定我们是否之前见过它。我们不能通过查询签到流中的数据来实现,因为流不允许这种访问,并且会定期修剪,删除已处理且不再需要的旧签到。
我们可以将签到表示为 <userId>:<locationId>:<rating>
的形式。根据此模式,字符串 100733 代表用户 100 在位置 73 的 3 星签到。
然后我们需要记住每次签到,以确保它是用户 ID、位置 ID 和星级评分的唯一组合。我们可以为此使用 Redis Set。每当我们需要维护一组唯一成员时,Set 是一个很好的选择,因为它们不允许重复。使用 Redis Set,添加新成员和检查成员是否在 Set 中都是 O(1) 操作,这意味着它们的性能不会随着 Set 的增长而下降。
然而,我们添加的每个 Set 新成员(在我们的例子中是唯一的签到)都会导致 Set 在 Redis 服务器上占用更多内存。随着我们继续接收越来越多的签到,这种增长将成为一个问题。
但是,如果有一种方法可以检查潜在的新成员是否已存在于没有此内存消耗问题的集合中呢?布隆过滤器 (Bloom Filter) 是一种空间效率高的概率数据结构,可以在这里提供帮助。布隆过滤器存储 Set 成员的哈希表示,而不是实际成员数据本身。与 Set 不同,我们无法从布隆过滤器中获取成员,但我们可以测试某物是否已存在其中……由于哈希冲突可能会产生一些误报。当被问及某物是否是 Set 的成员时,布隆过滤器可以告诉我们“不是”,或者“很可能就是”。
这种哈希方法牺牲了使用 Set 可以获得的 100% 准确性,以显著减少内存开销。布隆过滤器可以配置期望的可接受错误率,因此对于我们的应用而言,这似乎是实施“无重复签到”规则且不会导致内存消耗失控的好方法。每当布隆过滤器告诉我们可能之前见过某个签到时,它大多是正确的,并且我们会接受有时我们会拒绝一个实际上之前未曾有的签到,以此作为控制内存使用的合理权衡。
Redis Stack 为 Redis 提供了布隆过滤器实现,以及其他有用的概率数据结构。在视频中,您将看到在 Node.js 应用中使用它有多么容易,无需任何数学技能!
在本次练习中,您将通过尝试多次向系统提交相同的签到,亲身体验布隆过滤器的工作原理。
您需要运行签到接收服务……如果它仍在上次练习中运行,请按 Ctrl-C 停止它。然后,使用以下命令重启它。此命令将禁用本次练习中不需要的登录要求
$ npm run checkinreceiver
> [email protected] checkinreceiver
> node ./src/checkinreceiver.js
info: Authentication disabled, checkins do not require a valid user session.
info: Checkin receiver listening on port 8082.
现在,打开 Postman 并创建一个新请求,选择 "POST" 作为 HTTP 动词。
localhost:8082/api/checkin
{ "userId": 100, "locationId": 73, "starRating": 3 }
您的请求应该看起来像这样
单击“Send”将您的签到提交到签到接收器,它应该响应 202 Accepted 状态和空响应体
第二次单击“Send”,您应该会收到来自签到接收器的 422 Unprocessable Entity 响应以及一条错误消息
在签到接收服务仍在运行时,启动生成随机签到的签到生成器工具
node-js-crash-course $ npm run checkingenerator
> [email protected] checkingenerator
> node ./src/checkingenerator.js
info: Started checkin generator.
让签到生成器继续运行。它每隔几秒会生成一个新的随机签到。让它运行并生成几百个签到。在它运行的同时,定期使用 redis-cli 或 RedisInsight 中的 CLI 选项卡监视布隆过滤器所需的内存使用量
127.0.0.1:6379> bf.info ncc:checkinfilter
1) Capacity
2) (integer) 1000000
3) Size
4) (integer) 2576760
5) Number of filters
6) (integer) 1
7) Number of items inserted
8) (integer) 269
9) Expansion rate
10) (integer) 2
随着更多签到被生成,多次运行此命令,并注意布隆过滤器所需的存储空间大小不会随着插入项目数量的增加而增加。虽然牺牲了一些准确性,但布隆过滤器对于此类用例而言是一种存储高效的解决方案。
在此视频中,Guy Royse 解释了什么是布隆过滤器以及如何在 Redis 中使用它们
Redis Sets 是一个需要了解的强大数据类型,通过 Andrew 在 Redis University YouTube 频道上的两个视频了解更多信息。首先是《Redis Sets 讲解》
接下来是《Redis Sets 深入》