【 ?? YouTube | ?通讯】
使用视觉效果和简单术语解释复杂的系统。
无论您是准备系统设计面试,还是只是想了解系统在底层如何工作,我们希望这个存储库能够帮助您实现这一目标。
架构风格定义了应用程序编程接口 (API) 的不同组件如何相互交互。因此,它们通过提供设计和构建 API 的标准方法来确保效率、可靠性以及与其他系统集成的便捷性。以下是最常用的样式:
肥皂:
成熟、全面、基于 XML
最适合企业应用
宁静:
流行的、易于实现的 HTTP 方法
网络服务的理想选择
图形语言:
查询语言,请求特定数据
减少网络开销,加快响应速度
远程过程调用:
现代、高性能协议缓冲区
适合微服务架构
网络套接字:
实时、双向、持久连接
非常适合低延迟数据交换
网络钩子:
事件驱动、HTTP 回调、异步
事件发生时通知系统
在 API 设计方面,REST 和 GraphQL 各有优缺点。
下图显示了 REST 和 GraphQL 之间的快速比较。
休息
GraphQL
REST 和 GraphQL 之间的最佳选择取决于应用程序和开发团队的具体要求。 GraphQL 非常适合复杂或频繁变化的前端需求,而 REST 适合首选简单且一致的合约的应用程序。
这两种 API 方法都不是灵丹妙药。仔细评估要求和权衡对于选择正确的风格非常重要。 REST 和 GraphQL 都是公开数据和支持现代应用程序的有效选项。
RPC(Remote procedure Call)之所以被称为“远程”,是因为在微服务架构下,当服务部署到不同的服务器上时,它可以实现远程服务之间的通信。从用户的角度来看,它的作用就像本地函数调用。
下图说明了gRPC的整体数据流。
步骤 1:从客户端发出 REST 调用。请求正文通常为 JSON 格式。
步骤 2 - 4:订单服务(gRPC 客户端)接收 REST 调用,对其进行转换,并对支付服务进行 RPC 调用。 gRPC 将客户端存根编码为二进制格式并将其发送到低级传输层。
步骤 5:gRPC 通过 HTTP2 通过网络发送数据包。由于二进制编码和网络优化,gRPC 据说比 JSON 快 5 倍。
步骤 6 - 8:支付服务(gRPC 服务器)从网络接收数据包,对其进行解码,然后调用服务器应用程序。
步骤 9 - 11:结果从服务器应用程序返回,经过编码后发送到传输层。
步骤 12 - 14:订单服务接收数据包,对其进行解码,并将结果发送到客户端应用程序。
下图显示了轮询和 Webhook 之间的比较。
假设我们运营一个电子商务网站。客户端通过API网关向订单服务发送订单,订单服务再到支付服务进行支付交易。然后,支付服务与外部支付服务提供商 (PSP) 对话以完成交易。
有两种方法可以处理与外部 PSP 的通信。
1. 短轮询
支付服务向PSP发送支付请求后,不断向PSP询问支付状态。经过几轮之后,PSP终于返回状态。
短轮询有两个缺点:
2. 网络钩子
我们可以向外部服务注册一个 webhook。这意味着:当您有请求更新时,通过某个 URL 给我回电。当 PSP 完成处理后,它将调用 HTTP 请求来更新支付状态。
这样就改变了编程范式,支付服务不再需要浪费资源来轮询支付状态。
如果 PSP 不回电怎么办?我们可以设置一个内务工作来每小时检查付款状态。
Webhooks 通常称为反向 API 或推送 API,因为服务器向客户端发送 HTTP 请求。使用webhook时我们需要注意3件事:
下图显示了提高 API 性能的 5 个常见技巧。
分页
当结果很大时,这是一种常见的优化。结果会流回客户端以提高服务响应能力。
异步日志记录
同步日志记录每次调用都会处理磁盘,并且会降低系统速度。异步日志记录首先将日志发送到无锁缓冲区并立即返回。日志将定期刷新到磁盘。这显着减少了 I/O 开销。
缓存
我们可以将经常访问的数据存储到缓存中。客户端可以先查询缓存,而不是直接访问数据库。如果缓存未命中,客户端可以从数据库中查询。像Redis这样的缓存将数据存储在内存中,因此数据访问比数据库要快得多。
有效负载压缩
可以使用 gzip 等压缩请求和响应,以便传输的数据大小小得多。这可以加快上传和下载速度。
连接池
在访问资源时,我们经常需要从数据库加载数据。打开正在关闭的数据库连接会增加大量开销。因此,我们应该通过打开的连接池连接到数据库。连接池负责管理连接生命周期。
每一代HTTP解决了什么问题?
下图说明了主要功能。
HTTP 1.0 于 1996 年最终确定并完整记录。对同一服务器的每个请求都需要单独的 TCP 连接。
HTTP 1.1 于 1997 年发布。TCP 连接可以保持打开状态以供重用(持久连接),但它并没有解决 HOL(队头)阻塞问题。
HOL阻塞——当浏览器允许的并行请求数用完时,后续请求需要等待前面的请求完成。
HTTP 2.0于2015年发布,通过请求复用解决了HOL问题,消除了应用层的HOL阻塞,但传输层(TCP)仍然存在HOL。
如图所示,HTTP 2.0 引入了 HTTP“流”的概念:一种允许将不同的 HTTP 交换复用到同一 TCP 连接上的抽象。每个流不需要按顺序发送。
HTTP 3.0 第一稿于 2020 年发布。它是 HTTP 2.0 的拟议后继者。它使用 QUIC 而不是 TCP 作为底层传输协议,从而消除了传输层的 HOL 阻塞。
QUIC 基于 UDP。它将流作为传输层的一等公民引入。 QUIC 流共享相同的 QUIC 连接,因此创建新流不需要额外的握手和缓慢启动,但 QUIC 流是独立交付的,因此在大多数情况下,影响一个流的数据包丢失不会影响其他流。
下图展示了 API 时间线和 API 风格比较。
随着时间的推移,不同的 API 架构风格被发布。它们每个都有自己的标准化数据交换模式。
您可以在图中查看每种样式的用例。
下图显示了代码优先开发和 API 优先开发之间的区别。为什么我们要考虑API优先设计?
在编写代码并仔细定义服务的边界之前,最好先考虑一下系统的复杂性。
我们可以在编写代码之前模拟请求和响应来验证 API 设计。
开发人员也对这个过程感到满意,因为他们可以专注于功能开发,而不是协商突然的变化。
在项目生命周期结束时出现意外的可能性会降低。
因为我们先设计了API,所以可以在开发代码的同时设计测试。在某种程度上,我们在使用API优先开发时也有TDD(测试驱动设计)。
HTTP 的响应代码分为五类:
信息性 (100-199) 成功 (200-299) 重定向 (300-399) 客户端错误 (400-499) 服务器错误 (500-599)
下图显示了详细信息。
步骤 1 - 客户端向 API 网关发送 HTTP 请求。
步骤 2 - API 网关解析并验证 HTTP 请求中的属性。
步骤 3 - API 网关执行允许列表/拒绝列表检查。
步骤 4 - API 网关与身份提供商对话以进行身份验证和授权。
步骤 5 - 将速率限制规则应用于请求。如果超过限制,请求将被拒绝。
步骤 6 和 7 - 现在请求已通过基本检查,API 网关通过路径匹配找到要路由到的相关服务。
步骤 8 - API 网关将请求转换为适当的协议并将其发送到后端微服务。
步骤9-12:API网关可以正确处理错误,如果错误需要较长时间才能恢复(熔断),则处理故障。它还可以利用 ELK(Elastic-Logstash-Kibana)堆栈进行日志记录和监控。我们有时会在 API 网关中缓存数据。
下图显示了典型的 API 设计和购物车示例。
请注意,API 设计不仅仅是 URL 路径设计。大多数时候,我们需要选择正确的资源名称、标识符和路径模式。设计正确的 HTTP 标头字段或在 API 网关内设计有效的速率限制规则同样重要。
数据如何通过网络发送?为什么 OSI 模型需要这么多层?
下图显示了数据在网络传输时如何封装和解封装。
步骤1:当设备A通过HTTP协议通过网络向设备B发送数据时,首先在应用层添加HTTP头。
步骤2:然后将TCP或UDP标头添加到数据中。它在传输层被封装成 TCP 报文段。标头包含源端口、目标端口和序列号。
步骤 3:然后在网络层用 IP 标头封装这些段。 IP 标头包含源/目标 IP 地址。
步骤 4:IP 数据报在数据链路层添加 MAC 标头,其中包含源/目标 MAC 地址。
步骤5:封装后的帧被发送到物理层并以二进制位通过网络发送。
步骤6-10:当Device B从网络接收到比特时,它执行解封装过程,这是封装过程的逆处理。头部被逐层去除,最终Device B可以读取数据。
我们在网络模型中需要分层,因为每一层都专注于自己的职责。每层都可以依赖标头来处理指令,不需要知道最后一层数据的含义。
下图显示了 ?????? 之间的差异????和一个??????? ????。
转发代理是位于用户设备和互联网之间的服务器。
转发代理通常用于:
反向代理是一种服务器,它接受客户端的请求,将请求转发到 Web 服务器,并将结果返回给客户端,就好像代理服务器已经处理了请求一样。
反向代理适用于:
下图展示了6种常见的算法。
循环赛
客户端请求按顺序发送到不同的服务实例。这些服务通常要求是无状态的。
粘性循环法
这是循环算法的改进。如果 Alice 的第一个请求发送到服务 A,则后续请求也会发送到服务 A。
加权循环赛
管理员可以指定每项服务的权重。权重较高的人比其他人处理更多的请求。
哈希值
该算法对传入请求的 IP 或 URL 应用哈希函数。根据哈希函数结果将请求路由到相关实例。
最少连接数
新请求将发送到并发连接数最少的服务实例。
最短响应时间
新请求将发送到响应时间最快的服务实例。
下图显示了 URL、URI 和 URN 的比较。
URI 代表统一资源标识符。它标识网络上的逻辑或物理资源。 URL 和URN 是URI 的子类型。 URL定位资源,而URN命名资源。
URI 由以下部分组成:scheme:[//authority]path[?query][#fragment]
URL 代表统一资源定位符,是 HTTP 的关键概念。它是网络上独特资源的地址。它可以与 FTP 和 JDBC 等其他协议一起使用。
URN 代表统一资源名称。它使用 urn 方案。 URN 不能用于定位资源。图中给出的一个简单示例由命名空间和特定于命名空间的字符串组成。
如果您想了解有关该主题的更多详细信息,我建议您查看 W3C 的说明。
第 1 节 - SDLC 与 CI/CD
软件开发生命周期(SDLC)由几个关键阶段组成:开发、测试、部署和维护。 CI/CD 自动化并集成这些阶段,以实现更快、更可靠的发布。
当代码被推送到 git 存储库时,它会触发自动构建和测试过程。运行端到端 (e2e) 测试用例来验证代码。如果测试通过,代码可以自动部署到登台/生产环境。如果发现问题,代码将被发送回开发以修复错误。这种自动化为开发人员提供了快速反馈,并降低了生产中出现错误的风险。
第 2 节 - CI 和 CD 之间的区别
持续集成 (CI) 自动执行构建、测试和合并过程。只要提交代码,它就会运行测试以尽早检测集成问题。这鼓励频繁的代码提交和快速反馈。
持续交付 (CD) 可自动执行基础架构更改和部署等发布流程。它确保可以通过自动化工作流程随时可靠地发布软件。 CD 还可以自动执行生产部署之前所需的手动测试和批准步骤。
第 3 节 - CI/CD 管道
典型的 CI/CD 管道有几个相连的阶段:
规划:Netflix 工程使用 JIRA 进行规划,使用 Confluence 进行文档编制。
编码:Java 是后端服务的主要编程语言,而其他语言则用于不同的用例。
构建:Gradle 主要用于构建,Gradle 插件的构建是为了支持各种用例。
打包:包和依赖项打包到 Amazon 系统映像 (AMI) 中以供发布。
测试:测试强调生产文化对构建混乱工具的关注。
部署:Netflix 使用自建的 Spinnaker 进行金丝雀部署。
监控:监控指标集中在Atlas中,使用Kayenta来检测异常情况。
事件报告:事件按照优先级调度,使用PagerDuty进行事件处理。
这些架构模式是应用程序开发中最常用的模式,无论是在 iOS 还是 Android 平台上。开发人员引入它们是为了克服早期模式的局限性。那么,它们有何不同?
模式是常见设计问题的可重用解决方案,从而实现更顺畅、更高效的开发过程。它们充当构建更好的软件结构的蓝图。这些是一些最流行的模式:
为您的项目选择正确的数据库是一项复杂的任务。许多数据库选项都适合不同的用例,很快就会导致决策疲劳。
我们希望这份备忘单提供高级指导,以找到符合您项目需求的正确服务并避免潜在的陷阱。
注意:Google 关于其数据库用例的文档有限。尽管我们尽力查看可用的内容并得出最佳选择,但某些条目可能需要更准确。
答案会根据您的用例而有所不同。数据可以在内存或磁盘上建立索引。同样,数据格式也各不相同,例如数字、字符串、地理坐标等。系统可能是写入密集型的,也可能是读取密集型的。所有这些因素都会影响您对数据库索引格式的选择。
以下是一些用于索引数据的最流行的数据结构:
下图显示了该过程。请注意,不同数据库的架构有所不同,该图演示了一些常见的设计。
步骤1 - 通过传输层协议(例如TCP)将SQL 语句发送到数据库。
步骤 2 - SQL 语句被发送到命令解析器,在其中进行语法和语义分析,然后生成查询树。
步骤 3 - 查询树被发送到优化器。优化器创建执行计划。
步骤 4 - 将执行计划发送给执行者。执行器从执行中检索数据。
第 5 步 - 访问方法提供执行所需的数据获取逻辑,从存储引擎检索数据。
步骤 6 - 访问方法决定 SQL 语句是否是只读的。如果查询是只读的(SELECT 语句),则会将其传递到缓冲区管理器以进行进一步处理。缓冲区管理器在缓存或数据文件中查找数据。
步骤 7 - 如果语句是 UPDATE 或 INSERT,则将其传递到事务管理器以进行进一步处理。
步骤 8 - 在事务期间,数据处于锁定模式。这是由锁管理器保证的。它还确保了事务的 ACID 属性。
CAP 定理是计算机科学中最著名的术语之一,但我敢打赌不同的开发人员有不同的理解。让我们来看看它是什么以及为什么它会令人困惑。
CAP 定理指出,分布式系统不能同时提供这三个保证中的两个以上。
一致性:一致性是指所有客户端无论连接到哪个节点,都同时看到相同的数据。
可用性:可用性意味着即使某些节点发生故障,任何请求数据的客户端都会得到响应。
分区容错性:分区表示两个节点之间的通信中断。分区容错意味着尽管存在网络分区,系统仍能继续运行。
“3 之 2”的表述可能很有用,但这种简化可能会产生误导。
选择数据库并不容易。仅仅根据 CAP 定理证明我们的选择是不够的。例如,公司不会仅仅因为 Cassandra 是 AP 系统就选择它作为聊天应用程序。 Cassandra 具有一系列良好的特性,使其成为存储聊天消息的理想选择。我们需要更深入地挖掘。
“CAP 仅禁止设计空间的一小部分:在存在分区的情况下实现完美的可用性和一致性,这很少见”。引自论文:CAP 十二年后:“规则”如何变化。
该定理大约是 100% 的可用性和一致性。更现实的讨论是没有网络分区时延迟和一致性之间的权衡。有关详细信息,请参阅 PACELC 定理。
CAP定理真的有用吗?
我认为它仍然有用,因为它让我们能够进行一系列权衡讨论,但这只是故事的一部分。在选择正确的数据库时,我们需要更深入地挖掘。
SQL语句由数据库系统分几个步骤执行,包括:
SQL的执行非常复杂,涉及很多考虑因素,例如:
1986 年,SQL(结构化查询语言)成为标准。在接下来的 40 年里,它成为关系数据库管理系统的主导语言。阅读最新标准 (ANSI SQL 2016) 可能非常耗时。我怎样才能学习它?
SQL 语言有 5 个组件:
对于后端工程师来说,你可能需要了解其中的大部分内容。作为数据分析师,您可能需要对 DQL 有很好的了解。选择与您最相关的主题。
该图说明了我们在典型架构中缓存数据的位置。
沿着流程有多个层次。
主要原因有3个,如下图所示。
问:另一种流行的内存存储是 Memcached。你知道Redis和Memcached的区别吗?
您可能已经注意到该图的风格与我之前的帖子不同。请告诉我您更喜欢哪一个。
Redis 不仅仅是缓存。
Redis 可用于多种场景,如图所示。
会议
我们可以使用Redis在不同服务之间共享用户会话数据。
缓存
我们可以使用Redis来缓存对象或页面,尤其是热点数据。
分布式锁
我们可以使用Redis字符串来获取分布式服务之间的锁。
柜台
我们可以统计文章的点赞数或阅读量。
速率限制器
我们可以对某些用户 IP 应用速率限制器。
全局 ID 生成器
我们可以使用 Redis Int 作为全局 ID。
购物车
我们可以使用 Redis Hash 来表示购物车中的键值对。
计算用户保留率
我们可以使用Bitmap来表示每天的用户登录情况并计算用户留存情况。
消息队列
我们可以使用 List 作为消息队列。
排行
我们可以使用ZSet对文章进行排序。
设计大型系统通常需要仔细考虑缓存。以下是五种常用的缓存策略。
下图展示了一个典型的微服务架构。
微服务的好处:
一图胜千言:开发微服务的 9 个最佳实践。
当我们开发微服务时,需要遵循以下最佳实践:
在下面,您会找到一个图表,显示了用于开发阶段和生产的微服务技术堆栈。
有许多设计决策导致了卡夫卡的表现。在这篇文章中,我们将重点关注两个。我们认为这两个重量最大。
该图说明了如何在生产者和消费者之间传输数据,以及零拷贝的含义。
2.1数据从磁盘加载到OS缓存
2.2数据从OS缓存到Kafka应用程序
2.3 Kafka应用程序将数据复制到套接字缓冲区中
2.4数据将从插座缓冲区复制到网卡
2.5网卡将数据发送给消费者
3.1:将数据从磁盘加载到OS CACH 3.2 OS CACHE通过sendfile()命令将数据直接复制到网络卡3.3网络卡将数据发送到消费者
零副本是在应用程序上下文和内核上下文之间保存多个数据副本的快捷方式。
下图显示了信用卡支付流程的经济学。
1。持卡人支付商人100美元购买产品。
2。商人从使用销量更高的信用卡中受益,需要补偿发行人和卡网络提供付款服务。收购银行向商人收取了费用,称为“商人折扣费”。
3-4。收购银行作为收购标记的$ 0.25,并以1.75美元的价格将$ 1.75支付给发行银行作为互换费。商户折扣费应包含交换费。
交换费由卡网络设定,因为每个发卡行与每个商户协商费用的效率较低。
5。卡网络与每个银行设置了网络评估和费用,该银行每月为其服务支付卡网络。例如,VISA 对每次刷卡收取 0.11% 的评估费,另加 0.0195 美元的使用费。
6。持卡人向发行银行支付其服务。
开证行为何要赔偿?
Visa,MasterCard和American Express作为清算和结算资金的卡网络。收购银行的卡和卡发行银行的卡可能而且通常是不同的。如果银行要一一解决没有中介的交易,则每家银行将不得不与所有其他银行解决交易。这是非常效率的。
下图显示了Visa在信用卡支付过程中的作用。涉及两个流。当客户刷信用卡时,会发生授权流。当商人想在一天结束时赚钱时,就会发生捕获和和解流。
步骤0:发行银行的卡向其客户发行信用卡。
步骤1:持卡人希望在商店商店的销售点(POS)终端购买产品并刷信用卡。
步骤2:POS终端将交易发送给已提供POS终端的收购银行。
步骤3和4:收购银行将交易发送到卡网络,也称为卡方案。卡网络将交易发送给发行银行批准。
步骤4.1、4.2和4.3:如果交易获得批准,则发行银行将冻结钱。批准或拒绝将发送回收购方以及POS终端。
步骤1和2:商人希望在一天结束时收集钱,因此他们在POS终端上击中了“捕获”。交易将分批发送给收购方。收购方将带有交易的批处理文件发送到卡网络。
步骤3:卡网络对从不同收购方收集的交易进行清算,并将清算文件发送给不同的发行银行。
步骤4:发行银行确认清算文件的正确性,并将资金转移到相关的收购银行。
步骤5:收购银行然后将资金转交给商户的银行。
步骤4:卡网络清除了来自不同收购银行的交易。清算是一个净额交易的过程,因此减少了总交易的数量。
在此过程中,卡网络承担与每家银行交谈并收到服务费的负担。
什么是UPI? UPI是印度国家支付公司开发的即时实时支付系统。
当今印度的数字零售交易占数字零售交易的60%。
UPI =付款标记语言 +可互操作付款的标准
DevOps,SRE和平台工程的概念已经在不同的时间出现,并且已经由各种个人和组织开发。
Devops作为一个概念是由Patrick Debois和Andrew Shafer在敏捷会议上引入的。他们试图通过促进协作文化并对整个软件开发生命周期分担责任来弥合软件开发与运营之间的差距。
SRE或站点可靠性工程在2000年代初被Google开创,以应对管理大规模,复杂系统的运营挑战。 Google开发了SRE实践和工具,例如Borg群集管理系统和君主监控系统,以提高其服务的可靠性和效率。
平台工程是基于SRE Engineering的基础的最新概念。平台工程的确切起源尚不清楚,但通常被认为是DevOps和SRE实践的扩展,重点是为产品开发提供全面的平台,以支持整个业务视角。
值得注意的是,尽管这些概念在不同的时间出现。它们都与改善协作,自动化和软件开发和运营效率的更广泛趋势有关。
K8S是一个容器编排系统。它用于容器部署和管理。 Google的内部系统Borg极大地影响了它的设计。
一个K8S群集由运行容器化应用程序的一组工具组成,称为节点。每个集群至少有一个工作节点。
工作节点托管作为应用程序工作负载组件的 Pod。控制平面管理集群中的工作节点和 Pod。在生产环境中,控制平面通常跨多台计算机运行,并且群集通常运行多个节点,从而提供容错和高可用性。
API服务器
API 服务器与 k8s 集群中的所有组件进行通信。 POD上的所有操作都是通过与API服务器交谈来执行的。
调度程序
调度程序观看POD工作负载,并在新创建的POD上分配负载。
控制器经理
控制器管理器运行控制器,包括节点控制器、作业控制器、EndpointSlice 控制器和 ServiceAccount 控制器。
等
ETCD是一家钥匙值商店,用作所有集群数据的Kubernetes的支持商店。
豆荚
POD是一组容器,是K8管理的最小单元。 Pods在POD中的每个容器中都有一个IP地址。
库贝莱特
在集群中的每个节点上运行的代理。它确保容器在吊舱中运行。
Kube代理
Kube-Proxy是一个网络代理,可在群集中的每个节点上运行。它将流量路由到服务中的节点。它将工作请求转发到正确的容器。
什么是Docker?
Docker是一个开源平台,可让您在隔离容器中打包,分发和运行应用程序。它着重于容器化,提供了封装应用程序及其依赖性的轻质环境。
什么是Kubernetes?
Kubernetes通常称为K8,是一个开源的容器编排平台。它提供了一个框架,用于自动化一组节点群体的容器化应用程序的部署,扩展和管理。
彼此之间有何不同?
Docker:Docker在单个操作系统主机上在单个容器级别运行。
您必须手动管理每个主机,并为多个相关容器设置网络,安全策略和存储可能很复杂。
Kubernetes:Kubernetes在集群级别运行。它在多个主机上管理多个容器化应用程序,为负载平衡,缩放和确保所需的应用程序状态等任务提供自动化。
简而言之,Docker专注于在单个主机上的容器化和运行容器,而Kubernetes专门研究和在各种主机群体上进行规模管理和编排容器。
下图显示了Docker的架构及其运行“ Docker Build”,“ Docker Pull”和“ Docker Run”时的工作方式。
Docker体系结构中有3个组件:
Docker客户端
Docker客户与Docker守护程序进行了交谈。
Docker主机
Docker守护程序聆听Docker API请求并管理Docker对象,例如图像,容器,网络和卷。
Docker 注册表
Docker 注册表存储 Docker 镜像。 Docker Hub是任何人都可以使用的公共注册表。
让我们以“ Docker Run”命令为例。
首先,必须确定我们的代码的存储位置至关重要。常见的假设是,只有两个位置 - 一个位于GitHub等远程服务器上,另一个在我们本地计算机上。但是,这并不是完全准确的。 GIT在我们的机器上维护三个本地存储,这意味着我们的代码可以在四个地方找到:
大多数git命令主要在这四个位置之间移动文件。
下图显示了GIT工作流程。
Git 是一个分布式版本控制系统。
每个开发人员都维护主要存储库的本地副本,并编辑并提交本地副本。
该提交非常快,因为该操作不会与远程存储库相互作用。
如果远程存储库崩溃,则可以从本地存储库中恢复文件。
有什么区别?