负载可以用称为「负载参数」(load parmeters)的若干数字来描述。参数的最佳选择取决于系统的体系结构,常见的选择有:
- Web 服务器的每秒请求处理次数
- 数据库的写入率
- 聊天室的同时活跃用户数量
- 缓存的命中率
有时平均值很重要,而有时少数的峰值更加重要。原文这里给出了一个 Twitter 的例子来说明负载,Twitter 的两个典型业务操作是:
- 「发布推特」:一个用户可以发布一条新消息到其所有的关注者(平均 4.6k 请求/秒,峰值 12k 请求/秒)
- 「主页时间线」:一个用户可以查看其关注对象发布的推特(平均 300k 请求/秒)
上述操作的难点在于巨大的「扇出」(fan-out)结构,即每个用户会关注很多人,也会被很多人圈粉。Twitter 给出了如下图所示的两种处理方案:
方法 1 是将发送的新推特插入到全局的推特集合中,当用户查看时间线时,首先查找其所有的关注对象,列出这些人的所有推特,最后以时间为序进行合并。在关系型数据库中,可以通过如下查询语句实现:
SELECT tweets.*, users.* FROM tweets JOIN users ON tweets.sender_id = users.id JOIN follows ON follows.followee_id = users.id WHERE follows.follower_id = current_user
方法 2 则是对每个用户的时间线维护一个缓存,当用户发布新推特时,查询其关注者,将该推特插入到每个关注者的时间线缓存中。由于已经预先计算了时间线,所以访问时间线的性能会非常快。
Twitter 在最初的版本中使用了方法 1,但随着主页时间线的读负载压力的与日俱增,开始切换为方法 2,因为时间线浏览的压力要比发布推特高的多,所以在发布时多完成一些事情可以加速读性能。然而,方法 2 也存在着一定缺陷,如图中所示,假定每名用户平均 75 个关注者,则需要每秒约 345k 次写入缓存,而由于关注者数量的偏差,对于某些超级用户来说,其发布一条推特可能会导致数千万次的写入发生,这对写性能的要求极大。目前 Twitter 正在考虑将两种方法结合起来,大部分用户发布推特时采用方法 2,以一对多写入时间线,而部分超级用户则才用类似方法 1 的方法,其推特被单独提取,当读取时才和用户的时间线合并。
在本例中,每个用户关注者的分布情况时可扩展性的关键负载参数,其决定了扇出数,在不同的应用中存在着类似的关键负载参数,需要根据具体情况进行判断。