【问题标题】:Storing and Searching through Redis Hash通过 Redis Hash 存储和搜索
【发布时间】:2020-04-22 11:06:03
【问题描述】:

我有大约 1000 万 + 越来越多的用户使用电子邮件和电话号码。两者都指向一个用户 ID。我创建了 2 个哈希。一个用于电子邮件,另一个用于电话号码,例如

//A single user with Both Email and Phone number pointing to same User ID
$redis->hSet('email-users', 'abc@xyz.com', 1);
$redis->hSet('phone-users', '+192938384849', 1);

现在大约有数百万用户,Hash 正在变得超载,我也想搜索这些哈希。就像我想从电子邮件用户哈希的电子邮件中获取用户 ID。

我发现哈希应该使用Redis — best way to store a large map (dictionary) 的 ZipList 维护,并分成固定大小的较小存储桶,例如单个哈希中最多 10000 个键。

因此,如果我将我的 1000 万用户划分为包含 10000 个键的存储桶,那么电子邮件的哈希值大约为 1000,电话号码的哈希值约为 1000。

我的问题是,我应该将我的用户分成这 1000 个桶吗?如果是,那么我该如何搜索这 1000 个存储桶?还是有更好的选择?

附:我正在使用 PHP 并获取所有 1000 个哈希并遍历它们可能会占用大量资源,而且我担心使用错误的方法也会降低 Redis Power 的实际性能。

顺便说一句,我认为我们可以创建一些像 libketama 这样的算法来实现一致的散列,从而将密钥放置在随机服务器中。

此外,如果很难处理字母表,我们可以先将每封电子邮件转换为数字,例如 a=1、b=2、c=3 ... z=26,并附加 0(零)以使其唯一并且+s 表示 @ 和 .人物。例如

abcd@gmail.com  ->  10203040+901301090+3015013

所以,现在我们有了可以更轻松地应用任何计算的数字。

【问题讨论】:

  • 电话号码总是以+ 开头吗?
  • @Ersoy 是的,电话号码总是以 + 开头

标签: php hash redis pagination


【解决方案1】:

你可以做的是根据第一个或前几个字母/数字来分配字母和数字。

你可以像这样创建你的哈希值;电子邮件第一个字母、电话号码在前或前两位数

  • 电子邮件用户-a
  • 电子邮件用户-b
  • 电子邮件用户-c
  • phone-users-10
  • phone-users-11

当您执行 hset/hget 时,您可以在代码级别进行安排。

编辑:

假设我们将使用first two digits 作为电话号码,使用first two letters 作为电子邮件;

然后我们将有如下键;

  • 电子邮件用户-aa
  • 电子邮件用户-ab
  • phone-users-11
  • phone-users-12

当我们收到ersoy@gmail.com 这样的电子邮件时,我们将转到er 电子邮件哈希组email-users-er 并执行hget email-users-er ersoy@gmail.com

当我们有像123456789 这样的电话号码时,我们将转到12 电话哈希组phone-users-12 并执行hget phone-users-12 123456789

【讨论】:

  • 这似乎是个好主意。但它仍会为每个 alpha 订单创建大约数百个哈希值。而且我每次仍然必须遍历所有这些数百个哈希值。这会是个好主意吗。你的技术肯定会减少开销,但它仍然存在
  • 实际上在您的代码级别中,您将检查电话的第一个数字,假设它是 1,然后您将转到 phone-users-1 - 如果您的电子邮件以 ar 开头,那么您将转到 email-用户-ar。所以你不会迭代每个哈希组。
  • @Ersoy,我已经在回答中回答了这个问题。您可以根据某种散列算法将密钥分配到存储桶中,该算法将始终在给定密钥的情况下产生相同的存储桶。这将消除关于将哪个密钥添加到哪个存储桶的不必要的簿记。
  • @Ersoy,使用您的方法,您不认为由于电子邮件 ID 中字母的分布,某些存储桶可能比其他存储桶“更满”吗?我的意思是有些人比其他人更有可能在一起:jo(hn) vs xz。恕我直言,在存储桶中均匀分配密钥会更好。
  • @Ersoy,哈哈,当然。根据这个论点,为什么不将所有电子邮件 ID 散列到单个散列中,而不是拆分成桶,因为它仍然是 O(1)? ;) 这个练习的重点是看看我们是否可以以任何方式进行优化。不客气,也感谢您的 cmets :) 祝您有美好的一天!
