推拉模型系统设计分析(下)

276 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第22天,点击查看活动详情

推 or 拉?

img

两个方案各领风骚。

但实际系统设计更多选择【拉模型】,如:

  • Facebook、Twitter 使用 Pull 模型
  • Instagram 使用的是 Push + Pull 模型

只使用推模型的例子较少,毕竟我们注重用户体验。实际情况中,若系统流量不大,使用 Push最经济/省力。系统设计面试,方案比较分析与回答存在误区:

  • 不要不坚定想法,在几个方案间摇摆: 比如面试官对你的 Pull 模型提了质疑,指出缺陷,你就摇摆到 Push 模型,这是错误的回答方式
  • 要展现出 tradeoff 能力: 要针对当前系统设计方案的缺陷做权衡

如何优化?

即【扩展 - Scale】部分,解决设计缺陷。

解决 Pull 模型的缺陷

Pull 模型系统瓶颈(bottleneck),该模型最慢部分在于用户请求新鲜事列表时,且这一过程需消耗用户等待时间。

可在访问DB前加个缓存层,若缓存命中,直接将数据返给用户,大大缩减用户等待时间。

那缓存存啥?

每个用户的时间线(Timeline)

相当于把之前 N 次(N 为关注的好友的个数) DB 请求替换成 N 次缓存访问,可以提速。但缓存不能存储海量数据,因此要做出tradeoff,如每个用户只缓存最新 1000 条或最新 100 条新鲜事,还可将明星、热点用户(用友大量关注者的用户)的缓存长期保存在缓存系统,不轻易让缓存失效

每个用户的新鲜事列表(News Feed)

拉取新鲜事列表时,就仅需一次 Cache 访问:

  • 无缓存新鲜事列表的用户,归并 N 个关注好友的最新 100 条新鲜事,取出前 100 条放入缓存
  • 已做缓存的用户,归并 N 个用户在某个时间之后的所有新鲜事,加入缓存

该方案中,也可针对不同用户做优化,如针对经常使用系统、经常频繁刷新新鲜事的用户做优化,将他们的缓存长期存放,对一些长期不使用的僵尸用户,将他们的缓存清掉。

由于系统的突然爆发式的访问,造成缓存失效,如何处理?

解决 Push 模型缺陷

浪费DB存储空间,但这不算问题,毕竟“Disk is cheap.”。

由于 Fanout 时间可能较长,导致用户有可能在自己所关注的用户发布新鲜事一段时间后,才能够在自己的新鲜事列表里刷到该条消息。可采用一定方案提升用户体验,在 Fanout 时,先对粉丝按规则排序,如按用户活跃度(按最新登录系统时间排序),针对活跃度越高用户,优先进行推送。

另外,针对某些明星用户或者说热点用户(关注他们的用户数量远远大于他们自己所关注用户的数目),整个 Fanout 的时间可能会很长。解决这一个问题有一个很粗暴的方案,就是增加机器的数量,采用分布式 Fanout 的方案。

Push 模型 + Pull 模型的方案

将 Push 方案 和 Pull 方案结合优化。

针对普通用户采用 Push

针对明星这类热点用户,在系统里进行标记。

对于明星用户发布新鲜事,不将新鲜事 Push 到关注了这一明星的列表。当用户需获取自己的新鲜事列表时,到自己所关注的明星用户的时间线上取并合并到自己的 News Feed 列表。

热点用户迅速涨粉/掉粉(摇摆问题)

将明星用户标记,对非明星用户使用“Push”的方案,对明星用户采用“Pull”方案。这会带来什么问题呢?

如系统定义被关注数量有 1M 以上用户为明星用户,此时有一个用户被关注数量为 1M,同时存在大量用户在点击对该用户的关注/取关,这一用户在系统中就会被频繁被标记为“明星”/“非明星”用户,在“Pull”和“Push”模型之中来回切换,导致有用户收不到该“明星用户”发布的新鲜事。这一问题称为“摇摆问题”。

img

最简单的:不对动态的对明星用户进行标记。即用户的关注、取关行为不会立刻影响到该用户会不会被标记为“明星用户”,这一标记过程可异步使用一个线程,定期做。

还有一种解决思路,使用一个缓冲地带,如给用户加入一个“伪明星”状态,针对这一状态的用户单独处理。

僵尸粉

系统中长期不活跃的用户,但是他们也有许多关注的用户,也有少量粉丝。

这种用户有时会给系统带来一些不必要负载,影响整个系统性能。

可单独针对这一类用户处理。比如,我们在系统中对这些长期不活跃的用户进行标记,系统如果使用的是 Push 模型的话,在 Fanout 的过程中,将该类用户设置优先级,放到该过程最后执行。