问:
    假设有一个移动互联网应用,要实现找到附近具有相同兴趣的人功能。这里面的相同兴趣,指的是具有相同兴趣标签的人。如果一个人身上有多个标签,那只要有一个标签和其他人相同,就算有相同兴趣。

    在这种情况下,我们需要支持以下功能:

    1. 列出附近兴趣相同的人,允许结果为空;
    2. 系统要具备实时性,如果有用户的标签发生变化或者位置发生变化,需要及时在系统中得到体现;
    3. 如果附近兴趣相同的人很多,那么需要将这些人进行排序,需要设计排序方案。

    如果使用我们在进阶实战篇中学到的知识,你会怎么来设计和实现这个功能呢?

    答:
    这是一道开放的设计题,并没有标准答案,但是,我会给你一个参考的解答思路。你可以和你自己的方案进行对比,看看有哪些相同或者不同的地方,这些地方是否合理。下面是具体的解答思路。

    对于第一个问题,其实是有着两个检索维度,一个维度是地理位置,另一个维度是兴趣标签。因此,我们可以将这两个维度进行解耦,分别建立两个索引。

    对于基于地理位置,查询附近的人,我们可以使用区域编码,或者Geohash,将区域进行划分。由于允许检索结果为空,因此,我们可以根据应用的需求,选择合适的区域大小。然后对于所有的区域进行索引构建。我们可以使用跳表,也可以使用倒排索引。在构建好索引以后,针对要查询的区域,进行一次查询即可。如果希望更精准一些,那么可以查询周边的区域。这样,我们就可以得到“附近的人”的列表。

    而对于基于兴趣标签,查找具有相同兴趣的人,我们可以通过以标签为Key,来构建倒排索引。这样,通过查询兴趣标签,我们就能找到“具有相同兴趣”的人的列表。如果一个人身上有多个兴趣标签,那么我们会查询倒排索引多次,得到每个标签对应的posting list,并将它们求并集即可。

    那么,对于“附近的具有相同兴趣的人”,我们将这两个候选集求交集,就可以得到我们期望的结果。

    如果不考虑该应用是否还有其他的和兴趣检索有关的需求,那么为了提高检索效率,我们其实还能将这两个维度的索引层次组合起来。我们可以根据“地理位置”将所有的人进行分片,对于同一个区域里的所有人,我们再根据兴趣标签来为每个区域的人建立倒排索引。这种组合索引结构,你会发现和层次聚类,或者倒排乘积量化的结构有点相似。它们都是通过将不同的索引进行层次组合,使得我们能快速减少减少空间。

    那第二个问题,关于索引更新,在内存都可以加载的情况下,我们可以使用double buffer机制来实现。
    当一个用户的位置发生变化时,如果他的位置区域没变,那么基于地理位置的索引就不用改变;而如果所属区域变化了,那么我们就将这个用户从原有区域列表中删除,然后加入到新的区域列表中。
    而当一个用户的兴趣发生变化时,我们也用同样的操作,去修改兴趣标签的倒排索引。

    第三个问题,要对所有符合条件的人进行打分排序,我们可以考虑一下有哪些重要的因子。在这个应用中,兴趣相似度和距离关系是最重要的因子。
    关于计算两个人的兴趣相似度,其实这个和计算两个文档的相似度非常像。我们可以将每个人看作是一个文档,将每个兴趣标签看作是一个关键词。因此,我们可以使用TF-IDF的思想,或者BM25算法来进行打分。

    而地理位置的距离关系,其实也是很重要的一个指标,我们可以把它当作一个独立的因子,赋予你认为合适的权重,和相关性打分进行累加即可。
    当然,你也可以使用机器学习模型,将所有的因子都考虑进去,用机器学习来学习权重和进行打分。

    详见:
    https://time.geekbang.org/column/article/232713