面向高级初学者的系统设计

2020-07-21 12:15:06

这篇文章是我为“高级初学者”系列所做的计划的一部分。现在就订阅,每隔一周接收一次具体的、可行的方法,让您的代码变得更干净,而且是完全免费的。

你和你的好朋友史蒂夫·史蒂文顿又开了一家公司。这是一个在线市场,人们可以在这里买卖东西,没有人会问太多问题。这基本上是对Craigslist的抄袭,但用的是史蒂夫的名字,而不是克雷格的名字。

您将负责构建整个Steveslist技术平台,包括其所有网站、移动应用程序、数据库和其他基础设施。你很兴奋,但也很紧张。你认为你可能会拼凑出一个小网站,因为你之前已经这样做过几次了,这是你之前与Stevester进行的娱乐-如果是道德上的-有问题的越轨行为的一部分。但你甚至不知道如何开始建设你认为隐藏在大型成功在线平台背后的所有其他基础设施和工具。

你迫切需要一个详细而简明的概述,了解真正的公司是如何做到这一点的。他们如何存储数据?它们的不同应用程序如何相互通信?他们如何扩展其系统以满足数百万用户的需求?他们如何保证他们的安全呢?他们如何确保不出任何差错?什么是API、WebHook和客户端库,当您真正开始讨论它们的时候?

你快速发送一个WhatsApp给你的另一个好朋友凯特·凯特伯里(Kate Kateberry),看看她是否能帮上忙。你们过去的合作非常有效,她有几十年在硅谷最大、最有争议的公司创建这些类型的系统的经验。

她立即接受了你的工作邀请。实际上,你只是打电话寻求一些粗略的指导和一些好的八卦,但你仍然立即接受了她的接受。即使你没有钱付钱给她,也没有必要对她吹毛求疵。凯特建议她的第一天是5周前,以帮助她平息一些会计违规行为。她可以在下周的某个时候来办公室。她的热情让你感到鼓舞和威胁。

凯特蹦蹦跳跳地走进你在旧金山公共图书馆19世纪文学区的办公室。“好的,我们开始吧!”她轻声喊道。“到目前为止,我们有什么进展?我们所有的系统是如何设置的?有什么计划?“。你向后靠在椅子上,合上笔记本电脑,因为你把充电器忘在家里了,笔记本电脑没有开机。你用一种你希望可以被描述为“深思熟虑”的方式竖起你的手指。

“让我把这个问题反驳一下,凯特。你认为这个计划应该是什么?“

凯特深吸了一口气,描绘了对Steveslist平台未来五年的极其详细的愿景,以及将为其提供动力的基础设施。

在我们开始之前(凯特说),我想说清楚,我并不是说这一切都一定是建立我们基础设施的“正确”方式。如果你比我更信任的人说了一些不同的话,那么你可能应该照他们说的做。市场上有很多工具,每个工具都有不同的优点和缺点,也有很多方法来建立一家技术公司。我们将做出许多技术选择的真实、诚实的原因将是“我们选择X是因为Sara对X了解很多”,以及“我们选择Y是出于一时的冲动,当时这似乎不是一个重大决定,我们从未找到时间重新评估。”

尽管如此,让我们快进到未来的五年。现在,Steveslist有两个主要面向消费者的产品:

这些是用户与Steveslist平台直接交互的主要方式。此外,我们还提供了一个API,允许程序员在Steveslist平台上构建强大的工具,例如,以编程方式创建数百个项目的清单。为支持这一点,我们提供:

Steveslist API客户端库,使程序员可以轻松编写与我们的API对话的代码。

+-+|Web浏览器||智能手机应用||客户端库/|+-+-+|其他API代码|+-+|v|。-+|+->;+Steveslist+<;-+|服务器|+-+。

最后,我们有许许多多的服务在后台运行,为这些面向外部的应用程序提供数据和功能:

WebHooks-当用户的帐户出现问题时通知用户,例如“已下订单”

SQL数据库-主Steveslist数据存储。需要高度可扩展和可靠。

自由文本搜索系统-支持搜索框,人们可以在其中查找广泛的搜索词,如“电视”或“摩托车”

内部工具-帮助我们管理Steveslist平台,并采取行动,如向恶意用户发出措辞严厉的警告。