【解决方案2】:

我的问题是,我应该将我的用户分成这 1000 个桶吗?和 如果是,那么我该如何搜索这 1000 个存储桶?或者有没有 更好的选择?

是的。该方法可以通过以下方式起作用。

对于此示例,我们将电话号码和电子邮件 ID 都视为字符串。

假设您有以下存储桶(Redis Hash):

For Email Ids: email_0001, email_0002, ..., email_1000
For Phone Numbers: phone_0001, phone_0002, ..., phone_1000
  1. 给定一个电子邮件 ID,通过散列电子邮件 ID 来确定存储桶(最大值为 1000)。为此,您可以使用一致的散列。现在将键和值添加到适当的“桶”中。

    $ HSET "email_0032" "abc@xyz.com" "UID_987"
    
  2. 对电话号码重复第 1 步。这可以防止您需要记录哪个密钥进入哪个存储桶。给定相同的键,哈希将始终给出相同的值,从而返回相同的桶号。

    $ HSET "phone_0091" "+192938384849" "UID_987"
    
  3. 要检索一个值,首先通过散列电子邮件/电话找到存储桶,然后在适当的存储桶中查找该值。

    $ HGET "phone_0091" "+192938384849"
      UID_987
    
import java.nio.charset.Charset;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;

public class Sample {

    private static final int BUCKET_SIZE = 1000;
    private static final HashFunction hashFunction = Hashing.murmur3_128();
    private static final Charset UTF8 = Charset.forName("UTF-8");

    private Sample() {
    }

    public static int pickBucket(String key, int buckets) {
        int bucket = com.google.common.hash.Hashing.consistentHash(hashFunction.hashString(key, UTF8).asLong(), buckets);
        return bucket;
    }

    private static void getFromRedisHash(String key) {

        int bucket = pickBucket(key, BUCKET_SIZE);
        // Get From Redis based on the bucket number
    }

    public static void main(String[] args) {

        System.out.println(pickBucket("abc@xyz.com", BUCKET_SIZE));
        System.out.println(pickBucket("+192938384849", BUCKET_SIZE));
    }
}

上面的例子是用 Java 编写的,我假设 PHP 会有类似的哈希库。

【讨论】:

  • 抱歉回复晚了。我刚刚浏览了您的回答以及您在回答中与 Ersoy 进行的所有讨论。而且您非常了解我的问题,特别是关于 1000 个键的限制。请让我把它转成 PHP 看看效果如何。
  • 卡维斯!由于我对 Java 的了解不够,您能否通过将桶大小设置为 1000 来详细说明 com.google.common.hash.Hashing.consistentHash 行发生了什么?我的意思是它是限制哈希创建最多 1000 个桶,还是告诉每个哈希在每个桶中最多有 1000 个条目?
  • 请注意,问题中提到的一个问题是我们可以有 N 个桶,但每个桶最多可以有 10000 个条目
  • @Airy,我们在您提到的行中试图实现的是使用给定的输入(电子邮件/电话)找到存储桶编号(在 1 到 1000 的范围内)。每个存储桶都是一个名为 email_0001、email_0002 等的 Redis Hash。既然您知道密钥将被添加到哪个存储桶(使用 pickBucket()),您可以使用“hget ”直接从该存储桶中查找值。这能回答你的问题吗?
  • @Airy,注册。另一个问题,是的,假设每个桶最多可以有 10k 个条目。您必须设置该配置。详细信息可以在这里找到:redis.io/topics/memory-optimization。这是为了确保哈希性能处于最佳状态。一旦我们超过了这个限制,“特殊”编码将被删除,并且哈希将被 Redis 转换为正常编码。这在该链接的第 3 段中有说明。由该链接提供:Ersoy。与往常一样,建议您在使用和不使用配置的情况下进行测试,以确保获得最佳性能。
猜你喜欢
  • 1970-01-01
  • 2015-04-27
  • 1970-01-01
  • 2011-09-06
  • 1970-01-01
  • 1970-01-01
  • 2017-07-17
  • 1970-01-01
  • 2011-11-17
相关资源
最近更新 更多