自动补全最近联系人

本节将实现一个用于记录最近联系人的自动补全程序。为了增加游戏的社交元素,并让用户快速的查找和记忆亲密的玩家,Fake Game 公司正考虑为他们的客户端创建一个联系人列表,并使用这个列表来记录每个用户最近联系过的100个玩家。当用户打算在客户端发起一次聊天并开始输入聊天对象的名字时,自动补全就会根据用户已经输入的文字,列出哪些昵称以输入文字为开头的人。

因为服务器上的数百万用户都需要有一个属于自己的联系人列表来存储最近联系过的100个人,所以我们需要在能快速向列表里面添加用户或者删除用户的前提下,尽管减少存储这些联系人列表带来的内存消耗。因为Redis的列表会以有序的方法来存储元素,并且和Redis提供的其他结构相比,列表占用的内存是最小的,所以我们选择使用列表来存储用户的联系人信息。可惜的是,Redis列表提供的功能并不足以让我们在Redis内部完成自动补全操作,因此实际的自动补全操作将会放到Redis之外的Python里面执行,这种做法使得程序可以尽量减少Redis存储和更新用户最近联系人列表所需的内存数量,并将较为简单的过滤工作交给Python来执行。

构建最近联系人自动补全列表通常需要对Redis执行3个操作。第一个操作就是添加或者更新一个联系人,让他成为最新的被联系用户,这个操作包含以下3个步骤。

  1. 如何制定的联系人已经存在于最近联系人列表里面,那么从列表里面移除他。
  2. 将指定的联系人添加到最近联系人列表的最前面。
  3. 如果在添加操作完成之后,最近联系人列表包含的联系人数量超过了100个,那么对列表进行修剪,只保留位于列表前面的100个联系人。

以上描述的3个操作可以通过依次执行lrem命令、lpush命令和ltrim命令来实现,并且为了确保操作不会带有任何竞争条件,我们会像之前章节介绍的那样,使用由multi命令和exec命令构成的事务包裹起lrem、lpush、ltrim这3个命令。

下面是这个操作的具体代码实现:

def add_update_contact(conn,user,contact):
    ac_list='recent:'+user
    #准备执行原子操作
    pipeline=conn.pipeline(True)
    #如果联系人已经存在,那么移除他
    pipeline.lrem(ac_list,contact)
    #将联系人推入列表的最前端
    pipeline.lpush(ac_list,contact)
    #只保留列表里面的前100个联系人
    pipeline.ltrim(ac_list,0,99)
    #实际地执行以上操作
    pipeline.execute()

跟之前提到过的一样,我们指定的联系已经存在,那么上面函数从列表里面移除该联系人,然后将他重新推入列表的最左端,最后对列表进行修剪以防止联系人人数超过限制。

构建最近联系人自动补全列表要做的第二个操作,就是在用户不想再看见某个联系人的时候,将制定的联系人从联系人列表里面移除掉,这个操作可以通过以下这个lrem调用来完成:

def remove_contact(conn,user,contact):
    conn.lrem('recent:'+user,contact)

构建最近联系人自动补全列表需要执行的最后一个操作,就是获取自动补全列表并查找匹配的用户。因为实际的自动补全处理是在Python里面完成的,所以操作需要首先获取整个列表结构,然后再在Python里面处理它,正如下面代码:

def fetch_autocomplete_list(conn,user,prefix):
    #获取自动补全列表
    candidates=conn.lrange('recent:'+user,0,-1)
    matches=[]
    #检查每个候选联系人
    for candidate in candidates:
        if candidate.lower().startswith(prefix):
            #发现匹配联系人
            matches.append(candidate)      
    #返回所有匹配的联系人
    return matches

上面函数首选获取整个自动补全列表,然后根据联系人的名字是否带有指定的前缀来判断是否对联系人进行过滤,然后返回过滤后的结果。因为这个操作非常简单,所以如果我们发现服务器在计算自动补全列表方面花费了太多时间,那么可以考虑把这个功能交给客户端来实现,客户端只需要在联系人列表被更新之后重新获取一次整个列表就可以了。

因为我们已经预先考虑到【从列表里面移除一个元素需要的时间与列表长度称作正比】这个问题,并明确地限制最近联系人列表最多只能存储100个联系人,所以本节给吃的自动补全实现可以运行得非常好,并且速度也足够快,但它并不适合用来处理非常大的列表。如果你需要一个能够存储更多元素的最常使用列表或者最少使用列表,那么可以考虑使用带有时间戳的有序集合来替代本节介绍的最近联系人列表。

results matching ""

    No results matching ""