Cron作业-运行常规任务,从生成发票到向客户开具账单,再到将尽可能多的用户数据发送到第三方广告网络。

“Pubsub”系统-允许我们对不同的触发事件采取异步操作(例如“当新用户注册时,向他们发送欢迎电子邮件”)。

大数据分析系统-允许我们对Steveslist的全部数据运行大量查询。

让我们依次检查一下这些系统。如果有什么不清楚的地方,如果你有什么问题,或者如果你认为我搞错了,请告诉我。

在我们开始之前,让我们先定义一些重要的术语。事实上,让我们只定义一个。今天我们将谈论很多关于“服务器”的话题。但是,当您真正深入到服务器时,什么是服务器呢?

就我们的目的而言,服务器是指在网络上运行并侦听来自其他计算机的通信的计算机。当它从另一台计算机接收到一些数据时,它会执行某种响应操作,并且通常会发回自己的一些数据。例如,Web服务器在网络上侦听HTTP请求并发回网页和信息作为响应。数据库服务器侦听数据库查询,并将数据读写到它正在运行的数据库。

这个简短的描述忽略了整个学位和职业的细节,当然还有更精确和准确的方式来定义“服务器”这个词。但这应该能让我们撑到晚餐时间。您说什么?什么是“网络”?这是一个改天再问的好问题。

这是Steveslist的主要产品。这只是一个普通的网络应用程序,与你以前创建的任何网站非常相似,只是要大得多。这是一款现代化的“单页应用程序”(SPA)。“单页应用”中的“单页”指的是这样一个事实,即当用户在我们的站点周围点击时,用户的浏览器几乎从不需要完全重新加载页面。相反,当浏览器向我们的服务器发出它的第一个HTTP请求时,我们向它发回一个基本的框架HTML页面和一大堆JavaScript代码。此JavaScript代码在浏览器内执行,并根据用户的操作更新页面视图。当JavaScript想要从Steveslist发送或检索数据时,它会在后台向URL发送异步JavaScript XML请求(几乎总是简称为Ajax)。当我们的服务器响应时,JavaScript使用响应相应地更新浏览器视图。

+-+1.用户的浏览器访问steveslist.com。+-+||它将HTTP请求发送到|Steveslist服务器|+->;|User';的Web||Steveslist||Browser|2.Steveslist服务器以|Servers|骨架HTML页面响应,该页面指示|。|<;-+|3.用户的浏览器请求并从|Steveslist服务器接收|这些JavaScript文件。||+->;|<;-+|4.用户浏览器执行JavaScript|代码。代码向Stevelist|服务器|发送对|用户数据的更多请求,并更新浏览器UI以|显示该数据。||+->;|<;-++-+。

Robertheaton.com不是一个单页面的应用程序。无论何时点击链接,浏览器都必须重新加载整个页面。Twitter.com是一款单页面应用程序。只要你点击一个链接,浏览器就会动态更新页面的一小部分,而不会强制完全刷新。

水疗中心的建造和维护工作量很大,但它们看起来确实不错。

我们提供适用于iOS和Android的Steveslist智能手机应用程序。它们在概念上与我们的单页面Web应用程序非常相似。我们的智能手机和网络应用程序都会向我们的服务器发出HTTP请求。然后,我们的服务器接收这些请求,执行一些工作并返回HTTP响应。最后,我们的智能手机和网络应用程序都会更新它们的显示,以便与用户进行交流。

由于我们的智能手机应用程序执行的操作与Web应用程序相同(例如,创建列表、发送消息等),因此它们通常甚至可以将请求发送到与Web应用程序完全相同的URL。我们唯一需要做的额外工作就是开发应用程序本身的前端。一些框架甚至可以使用JavaScript编写移动应用程序,从而允许您跨平台重用代码和逻辑。

我们允许用户和第三方编写以编程方式与我们的平台交互的代码。就像人们可以使用Twitter API编写代码来阅读、点赞和创建Twets一样,我们允许他们使用Steveslist API来搜索、购买和列表项。

程序员通过编写向API端点发出HTTP请求的代码来使用我们的API。例如,为了检索所有列表,程序员向api.steveslist.com/v1/listings发送HTTP GET请求。我们用他们请求的数据进行响应,数据格式为JSON。JSON代表JavaScript对象表示法,但是JSON不是特定于JavaScript的,可以很容易地被任何编程语言解释。对检索用户所有清单的请求的JSON响应可能如下所示:

