System Design 101
使用视觉辅助和简单的术语来解释复杂系统。
无论您是在准备系统设计面试,还是只是想了解系统在表面之下的工作原理,我们希望这个资源库能帮助您实现这一目标。
架构风格定义了应用程序编程接口(API)的不同组件是如何相互交互的。因此,它们通过提供一种标准的方法来设计和构建API,确保了效率、可靠性和与其他系统的易于集成。以下是使用最广泛的几种架构风格:
SOAP(Simple Object Access Protocol):
RESTful(Representational State Transfer):
GraphQL:
gRPC:
WebSocket:
Webhook:
这些技术各有特点,适用于不同的应用场景和需求。选择合适的技术对于构建高效、可扩展和可靠的系统至关重要。
根据相关资料,在API设计方面,REST和GraphQL各自有其优势和劣势。以下图表展示了REST和GraphQL之间的快速比较:
REST(Representational State Transfer):
GraphQL:
在REST和GraphQL之间做出最佳选择取决于应用程序的具体要求和开发团队的情况。GraphQL适合复杂或经常变化的前端需求,而REST则适合偏好简单和一致合同的应用程序。
没有一种API方法是万能的。仔细评估需求和权衡是很重要的,以便选择正确的风格。REST和GraphQL都是公开数据和为现代应用程序提供动力的有效选项。
RPC(Remote Procedure Call)被称为“远程”的原因是它能够在微服务架构下部署到不同服务器上的远程服务之间进行通信。从用户的角度来看,它表现得像一个本地函数调用。
以下图表说明了gRPC的整体数据流:
以下是gRPC在处理REST调用时的步骤概述:
客户端发起一个REST调用,请求体通常是以JSON格式编码的。
订单服务(gRPC客户端)接收到REST调用,对其进行转换,并向支付服务发起一个RPC调用。gRPC将客户端存根(stub)编码成二进制格式,并将其发送到底层传输层。
gRPC通过HTTP/2协议在网络上传送数据包。由于二进制编码和网络优化,gRPC的传输速度据说比JSON快5倍。
支付服务(gRPC服务器)从网络接收到数据包,对其进行解码,并调用服务器应用程序。
服务器应用程序处理请求并生成结果,然后将结果编码并发送到传输层。
结果被发送回订单服务,订单服务对其进行解码。
订单服务将结果发送回客户端应用程序。
在这个过程中,gRPC负责处理网络层面的细节,包括数据的编码和解码、错误处理以及重试逻辑。这对于开发者来说是一个巨大的优势,因为它简化了远程服务调用的过程,并提高了通信效率。
假设我们运营一个电子商务网站。客户通过API网关向订单服务发送订单,订单服务随后与支付服务进行通信以处理支付事务。支付服务接着与外部支付服务提供商(PSP)通信以完成交易。
有两种方式来处理与外部PSP的通信:
在向PSP发送支付请求后,支付服务不断询问PSP关于支付状态的信息。经过几轮轮询后,PSP最终返回支付状态。
短轮询有两个缺点:
我们可以向外部服务注册一个webhook。这意味着当PSP有关于请求的更新时,它会调用我们提供的URL。当PSP完成处理后,它会发起一个HTTP请求来更新支付状态。
这样,编程范式就改变了,支付服务不再需要浪费资源去轮询支付状态。
如果PSP从未回调怎么办?我们可以设置一个定时任务,每隔一小时检查一次支付状态。
Webhooks通常被称为反向API或推送API,因为服务器向客户端发送HTTP请求。在使用webhook时,我们需要注意以下三点:
使用webhook可以提高效率,减少资源消耗,并且通过API网关的适当配置,还可以增强安全性。
分页(Pagination):
当结果集很大时,这是一种常见的优化手段。结果被分批返回给客户端,以提高服务的响应性。例如,社交媒体的动态页面通常不会一次性加载所有内容,而是显示一部分内容,并提供“加载更多”按钮。
异步日志记录(Asynchronous Logging):
同步日志记录每次调用都会与磁盘打交道,这可能会减慢系统速度。异步日志记录首先将日志发送到无锁缓冲区,然后立即返回。日志将定期刷新到磁盘。这显著减少了I/O开销。
缓存(Caching):
我们可以将频繁访问的数据存储到缓存中。客户端可以先查询缓存,而不是直接访问数据库。如果缓存未命中,客户端可以从数据库查询。像Redis这样的缓存将数据存储在内存中,因此数据访问速度比数据库快得多。
有效载荷压缩(Payload Compression):
请求和响应可以使用gzip等工具进行压缩,这样传输的数据大小就会小很多。这加快了上传和下载的速度。
连接池(Connection Pool):
在访问资源时,我们通常需要从数据库加载数据。打开和关闭数据库连接会增加很大的开销。因此,我们应该通过一组打开的连接来连接数据库。连接池负责管理连接的生命周期,这样可以避免频繁地建立和断开连接,从而提高效率。
QUIC基于UDP。它在传输层引入了流作为第一类公民。QUIC流共享同一个QUIC连接,因此创建新的流不需要额外的握手和慢启动,但是QUIC流是独立交付的,所以在大多数情况下,影响一个流的丢包不会影响其他流。
以下图表展示了API的时间线和API风格的比较。
随着时间的推移,不同的API架构风格被发布。每种风格都有自己的数据交换标准化模式。
你可以在图表中查看每种风格的用例。
微服务增加了系统的复杂性,因为我们有单独的服务来执行系统的不同功能。虽然这种架构促进了解耦和职责的划分,但我们需要处理服务之间的各种通信。
在编写代码之前,最好先考虑系统的复杂性,并仔细定义服务的边界。
不同的功能团队需要使用相同的语言交流,专门的功能团队仅负责自己的组件和服务。建议组织通过API设计来说同一种语言。
我们可以在编写代码之前模拟请求和响应来验证API设计。
提高软件质量和开发人员生产力:由于我们在项目开始时已经解决了大部分不确定性,整个开发过程更加顺畅,软件质量得到了极大提升。
开发人员也很高兴参与这个过程,因为他们可以专注于功能开发,而不是协商突然的变化。
项目生命周期结束时出现意外的可能性降低了。
由于我们首先设计了API,因此可以在开发代码的同时设计测试。在某种程度上,当我们使用API优先开发时,我们也实现了TDD(测试驱动设计)。
The response codes for HTTP are divided into five categories:
Informational (100-199) Success (200-299) Redirection (300-399) Client Error (400-499) Server Error (500-599)
以下是API网关处理HTTP请求的步骤:
客户端向API网关发送HTTP请求。
API网关解析和验证HTTP请求中的属性。
API网关执行白名单/黑名单检查。
API网关与身份提供者进行通信,以进行认证和授权。
API网关应用速率限制规则。如果请求超过限制,请求将被拒绝。
6 和 7. 既然请求已经通过了基本检查,API网关就会通过路径匹配找到相关的服务进行路由。
9-12. API网关可以正确处理错误,并在错误恢复需要较长时间时处理故障(断路器模式)。它还可以利用ELK(Elasticsearch-Logstash-Kibana)堆栈进行日志记录和监控。我们有时会在API网关中缓存数据。
幂等性(Idempotence)是计算机科学中的一个重要概念,特别是在网络通信、数据库操作和编程语言设计中。幂等性指的是一个操作或命令无论执行多少次,其结果都是一样的。换句话说,多次执行该操作不会改变最终的状态或结果。
在不同的上下文中,幂等性的含义略有不同:
网络通信:在网络通信中,幂等性通常指的是HTTP请求的幂等性。例如,GET请求就是幂等的,因为它只是从服务器获取数据,不会改变服务器上的任何状态。相比之下,POST请求通常不是幂等的,因为它会向服务器提交数据并可能改变服务器的状态。
数据库操作:在数据库操作中,幂等性意味着无论执行多少次相同的SQL语句,数据库的状态都不会发生变化。例如,如果一个UPDATE语句只更新了0行,那么这个操作就是幂等的,因为数据库的状态没有改变。
编程语言:在编程语言中,幂等性可以应用于函数或方法。如果一个函数或方法的多次调用总是产生相同的结果,那么它就是幂等的。例如,一个返回常量值的函数就是幂等的。
幂等性的重要性在于它可以保证系统的稳定性和一致性。在网络通信中,由于网络延迟、重试机制或者中间件的介入,同一个请求可能会被发送多次。如果请求是幂等的,那么即使发生了重复请求,也不会导致不一致的结果。这在金融交易、订单处理和其他需要高度一致性的场景中尤为重要。
为了实现幂等性,通常需要确保每个操作都有一个唯一的标识符(如请求ID),这样即使操作被重复执行,系统也能识别出这是一个重复的操作,并且不会再次执行实际的工作,而是返回之前的操作结果。
在使用Python的requests库发送POST请求时,引用request_id的作用主要体现在以下几个方面:
关联客户端请求与服务端日志:在微服务架构中,一个用户请求可能会被分解成多个子任务,由不同的服务处理。在这种情况下,如果没有request_id,我们可能无法将客户端的请求与服务端的日志准确关联起来。有了request_id,我们就可以通过这个唯一的标识符,轻松地将客户端的请求与服务端的日志关联起来。
提高日志查询效率:在没有request_id的情况下,我们可能需要依赖调用函数的日志关键信息和用户输入的参数、时间等信息来查找相关的日志。这种方法在分布式或微服务架构中效率很低,因为代码的层层封装使得我们难以直接通过日志关键信息与用户请求关联。而有了request_id,我们可以通过这个唯一标识符快速定位到相关的日志记录,大大提高了日志查询的效率。
跨服务日志关联:在微服务架构中,不同的服务之间可能存在交互。当出现问题时,我们需要能够快速地定位到问题发生的源头。request_id可以帮助我们跨服务地关联日志,即使问题发生在多个服务之间,我们也能够通过request_id快速地找到所有相关的日志记录,从而更快地定位和解决问题。
优化性能监控:request_id还可以用于性能监控。通过对同一个request_id下的日志进行分析,我们可以了解一个请求的处理时间,以及各个服务在这个请求处理过程中的响应时间。这有助于我们发现性能瓶颈,并采取相应的优化措施。
总的来说,request_id在发送POST请求时的作用主要在于提高日志查询的效率,简化问题排查的过程,以及优化性能监控。
根据相关资料,在数据库中实施软删除(Soft Delete)是一种常见的数据管理策略,它允许我们在不真正从数据库中移除记录的情况下,标记记录为已删除。这样做的好处包括更容易地恢复已删除的数据、保留历史记录等。
实施软删除通常涉及以下步骤:
IsDeleted
、IsActive
或IsArchived
,用于标记记录是否被删除。IsDeleted
列设置为TRUE(或者类似的逻辑值,取决于你的设计)。IsDeleted
列来标记已删除的记录,那么你的查询可能看起来像这样:sqlSELECT * FROM parent_data WHERE IsDeleted = FALSE;
这个查询将返回所有未被标记为已删除的记录。
在Entity Framework Core中实现软删除,你可以通过以下方式:
IsDeleted
。请注意,实施软删除后,你需要确保所有的数据访问代码都遵循这一约定,以便正确处理软删除的记录。此外,软删除可能会导致数据库中的数据冗余,因此在决定是否采用这种方法之前,应该仔细考虑其优缺点。
根据相关资料,资源交叉引用(Resource Cross Reference)是指在不同的项目或系统之间共享和引用资源的过程。这种做法在软件开发中尤为常见,尤其是在使用开源软件代码托管平台和问题跟踪器时。资源交叉引用可以帮助开发者更好地理解和利用现有的代码库和文档,提高开发效率和代码质量。
然而,资源交叉引用也带来了一些挑战,特别是在跨域资源共享(CORS)方面。CORS是一种HTTP头部机制,允许服务器指示浏览器允许从不同域加载资源。这是因为出于安全考虑,浏览器通常会限制对第三方网站上资源的访问。CORS定义了一种方式,使得客户端Web应用程序可以从一个域加载资源并与另一个域的资源进行交互,这对于复杂的Web应用程序来说非常有用,因为它们经常在其客户端代码中引用第三方API和资源。
总的来说,资源交叉引用是一个强大的工具,它可以促进不同项目之间的协作和资源共享。但是,为了确保安全性和合规性,开发者需要了解并遵守相关的CORS规则和最佳实践。
确实,API设计不仅仅是URL路径的设计。一个良好的API设计应该考虑到资源的命名、标识符的选择、路径模式的制定,以及HTTP头字段的设计,还包括在API网关内设计有效的速率限制规则。以下是一些关键点,它们共同构成了一个健壮、可维护和易于使用的API设计:
资源命名:资源应该是名词,代表API中的实体。例如,对于一个电子商务网站,资源可能是“products”、“customers”和“orders”。
标识符选择:资源的唯一标识符应该是有意义的,通常是UUID或其他全局唯一标识符。
路径模式:API的路径应该清晰地反映资源的位置。例如,/products/{id}
表示获取特定产品的详情,而 /orders/{id}/items
表示获取某个订单的所有商品。
HTTP方法:每个资源都应该有适当的HTTP方法(GET、POST、PUT、PATCH、DELETE等),以符合RESTful架构原则。
HTTP头字段:设计API时,应该考虑哪些HTTP头字段是必要的,例如认证令牌(如JWT)、内容类型(Content-Type)、接受类型(Accept)等。
版本控制:API设计应该考虑到未来的变化,通过版本控制来支持API的演进,例如通过URL路径(如/v1/products)或自定义HTTP头字段。
错误处理:API应该提供清晰的错误响应,包括错误码和描述,以便调用者能够理解发生了什么问题。
文档和示例:提供详细的API文档和使用示例,帮助开发者快速理解和使用API。
安全性:确保API的安全性,包括认证、授权、加密通信(如使用HTTPS)和防止常见的网络攻击。
速率限制:在API网关层设计有效的速率限制规则,以防止滥用和DDoS攻击,同时确保公平的资源分配。
缓存策略:合理使用缓存可以提高API的性能,减少服务器负载。
扩展性:设计时应考虑API的扩展性,以便在未来可以轻松地添加新功能或修改现有功能。
API设计是一个综合性的过程,需要考虑到用户体验、可维护性、安全性和性能等多个方面。通过遵循上述原则,可以设计出既强大又易于使用的API。
How is data sent over the network? Why do we need so many layers in the OSI model?
The diagram below shows how data is encapsulated and de-encapsulated when transmitting over the network.
步骤 1: 当设备A通过HTTP协议向设备B发送数据时,首先在应用层添加了一个HTTP头部。
步骤 2: 然后,数据上添加了一个TCP或UDP头部。在传输层,数据被封装成TCP段。头部包含源端口、目的端口和序列号。
步骤 3: 这些段随后在网络层被封装上IP头部。IP头部包含源/目的IP地址。
步骤 4: IP数据报在数据链路层加上了MAC头部,带有源/目的MAC地址。
步骤 5: 封装好的帧被送到物理层,并以二进制比特流的形式通过网络发送。
步骤 6-10: 当设备B从网络接收到比特流时,它执行去封装过程,这是一个封装过程的逆过程。头部逐层被移除,最终,设备B可以读取数据。
我们需要网络模型中的层次,因为每一层都专注于自己的职责。每一层都可以依赖头部来进行处理指令,而不必知道来自上一层的数据的含义。
下图展示了正向代理(forward proxy)与反向代理(reverse proxy)之间的区别。
正向代理是一个位于用户设备和互联网之间的服务器。
正向代理通常用于:
反向代理是一个接受客户端请求的服务器,将请求转发给web服务器,并将结果返回给客户端,就好像代理服务器处理了请求一样。
反向代理适用于:
静态算法
动态算法
URI
URI代表统一资源标识符。它标识了网络上的一个逻辑或物理资源。URL和URN是URI的子类型。URL定位资源,而URN命名资源。
URI由以下部分组成:scheme:[//authority]path[?query][#fragment]
URL
URL代表统一资源定位符,是HTTP的关键概念。它是网络上唯一资源的地址。它可以用在其他协议中,如FTP和JDBC。
URN
URN代表统一资源名称。它使用urn方案。URN不能用来定位资源。图中给出的一个简单例子由一个命名空间和一个命名空间特有的字符串组成。
如果您想了解更多关于这个主题的细节,我建议您参考W3C。
本文作者:Eric
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!