当前位置: 首页 > 科技观察

看看Quora如何实现贡献者排名

时间:2023-03-20 22:36:18 科技观察

今年早些时候,我们推出了访问最多的作家(MVW)功能,该功能可以识别他们了解和关心的主题上最活跃的贡献者。MVW是突出贡献者和帮助读者发现贡献者自己感兴趣的主题的好方法。作为一名产品工程师,我全身心投入到这个功能的开发和设计中。很高兴能有这样的机会,结合自己的技术和热情,打造一款用户体验很好的产品。在此过程中,我能够与产品经理、产品设计师和数据科学家一起确定产品的最终目标和实现它的最佳方式。在这篇文章中,我们将深入探讨该功能的一些模块,以及各种场景背后的架构实力,以及如何整合这些模块来完成最终的MVWs功能。***访问作者(MVW)的定义***某个主题的访问作者(MVM)是在过去30天内对该主题下公开答案访问次数最多的用户。因此,我们的最终目标是确定每个主题下的活跃作者。实现这一目标的第一步是定义一个明确的类别。更具体地说,我们想要创建一个系统来执行以下操作:1.跟踪过去30天内每个答案的访问次数。2.给定一个主题,返回最近30天内按回答访问量排名的MVW列表。3.给定一个用户,返回该用户在MVW中所属的主题列表。4.给定一个主题,返回将收到通知的新MVW用户列表。正如我们所见,明确定义MVW产品需求直接关系到正确选择满足需求的最佳架构。访问数据流在跳转到如何使用访问数据创建MVW之前,让我们退后一步,弄清楚访问数据从何而来?我们使用产品不同部分的实时流量来显示每个内容的浏览方式。每当有人在Quora上查看某个内容时,例如一个答案,我可以将查看计数加1,我们希望将所有数据保存在持久存储中,因为我们不想丢失任何数据。一个更天真的实现是通过以下方式记录访问:##Viewsmodule(version1)#deflog_views(answer_id,count=1):#Calledeverytimewhenthereisaview#Singleupdatethathappensinlineviews_datastore.increment(answer_id,count=count)这看起来很简单,对吧?如果系统每秒只需要处理10次访问,即每100毫秒1次访问,这是非常合理的。但是如果访问次数是每秒100次呢?1000次?更?如果系统中有成千上万的答案,并且这些答案会不断获得新的流量,系统会是什么样子?对于一个产品来说,访问是最频繁的操作(基本上每个页面都会被加载),如果每个行为都被持久化,将会对真值数据存储(HBase)的写操作产生太大的影响,无法满足扩展性问题。一个更好的扩展性方案是使用事件队列,然后按照一定的规则批量增加。##Viewsmodule(version2)#defenqueue(answer_id,count=1):#Calledeverytimewhentheresaview#Enqueuetobeupdatedvia“batch_update”views_event_queue.enqueue(Item(answer_id,count))defbatch_update():#Processenqueueditemsregularlyitems=views_event_queue.dequeue_recent_items()#datastore.incrementbatchviews(items)对于这个设计,访问数据首先会进入一个简单的持久化事件队列(在Redis上实现)。有了事件队列,我们??可以有效的摊销I/O延迟,可以大大提高系统的吞吐量。从产品的角度来看,如果我们展示访问频率高于批量操作,实时访问可以通过***获取真实数据,然后通过数据递增来实现在Redis缓存中。下面是该过程的简化图:将流量聚合到MVW既然我们在某处拥有实时流量数据,让我们尝试在其之上创建MVW。根据产品定义,最重要的步骤之一是通过每个答案的视图汇总每个用户的每个主题的视图。这种聚合对于集成多个数据源(包括答案??的访问次数、问题-主题关系和问题-答案关系)的计算成本很高。此外,任何时候删除一个答案,都会引起连锁反应。删除答案的访问次数不应计入与此相关主题的用户排名,这意味着我们需要更新这些主题的排名。同样,如果答案出现质量问题,也会出现类似的情况。这导致更多的计算和聚合。而且我们也开始注意到,Quora系统的许多现有功能也影响了MVW的设计。下面是计算MVW数据所需的实体之间的整体依赖关系图:用户在访问MVW页面时期望快速加载。但是聚合操作很慢,页面加载时计算机之间的通信不够,需要在页面加载前缓存数据。但是,如果这些依赖项是不断变化的,我们就需要不断的重新计算来缓存。关于访问频率的记录,我们在上面已经讨论过了。如果访问量的任何变化要反映在排名中,我们需要进行大量的重新聚合操作。当然,我们可以投入更多的技术资源来支持这种复杂的场景,但在这样做之前,我们会考虑这是否真的是产品需求?归根结底,我们的目标不仅仅是解决一个棘手的工程问题,而是通过良好的产品功能实现最佳的用户体验。回到为每个主题确定优秀作者的最初目标,我们确定的是当一个主题有新的MVW时需要通知用户。如果排名非常不稳定,可能每秒都在变化,可能刚通知一个新的MVW进入前10,打开才发现数据已经过时了。经过工程师、设计师、数据科学家和产品经理的讨论,我们觉得如此频繁地重新计算如此复杂的聚合是一个非常糟糕的主意,因为:1.实时排名导致技术复杂性。上图中的任何依赖关系变化(例如,当有访问行为时或当答案被删除时)都会触发排名变化。2.从可用性的角度来看,实时排名也很糟糕。不稳定的排名会导致用户混淆。我们已经确定对所有状态变化敏感的排名不是产品的要求(实际上也不是期望的)。相反,更好的选择是定期预聚合离散版本的MVW,而不是像上述过程那样依赖实时访问。我们可以通过离线批量更新来计算下一个版本的MVW。再看访问数据流,为了支持离线计算,我们通过第二个离线查询管道上传访问数据。通过此设置,个人访问记录在Web服务器的订阅(Scribe)上,以便通过多阶段离线数据流,我们可以将数据上传到AmazonS3,并最终将其导入Redshift。我们选择Redshift是因为对它做聚合操作非常简单,非常适合大规模的复杂查询。除了门禁数据,其他相关数据也可以上传到Redshift。pipeline可以抽象如下图所示:***访问作者,从头到尾:现在,有了所有批量更新MVW的数据,我们来了解一下实际计算排名的过程。在讨论最终架构之前,让我们重新审视一下我们要检索的信息:1.给定一个主题,返回一个MVW列表,该列表按过去30天内的回答访问量排序。2.给定一个用户,返回该用户属于MVW的主题列表。3.给定一个主题,返回新的MVWs用户列表并向他们发送通知。我们选择使用HBase(一种开源、非关系、分布式数据库,基于Google的BigTable)作为我们的主要后端,以持久存储MVW的缓存数据。一个优势是使用HBase可以实现比关系数据库管理系统(RDBMS)(如MySQL)或简单的内存缓存(如Redis)更好的大数据量读写性能。由于所有MVW数据都是预先聚合的,因此只需要简单的键值查询,不需要范围或聚合查询。因此,我们的使用场景不涉及需要关系数据库索引的复杂查询。例如,我们要缓存一些预先聚合的数据,以支持如下1-10的排名查询:(Topic,Rank)->(User,TopicViewCount)我们可以创建一个HBase表,关键字为“topic”和“rank”,并且每个A行都需要一个“用户”列和一个“主题查看次数”列。HBase的另一个非常好的特性是我们实际上可以定义一个“用户”列族和一个“主题视图计数”列族,每个列族包含一系列版本值。HBase允许每个列族动态聚合新列。每次计算出的新版本排名都会周期性更新,我们可以新建一个列来存储它。通过直接指定这些列的生存时间,旧版本会自动过期。回顾一下,我们将访问数据记录到离线数据流(Redshift),预先聚合每个主题的排名,并将结果缓存在HBase中,以便在最终的MVW功能中可以高效地查询数据。下图总结了从访问记录到聚合MVW数据再到如何使用这些数据的流程:Quora上的产品工程课程通过创建MVW的过程,我学到了很多关于产品工程的有效方法。本项目的成功主要归功于坚持了以下原则:1.明确定义技术和功能目标。例如,通过使用完整的依赖关系图(上图)清楚地映射产品功能目标,我们能够避免因对用户体验没有任何价值的微小变化而导致的排名极不稳定。这有助于我们做出正确的权衡,实现基于版本的设计,既有利于技术扩展,也有利于直观的用户体验。2.支持简单性和可扩展性。如有疑问,宁愿降低复杂性。从产品的角度来看,这可以为用户提供更清晰易懂的排名功能。从技术角度来说,它为我们继续迭代一个简单易懂的系统打开了一扇门。总而言之,作为一名产品工程师,我已经能够更好地看到MVW的影响,并期待在未来做出更多改进。有兴趣创建Quora的下一个很酷的功能吗?查看职业页面,了解更多关于在这里成为产品工程师的感受!