{";Listings";:[{";id";:2178123867,";姓名";:";被盗电视";,";国家";:";美国";,";城市";:";旧金山";,";PRICE_AMOUNT";:1,000,&34;Price_Currency";:";USD";,#Et...},{";id";:182312679,";名称";:";被盗自行车";,";国家";:";美国";,";城市";:";旧金山";,";Price_Amount";:2000,";Price_Currency";:";USD";,#etc...}]}。

这种结构化响应格式对于程序来说非常容易解析,这意味着发出请求的代码可以很容易地解释和使用我们API中的数据。

用户使用API密钥向我们的API标识或验证自己。粗略地说,这相当于API的密码。它是我们生成并显示在用户的“设置”页面上的一个长的随机字符串。用户将其API密钥作为HTTP头包含在他们(或其代码)向API发出的每个HTTP请求中。当我们收到API请求时,我们检查附加的API密钥是否对应于Steveslist用户。如果是,则我们代表该用户执行请求。

如果程序员愿意,他们可以使用他们语言的标准HTTP库手动构建对我们API的HTTP请求。例如,在Python中,他们可能会写道:

导入请求URL=';https://api.steveslist.com/v1/listings/';列表参数={";名称";:";被盗电视";,";国家";:";美国";,";城市";:";旧金山";,";价格_金额";:1,000,";Price_Currency";:";USD";,}API_KEY=";YOUR_API_KEY_GOES_HERE";RESPONSE=REQUESTS。POST(url,data=Listing_params,Headers={";X-Steveslist-api-key";:api_key},)。

客户端库是“包装”Steveslist API功能的库。这意味着使用客户端库的任何人都不需要了解有关Steveslist API的任何细节。取而代之的是,他们可以只写:

我们的库将它们提供的参数转换为适当格式化的HTTP请求,它们照常发送到Steveslist API。我们已经为我们能想到的每一种主要编程语言编写了客户端库,到目前为止,它们是人们与我们的API交互的最常见方式。

我们已经看到了Steveslist用户如何使用我们的API以编程方式与他们的帐户交互。此外,许多用户还希望我们在他们的Steveslist个人资料发生变化时主动告知他们。例如,假设有人想要完全自动化在Steveslist上销售项目的过程。每当客户使用我们新的、基本安全的StevePay系统付款时,他们都想给客户发送一封感谢电子邮件,并自动指示他们的仓库将一台偷来的电视发货到订单地址。我们的销售商可以不断地反复查询Steveslist API,询问“是否有新的销售?有没有新的销售情况?“。然而,这将是非常低效的,并且会给我们的服务器带来很多不必要的负载。

相反,我们提供了一个称为WebHooks的行业标准系统。WebHook是一种HTTP请求,每当用户的帐户发生有趣的事情时,我们都会将其发送给用户。它包含描述刚刚发生的事件的所有数据-例如,商品ID、价格、买家ID、买家地址等。WebHook允许用户自动执行响应操作,例如前面提到的电子邮件和自动发货。

要使用WebHook,用户需要告诉我们他们希望我们将其WebHook发送到的URL(例如,steveslistwebhooks.robertheaton.com)。他们在该URL上设置一个Web服务器,该服务器将接收和操作这些WebHook通知。

1.买方购买An 3.卖方的服务器照常接收网钩项目。并使用其中的信息自动处理订单。+-++-+|买方浏览器||卖方的WebHook-++-+|接收服务器||+-+|^|++-||^||。+Steveslist+-+|服务器|+-+2.交易完成后,Steveslist将查找卖家的网络挂钩URL(如果已设置)。然后,我们向此URL发送一个HTTP请求,其中包含购买的详细信息。

用户在其Web服务器上部署代码,该代码在收到我们的WebHook时执行适当的响应操作。我们不仅仅在购买物品时发送网络挂钩,当用户收到消息时,当管理员删除他们的物品时,或者当买家投诉时,我们也会发送它们。这使得Steveslist卖家不仅可以自动列出商品,还可以自动销售和发货。

WebHooks有两个主要的复杂性-安全性和可靠性。首先,我们来谈一谈安全问题。卖家指示我们将其网络挂钩发送到的端点可供互联网上的任何人访问。任何知道URL的人都可以给它发送假的网络挂钩,如果我们的卖家不小心,这可能会让攻击者欺骗他们,例如,向攻击者发送免费的东西。卖家的WebHook URL应该很难找到,因为卖家不会公开它的存在,但默默无闻并不等同于安全。

为了允许我们的卖家验证WebHook是否真的是由Steveslist发送的,我们对WebHook内容进行了加密签名。

密码签名是一个深刻而微妙的话题。下面是我们如何使用它来保护我们的网络挂钩的一个精简版本。

当卖家为他们的账户启用WebHook时,我们会生成一个随机的“共享密钥”。我们告诉卖家将此密钥复制到他们的WebHook接收服务器并确保其安全,以便只有我们和卖家知道它的价值。每当我们发送一个WebHook时,我们都会获取这个共享的秘密,通过使用一个称为HMAC的加密散列函数将它与WebHook的内容结合起来,然后得到一个看似随机但完全确定的WebHook的长长的“签名”。

WebHook内容+-+(例如,{";id";:123...})|v+-+-+|HMAC算法|-&>;WebHook签名+-+-+^共享密钥|(例如,123mhu23jy8xdwgmd...)+-+。

当卖家的WebHook接收服务器接收到WebHook时,它将获取共享密钥和WebHook内容,并计算它期望的签名与我们完全相同的方式。它将结果与附加到网络挂钩的签名进行比较;如果匹配,则接受并处理网络挂钩。由于签名只能使用只有我们和卖家知道的共享秘密来生成,因此WebHook接收服务器可以确信WebHook是由我们发送的。但是,如果签名不匹配,服务器将拒绝WebHook。

请注意,所有签名验证码必须由卖方编写和维护。我们可以为他们提供鼓励和榜样,但我们不能强迫他们正确验证签名,甚至根本不能。对于一些现实世界的例子,请看一下Strip和GitHub是如何签署他们的WebHook的。

我们还需要考虑当我们的网络钩子出错时会发生什么,以及我们想要对用户做出什么保证。如果我们尝试发送一个网络挂钩,但无法连接到用户的服务器,我们应该怎么办?如果我们成功连接到他们的服务器并发送WebHook,但他们的服务器发回错误,该怎么办?如果我们发送网络挂钩,但服务器挂起了20秒,然后在没有告诉我们发生了什么情况的情况下断开连接,会怎么样呢?

这里涉及到一些权衡,需要清晰的沟通和数量惊人的基础设施来管理。我们在Steveslist选择保证我们将“至少交付一次”网络挂钩。这意味着如果一个网络钩子发送失败,我们将继续尝试(大量但不是无限次),直到它成功为止。如果我们不确定网络挂钩是成功还是失败,我们将继续尝试,直到我们确定尝试已经成功。这可能偶尔会导致我们发送两次相同的网络挂钩,但卖家有责任让他们的代码优雅地处理这一问题,而不是向同一客户发送五台偷来的电视,而不是他们订购的那台。

“到目前为止,你是怎么想的?”凯特问道。“这大概就是你一直在想的吗?”你做了个不置可否的表情,咬了一大口附近的三明治,以避免进一步的讨论。凯特继续说道:

让我们讨论一下后端系统,这些系统将支持我们的一些最重要的功能。

大多数用户使用用户名和密码登录Steveslist网站和智能手机应用程序。还有其他登录网站的方式,比如OAuth-我们将在下一次介绍它们。

我们必须非常安全地存储我们用户的密码。用户的密码不仅是他们用来登录(或验证)stevelist的东西,而且对于许多用户来说,他们很可能也是他们用来登录许多其他服务的相同密码,尽管所有人都警告说这不是一个好主意。毕竟,他们只有这么多生日的孩子。

鲁伯特·赫普顿(Rupert Herpton)写了关于如何保护密码的开创性教程,我相信你已经读过了。以防您需要更新密码,问题的关键在于,我们绝不能在任何地方存储原始明文形式的密码。我们不能将它们以明文形式存储在数据库、日志文件或系统的任何其他部分。相反,在存储密码之前,我们必须首先使用散列函数(如bcrypt)对其进行散列。

散列函数是“单向”函数t。

.