QQ是怎样创造出来的?——解密好友系统的设计

  

 QQ是怎样创造出来的?——解密好友系统的设计

  

本篇介绍笔者接触的第一个后台系统,从自身见闻出发,因此涉及的内容相对比较基础,后台大牛请自觉略过。

  

什么是好友系统?

  

简单的说,好友系统是维护用户好友关系的系统。我们最熟悉的好友系统案例当属QQ,实际上QQ是一款即时通讯工具,凭着好友系统沉淀了海量的好友关系链,从而铸就了一个坚不可摧的商业帝国。好友系统的重要性可见一斑。

  

熟悉互联网产品的人都知道,当产品有了一定的用户量,往往会开发一个好友系统。其主要目的是增加用户粘性(有了好友就会常来)或者增加社区活跃度(有了好友就会多交流)。

  

而我的后台开发生涯就是从这样一个系统开始的。

  

那时候,好友系统对于我们团队大部分人来说,都是一个全新的事物,因为我们大部分人都是应届生。整个系统的架构自然不是我们一群黄毛小孩所能创造。当年的架构图已经找不到了,但是凭着一点记忆和多年来的经验积累,还是可以把当年的架构勾勒出来。
 QQ是怎样创造出来的?——解密好友系统的设计

  

如图,好友系统的架构是常见的3层结构,包括接入层,逻辑层和数据层。

  

我们先从数据层讲起。

  

因为我们对QQ太熟悉了,我们可以很容易地列出好友系统的数据主要包括用户资料,好友关系链,消息(聊天消息和系统消息),在线状态等。

  

互联网产品往往要面对海量的请求并发,传统的关系型数据库比较难满足读写需求。在存储中,一般是读多写少的数据才会使用MySQL等关系型数据库,而且往往还需要增加缓存来保证性能,NoSQL(不是alt=" QQ是怎样创造出来的?——解密好友系统的设计">

  

如图所示,用户如果尝试登录两次,接口机通过会话就可以将第一次的登录踢下线,从而保证只有一个终端在线。

  

问题解决了吗?

  

没有。因为实际系统肯定不会只有一台接口机,在多台接口的情况下,上面的方法就不可行了。因为每个接口机只能维护部分用户的会话,所以如果用户先后连接到不同的接口机,就会造成用户多处登录的问题。
 QQ是怎样创造出来的?——解密好友系统的设计

  

自然可以想到,解决的方法就是要维护一个用户状态的全局视图。在我们的好友系统中,称为在线状态服务。

  

在线状态服务,顾名思义就是维护用户的在线状态(登录时间,接口机IP等)的服务。用户登录和退出会通过接口机触发这里的状态变更。因为登录包和退出包都可能丢包,所以心跳包也用作在线状态维护(收到一次心跳标记为在线,收不到n次心跳标记为离线)。

  

一种常用的方法是,采用位图存储在线状态,具体是指在内存中分配一块空间,32位机器上的自然数一共有4294967296个,如果用一个比特来表示一个用户ID(例如QQ号),1代表在线,0代表离线,那么把全部自然数存储在内存只要4294967296/(8 1024 1024)=512 mb(8位=1字节)。当然,实现中也可以根据需要给每个用户分配更多的。

  

于是,踢下线功能如图所示。
 QQ是怎样创造出来的?——解密好友系统的设计

  

用户登录的时候,接口机首先查找本机上是否有会话,如果有则更新会话,接着给在线状态服务发送登录包,在线状态服务检查用户是否已经在线,如果在线则更新状态信息,并向上次登录的接口机IP发送踢下线包;接口机在收到踢下线包时会检查包中的用户ID是否存在会话,如果存在则给客户端发送踢下线包并删除会话。

  

在实际中,踢下线功能还有很多细节问题需要注意。

  

又回到用户先后登录同一台接口机的情况:

  

 QQ是怎样创造出来的?——解密好友系统的设计”> <br/>图中踢下线流程是正确的,但是如果步骤10和13调换了顺序(在UDP传输中是常见的)会发生什么?大家可以自己推演一下,后到的踢下线包会把第二次登录的“踢下线了。这不是我们期望的。怎么办呢? </p>
  <p>解决方法分几个细节,①接口机在收到13号登录成功包时,先将会话一个替换成会话的,然后给客户端一个发生踢下线包(避免多处存活导致互相踢下线);②踢下线包中必须包含除用户身份证外的其他标识信息,会话的唯一标识应该是ID + XXX的形式(我最开始采用的是ID + LoginTime), XXX是为了区分某次的登录;③接口机在收到踢下线包的时候只要判断ID + XXX是否吻合来决定是否给客户端发踢下线包。<h2 class=QQ是怎样创造出来的?——解密好友系统的设计