通过Redis哈希表进行存储和搜索。

3

我拥有超过1000万数量不断增长的用户,他们都有相对应的电子邮件和电话号码,这两者都指向一个用户ID。我创建了两个散列值。一个是用于电子邮件,另一个是用于电话号码,例如:

//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已经过载,我也想通过这些Hash搜索。比如说我想从email-users哈希中获取电子邮件对应的用户ID。
我发现应该使用ZipList维护哈希表,参见Redis - 存储大型映射(字典)的最佳方式,并将其分成较小的桶,每个桶容量固定为10000个键,如果我将我的1000万用户分成10000个键的桶,那么电子邮件将有约1000个哈希表,电话号码也有1000个哈希表。
我的问题是:我应该将我的用户分成这1000个桶吗?如果是,那么我如何搜索这1000个桶?还是有更好的选择?
附言:我正在使用PHP,获取所有1000个哈希表并循环遍历它们可能非常耗费资源,我担心使用错误的方法也会影响到Redis的实际性能。
另外,我认为我们可以创建一些算法,例如libketama用于一致性哈希,以在随机服务器上放置键。
如果难以处理字母,我们可以先将每个电子邮件转换为数字,例如a=1,b=2,c=3......z=26,并在其后附加0(零)使其成为唯一值,@和.字符可以用+号代替。例如:
abcd@gmail.com  ->  10203040+901301090+3015013

现在我们有数字,这使得应用任何计算更加容易。


电话号码是否总是以“+”开头? - Ersoy
@Ersoy 是的,电话号码始终以 + 开头。 - Airy
2个回答

3
你可以进行字母和数字的分发,根据电子邮件或电话号码的前一位或前两位数字来创建哈希值。
例如:
电子邮件的哈希值:使用前两个字母 电话号码的哈希值:使用前一或前两个数字
生成的键如下:
email-users-aa email-users-ab phone-users-11 phone-users-12
当执行hset/hget操作时,需要在代码级别上进行排序。如果有一个电子邮件地址为ersoy@gmail.com,则会进入email-users-er哈希组中,该组的名称为email-users-er,并执行hget email-users-er ersoy@gmail.com命令。如果有一个电话号码为123456789,则会进入phone-users-12哈希组中,该组的名称为phone-users-12,并执行hget phone-users-12 123456789命令。

那似乎是个好主意。但是对于每个字母顺序,仍然会创建数百个哈希值。而且每次仍需循环遍历所有这些数百个哈希值。这样做是否明智?你的技术肯定会减少开销,但仍将存在一些开销。 - Airy
实际上,在您的代码层面上,您将检查电话号码的第一个数字,比如说它是1,然后您将转到电话用户组1-如果您的电子邮件以 ar 开头,则会转到电子邮件用户组 ar。这样,您就不需要迭代每个哈希组。 - Ersoy
1
@Ersoy,我在我的答案中已经回答了那个问题。您可以根据某些哈希算法将密钥分配到桶中,这将始终产生相同的桶,给定密钥。这将消除哪个密钥添加到哪个桶的不必要的簿记。 - kayvis
1
@Ersoy,使用您的方法,您不认为由于电子邮件ID中字母的分布,有些桶可能比其他桶“更满”吗?我的意思是,有些组合比其他组合更有可能出现:例如jo(hn)与xz。在我看来,均匀地分配密钥到各个桶中会更好。 - kayvis
1
@Ersoy,哈哈,当然可以。按照这个论点,为什么不将所有电子邮件ID哈希到单个哈希中,而不是拆分成桶,因为它仍然是O(1)? ;) 这个练习的整个重点是看看我们是否可以以任何方式进行优化。不客气,也谢谢您的评论 :) 祝您有愉快的一天! - kayvis
显示剩余14条评论

1

我的问题是,我应该将我的用户分成这1000个桶吗?如果是的话,我如何搜索这1000个桶?或者有更好的替代方案吗?

是的。可以按以下方式进行。

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

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

For Email Ids: email_0001, email_0002, ..., email_1000
For Phone Numbers: phone_0001, phone_0002, ..., phone_1000
  1. Given an email Id, determine the bucket (max being 1000) by hashing the email Id. You can use consistent hashing for this purpose. Now add the key and value to the appropriate 'bucket'.

    $ HSET "email_0032" "abc@xyz.com" "UID_987"
    
  2. Repeat step 1 for phone numbers. This prevents you from the need of bookeeping which key goes into which bucket. Given the same key, the hash will always give the same value thereby returning the same bucket number.

    $ HSET "phone_0091" "+192938384849" "UID_987"
    
  3. To retrieve a value, first find the bucket by hashing the email/phone and then looking up the value in appropriate bucket.

    $ 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并查看其效果如何。 - Airy
请注意,正如问题中所提到的,我们可能会有N个桶,但每个桶最多可以有10000个条目。 - Airy
@Airy,我们在你提到的那一行中想要实现的是使用给定的输入(电子邮件/电话)找到桶号(在1到1000的范围内)。每个桶都是一个Redis哈希,名为email_0001、email_0002等。现在你知道了键会被添加到哪个桶中(使用pickBucket()),你可以直接使用“hget <bucket> <key>”从该桶中查找值。这回答了你的问题吗? - kayvis
@Airy,你的代码中没有提到这一点。你需要将其设置为配置。参考:https://www.peterbe.com/plog/understanding-redis-hash-max-ziplist-entries和https://redis.io/commands/config-get。这意味着,Redis中的所有哈希都将使用“特殊”编码,直到10k个键。一旦溢出,Redis将把“特殊编码”的哈希转换为普通哈希。 - kayvis
让我们在聊天中继续这个讨论 - kayvis
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接