<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ 软件架构 - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ freeCodeCamp 是一个免费学习编程的开发者社区，涵盖 Python、HTML、CSS、React、Vue、BootStrap、JSON 教程等，还有活跃的技术论坛和丰富的社区活动，在你学习编程和找工作时为你提供建议和帮助。 ]]>
        </description>
        <link>https://www.freecodecamp.org/chinese/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ 软件架构 - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 11 May 2026 15:33:09 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/tag/software-architecture/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 软件架构手册 ]]>
                </title>
                <description>
                    <![CDATA[ 大家好！在本文中，我们将讨论一个非常有趣、广泛且复杂的主题：软件架构。 我刚开始写代码时就被这个主题困扰过，在这篇文章中，我将尝试为你提供简单的、表面的、易于理解的介绍。 我们将讨论软件领域中的架构是什么、一些主要概念以及当今最流行的架构模式。 对于每个主题，我都会给出一个简单、初级的理论介绍和代码或者伪代码示例，你可以从中了解每个概念是如何运行的。让我们开始吧！ 目录  * 什么是软件架构  * 重要的软件架构概念 * 什么是客户端——服务器模型     * 什么是 API     * 什么是模块化          * 你的基础架构是什么样的 * 单体式架构     * 微服务架构     * ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/an-introduction-to-software-architecture-patterns/</link>
                <guid isPermaLink="false">62e7a9728d13aa0845c6373a</guid>
                
                    <category>
                        <![CDATA[ 软件架构 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ PapayaHUANG ]]>
                </dc:creator>
                <pubDate>Mon, 01 Aug 2022 07:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/08/pexels----3172740-1.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/an-introduction-to-software-architecture-patterns/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">The Software Architecture Handbook</a>
      </p><!--kg-card-begin: markdown--><p>大家好！在本文中，我们将讨论一个非常有趣、广泛且复杂的主题：软件架构。</p>
<p>我刚开始写代码时就被这个主题困扰过，在这篇文章中，我将尝试为你提供简单的、表面的、易于理解的介绍。</p>
<p>我们将讨论软件领域中的架构是什么、一些主要概念以及当今最流行的架构模式。</p>
<p>对于每个主题，我都会给出一个简单、初级的理论介绍和代码或者伪代码示例，你可以从中了解每个概念是如何运行的。让我们开始吧！</p>
<h2 id="">目录</h2>
<ul>
<li><a href="#what-is-software-architecture">什么是软件架构</a></li>
<li><a href="#important-software-architecture-concepts-to-know">重要的软件架构概念</a>
<ul>
<li><a href="#whats-the-client-server-model">什么是客户端——服务器模型</a></li>
<li><a href="#what-are-apis">什么是 API</a></li>
<li><a href="#what-is-modularity">什么是模块化</a></li>
</ul>
</li>
<li><a href="#what-s-your-infrastructure-like">你的基础架构是什么样的</a>
<ul>
<li><a href="#monolithic-architecture">单体式架构</a></li>
<li><a href="#microservices-architecture">微服务架构</a></li>
<li><a href="#what-is-back-end-for-front-end-bff-">服务于前端的后端是什么（BFF）</a></li>
<li><a href="#how-to-use-load-balancers-and-horizontal-scaling">如何使用负载均衡器和水平扩展</a></li>
</ul>
</li>
<li><a href="#where-your-infrastructure-lives">你的基础架构所在的位置</a>
<ul>
<li><a href="#on-premise-hosting">本地托管</a></li>
<li><a href="#traditional-server-providers">传统服务器供应商</a></li>
<li><a href="#hosting-on-the-cloud">云托管</a>
<ul>
<li><a href="#traditional">传统的</a></li>
<li><a href="#elastic">弹性的</a></li>
<li><a href="#serverless">无服务的</a></li>
<li><a href="#lots-of-other-services">更多其他服务</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#different-folder-structures-to-know">不同的文件夹结构</a>
<ul>
<li><a href="#all-in-one-place-folder-structure">全在一个文件夹中的结构</a></li>
<li><a href="#layers-folder-structure">分层文件夹结构</a></li>
<li><a href="#mvc-folder-structure">MVC 文件夹结构</a></li>
</ul>
</li>
<li><a href="#conclusion">总结</a></li>
</ul>
<h1 id="what-is-software-architecture">什么是软件架构</h1>
<p><a href="https://www.sei.cmu.edu/our-work/software-architecture/">卡耐基·梅隆大学软件工程学院给的定义</a>：</p>
<blockquote>
<p>系统的软件架构代表与整个系统结构和行为相关的设计决策。</p>
</blockquote>
<p>这个说法很笼统，对吧？当然！这正是在研究软件架构时让我非常困惑的地方。软件架构包含很多内容，这个术语可以指代不同的事物。</p>
<p>我用简单的话来概括就是：软件架构是指你在创建软件的过程中如何组织内容。而这里的“内容”可以指：</p>
<ul>
<li><strong>实现细节</strong>（即你仓库的文件夹结构）</li>
<li><strong>实现</strong> <strong>设计</strong> 决策（你是使用服务端还是客户端渲染？使用关系型还是非关系性数据库）</li>
<li>你选择的<strong>技术</strong>（你是使用 REST 还是 GraphQl API? 后端使用 Python/Django 还是 Nod/Express 技术栈？）</li>
<li><strong>系统</strong> <strong>设计</strong> 决策（你的系统是采用单体式架构还是微服务架构？）</li>
<li><strong>基础设施</strong>决策（你是在本地还是在云提供商上托管软件？）</li>
</ul>
<p>以上概括了非常多的选择和可能性。让情况变得更复杂的是，在这个五个类别中，不同的模式可以结合。比方说，我可以采用一个单体式的 REST 或者 GraphQL 的 API，或者一个微服务架构的应用托管在云供应商或者本地。</p>
<p>为了更好地解释这些混沌的概念，首先我们将讨论一些基础的概念，然后再逐条讲解这些分类，并解释时下搭建应用最常用的架构模式和选择。</p>
<h1 id="#important-software-architecture-concepts-to-know">重要的软件结构概念</h1>
<h2 id="whats-the-client-server-model">什么是客户端-服务器模型</h2>
<p><strong>客户端-服务器</strong>是一种构建应用程序任务或者工作负载结构的模型，连接资源或服务<strong>提供者</strong>（服务器）和服务或资源请求者（客户端）。</p>
<p>简言之，客户端就是请求信息或者行为的应用程序；服务器就是根据客户端的请求，发送信息或者执行行为的程序。</p>
<p>客户端通常是前端应用，可以在 Web 或者手机应用上运行（虽然也可以通过其他平台使用以及后端应用也可以被当作客户端）；服务器通常是后端应用。</p>
<p>举个例子，想象你在浏览你最喜欢的社交网络，当你在浏览器输入 URL 并点击回车之后，你的浏览器就像客户端应用一样，向社交网络服务器<strong>发送请求</strong>，社交网络服务器<strong>响应</strong>请求，并向你发送网站内容。</p>
<p>时下大部分应用都采用客户端-服务器模型，最重要的概念是<strong>客户端请求资源和服务</strong>，<strong>服务器实现</strong>。</p>
<p>另一个重要的概念是，虽然客户端和服务器隶属于同一个系统，但是两者各自都拥有自己的应用或者程序。也就是说你可以分别开发、托管和执行两者。</p>
<p>如果你不熟悉前端和后端的区别，<a href="https://chinese.freecodecamp.org/news/frontend-vs-backend-whats-the-difference/">这里有一篇写得不错的文章，供你参考</a>。这里还有<a href="https://www.freecodecamp.org/news/how-the-web-works-part-ii-client-server-model-the-structure-of-a-web-application-735b4b6d76e3/">另一篇文章</a>介绍了客户端-服务器的概念。</p>
<h2 id="what-are-apis">什么是 API</h2>
<p>我们刚刚讲解了客户端和服务器是两个相互通信的实体，前端发送请求，后端响应请求。两者相互通信通常是通过 API（应用程序接口）。</p>
<p>API 只不过是一系列确定应用间如何通信的规则，就像两方之间的协议：“如果你发送 A，我就响应 B；如果你发送 C，我就响应 D……”。</p>
<p>有了这一系列规则，客户端就知道完成特定任务需要发送什么请求；而服务器也知道客户端特定行为意味着什么需求。</p>
<p>API 的实现方式多种多样，时下最常用的是 REST、SOAP 和 GraphQL。</p>
<p>在 API 通信中，HTTP 协议是最常使用的，内容通常采用 JSON 或者 XML 格式。不过也存在其他的协议和内容格式。</p>
<p>如果你想要进一步了解这个话题，推荐你阅读<a href="https://chinese.freecodecamp.org/news/http-request-methods-explained/">这篇文章</a> 。</p>
<h2 id="#what-is-modularity">什么是模块化</h2>
<p>当我们在软件工程中讨论“模块化”，我们指的是将大事化小的行为。拆解的目的是为了简化庞大的应用或者代码库。</p>
<p>模块化具备以下优势：</p>
<ul>
<li>这有利于将关注点和功能分离，有助于项目的可视化、理解和组织。</li>
<li>当项目被清晰地构建和细分之后，就更容易维护也更不容易出错。</li>
<li>如果项目被细分为许多不同的部分，每个部分可以单独进行处理和修改，这样更利于软件开发。</li>
</ul>
<p>这听上去有些笼统，但是模块化或者说将项目细分是软件架构中非常重要的一部分。所以只要记住这个概念，通过一些例子，你对它的理解会更加清晰。;)</p>
<p>如果你想要阅读更多该话题相关内容，我最近写了一篇 <a href="https://chinese.freecodecamp.org/news/modules-in-javascript/">关于在 JS 中使用模块的文章</a> ，希望对你有帮助。</p>
<h1 id="what-s-your-infrastructure-like">你的基础架构是什么样的</h1>
<p>好的，我们进入文章的精华部分了。我们将讨论构建软件应用程序的不同方式，从项目的基础架构开始。</p>
<p>为了让概念不那么抽象，我将创建一个虚构的应用，叫作 Notflix。🤔🤫</p>
<p>注意：请记住这个例子可能不太现实，我仅以此作为讲解概念的例子。这里只是为了帮助你通过例子来了解架构的核心概念，而不是分析现实例子。</p>
<h2 id="monolithic-architecture">单体式架构</h2>
<p>Notflix 将是一视频流媒体应用，用户可以使用它观看电影、剧集、纪录片等。用户可以在 Web 浏览器、手机和 TV 应用上使用它。</p>
<p>这个应用的主要服务包括：<strong>验证</strong>（用户可以创建账户、登陆等）、<strong>支付</strong>（用户可以订阅并获取内容，你不希望服务完全免费，对吧？😑）和<strong>流媒体</strong>（用户可以观看付费内容）。</p>
<p>基础的架构如图：</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/Untitled-Diagram.drawio-3.png" alt="经典的单体式架构" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>经典的单体式架构</figcaption>
</figure>
<p>左手边是三种不同的前端应用，将作为系统中的客户端。它们可以通过 React 和 React-native 开发。</p>
<p>一个服务器接受三个客户端应用的请求，并在必要的时候和数据库通信，并返回给对应的前端。后端可以由 Node 和 Express 开发。</p>
<p>这种形式的架构就被称为<strong>单体式</strong>，因为仅有一个服务器应用来负责系统的所有功能。在我们的例子中，如果用户需要注册、支付或者观看任意一部影片，所有的请求都发送到同一个服务器应用。</p>
<p>单体式的优势在于设计简单。这种架构的功能和设置简单易操作，这也是为什么大多数应用采用这种架构的原因。</p>
<h2 id="microservices-architecture">微服务架构</h2>
<p>结果 Noflix 表现相当不错。我们刚刚发布了最新一季的《怪奇物语》，这是一部关于青少年说唱歌手的科幻片，以及一部关于一个人潜入公司假扮资深程序员的电影，创造了新的收视纪录。</p>
<p>每个月来自世界各地成千上万的新用户注册 Noflix，这对于我们的经营状况来说是好事，但对于单体式的应用来说可不妙。</p>
<p>最近我们一直在经历服务器响应时间延迟，尽管我们已经<strong>垂直扩展</strong>了服务器（增加了 RAM 和GPU），但是服务器还是超负载了。</p>
<p>此外，我们也在系统中开发新的功能（如根据用户喜好推荐电影的推荐工具），<strong>代码库变得臃肿且复杂</strong>。</p>
<p>深入分析问题之后，我们发现是流媒体占用了大量的资源，其他服务如认证和支付资源占比不大。</p>
<p>为了解决这个问题，我们决定实现<strong>微服务架构</strong>，如图所示：</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/Untitled-Diagram.drawio--1-.png" alt="我们的首个微服务架构" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>我们的首个微服务架构</figcaption>
</figure>
<p>如果你刚接触这个概念，你可能会问“微服务到底是个什么玩意儿？”，其实就是把服务器细分成不同的小服务器，负责一个或者几个功能。</p>
<p>在我们例子中，起初我们仅有一个服务器来响应所有功能（单体式架构），实现微服务架构后，我们就有一个服务器负责认证，另一个负责支付，还有一个负责流媒体，最后一个负责推荐。</p>
<p>当需要登陆的时候，客户端应用与认证服务通信，用户需要支付时，向支付服务器通信，需要观看视频时向流媒体服务器通信。</p>
<p>所有<strong>通信都通过 API 实现</strong>，这和单体式架构一样（或者通过如 <a href="https://kafka.apache.org/">Kafka</a> 或 <a href="https://www.rabbitmq.com/">RabbitMQ</a> 等通信系统）。唯一的区别是，现在我们使用不同的服务器负责不同的行为，而不是采用一个服务器解决所有问题。</p>
<p>听上去有一点点复杂，确实如此，微服务的优势在于：</p>
<ul>
<li>你可以<strong>根据需要扩展特定服务</strong>，而不是扩展整个后端。在我们的示例中，当碰到体验问题时，我们垂直扩展了整个服务器，但实际上需要更多资源的仅为流媒体部分。把流媒体功能分离到单个服务器，我们就可以扩展这一个服务器，继续其他部分的正常工作。</li>
<li>功能将<strong>松散耦合</strong>，意味着我们可以独立开发和部署这些功能。</li>
<li>每一个服务器的<strong>代码库</strong>更加<strong>短小精悍</strong>，这对于一开始就一起工作的开发者来说是一件好事，对新加入的开发者快速融入也是好事。</li>
</ul>
<p>微服务是一个设置和管理更为复杂的架构，这也是为什么仅有一些非常大的项目才使用这种架构。大部分项目一开始使用的是单体式架构，仅在性能需要时迁移到微服务架构。</p>
<p>如果你想了解更多微服务相关的知识，<a href="https://www.youtube.com/watch?v=CdBtNQZH8a4">这里有一个很好的解释视频</a>。</p>
<h3 id="what-is-back-end-for-front-end-bff-">服务于前端的后端是什么（BFF）</h3>
<p>实现微服务的一个问题是与前端的通信变得复杂。在我们示例中，多个服务器负责不同的行为也就意味着前端应用需要记录是谁发起的请求。</p>
<p>通常解决这个问题的方式是在前端应用和微服务之间增加一个中间层。这个中间层将接受所有前端的请求，重定向到对应的微服务，接受微服务的回应，然后重定向到对应的前端应用。</p>
<p>BFF 模式的好处在于我们在使用了微服务架构的同时，没有复杂化前端应用的通信。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/Untitled-Diagram.drawio--2-.png" alt="BFF实现" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>BFF 实现</figcaption>
</figure>
<p>如果你想了解更多相关内容，这里有一期<a href="https://www.youtube.com/watch?v=SSo-z16wEnc">解释 BFF 模式的视频</a>。</p>
<h3 id="how-to-use-load-balancers-and-horizontal-scaling">如何使用负载均衡器和水平扩展</h3>
<p>我们的流媒体应用正在呈指数型增长，来自世界各地百万量级的用户全天候使用 Noflix 观看电影，马上我们又要面临新的性能问题。</p>
<p>我们再一次发现是流媒体服务承受最大的压力，我们已经尽我们所能<strong>垂直扩展</strong>了这个服务器，进一步细分这个服务成更多微服务没有意义。所以我们决定<strong>水平扩展</strong>服务器。</p>
<p>在前文中我们提到<strong>垂直扩展</strong>就是给单个服务器或者计算机增加更多资源（RAM、磁盘空间、GPU 等）；<strong>水平扩展</strong>就是设置更多的服务器来处理同一个任务。</p>
<p>我们不再只使用一个服务器来负责所有流媒体工作，而是使用三个。这样来自客户端的请求将被平均分配到这三个服务器处理，每一个服务器的负载就被控制在可承受范围内。</p>
<p>请求的分配通常由<strong>负载均衡器</strong>来实现。 负载均衡器如同服务器的**<a href="https://www.strongdm.com/blog/difference-between-proxy-and-reverse-proxy#:~:text=A%20traditional%20forward%20proxy%20server,on%20behalf%20of%20multiple%20servers.">反向代理</a>** ，拦截请求并重定向到对应的服务器。</p>
<p>一个典型的客户端-服务器连接如图：</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/1234.png" alt="这是我们之前的形式" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>这是我们之前的形式</figcaption>
</figure>
<p>使用负载均衡器在多个服务器间分发客户端请求如图：</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/4312.drawio-1.png" alt="这是我们想要的形式" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>这是我们想要的形式</figcaption>
</figure>
<p>水平扩展可以在服务器实现就可以在代码库实现。其中一个实现办法是通过源-副本模型（source-replica model），一个特定的源 DB 将接受所有写入的请求然后复制这些数据到更多的副本 DB，副本 DB 将接受和响应所有读取的请求。</p>
<p>DB 副本的优势在于：</p>
<ul>
<li>更优的性能：这一模型使得更多个请求可以并行。</li>
<li>可靠性和可用性：如果一个数据库服务器因为任何原因被破坏或者无法访问，其他 DB 仍保有数据。</li>
</ul>
<p>实现了负载均衡器、水平扩展和 DB 副本之后，我们的架构如图：</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/Untitled-Diagram.drawio--3--2.png" alt="水平扩展架构" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>水平扩展架构</figcaption>
</figure>
<p>如果你想要了解更多内容，这里有<a href="https://www.youtube.com/watch?v=sCR3SAVdyCc">一个介绍负载均衡器的视频</a>。</p>
<p>注意：当我们在讨论微服务、负载均衡器和水平扩展的时候，我们讨论的是后端应用。对于前端应用来说，我们通常是以单体式架构开发的，当然也有一个有趣的概念叫作<a href="https://www.youtube.com/watch?v=w58aZjACETQ">微前端</a> 。🧐</p>
<h1 id="where-your-infrastructure-lives">你的基础架构所在的位置</h1>
<p>现在我们对应用的基础架构是如何组织的有了一定了解，现在让我们来看看我们把基础架构放在哪里。</p>
<p>主要有三种托管应用程序的方式：本地、传统服务器供应商和云。</p>
<h2 id="on-premise-hosting">本地托管</h2>
<p>本地托管意味着你拥有运行应用软件的硬件。这曾是最传统的托管方式。软件公司为服务器专门提供房间，并且有专业的团队致力于设置和维护硬件。</p>
<p>这样做的好处是公司全权掌握硬件，坏处是这样耗费空间、时间和金钱。</p>
<p>假设你需要水平扩展一个服务器，你需要购买更多的设备，设置好，并且持续监控，一旦出现问题就要维修……如果之后你需要缩小服务器，你通常也没办法退换你购买的设备。</p>
<p>对于公司来说，采用本地托管意味着将资源和精力分配到非公司目标上。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-221.png" alt="我们想象中的Notflix服务器机房" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>我们想象中的 Notflix 服务器机房</figcaption>
</figure>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-222.png" alt="实际的画面" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>实际的画面</figcaption>
</figure>
<p>当需要处理精密或者私人信息的时候，本地托管还是能派上用场的。假设这个软件需要处理一个发电厂或者私人的银行信息，软件公司会决定使用本地托管服务器来全权控制软件和硬件。</p>
<h2 id="traditional-server-providers">传统服务器供应商</h2>
<p>对于大多数公司来说一个更舒适的选择是传统服务器供应商。供应商有自己的服务器，并且提供租赁。你决定为你的项目使用什么样的硬件，并且提交月费（或者根据其他条件确定的费用）。</p>
<p>使用服务器供应商的好处是你不需要担心硬件相关的问题，供应商会处理好。软件公司只需要关注自己的主要目标，软件本身。</p>
<p>另一个好处是，扩展或者缩小变得更加方便自由。如果需要更多硬件，你就购买；如果不需要了，就停止付费。</p>
<p>一个知名供应商的例子是 <a href="https://www.hostinger.com">hostinger</a>。</p>
<h2 id="hosting-on-the-cloud">云托管</h2>
<p>如果你在科技圈待过一阵子，你可能已经听说过不止一次“云”。乍一听，这好像是某种抽象的魔术，实际上云只不过是由 Amazon、Google 和 Microsoft 这样的大公司拥有的超大数据中心。</p>
<p>这些大公司拥有巨大的算力，这些算力并不是时时被利用。与其让这些硬件白白浪费钱，更聪明的做法是将这些算力商业化。</p>
<p>这就是云计算。数据中心可以利用这些算力，使用 <strong>AWS</strong>（Amazon 的 Web 服务）、<strong>Google Cloud</strong> 或 Microsoft 的 <strong>Azure</strong>。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-219.png" alt="“云”实际的样子" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>“云”实际的样子</figcaption>
</figure>
<p>提到云服务，一个很重要的知识点是存在不同的使用方式：</p>
<h3 id="traditional">传统的</h3>
<p>第一种方法与使用传统服务器提供商类似。你可以选择所需的硬件类型并按月支付费用。</p>
<h3 id="elastic">弹性的</h3>
<p>第二种方法利用了大多数供应商提供的“弹性”算力。“弹性”意味着你的应用使用的硬件大小会根据使用情况，自动放大或缩小。</p>
<p>例如，你开始使用的是 8gb 内存和 500gb 磁盘空间的服务器。如果服务器收到越来越多的请求并且这些容量不再足以提供良好的性能，系统可以自动执行垂直或水平扩展。</p>
<p>这样做的好处是，你预先配置服务器后，就没有必再担心它的变化。服务器自动扩展和缩减，你只需为使用的资源付费。</p>
<h3 id="serverless">无服务的</h3>
<p>使用云计算的另一种方式是使用无服务架构。</p>
<p>在这个模式中，没有接受所有请求并响应的服务器，而是独立的函数映射到访问点（类似于 API 端点）。</p>
<p>每当接受到一个请求，这些函数就会执行你编写的程序（链接数据库、执行 CRUD 等普通服务器会做的事情）。</p>
<p>无服务架构的好处是可以免去服务器维护和扩展。如果需要使用，你只需要编写执行的函数，函数会自动根据需要扩展或者缩小。</p>
<p>作为消费者，你只需要支付函数执行的次数以及函数执行持续时长的费用。</p>
<p>如果你想了解更多这方面的内容，<a href="https://www.youtube.com/watch?v=vxJobGtqKVM">这里有一个介绍无服务架构的视频</a>。</p>
<h3 id="lots-of-other-services">更多其他服务</h3>
<p>你很容易发现无服务和弹性云计算提供的简单便捷的设置软件架构的方式。</p>
<p>除了提供服务器相关服务，云供应商还提供许多其他的解决方案，如：关系型和非关系型数据库、文件存储服务、缓存服务、认证服务、机器学习和数据处理服务、监控和性能分析等。这些服务都托管在云。</p>
<p>通过如 <a href="https://www.terraform.io/">Terraform</a> 或 AWS 的 <a href="https://aws.amazon.com/es/cloudformation/">Cloud formation</a> 这样的工具，我们甚至可以通过编写代码来设置基础架构，也就是说我们可以花几分钟编写脚本来设置服务器、数据库等在云上的内容。</p>
<p>对于软件工程来说这是颠覆想象的举措，这也给开发者提供了巨大的便利。云计算提供了丰富的解决方法应对小微项目，也可以处理好非常大的数字产品。这也是为什么越来越多的软件工程项目选择在云上搭建基础架构。</p>
<p>如前文所述，时下最知名且最常用的云有 <a href="https://aws.amazon.com/">AWS</a>、<a href="https://cloud.google.com/">Google Cloud</a> 和 <a href="https://azure.microsoft.com/">Azure</a>。当然还有其他的选择如 <a href="https://www.ibm.com/cloud">IBM</a>、<a href="https://www.digitalocean.com/">DigitalOcean</a> 和 <a href="https://www.oracle.com/cloud/">Oracle</a>。</p>
<p>大部分云供应商都提供同样的服务，虽然服务的命名不相同。同样是无服务功能，在 AWS 被叫作 “lambdas”，在 Google Cloud 被叫作 “cloud functions”。</p>
<h1 id="different-folder-structures-to-know">不同的文件夹结构</h1>
<p>目前我们讨论的架构指的是基础架构的组织和托管，现在让我们看看一些代码，以及架构在文件结构和代码模块化方面的作用。</p>
<h2 id="all-in-one-place-folder-structure">全在一个文件夹中的结构</h2>
<p>为了演示为什么文件夹结构很重要，我们一起来搭建一个简单的示例 API。我们将使用一个模拟的数据库，名为兔子🐰🐰，这个 API 会执行 <a href="https://chinese.freecodecamp.org/news/crud-operations-explained/">CRUD</a> 操作，我们将使用 Node 和 Express 来搭建。</p>
<p>下图是我们的第一步，没有任何文件夹结构，我们的仓库包含<code>node modules</code>文件夹，<code>app.js</code>、 <code>package-lock.json</code> 和 <code>package.json</code> 文件。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-227.png" alt="image-227" width="600" height="400" loading="lazy"></p>
<p>在<code>app.js</code>文件包含一个小服务器，虚拟 DB（数据库）和两个端点：</p>
<pre><code class="language-javascript">// App.js
const express = require('express');

const app = express()
const port = 7070

// 虚拟DB
const db = [
    { id: 1, name: 'John' },
    { id: 2, name: 'Jane' },
    { id: 3, name: 'Joe' },
    { id: 4, name: 'Jack' },
    { id: 5, name: 'Jill' },
    { id: 6, name: 'Jak' },
    { id: 7, name: 'Jana' },
    { id: 8, name: 'Jan' },
    { id: 9, name: 'Jas' },
    { id: 10, name: 'Jasmine' },
]

/* 路由 */
app.get('/rabbits', (req, res) =&gt; {
    res.json(db)
})

app.get('/rabbits/:idx', (req, res) =&gt; {
    res.json(db[req.params.idx])
})

app.listen(port, () =&gt; console.log(`⚡️[server]: Server is running at http://localhost:${port}`))
</code></pre>
<p>测试两个端点，发现它们运行正常：</p>
<pre><code>http://localhost:7070/rabbits

# [
#   {
#     "id": 1,
#     "name": "John"
#   },
#   {
#     "id": 2,
#     "name": "Jane"
#   },
#   {
#     "id": 3,
#     "name": "Joe"
#   },
#   ....
# ]

###

http://localhost:7070/rabbits/1

# {
#   "id": 2,
#   "name": "Jane"
# }
</code></pre>
<p>这有什么问题吗？其实没有，一切运行良好。但当代码库变得更大更复杂，我们在 API 中添加新的功能后，问题就会浮现。</p>
<p>这和我们讨论单体式架构的问题一样，一开始把所有内容放在一个地方很方便，但是随着内容变得更大更复杂，这个方式就会让人困惑。</p>
<p>根据模块化原则，更好的处理方法是使用不同的文件夹和文件来执行不同的责任和行为。</p>
<p>为了更好地演示，让我们给 API 添加新的功能，看看我们怎么使用模块的方法来给文件夹结构添加不同层级。</p>
<h2 id="layers-folder-structure">分层文件夹结构</h2>
<p>分层文件夹结构是将关注点和责任分配到不同的文件夹和文件中，仅允许在特定的文件夹和文件中进行直接通信。</p>
<p>一个项目应该有几个层级，每个层级如何命名，应该处理什么行为都是需要讨论的问题。让我们一起来看看我的例子：</p>
<p>我们的应用程序将有五个层级，并以下面的顺序排列：</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/layers.png" alt="应用程序分层" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>应用程序分层</figcaption>
</figure>
<ul>
<li>应用层（application layer）将处理服务器的基本设置，并且连接到路由（下一层）。</li>
<li>路由层（routes layer）将定义所有路由以及连接到控制器层（下一层）。</li>
<li>控制器层（controllers layer）是每个端点的实现具体逻辑，并且连接到模型层（下一层，你已经知道这是怎么一回事了……）。</li>
<li>模型层（model layer）是与虚拟数据库的交互逻辑。</li>
<li>最终持久层（persistence layer）存储了所有数据。</li>
</ul>
<p>采用这样的方法就更有结构感，关注点也实现了分离。这个方法看上去比较像样板，但设置以后，这样的结构能够帮助我们清晰地了解文件夹和文件具体负责应用程序的哪个行为。</p>
<p>需要注意的是，在这样的结构中层级间的<strong>通信流是确定的</strong>，这样这个结构才成立。</p>
<p>也就是说一个请求必须先通过第一层，然后是第二层，然后第三层，以此类推。请求不能够跳过层级，因为这样会使得结构的逻辑混乱，就借助不了组织和模块化带来的好处。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/layers--1--1.png" alt="结构的另一种表现形式" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>结构的另一种表现形式</figcaption>
</figure>
<p>让我们看一些代码，以上面的分层结构为基础，我们的文件夹结构如下：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-229.png" alt="image-229" width="600" height="400" loading="lazy"></p>
<ul>
<li>一个名为 <code>db</code> 的新文件夹保存所有数据文件</li>
<li>另一个名为 <code>rabbits</code> 的文件夹包含所有路由、控制器和模型</li>
<li><code>app.js</code> 设置服务器，并与路由连接</li>
</ul>
<pre><code class="language-javascript">// App.js
const express = require('express');

const rabbitRoutes = require('./rabbits/routes/rabbits.routes')

const app = express()
const port = 7070

/* 路由 */
app.use('/rabbits', rabbitRoutes)

app.listen(port, () =&gt; console.log(`⚡️[server]: Server is running at http://localhost:${port}`))
</code></pre>
<ul>
<li><code>rabbits.routes.js</code> 连接实体的端点和对应控制器的路由（执行请求到达端点的函数）。</li>
</ul>
<pre><code class="language-javascript">// rabbits.routes.js
const express = require('express')
const bodyParser = require('body-parser')

const jsonParser = bodyParser.json()

const { listRabbits, getRabbit, editRabbit, addRabbit, deleteRabbit } = require('../controllers/rabbits.controllers')

const router = express.Router()

router.get('/', listRabbits)

router.get('/:id', getRabbit)

router.put('/:id', jsonParser, editRabbit)

router.post('/', jsonParser, addRabbit)

router.delete('/:id', deleteRabbit)

module.exports = router
</code></pre>
<ul>
<li><code>rabbits.controllers.js</code> 处理每个端点的逻辑。在这里函数接受输入，然后处理输出和返回。😉 另外，每一个控制器都连接到对应的模型函数（处理数据相关的操作）。</li>
</ul>
<pre><code class="language-javascript">// rabbits.controllers.js
const { getAllItems, getItem, editItem, addItem, deleteItem } = require('../models/rabbits.models')

const listRabbits = (req, res) =&gt; {
    try {
        const resp = getAllItems()
        res.status(200).send(resp)

    } catch (err) {
        res.status(500).send(err)
    }
}

const getRabbit = (req, res) =&gt; {
    try {
        const resp = getItem(parseInt(req.params.id))
        res.status(200).send(resp)

    } catch (err) {
        res.status(500).send(err)
    }
}

const editRabbit = (req, res) =&gt; {
    try {
        const resp = editItem(req.params.id, req.body.item)
        res.status(200).send(resp)
    } catch (err) {
        res.status(500).send(err)
    }
}

const addRabbit = (req, res) =&gt; {
    try {
        console.log( req.body.item )
        const resp = addItem(req.body.item)
        res.status(200).send(resp)
    } catch (err) {
        res.status(500).send(err)
    }
}

const deleteRabbit = (req, res) =&gt; {
    try {
        const resp = deleteItem(req.params.idx)
        res.status(200).send(resp)
    } catch (err) {
        res.status(500).send(err)
    }
}

module.exports = { listRabbits, getRabbit, editRabbit, addRabbit, deleteRabbit }
</code></pre>
<ul>
<li><code>rabbits.models.js</code> 定义了使用 CRUD 处理数据库的函数。每一个函数都代表了一种行为（读取一个数据、读取所有数据、编辑数据、删除数据等），这个文件与 DB 连接。</li>
</ul>
<pre><code class="language-javascript">// rabbits.models.js
const db = require('../../db/db')

const getAllItems = () =&gt; {
    try {
        return db
    } catch (err) {
        console.error("getAllItems error", err)
    }
}

const getItem = id =&gt; {
    try {
        return db.filter(item =&gt; item.id === id)[0]
    } catch (err) {
        console.error("getItem error", err)
    }
}

const editItem = (id, item) =&gt; {
    try {
        const index = db.findIndex(item =&gt; item.id === id)
        db[index] = item
        return db[index]
    } catch (err) {
        console.error("editItem error", err)
    }
}

const addItem = item =&gt; {
    try {
        db.push(item)
        return db
    } catch (err) {
        console.error("addItem error", err)
    }
}

const deleteItem = id =&gt; {
    try {
        const index = db.findIndex(item =&gt; item.id === id)
        db.splice(index, 1)
        return db
        return db
    } catch (err) {
        console.error("deleteItem error", err)
    }
}

module.exports = { getAllItems, getItem, editItem, addItem, deleteItem }
</code></pre>
<ul>
<li>最后，<code>db.js</code> 托管了我们的模拟数据库。在真实的项目中，这里是连接真实数据库的地方。</li>
</ul>
<pre><code class="language-javascript">// db.js
const db = [
    { id: 1, name: 'John' },
    { id: 2, name: 'Jane' },
    { id: 3, name: 'Joe' },
    { id: 4, name: 'Jack' },
    { id: 5, name: 'Jill' },
    { id: 6, name: 'Jak' },
    { id: 7, name: 'Jana' },
    { id: 8, name: 'Jan' },
    { id: 9, name: 'Jas' },
    { id: 10, name: 'Jasmine' },
]

module.exports = db
</code></pre>
<p>如你所见，现在就有更多的文件夹和文件。但是作为回报，我们的代码库变得结构感更加明显，并且组织更加清晰。每一个代码都待在应该在的地方，文件之间的通信也被清晰地定义了。</p>
<p>这样的组织形式能够极大地方便添加新的功能、修改代码和改 bug。</p>
<p>一旦你熟悉了这样的文件夹结构，知道去哪儿找你想要的内容。你就会发现在更短更小的文件中工作，比在一到两个巨大的文件中滑动寻找想要的内容要方便得多。</p>
<p>我也支持为应用的每一个实体（在我的例子中是兔子）创建一个文件夹。这样我们就能够更清晰地知道每一个文件和什么内容相关。</p>
<p>假设我们需要添加新的功能去添加、修改、删除猫咪或者小狗，我们就为这些新的动物创建文件夹，每一个文件夹里包含各自的路由、控制器和模型文件。这一方法就是将关注点分离。👌👌</p>
<h2 id="mvc-folder-structure">MVC 文件夹结构</h2>
<p>MVC 的全称是 <strong>Model View Controller（模型视图控制器）</strong>。我们可以说 MVC 结构就像是分层结构的简化版，并包含了应用程序的前端（UI）。</p>
<p>在这个结构中只有三层：</p>
<ul>
<li>视图层负责渲染 UI</li>
<li>控制层负责定义路由和路由背后的逻辑</li>
<li>模型层负责和数据库的交互</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/mvc--2-.png" alt="mvc--2-" width="600" height="400" loading="lazy"></p>
<p>和之前的一样，每一个层级只和下一个层级交互，所以必须是清晰定义的通信流。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/mvc.png" alt="另一种展现层级的方式" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>另一种展现层级的方式</figcaption>
</figure>
<p>有许多实现 MVC 结构的框架（如<a href="https://www.djangoproject.com/">Django</a> 或 <a href="https://rubyonrails.org/">Ruby on Rails</a> ）。如果要在 Node 和 Express 中使用这个结构，我们需要借助模版引擎，如<a href="https://ejs.co/">EJS</a>。</p>
<p>如果你对模版引擎这个概念不是太熟悉的话，可以把它理解成更容易渲染的 HTML，它利用了如变量、循环和条件句这些编程特性使得渲染更加容易（和 React 中的 JSX 很像）。</p>
<p>在下面的例子中，我们会使用 EJS 文件来创建每一个页面，并且由控制器来处理响应，传入到对应的响应变量。</p>
<p>文件夹结构如下：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-230.png" alt="image-230" width="600" height="400" loading="lazy"></p>
<ul>
<li>我们删掉了大部分文件夹，但保留了 <code>db</code>、<code>controllers</code> 和 <code>models</code>。</li>
<li>我们添加了 <code>views</code> 文件夹保存我们需要渲染的页面或响应。</li>
<li><code>db.js</code> 和 <code>models.js</code> 保持不变。</li>
<li><code>app.js</code> 如下：</li>
</ul>
<pre><code class="language-javascript">// App.js
const express = require("express");
var path = require('path');

const rabbitControllers = require("./rabbits/controllers/rabbits.controllers")

const app = express()
const port = 7070

// Ejs 设置
app.set("view engine", "ejs")
app.set('views', path.join(__dirname, './rabbits/views'))

/* 控制器 */
app.use("/rabbits", rabbitControllers)

app.listen(port, () =&gt; console.log(`⚡️[server]: Server is running at http://localhost:${port}`))
</code></pre>
<ul>
<li><code>rabbits.controllers.js</code>用来定义路由、连接对应的模型函数以及渲染每一个请求对应的视图。可以看到在每一个渲染方法中我们传入了请求响应作为参数。 😉</li>
</ul>
<pre><code class="language-javascript">// rabbits.controllers.js
const express = require('express')
const bodyParser = require('body-parser')

const jsonParser = bodyParser.json()

const { getAllItems, getItem, editItem, addItem, deleteItem } = require('../models/rabbits.models')

const router = express.Router()

router.get('/', (req, res) =&gt; {
    try {
        const resp = getAllItems()
        res.render('rabbits', { rabbits: resp })

    } catch (err) {
        res.status(500).send(err)
    }
})

router.get('/:id', (req, res) =&gt; {
    try {
        const resp = getItem(parseInt(req.params.id))
        res.render('rabbit', { rabbit: resp })

    } catch (err) {
        res.status(500).send(err)
    }
})

router.put('/:id', jsonParser, (req, res) =&gt; {
    try {
        const resp = editItem(req.params.id, req.body.item)
        res.render('editRabbit', { rabbit: resp })

    } catch (err) {
        res.status(500).send(err)
    }
})

router.post('/', jsonParser, (req, res) =&gt; {
    try {
        const resp = addItem(req.body.item)
        res.render('addRabbit', { rabbits: resp })

    } catch (err) {
        res.status(500).send(err)
    }
})

router.delete('/:id', (req, res) =&gt; {
    try {
        const resp = deleteItem(req.params.idx)
        res.render('deleteRabbit', { rabbits: resp })

    } catch (err) {
        res.status(500).send(err)
    }
})

module.exports = router
</code></pre>
<ul>
<li>最后，在视图文件中，我们将变量作为参数并且渲染为 HTML。</li>
</ul>
<pre><code class="language-html">&lt;!-- Rabbits view --&gt;
&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
    &lt;body&gt;
        &lt;header&gt;All rabbits&lt;/header&gt;
        &lt;main&gt;
            &lt;ul&gt;
                &lt;% rabbits.forEach(function(rabbit) { %&gt;
                    &lt;li&gt;
                        Id: &lt;%= rabbit.id %&gt;
                        Name: &lt;%= rabbit.name %&gt;
                    &lt;/li&gt;
                &lt;% }) %&gt;
            &lt;/ul&gt;
        &lt;/main&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<pre><code class="language-html">&lt;!-- Rabbit view --&gt;
&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
    &lt;body&gt;
        &lt;header&gt;Rabbit view&lt;/header&gt;
        &lt;main&gt;
                &lt;p&gt;
                    Id: &lt;%= rabbit.id %&gt;
                    Name: &lt;%= rabbit.name %&gt;
                &lt;/p&gt;
        &lt;/main&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>打开浏览器，登陆<a href="http://localhost:7070/rabbits"><code>http://localhost:7070/rabbits</code></a>，会得到：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-232.png" alt="image-232" width="600" height="400" loading="lazy"></p>
<p>或者<code>[http://localhost:7070/rabbits](http://localhost:7070/rabbits)/2</code> 会得到：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-233.png" alt="image-233" width="600" height="400" loading="lazy"></p>
<p>这就是 MVC！</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/bugs-bunny-looney-tunes.gif" alt="bugs-bunny-looney-tunes" width="600" height="400" loading="lazy"></p>
<h1 id="conclusion">总结</h1>
<p>希望这些示例能够帮助你理解软件工程世界里的“架构”。</p>
<p>如我在文章开头中所说，架构是一个非常巨大且复杂的概念，包含了非常多的内容。</p>
<p>在这篇文章中，我们介绍了架构模式和系统、托管的选择以及云供应商，以及一些通用的文件夹结构。</p>
<p>我们还学习了垂直和水平扩展、单体式应用和微服务、弹性和无服务云计算……非常多的内容。但这些只是冰山一角！请再接再厉，探索更多内容。💪💪</p>
<p>希望你喜欢这篇文章，并且有所收获，你可以在 <a href="https://www.linkedin.com/in/germancocca/">LinkedIn</a> 或 <a href="https://twitter.com/CoccaGerman">Twitter</a> 上关注我。</p>
<p>这是一首<a href="https://www.youtube.com/watch?v=PDilu87kQCk">告别曲</a>， 哈哈......调皮一下！ 🤷‍♂️</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/7zSe.gif" alt="7zSe" width="600" height="400" loading="lazy"></p>
<p>干杯！下篇文章见！✌️</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ MVC 模式是什么 ]]>
                </title>
                <description>
                    <![CDATA[ 原文：MVC in Computer Science – The MVC Model [https://www.freecodecamp.org/news/what-does-mvc-mean-in-computer-science/]，作者： Kolade Chris [https://www.freecodecamp.org/news/author/kolade/] MVC是模型（Model）、视图（View）和控制器（Controller）的缩写。这一软件架构模式诞生于20世纪70年代后期，被用于创建桌面应用。当然现在这一模式也在web应用中被广泛使用。 我将在这篇文章中深入讲解什么是MVC以及它代表的三个组件。 我也准备了图片辅助你理解MVC，不过咱还是先读文章：） 目录  * 什么是MVC，为什么使用它？  * 哪些语言和框架使用MVC？  * MVC中的模型是什么？  * MVC中的视图是什么？  * MVC中的控制器是什么？  * 总结 什么是MVC，为什么使用它？ 在计算机科学中，MVC是一种软件设计模式，这种模式将应用代码组织成三个相互交织的部分——模型、视 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/what-does-mvc-mean-in-computer-science/</link>
                <guid isPermaLink="false">62c54fbf8ada24082688b58c</guid>
                
                    <category>
                        <![CDATA[ 软件架构 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ PapayaHUANG ]]>
                </dc:creator>
                <pubDate>Wed, 06 Jul 2022 09:06:55 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/07/mvc-cover.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>原文：<a href="https://www.freecodecamp.org/news/what-does-mvc-mean-in-computer-science/">MVC in Computer Science – The MVC Model</a>，作者：<a href="https://www.freecodecamp.org/news/author/kolade/">Kolade Chris</a></p><!--kg-card-begin: markdown--><p>MVC是模型（Model）、视图（View）和控制器（Controller）的缩写。这一软件架构模式诞生于20世纪70年代后期，被用于创建桌面应用。当然现在这一模式也在web应用中被广泛使用。</p>
<p>我将在这篇文章中深入讲解什么是MVC以及它代表的三个组件。</p>
<p>我也准备了图片辅助你理解MVC，不过咱还是先读文章：）</p>
<h2 id="">目录</h2>
<ul>
<li><a href="#whatismvcandwhyisitused">什么是MVC，为什么使用它？</a></li>
<li><a href="#whichlanguagesandframeworksusemvc">哪些语言和框架使用MVC？</a></li>
<li><a href="#whatisthemodelinmvc">MVC中的模型是什么？</a></li>
<li><a href="#whatistheviewinmvc">MVC中的视图是什么？</a></li>
<li><a href="#whatisthecontrollerinmvc">MVC中的控制器是什么？</a></li>
<li><a href="#conclusion">总结</a></li>
</ul>
<h2 id="whatismvcandwhyisitused">什么是MVC，为什么使用它？</h2>
<p>在计算机科学中，MVC是一种软件设计模式，这种模式将应用代码组织成三个相互交织的部分——模型、视图和控制器。</p>
<p>模型是与数据库交互的逻辑；视图是用户接口和交互，控制器是是视图和数据库之间的中介。</p>
<p>大多数情况下，视图不直接和模型交互——这个功能由控制器执行。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/mvc1.png" alt="mvc1" width="600" height="400" loading="lazy"></p>
<p>在其他一些框架中，模型与视图直接交互。<br>
<img src="https://www.freecodecamp.org/news/content/images/2022/06/Copy-of-mvc2.png" alt="Copy-of-mvc2" width="600" height="400" loading="lazy"></p>
<p>MVC设计模式旨在将应用代码分成各自的单位，来简化维护和优化。这种方式被称为“关注点分离”。</p>
<h2 id="whichlanguagesandframeworksusemvc">哪些语言和框架使用MVC？</h2>
<p>过去，MVC仅被用于桌面（GUI）[<a href="https://zh.wikipedia.org/wiki/%E5%9B%BE%E5%BD%A2%E7%94%A8%E6%88%B7%E7%95%8C%E9%9D%A2">https://zh.wikipedia.org/wiki/图形用户界面</a>]，现在许多语言和框架也使用MVC来设计web应用。</p>
<p>一些框架甚至强制使用MVC，所以你可能没意识到你已经使用过MVC。</p>
<p>例如在一个全栈Express应用中，开发者往往把代码打包到模型、控制器和客户（视图）三个文件夹。<br>
<img src="https://www.freecodecamp.org/news/content/images/2022/06/Annotation-2022-06-20-103520.png" alt="Annotation-2022-06-20-103520" width="600" height="400" loading="lazy"></p>
<p>截图是我为我最喜欢的足球运动员编写的<a href="https://blooming-reef-46396.herokuapp.com/">玩笑生成器</a>的文件结构。</p>
<p>使用MVC的编程语言包括：C、 C++、C#、 Java、 Ruby、 Smalltalk等。</p>
<p>使用MVC的框架包括：Angular、 Express、 Django、 Flask、 Laravel、 Ruby on rails等。</p>
<h2 id="whatisthemodelinmvc">MVC中的模型是什么？</h2>
<p>模型组件负责从数据库获取数据的逻辑。同样，你也可以使用JSON文件来提供数据。</p>
<p>例如，如果有一个电子商务应用的SQL数据库，模型的代码可能是<code>product-data = db.get(SELECT * FROM products;</code>。</p>
<p>在大多数情况下，模型和控制器沟通，然后给视图（UI）发送数据；另一些情况下，模型直接给视图发送数据。</p>
<h2 id="whatistheviewinmvc">MVC中的视图是什么？</h2>
<p>视图组件是直接和用户交互的组件，视图与控制器沟通用户通过鼠标或者键盘发出的请求。</p>
<p>如HTML、CSS和JavaScript这类语言经常用于编写视图组件。你也可以使用React、Vue和Svelte这类框架。</p>
<p>一些开发者也会使用如：Handlebars、ejs和liquidjs这类模板引擎来执行视图。</p>
<p>在电商应用中，视图组件的代码如下：</p>
<pre><code class="language-js">&lt;h1&gt;{{product.name}}&lt;/h2&gt;
&lt;ul&gt;
&lt;p&gt;{{product.description}}&lt;/p&gt;
&lt;p&gt;{{product.delivery-modes}}&lt;/p&gt;
</code></pre>
<h2 id="whatisthecontrollerinmvc"> MVC中的控制器是什么？</h2>
<p>控制器组件是连接模型和视图的中介。它既不是模型也不是视图，而是链接这两者的部分。</p>
<p>控制器的工作是接受和处理视图（UI）的请求和行为。 所以会处理<code>GET</code>、<code>POST</code>、<code>PUT</code> 或<code>PATCH</code>和<code>DELETE</code>请求。</p>
<p>当控制器收到用户请求时，会和模型沟通用户需要什么，然后返回响应给视图（UI）供用户使用。</p>
<p>以下是控制器会执行的伪代码：</p>
<pre><code>if (success) {
      show products;
} else {
      show error;
}
</code></pre>
<h2 id="conclusion">总结</h2>
<p>在web应用和其他软件产品中MVC模式被广发使用。</p>
<p>可能刚接触MVC时你会觉得困惑，但是持续学习一段时间，之后你一定会豁然开朗。</p>
<p>如果你仍对MVC感到困惑，你可以这样类比：</p>
<ul>
<li><strong>你</strong>打电话给餐厅点了一份披萨 – 你是<code>view</code></li>
<li>你向<strong>服务员</strong>点单 – 服务员是<code>controller</code></li>
<li>服务员从<strong>披萨店</strong>拿到披萨并送给你 – 披萨店是<code>model</code></li>
</ul>
<p>你会发现你作为<code>view</code>不直接去披萨店拿披萨，就像在大多数情况下视图从不直接从模型获取数据。</p>
<p>感谢你阅读本文。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 文件系统是什么？介绍几种计算机文件系统及其运行原理 ]]>
                </title>
                <description>
                    <![CDATA[ 文件系统到底是个啥？单凭一句话来解释清楚这个问题可不容易。 所以我决定写下这篇文章来聊这个话题。写这篇帖子的初衷是想从一个比较宏观的角度去谈各种文件系统，但文中时不时也会提及一些微观层面的概念，但愿你读到时不会睡着。 :) 什么是文件系统？ 先来上一个简单的定义： 文件系统  决定着从存储设备中对文件进行 命名、 存储  和 检索  的方式。 当说到“文件系统”一词时，基于不同的语境，人们实际所指的可能是有关“文件系统”一词多重定义的某个方面 - 这也是让问题变得棘手的地方。 最后，你可能会“扪心自问”，文件系统到底是个啥？ 🤯 在本份指南，我将帮你理解这一问题，并助你搞定任何关于文件系统的谈话。此外，为了帮助你理解有关文件系统的一些概念，指南也会涉及分区和启动程式的讲解。 为保证指南的可操作性，在解释较低级别的结构或控制台命令时，我将专注Unix一类的环境下进行讲解。虽说如此，讲到的概念和其它环境及文件系统也是相通的。 你可能有这样一个疑问，我们在最初为什么需要文件系统？ 嗯，答案是，如果没有文件系统，存储设备会将大量数据简单地堆积存储，如此一来数据间便无法区分。  ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/file-systems-architecture-explained/</link>
                <guid isPermaLink="false">604afaa16ce45b059394b7f6</guid>
                
                    <category>
                        <![CDATA[ 软件架构 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Michael He ]]>
                </dc:creator>
                <pubDate>Fri, 24 Dec 2021 05:26:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/03/pexels-photo-6571015.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>文件系统到底是个啥？单凭一句话来解释清楚这个问题可不容易。</p>
<p>所以我决定写下这篇文章来聊这个话题。写这篇帖子的初衷是想从一个比较宏观的角度去谈各种文件系统，但文中时不时也会提及一些微观层面的概念，但愿你读到时不会睡着。 :)</p>
<h2 id="">什么是文件系统？</h2>
<p>先来上一个简单的定义：</p>
<p><strong>文件系统</strong> 决定着从存储设备中对文件进行 <strong>命名</strong>、 <strong>存储</strong> 和 <strong>检索</strong> 的方式。</p>
<p>当说到“文件系统”一词时，基于不同的语境，人们实际所指的可能是有关“文件系统”一词多重定义的某个方面 - 这也是让问题变得棘手的地方。</p>
<p>最后，你可能会“扪心自问”，文件系统到底是个啥？ 🤯</p>
<p>在本份指南，我将帮你理解这一问题，并助你搞定任何关于文件系统的谈话。此外，为了帮助你理解有关文件系统的一些概念，指南也会涉及分区和启动程式的讲解。</p>
<p>为保证指南的可操作性，在解释较低级别的结构或控制台命令时，我将专注Unix一类的环境下进行讲解。虽说如此，讲到的概念和其它环境及文件系统也是相通的。</p>
<h3 id="">你可能有这样一个疑问，我们在最初为什么需要文件系统？</h3>
<p>嗯，答案是，如果没有文件系统，存储设备会将大量数据简单地堆积存储，如此一来数据间便无法区分。</p>
<p>文件系统的命名源于过去的纸质数据管理系统。纸质系统中，我们将文档保留为纸质文件，然后将其存放在各个目录中。</p>
<p>试想如若没有分类，存放文件的房间便到处都会堆着杂乱无章的文件。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/pexels-photo-6571015-1.jpg" alt="pexels-photo-6571015-1" width="600" height="400" loading="lazy"></p>
<p>类似地，当存储设备缺少了文件系统时便会陷入同样的杂乱无序之中，存储设备本身也将毫无用处。</p>
<p>然而，有了文件系统之后，一切便会全然不同：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/pexels-photo-6571015-2.jpg" alt="pexels-photo-6571015-2" width="600" height="400" loading="lazy"></p>
<p>但也不能说文件系统的功能仅限于整理数据。</p>
<p>空间管理，元数据，数据加密，文件访问控制和数据完整性同样是文件系统施展拳脚的阵地。</p>
<h2 id="">一切始于分区</h2>
<p>首次使用之前，必须对存储设备进行 <strong>分区</strong> and <strong>格式化</strong>。</p>
<p>什么是分区？</p>
<p>分区是指将存储设备划分为几个 <em>逻辑区域</em> 的过程。分区后，就能像管理一个个彼此独立的存储设备一样对这些逻辑区域进行一一单独管理。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/partitions.jpg" alt="partitions" width="600" height="400" loading="lazy"></p>
<p>分区过程可通过操作系统提供的磁盘管理工具完成，亦或通过系统固件提供的基于文本\的CLI工具完成。</p>
<p>一台存储设备应至少有一个分区，视需求而定，也可进行更多分区。</p>
<p>举例来说，一个基础的Linux安装就具有三个分区：一个专门用于存储操作系统，一个专门用于存放用户文件，外加一个交换分区。</p>
<p>在Windows和Mac OS系统中并没有专门的交换分区，它们在操作系统所安装的分区内管理交换，但二者的分区布局与Linux相似。</p>
<p>那么我们又为什么要把存储设备进行分区呢？</p>
<p>原因在于，我们不想将整个存储空间作为单个单元或出于单个目的进行管理和使用。</p>
<p>这和我们划分工作空间的理念类似，你不也会把办公空间划分为会客室、会议室、团队办公区域吗？</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/office-space.jpeg" alt="office-space" width="600" height="400" loading="lazy"></p>
<p>在具有多个分区的计算机上，你可以安装多种操作系统，并且每次可以选择不同的分区来启动系统。</p>
<p>就连恢复和诊断实用程序也有它们的专属分区。</p>
<p>比方说你要在恢复模式下重新启动MacBook，你就需要在重启或启动MacBook后迅速按住 <code>Command + R</code> 不松。</p>
<p>这样一来，你就在指示操作系统使用包含恢复程序的分区启动MacBook。</p>
<p>然而，分区的意义不仅仅限于可安装多种操作系统和工具。通过分区，我们还可以将重要系统文件与普通文件分开存放。</p>
<p>这样一来，无论你在计算机上安装了多少个大型游戏，都不会对操作系统的性能产生任何影响， - 因为它们和系统文件存放在不同的分区中。</p>
<p>再拿办公室那个例子来说，如果将一个呼叫中心团队和一个技术团队安排在同一个办公区域，那么如此对两个团队的生产力产生的影响都会是负面的，因为这两个团队都有自己独特的效率需求。</p>
<p>好比说，技术团队会更需要一个安静的工作环境。</p>
<p>有一些操作系统，比如Windows，会对磁盘分区分配以不同的字母编号（A，B，C，D）。例如，Windows上 <em>主分区</em> （Windows所安装的分区）的命名为 <strong>C</strong>：或驱动器C。</p>
<p>而在Unix一类的操作系统中，分区则以根目录下的普通目录显示，这点在后面会介绍到。</p>
<p>至于现在吗，我们要先绕个弯 ↩️</p>
<p>在下一小节，我们将更深入地理解分区，内容涉及 <strong>系统固件</strong> and <strong>启动程式</strong> 这两个概念，它们将改变你对文件系统的看法。</p>
<p>准备好了吗？</p>
<p>一起看看吧！ &nbsp;🏊‍♂️</p>
<h2 id="">分区方案，系统固件和启动程式</h2>
<p>在对存储设备进行分区时，有两种分区方法可供选择：</p>
<ul>
<li><strong>主引导记录（MBR）方案</strong></li>
<li><strong>GUID分区表（GPT）方案</strong></li>
</ul>
<p>无论选择哪种方案，存储设备上的前几个存储块所存储的始终都是有关分区的关键数据。</p>
<p>利用这些数据结构，系统的 <em>固件</em> 便能启动操作系统。</p>
<p>等等，你可能会问，啥又是系统固件？</p>
<p>解释如下：</p>
<p>固件是嵌入电子设备中以操作该设备或引导另一个程序来操作该设备的低级软件。</p>
<p>固件存在于计算机，外围设备（键盘，鼠标和打印机）中，甚至存在于家用电器中。</p>
<p>在计算机中，固件为诸如操作系统之类的复杂软件提供了启动和使用硬件组件的标准环境。</p>
<p>但是，在打印机等较为简单的系统上，固件却是设备的主要操作系统。打印机菜单就是其固件的人机界面。</p>
<p>计算机固件依据以下两个规范执行：</p>
<ul>
<li><strong>基本输入/输出（BIOS）</strong></li>
<li><strong>统一可扩展固件接口（UEFI）</strong></li>
</ul>
<p>固件（或基于BIOS或基于UEFI）存储在非易失性存储器中，例如连接到主板的Flash ROM（闪存）。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/5794340306_caef1e6960_b.jpg" alt="5794340306_caef1e6960_b" width="600" height="400" loading="lazy"></p>
<p><a href="https://www.flickr.com/photos/computerhotline/5794340306"><strong>BIOS</strong></a>，图源： <a href="https://www.flickr.com/photos/computerhotline/">Thomas Bresson</a>， 许可： <strong><a href="https://creativecommons.org/licenses/by/2.0/">CC BY 2.0</a></strong></p>
<p>当打开计算机电源时，固件是第一个运行的程序。</p>
<p>固件的任务（除其它事项外）包括启动计算机，运行操作系统，并将整个系统的控制权传递给操作系统。</p>
<p>固件还可以（在连网环境下）运行预操作系统，例如恢复或诊断工具，甚至还可以运行特殊的壳层来运行基于文本的命令。</p>
<p>在操作系统的徽标出现之前，你所看到的那几个页面就是计算机固件的输出，该输出用于验证硬件组件和内存的运行状况。</p>
<p>初始检查完成后会发出“哔”的一声，表明一切正常。</p>
<p>MBR分区方案是BIOS规范的一部分，由基于BIOS的固件使用。</p>
<p>在采用MBR分区方案的磁盘上，存储设备上的第一个扇区存储着启动系统所需的基本数据。</p>
<p>这一扇区被称为MBR（主引导扇区）。</p>
<p>MBR内存有以下信息：</p>
<ul>
<li>引导程序，它是（机器代码中的）一个 <strong>简单程序</strong>，用于启动引导过程的第一阶段</li>
<li><strong>分区表</strong>，其中包含有关分区的信息。</li>
</ul>
<p>MBR分区磁盘上，基于BIOS的固件与基于UEFI的固件会以不同的方式引导系统。</p>
<p>以下是其工作过程：</p>
<p>系统启动后，BIOS固件将启动并将MBR的内容加载到内存中，并在其中运行引导加载程序。</p>
<p>通过将引导加载程序和分区表放置在MBR之类的预定义位置中，便可使BIOS来引导系统，而无需处理任何文件。</p>
<p>MBR中的引导加载程序代码占用MBR 512字节空间中的434字节至446字节，另有64字节分配给了分区表，分区数量最多为四个。</p>
<p>446字节并不足以容纳很多的代码。正因如此，复杂的引导加载程序（例如Linux上的GRUB 2）会将其功能切分为多个部分或多个阶段。</p>
<p>其中，最小的部分被称为第一阶段引导加载程序，位于MBR内。</p>
<p>第一阶段引导加载程序将启动引导过程的下一个阶段。</p>
<p>MBR之后紧接第一个分区之前，还有一个很小的空间，大约1MB，被称为 <strong>MBR间隙</strong>。必要时，它也可以用来放置一部分引导加载程序。</p>
<p>利用MBR间隙，引导加载程序（例如GRUB 2）存储其功能的另一阶段。GRUB将此称为引导加载程序的 <em>第1.5个阶段</em> ，其中包含文件系统驱动程序。</p>
<p>1.5阶段使GRUB的下一阶段可以处理文件，而不再是再向第一阶段引导加载程序那样从存储设备中加载原始数据。</p>
<p>第二阶段引导加载程序，现在为file-system-aware，可以加载操作系统的引导加载程序文件来引导操作系统。</p>
<p>此时也是操作系统徽标逐渐淡出的时候...</p>
<p>下面是MBR分区存储设备的图示：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/mbr-partition.jpg" alt="mbr-partition" width="600" height="400" loading="lazy"></p>
<p>如果我们放大MBR这部分，其内容将显示如下：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/mbr.jpg" alt="mbr" width="600" height="400" loading="lazy"></p>
<p>尽管MBR非常简单并得到了广泛兼容，但它仍存在一些局限。</p>
<p>MBR的数据结构将分区数量限制为了仅 <em>四个主要</em> 分区。</p>
<p>一个常见的解决方法是在主分区旁创建一个 <em>拓展</em> 分区。总之，只要分区总数不超过四个即可。</p>
<p>扩展分区又可以分为多个 <em>逻辑分区</em>。</p>
<p>进行分区时，你可以在主分区和扩展分区之间进行选择。</p>
<p>解决此问题后，我们还面临着第二个限制。</p>
<p>每个分区的空间最大为2TiB。</p>
<p>等等，这还不只！</p>
<p>MBR扇区的内容还没有备份 😱，也就是说一旦MBR遭到意外损坏，我们的存储设备就变成废铁了。</p>
<p>比起MBR， <strong>GPT</strong> 分区方案要更复杂，但没有MBR那些限制。</p>
<p>比如，只要操作系统允许，你可以拥有尽可能多的分区。</p>
<p>而且每一个分区的大小都可以达到市场上最大的存储设备的大小， - 实际上还可以更大。</p>
<p>GPT正在逐步取代MBR，尽管旧PC和新PC仍广泛支持MBR。</p>
<p>如前所述，GPT是UEFI规范的一部分，该规范正在替代旧的BIOS。</p>
<p>这意味着基于UEFI的固件将使用GPT分区的存储设备来执行引导。</p>
<p>现在，许多固件和操作系统都支持UEFI，并使用GPT方案对存储设备进行分区。</p>
<p>在GPT分区方案下，出于与基于BIOS\的系统兼容的考虑，保留了存储设备的第一个扇区。这是因为某些系统可能仍使用基于BIOS\的固件，但却具有GPT\分区的存储设备。</p>
<p>这一扇区被称为 <strong>保护性MBR。</strong> （这也是第一阶段引导加载程序在MBR\分区的磁盘中存储的位置）</p>
<p>第一个扇区之后，将存储GPT数据结构，包括 <strong>GPT标头</strong> 和 <strong>分区表条目</strong>。</p>
<p>作为备份，GPT条目和GPT标头也会存储在存储设备的最后，这样即便主副本被损坏，也可以将其恢复。此备份被称为 <strong>辅助GPT。</strong></p>
<p>下面是GPT分区存储设备的图示：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/GUID_Partition_Table_Scheme.svg" alt="GUID_Partition_Table_Scheme" width="600" height="400" loading="lazy"></p>
<p><strong><a href="https://commons.wikimedia.org/wiki/File:GUID_Partition_Table_Scheme.svg">GUID Partition Table Scheme</a></strong> ，图源： <a href="https://en.wikipedia.org/wiki/User:Kbolino">Kbolino</a>， 许可： <strong><a href="https://creativecommons.org/licenses/by-sa/2.5/">CC BY-SA 2.5</a></strong></p>
<p>在GPT中，所有的引导服务（引导加载程序，引导管理器，预操作系统环境和壳层）都位于名为 **EFI系统分区（简称ESP）**的特殊分区中，UEFI固件可以使用该分区。</p>
<p>ESP甚至拥有自己专属的文件系统，该系统是 <strong>FAT</strong>的一个特定版本。在Linux上，ESP存储在 <code>/sys/firmware/efi</code> 路径下。</p>
<p>如果你在自己的系统上找不到此路径，那你的固件可能是基于BIOS的固件。</p>
<p>想要查看的话，你可以尝试将目录更改为ESP挂载点，如下所示：</p>
<pre><code>cd /sys/firmware/efi
</code></pre>
<p>基于UEFI\的固件会假定其存储设备使用GPT进行分区，并在GPT分区表中查找ESP。</p>
<p>找到EFI分区后，该固件将查找已配置的引导加载程序，该引导加载程序通常是以 <code>.efi</code> 结尾的文件。</p>
<p>基于UEFI\的固件从 <strong>NVRAM</strong> （一种非易失性RAM）中获取引导配置。NVRAM内有引导设置以及前往操作系统引导加载程序的路径。</p>
<p>如果进行了相应的配置，UEFI固件也能够执行BIOS-boot，进而从MBR磁盘启动系统。</p>
<p>在Linux上，你可以使用 <code>parted</code> 命令来查看用于存储设备的分区方案。</p>
<pre><code>sudo parted -l
</code></pre>
<p>命令输出如下：</p>
<pre><code>型号：Virtio Block设备（virtblk）
磁盘 /dev/vda: 172GB
扇区大小（逻辑/物理）: 512B/512B
分区表: gpt
磁盘标志:

编号  开始   结束     大小    文件系统  名称  标志
14      1049kB  5243kB  4194kB                     bios_grub
15      5243kB  116MB   111MB   fat32              msftdata
 1      116MB   172GB   172GB   ext4

</code></pre>
<p>根据上面的输出可知，示例存储设备的ID为 <code>/dev/vda</code>，容量为172GB。</p>
<p>该存储设备基于GPT进行分区，共具有三个分区，其中第二和第三分区分别基于FAT32和EXT4文件系统进行格式化。</p>
<p>该固件具有BIOS GRUB分区，表明其为基于BIOS的固件。</p>
<p>让我们借助 <code>dmidecode</code> 命令来确认这一点，输入如下：</p>
<pre><code>sudo dmidecode -t 0
</code></pre>
<p>输出结果如下：</p>
<pre><code># dmidecode 3.2
Getting SMBIOS data from sysfs.
SMBIOS 2.4 present.

...
</code></pre>
<p>✅ 我们的判断没错！</p>
<p>完成分区后，就要对分区进行格式化了。</p>
<p>大多数操作系统都支持基于一组文件系统来格式化分区。</p>
<p>例如，在Windows上格式化分区时，可以选择 <strong>FAT32</strong>， <strong>NTFS</strong> 或 <strong>exFAT</strong>。</p>
<p>格式化还会涉及创建各种 <strong>数据结构</strong> 和用于管理分区内文件的元数据。</p>
<p>这些数据结构便是文件系统诸多定义中的一个方面。</p>
<p>我们拿NTFS文件系统为例吧。</p>
<p>在将一个分区格式化为NTFS时，格式化过程会将关键NTFS数据结构以及 **主引导表（MFT）**放置在该分区上。</p>
<p>好了，关于分区和启动已经讲得够多了，我们还是绕回到文件系统吧。</p>
<h2 id="">文件系统一开始是怎么出现的，如今又发展到了什么地步</h2>
<p>文件系统由一套数据结构、连接电路、抽象概念和API构成。它们协同工作，步调一致地管理任何类型存储设备上的任何类型文件。</p>
<p>每种操作系统会使用特定的文件系统来管理文件。</p>
<p>过去，Microsoft曾在 <strong>MS-DOS</strong> 和 <strong>Windows 9x</strong> 家族系统中使用过 <strong>FAT</strong> （FAT12，FAT16和FAT32）。</p>
<p>但自Windows  <strong>NT 3.1</strong>  开始，Microsoft研发了 <strong>新技术文件系统（NTFS）</strong>。比起FAT32，NTFS具有诸多多优势，如支持更大的文件，更长的文件名，数据加密，访问管理，日志记录等等。</p>
<p>从那时起，NTFS就一直是Window NT家族（2000，XP，Vista，7、10等）的默认文件系统。</p>
<p>不过，NTFS并不适用于非Windows环境。</p>
<p>例如，在Mac OS上，你 <strong>只能读取</strong> NTFS格式存储设备（如闪存）上的内容，但无法写入任何内容，除非安装具有写入支持的NTFS驱动程序。</p>
<p>2006年，Microsoft创建 <strong>扩展文件分配表（exFAT）</strong> 文件系统，exFAT堪称NTFS的精简版。</p>
<p>exFAT的设计面向对象是大容量可移动设备（例如外部硬盘，USB驱动器和存储卡）。</p>
<p>它也是 <strong>SDXC</strong> <strong>卡</strong> 使用的默认文件系统。</p>
<p>与NTFS不同，exFAT在非Windows环境（包括Mac OS）上也支持 <strong>读写</strong> ，这也使其成为最佳的高容量可移动存储设备跨平台文件系统。</p>
<p>因此基本上可以这么说，如果你想同时在Windows、Mac和Linux上使用同一块可移动磁盘，就需要将其格式化为exFAT格式。</p>
<p>多年以来，Apple也在研发利用自己的各种文件系统，这就包括<br>
<strong>分层文件系统（HFS）</strong>, <strong>HFS+</strong> 以及最近推出的 <strong>苹果文件系统（APFS）</strong>.</p>
<p>和NTFS类似，APFS也是一个日志文件系统。自苹果在2017年推出 <strong>OS X High Sierra</strong> 以来，APFS一直使用至今。</p>
<p>文件系统中的 <strong>扩展文件系统（ext）</strong> 家族是专门为Linux内核（即Linux操作系统的核心）创建的。</p>
<p><strong>ext</strong> 的第一版发布于1991年，但不久便在1993年被 <strong>第二代扩展文件系统</strong> （<strong>ext2）</strong> 取代。</p>
<p>进入21世纪，针对Linux开发的具有日志功能的 <strong>第三代扩展文件系统</strong> （<strong>ext3）</strong> 和 <strong>第四代扩展文件系统 （ext4）</strong> 也相继出现。</p>
<p>如今，<strong>ext4</strong> 成为Linux的许多发行版本中的默认文件系统，这就包括 <a href="https://en.wikipedia.org/wiki/Debian">Debian</a> 和 <a href="https://en.wikipedia.org/wiki/Ubuntu">Ubuntu</a>。</p>
<p>在Linux上，你可以输入 <code>findmnt</code> 命令来陈列出ext4\格式的分区：</p>
<pre><code>findmnt -lo source,target,fstype,used -t ext4
</code></pre>
<p>输出结果如下：</p>
<pre><code>SOURCE    TARGET FSTYPE  USED
/dev/vda1 /      ext4    3.6G
</code></pre>
<h2 id="">文件系统的体系结构</h2>
<p>一个操作系统中的文件系统有三层结构：</p>
<ul>
<li><strong>物理文件系统</strong></li>
<li><strong>虚拟文件系统</strong></li>
<li><strong>逻辑文件系统。</strong></li>
</ul>
<p>不同层次之间既可彼此独立存在，也可紧密耦合为诸多抽象。</p>
<p>当人们谈论文件系统时，他们所指的就是这三层中的某一层。</p>
<p>尽管这些层次在不同操作系统之间有所不同，但这些概念基本是相通的。</p>
<p>物理层是文件系统的具体实现，负责数据存储和检索，以及存储设备上的空间管理（或者更确切地说是分区）。</p>
<p>物理文件系统通过 <a href="https://www.skillupp.tech/books/essentials-of-computing-for-the-new-coders#device-drivers">设备驱动程序</a>与实际的存储硬件进行交互。</p>
<p>下一层是虚拟文件系统，简称 <strong>VFS</strong>。</p>
<p>虚拟文件系统提供了一种支持在操作系统上安装的各类文件系统的 <strong>一致视图</strong>。</p>
<p>那么，这是否意味着一个操作系统可以同时使用多种文件系统呢？</p>
<p>答案是肯定的！</p>
<p>可移动存储工具通常都具有与计算机不同的文件系统。</p>
<p>例如，在使用NTFS作为主要文件系统的Windows环境下，闪存可能已格式化为exFAT或FAT32。</p>
<p>也就是说，操作系统需要能够在处理不同程序（文件浏览器和其他处理文件的应用）和不同的挂载文件系统（例如NTFS，APFS，EXT4， FAT32，exFAT和UDF）时提供一种 <strong>一致视图</strong>。</p>
<p>比如说，当你打开文件资源管理器时，你可以从EXT4文件系统复制一份图像，然后将其直接粘贴到exFAT格式的闪存中，而不必去管文件在后台进行了不同的管理。</p>
<p>VFS堪称连接用户（你）和后台文件系统的“便利层”。</p>
<p>它制定了一种 <em>合同</em> ，要求所有的物理文件系统都必须以操作系统支持的方式工作。</p>
<p>但是，这种合规性并未内置于文件系统的核心中，也就是说，文件系统的源代码并不包含对各种操作系统的支持。</p>
<p>事实是，这些源代码利用 <strong>文件系统驱动程序</strong> 来遵守VFS的规则。</p>
<p>驱动程序是一种能够使软件与另一个软件或硬件进行通信的特殊程序。</p>
<p>但用户程序不会直接与VFS交互。</p>
<p>而是利用位于程序和VFS之间的统一API实现交互。</p>
<p>没错，我们接下来要说的就是逻辑文件系统。</p>
<p>逻辑文件系统是文件系统中面向用户的一层。通过提供API，它能使用户程序无需处理任何存储硬件便能执行各种文件操作，例如 <code>打开</code>， <code>读</code>， <code>写</code>。</p>
<p>话说回来，VFS也在逻辑文件系统（程序与之交互）和一组物理文件系统之间搭建了桥梁。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/filesystem-1.jpg" alt="filesystem-1" width="600" height="400" loading="lazy"></p>
<p>文件系统层的高级架构</p>
<h3 id="">什么叫挂载文件系统？</h3>
<p>在Unix一类的系统上，VFS为每个分区或可移动存储设备都分配一个 <strong>device ID</strong> （如 <code>dev/disk1s1</code>）。</p>
<p>接着，它会创建一个 <strong>虚拟目录树</strong> ，并将每个设备的内容按照单独的目录放在该目录树下。</p>
<p>而在根目录树下给存储设备分配目录的活动就是 <strong>挂载</strong>，分配的目录被称为 <strong>挂载点</strong>。</p>
<p>就是说，在Unix一类的操作系统上，所有分区和可移动存储设备看起来就都好像是根目录下的目录。</p>
<p>例如，在Linux上，除非有所配置，否则默认情况下可移动设备（例如存储卡）的挂载点就是根目录下的 <code>/media</code>。</p>
<p>也就是说，当将闪存连接到一个操作系统时，它会被 <em>自动挂载</em> 在默认挂载点（在Linux环境下就是 <code>/media</code>），其内容也将在 <code>/media</code> 目录下显示。</p>
<p>但是，有些时后会需要你手动挂载文件系统。</p>
<p>在Linux上，可进行如下操作：</p>
<pre><code>mount /dev/disk1s1 /media/usb
</code></pre>
<p>在上面的命令中，第一个参数（<code>/dev/disk1s1</code>）是设备ID，第二个参数（<code>/media/usb</code>）是挂载点。</p>
<p>请注意，挂载点应当已经作为目录存在。</p>
<p>如果没有，则必须先创建它：</p>
<pre><code>mkdir -p /media/usb
mount /dev/disk1s1 /media/usb
</code></pre>
<h2 id="">文件元数据</h2>
<p>文件元数据是一种数据结构，存储 <strong>有关文件的数据</strong>，比如：</p>
<ul>
<li>文件大小</li>
<li>时间戳，如创建日期，上次访问日期和修改日期</li>
<li>文件所有者</li>
<li>文件权限状态（“谁”可以“如何”处理文件）</li>
<li>分区上的哪些块分配给了文件</li>
<li>等等</li>
</ul>
<p>不过，元数据不会与文件内容一起存储，而是存储在磁盘上的其它位置并与文件关联。</p>
<p>在Unix一类的系统中，元数据以一种特殊的数据结构形式存储，被称为 <strong>索引节点</strong>。</p>
<p>索引节点由唯一的 <em>索引节点号</em> 标识。</p>
<p>索引节点与 <em>索引节点表</em> （一种数据结构）中的文件关联。</p>
<p>存储设备上的每个文件都有一个索引节点，该索引节点包含有关文件的信息，包括分配给该文件的区块地址。</p>
<p>在一个ext4索引节点中，所分配区块的地址以索引节点中的 <strong>区段</strong> （一组数据结构）形式存储。</p>
<p>每个区段包含分配给文件的第一个数据块的地址，以及文件已占用的下面几个区块的数量。</p>
<p>如果文件是分段存储的，每个分段都会有其专属的范围。</p>
<p>这与ext3的指针系统不同，后者是通过间接块指针指向各个数据块的。</p>
<p>使用区段数据结构可使文件系统指向大型文件，但同时又不会占用太多空间。</p>
<p>每当请求文件时，其名称都会首先解析为一个索引节点号。</p>
<p>有了索引节点号之后，文件系统便会从存储设备中获取相应的索引节点。</p>
<p>提取索引节点过后，文件系统便开始根据索引节点中存储的数据块来组成文件。</p>
<p>在Linux上，你可以将 <code>df</code> 命令与 <code>-i</code> 参数一起使用，以查看分区中的索引节点（节点总计，已使用节点和可用节点）：</p>
<pre><code>df -i
</code></pre>
<p>输出结果如下：</p>
<pre><code>udev           4116100    378 4115722    1% /dev
tmpfs          4118422    528 4117894    1% /run
/dev/vda1      6451200 175101 6276099    3% /
</code></pre>
<p>可以看到，分区 <code>/dev/vda1</code> 的索引节点总数为6451200，其中3%（175101个）已被使用。</p>
<p>此外，如果要查看与目录中的文件相关联的索引节点，则可以使用带有 <code>-il</code> 参数的 <code>ls</code> 命令。</p>
<pre><code>ls -li
</code></pre>
<p>输出结果如下：</p>
<pre><code>1303834 -rw-r--r--  1 root www-data  2502 Jul  8  2019 wp-links-opml.php
1303835 -rw-r--r--  1 root www-data  3306 Jul  8  2019 wp-load.php
1303836 -rw-r--r--  1 root www-data 39551 Jul  8  2019 wp-login.php
1303837 -rw-r--r--  1 root www-data  8403 Jul  8  2019 wp-mail.php
1303838 -rw-r--r--  1 root www-data 18962 Jul  8  2019 wp-settings.php
</code></pre>
<p>第一列是与每个文件关联的索引节点号。</p>
<p>分区上的索引节点数是在格式化分区时确定的。也就是说，只要设备上还有可用空间并且有未使用的缩阴节点，文件就可以存储在存储设备上。</p>
<p>事实上，个人版的Linux操作系统不太可能会耗尽索引节点。但是，企业在使用需要处理大量文件的服务（例如邮件服务器）时，就必须聪明地管理其索引节点的配额了。</p>
<p>然而，在NTFS上，元数据的存储方式会有所不同。</p>
<p>NTFS将文件信息保存在 <strong>主引导表（MFT）</strong> 这一特殊数据结构中。</p>
<p>MFT中，每个文件都至少具有一个条目，和索引节点类似，其中包含有关相应文件的所有内容，包括其在存储设备上的位置。</p>
<p>在大多数操作系统上，你也可以从图形用户界面访问常规文件元数据。</p>
<p>例如，当在Mac OS上右键单击一个文件并选择“获取信息”（即Windows中的“属性”）时，将回弹出一个窗口，其中包含有关该文件的信息。该信息便是从相应文件的元数据中获取的。</p>
<h2 id="">空间管理</h2>
<p>存储设备内分为大小固定的区块，称为 <strong>扇区</strong>。</p>
<p>扇区是存储设备上的 <strong>最小存储单元</strong>，大小介于512字节和4096字节之间（高级格式）。</p>
<p>然而，文件系统实际使用高级概念 <strong>区块</strong> 作为存储单元。</p>
<p>块是对物理扇区的抽象，由多个扇区组成。</p>
<p>根据文件的大小，文件系统为每个文件分配一个或多个块。</p>
<p>进行空间管理时，文件系统很明确分区上每个 <em>已使用</em> 和 <em>未使用</em> 的块，因此便可以为新文件分配空间或在收到请求时获取现有文件。</p>
<p>ext4格式的分区中最基本的存储单元就是块。</p>
<p>为便于管理，连续的块会被集中在一起，组成 <strong>块组</strong>。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/block-group.jpg" alt="block-group" width="600" height="400" loading="lazy"></p>
<p>ext4分区中的块组图示</p>
<p>每个块组都有自己的数据结构和数据块。</p>
<p>H下面是一个块组可以包含的数据结构：</p>
<ul>
<li><strong>超级块：</strong> 元数据存储库，其中包含有关整个文件系统的元数据，例如文件系统中的总块数，块组中的块总数，索引节点等。但并非所有的块组都包含超级块。一定数量的块组会存储超级副本作为备份。</li>
<li><strong>组描述符：</strong> 组描述符同样包含每个块组的簿记信息</li>
<li><strong>索引节点位图：</strong> 每个块组都有自己的索引节点配额用于存储文件。块位图是一种数据结构，用于标识块组中 <em>已使用</em> 和 <em>未使用</em> 的索引节点。<code>1</code> 表示已使用的索引节点对象，<code>0</code> 表示未使用的索引节点对象。</li>
<li><strong>块位图:</strong> 一种数据结构，用于标识块组中已使用和未使用的数据块。<code>1</code> 表示已使用的数据块，<code>0</code> 表示未使用的数据块。</li>
<li><strong>索引节点表：</strong> 一种数据结构，用于定义文件及其索引节点的关系。存储在该区域中的索引节点的数量与文件系统使用的块大小有关。</li>
<li><strong>数据块：</strong> 存储文件内容的块组中的区域。</li>
</ul>
<p>与ext3相比，Ext4文件系统又往前更进一步，将块组集合成为了一个更大的组，被称为 <em>弹性块组。</em></p>
<p>每个弹性块组包含多个数字块组。</p>
<p>每个块组的数据结构（包括块位图，索引节点位图和索引节点表）被 <em>串联</em> 在一起并存储在相应的弹性块组内的 <em>首个块组</em> 中。</p>
<p>通过将所有数据结构连接到一个块组（第一个）中，便可以释放每个弹性块组内其它块组上的更多连续数据块。</p>
<p>第一个块组的图示如下：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/block-group-detail.jpg" alt="block-group-detail" width="600" height="400" loading="lazy"></p>
<p>ext4弹性块组中第一个块的图示</p>
<p>文件被写入磁盘时，会被写入某个块组中的一个或多个块。</p>
<p>通过在块组级别管理文件，可显著提高文件系统的性能。</p>
<h3 id="vs">文件大小vs占用磁盘大小</h3>
<p>你是否曾注意到文件浏览器会为每个文件显示两种大小： <strong>文件大小，</strong> 和 <strong>占用磁盘大小</strong>。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/disksize-1.jpg" alt="disksize-1" width="600" height="400" loading="lazy"></p>
<p>文件大小和占用磁盘大小</p>
<p>你可能会问，为什么文件大小和占用磁盘大小会略有不同？</p>
<p>解释如下：</p>
<p>我们已经知道，基于文件大小，一个文件会被分配一个或多个块。</p>
<p>而一个块是可以分配给文件的最小空间，这就意味着那些仅被部分占用的块的剩余空间是不能被另一个文件使用的。</p>
<p>由于文件的大小 <em>并不是块的整数倍</em>，因此很有可能最后一个块仅被部分占用，而该块剩余的空间将保持未使用的状态或会被零填充。</p>
<p>因此，“文件大小”基本上是指文件的实际大小，而“占用磁盘大小”是指文件已占用的空间，即使它没有完全使用这些空间。</p>
<p>在Linux上，你可以使用 <code>du</code> 命令来查看文件大小，</p>
<pre><code>du -b "some-file.txt"

</code></pre>
<p>输出结果如下：</p>
<pre><code>623 icon-link.svg
</code></pre>
<p>并查看其占用磁盘大小：</p>
<pre><code>du -B 1 "icon-link.svg"
</code></pre>
<p>输出结果如下：</p>
<pre><code>4096    icon-link.svg
</code></pre>
<p>可以看出，该文件被分配的块约为4kb，而文件的实际大小为623字节。</p>
<h3 id="">什么是磁盘碎片？</h3>
<p>随着时间的推移，新文件会被写入磁盘，现有文件或增大，或缩小，或被删除。</p>
<p>在存储工具中频繁进行这些更改便会在文件之间留下许多小的间隙（空白空间）。</p>
<p>当文件作为片段存储在存储设备上时便会产生文件碎片，这是因为文件系统已不能找到足够多的连续块来将整个文件存储在一行中。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/disk_image-1.jpg" alt="disk_image-1" width="600" height="400" loading="lazy"></p>
<p>碎片和非碎片文件的示例</p>
<p>让我们通过一个示例来更好地说明这一点。</p>
<p>假设你有一个名为 <code>myfile.docx</code> 的Word文档。</p>
<p>起初，<code>myfile.docx</code> 存储在磁盘上的几个连续块中，比方说就是 <code>LBA250</code>， <code>LBA251</code> 和 <code>LBA252</code> （这种命名方式是假设的）这几个块。</p>
<p>现在，如果你将更多内容添加到了 <code>myfile.docx</code> 中并将其保存，该文档将会在存储工具上占用更多的块。</p>
<p>考虑到 <code>myfile.docx</code> 当前存储在 <code>LBA250</code>， <code>LBA251</code>， 和 <code>LBA252</code> 上，因此，根据所需空间，新增加的内容最好存储在紧跟其后的 <code>LBA253</code> 等几个块上。</p>
<p>但是，如果现在 <code>LBA253</code> 已经被另一个文件占用（可能是另一个文件的第一个块）了，那么 <code>myfile.docx</code> 的新增内容就只能存储在磁盘上其它位置的块中，比方说是 <code>LBA312</code> 和 <code>LBA313</code>。</p>
<p><a href="https://www.freecodecamp.org/news/p/4521a4e0-fc09-4f63-80bb-6c01f617c4db/myfile.docx"><code>myfile.docx</code></a> got fragmented 💔.</p>
<p>文件碎片会给文件系统带来运行负担，因为每次用户程序请求碎片文件时，文件系统都需要从磁盘上的各个位置收集文件的每个片段。</p>
<p>这一过程也适用于将文件保存回磁盘的情况。</p>
<p>即便是第一次将文件写入磁盘时，也有可能发生碎片，原因可能是因为文件很大，但存储设备上已没有剩余空间。</p>
<p>碎片也是某些操作系统随着文件系统老化而性能变慢的原因之一。</p>
<h3 id="">如今我们还有必要担心碎片的问题吗？</h3>
<p>一个直截了当的回答是：再也不用了！</p>
<p>利用智能算法，时下的文件系统会尽可能避免（或及早发现）碎片的产生。</p>
<p>Ext4还会进行所谓的 <strong>预分配，</strong> 就是为文件提前保留一些块以备不时之需，以此来确保即便使用了一段时间文件也不会产生碎片。</p>
<p><em>预分配块</em> 的数量在文件索引节点对象区段的 <em>长度字段</em> 中有所定义。</p>
<p>此外，Ext4还会使用一种名为 <strong>延迟分配</strong> 的技术。</p>
<p>该技术不再在写入过程中一次写入一个数据块，而是先将分配请求累积在缓冲区。然后在分配完成后再将数据写入磁盘。</p>
<p>这种方法无需再在回应每个“写”请求时都调用块分配器，从而有助于文件系统在分配可用空间时做出更好的选择，如将大文件与小文件分开放置。</p>
<p>想象如果一个小文件存在了两个大文件之间。那如果删除了这个小文件，则这两个大文件之间便会滞留出一个小空间。</p>
<p>而当大文件和小文件保存在存储设备上的不同区域时，即便删除了小文件，也不会在存储设备上留下很多空白。</p>
<p>通过这种方式散布文件时，还可以在数据块之间留出足够的空隙，进而有助于文件系统更轻松地管理并避免产生碎片。</p>
<p>延迟分配同样可以有效减少碎片并提高系统性能。</p>
<h2 id="">目录</h2>
<p>目录（Windows中的文件夹）是一种特殊文件，在对文件和目录进行分组时发挥着 <strong>逻辑容器</strong> 的作用。</p>
<p>目录和文件的处理方式在NTFS和Ext4上是相同的。也就是说，目录只是具有自己的索引节点（在Ext4上）或MFT条目（在NTFS上）的文件。</p>
<p>目录的索引节点或MFT条目包含有关该目录的信息，并指向与该目录相关联文件的条目集合。</p>
<p>实际上，这些文件并不包含在目录中，但它们却以某种方式显示为了目录的子级，从而与目录相关联，就像是一个文件浏览器。</p>
<p>这些条目被称为 <strong>目录条目。</strong> 目录条目内含有映射到其索引节点或MFT条目的文件名。</p>
<p>除了目录条目，还存在另外两种条目，一个是 <code>.</code>，指向目录本身，一个是 <code>..</code>，指向其上级目录。</p>
<p>在Linux上，可以使用 <code>ls</code> 命令在目录中查看目录条目及其关联的索引节点编号：</p>
<pre><code>ls -lai
</code></pre>
<p>输出结果如下：</p>
<pre><code>63756 drwxr-xr-x 14 root root   4096 Dec  1 17:24 .
     2 drwxr-xr-x 19 root root   4096 Dec  1 17:06 ..
 81132 drwxr-xr-x  2 root root   4096 Feb 18 06:25 backups
 81020 drwxr-xr-x 14 root root   4096 Dec  2 07:01 cache
 81146 drwxrwxrwt  2 root root   4096 Oct 16 21:43 crash
 80913 drwxr-xr-x 46 root root   4096 Dec  1 22:14 lib

 ...
</code></pre>
<h2 id="">文件命名规则</h2>
<p>有些文件系统会对文件命名实加一些限制。</p>
<p>这种限制可以是 <strong>文件名的长度</strong> 或是 <strong>文件名区分大小写</strong>。</p>
<p>例如，在NTFS文件系统中，<code>MyFile</code> 和 <code>myfile</code> 所指的是同一文件，但在EXT4上，它们便指向不同的文件。</p>
<p>你可能会问，这有什么大不了的？</p>
<p>想象一下，假如你正在一台Windows环境的计算机上创建网页。网页内容包含你的品牌徽标，这一徽标为PNG文件，如下所示：</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;title&gt;Products - Your Website&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;!--SOME CONTENT--&gt;
        &lt;img src="img/logo.png"&gt;
        &lt;!--SOME MORE CONTENT--&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>即便该徽标文件的实际命名为 <code>Logo.png</code> （注意这里是大写的字母 <strong>L</strong>），你也仍可以在浏览器上打开网页时看到该徽标。</p>
<p>但是在将该网页部署到Linux服务器上进行实时查看时，徽标文件就会受到损坏。</p>
<p>为什么？</p>
<p>因为在Linux（EXT4文件系统）环境下，<code>logo.png</code> 和 <code>Logo.png</code> 分别指向两个不同的文件。</p>
<h2 id="">文件大小规则</h2>
<p>有关文件系统的一个重要方面是它们支持的 <strong>最大文件大小</strong>。</p>
<p>就MS-DOS + 7.1、Windows 9x家族和闪存使用的 <strong>FAT32</strong> 等旧文件系统来说，存储文件大小不能超过4GB，而其后继产品 <strong>NTFS</strong> 允许的文件大小最高可达 <strong>16 EB</strong>（1000 TB）。</p>
<p>和NTFS类似，exFAT也支持16EB的文件大小，这也使其成为存储海量数据文件（如视频文件）的理想选择。</p>
<p>实际上，exFAT和NTFS文件系统中对文件大小是没有限制的。</p>
<p>Linux的EXT4和Apple的APFS支持的文件大小分别高达 <strong>16 TiB</strong> 和 <strong>8 EiB</strong>。</p>
<h2 id="">文件资源管理器</h2>
<p>如你所知，文件系统的逻辑层通过提供一个API，可使用户应用程序对文件执行诸如 <code>读</code>， <code>写</code>， <code>删除</code> 和 <code>执行</code>等操作。</p>
<p>但文件系统的API其实是一种低级机制，专为计算机程序、运行时环境和壳设计，而非为日常使用而设计。</p>
<p>可以说，操作系统为我们日常的文件管理提供了多种非常方便、开箱即用的文件管理实用程序。例如，Windows上的 <strong>File</strong> <strong>Explorer</strong> ，Mac OS上的 <strong>Finder</strong> ，Ubuntu上的 <strong>Nautilus</strong> 都是文件浏览器的示例。</p>
<p>这些实用程序在后台使用逻辑文件系统的API。</p>
<p>除了这些GUI（图形用户界面）工具外，操作系统还能通过命令行界面显示文件系统的API，例如Windows上的命令提示符，以及Mac和Linux上的Terminal。</p>
<p>这些基于文本的界面可以帮助用户以文本命令的形式执行各种文件操作，就和我们在前面的示例中所做的一样。</p>
<h2 id="">文件访问权限管理</h2>
<p>如果说每个人都能删除或修改任何文件，哪怕是非自己所属的文件，或者在没有任何授权的情况下就更新这些文件，后果可想而知。</p>
<p>时下的文件系统故而提供了一种控制用户对文件的访问权限和访问功能的机制。</p>
<p>有关用户权限和文件所有权的数据存储在一个数据结构中。这一数据结构在Windows下被称为访问控制列表（ACL，Access Control List），在Unix一类的操作系统（Linux和Mac OS）上被称为访问控制项（ACE，Access Control Entry）。</p>
<p>此功能在CLI（命令行界面）也同样可用，用户可从命令行界面直接更改文件所有权或设置某个文件的权限。</p>
<p>例如，在Linux或Mac上，文件所有者可以将文件设置为可供所有人使用，步骤如下：</p>
<pre><code>chmod 777 myfile.txt
</code></pre>
<p><code>777</code> 的意思是，每个人都可以对文件 <code>myfile.txt</code> 进行读、写、执行等任何操作。</p>
<h2 id="">维护数据完整性</h2>
<p>假设你有一项论文研究已经进行了一个月。有一天，你打开论文文件，进行了一些修改，然后将其保存。</p>
<p>而在你点击保存文件后，你的文字处理器程序便会向文件系统的API（逻辑文件系统）发送“写入”请求。</p>
<p>该请求最终会传递到物理层，以将文件修改存储在诸多块中。</p>
<p>但是，如果在将文件的旧版本替换为新版本的过程中系统突然崩溃了该怎么办呢？</p>
<p>哈哈，在较老的文件系统（如FAT32或ext2）中，那意味着你的数据将面临损坏，因为只有部分数据被写入了磁盘。</p>
<p>但在今天，<strong>journaling</strong> 技术的使用让这种情况已不太可能再发生在时下的文件系统上。</p>
<p>日志文件系统记录着物理层中将要发生但尚未发生的每一项操作。</p>
<p>记录的主要目的就是跟踪记录物理层上尚未提交至文件系统的更改。</p>
<p>日志是磁盘上的一种特殊空间分配。在这里，每一次的“写入”尝试都将首先作为 <strong>事务</strong> 进行存储。</p>
<p>当数据存储在存储设备的物理层后，这一更改会立即被提交至文件系统。</p>
<p>发生系统崩溃时，文件系统将检测未完成的事务并将其回滚，就好像该事务从未发生一样。</p>
<p>这样一来，新内容（正在写入的内容）可能仍会丢失，但现有数据将完好无损。</p>
<p>诸如NTFS、APFS和EXT4（甚至EXT3）一类的现代文件系统都已使用日志功能来避免系统故障造成的数据损坏。</p>
<h2 id="">数据库文件系统</h2>
<p>大多文件系统通常都会将文件组织为目录形式。</p>
<p>要访问某个文件，只需层层“跨越”相关目录直至文件所在目录。</p>
<pre><code>cd /music/country/highwayman
</code></pre>
<p>但是，在数据库文件系统中，并不存在路径和目录的概念。</p>
<p>数据库文件系统是一个根据多种 <em>属性</em> 和 <em>维度</em> 对文件进行归组的 <strong>多面系统</strong>。</p>
<p>比如，它可以把一堆MP3文件按照艺术家、流派、发行年份和专辑等多种属性同时陈列。</p>
<p>数据库文件系统更像是一种高级应用程序，它可以帮助你更轻松、更有效地组织和访问文件。但是，你将无法访问此应用程序之外的文件。</p>
<p>然而，数据库文件系统并不能取代传统的文件系统。它只是一个可以高效处理文件的高级抽象。</p>
<p>Mac OS上的应用程序 <strong>iTunes</strong> 就是数据库文件系统的一个范例。</p>
<h2 id="">小结</h2>
<p>哇！你读到了最后，这也意味着现在的你已经懂得了很多文件系统的知识。但我也相信，这绝不是你文件系统学习的终点。</p>
<p>再次回到那个问题，我们能用一句话描述一下什么是文件系统以及它是如何工作的吗？</p>
<p>让我们就以我在开头写下的那句简单定义来结束本文吧：</p>
<p><strong>文件系统</strong> 决定着在存储设备中对文件进行 <strong>命名</strong>、 <strong>存储</strong> 和 <strong>检索</strong> 的方式。</p>
<p>如果你觉得我的讲解有遗漏之处或是我搞错了一些问题，欢迎在下面评论给我留言哦。</p>
<p>顺便告诉你，如果你想获得更多相关内容的详细讲解，欢迎访问我的网站 <a href="https://www.skillupp.tech">skillupp.tech</a> 并关注我的 <a href="https://twitter.com/lavary_">Twitter</a>，我经常在这两个地方分享我的日常发现。</p>
<p>再次感谢阅读，祝你学得开心哦！</p>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/file-systems-architecture-explained/">What Is a File System? Types of Computer File Systems and How they Work – Explained with Examples</a>，作者：<a href="https://www.freecodecamp.org/news/author/reza/">Reza Lavarian</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ MVC 架构简介 ]]>
                </title>
                <description>
                    <![CDATA[ 在过去的 20 年中，网站已经从带有 CSS 的简单页面变成了更加复杂和功能强大的应用程序。 为了使这些应用程序更易于开发，程序员们使用了不同的模式和软件架构，以减少代码的复杂性。 但是，首先，什么是软件架构？ 架构是描述软件的系统方法，它还描述了软件与其他软件的关系，以及软件之间如何相互影响。 软件架构还包括其他因素，例如业务策略、质量属性、人员动态、设计和 IT 环境。 换句话说，软件架构是系统的蓝图。 Model-View-Controller（ MVC）架构 目前最流行的软件架构是 Model-View-Controller 架构，简称 MVC。 MVC 将任何大型应用程序分为三个部分：  * Model（模型）  * View（视图）  * Controller（控制器） 这些组件中的每个组件都是为处理应用程序的特定方面而构建的，并且具有不同的用途。 Model Model 包含用户使用的所有与数据相关的逻辑，例如项目的模式和接口、数据库及其字段。 例如，客户对象将从数据库中检索客户信息，操纵或更新他们在数据库中的记录，或使用它来呈现数据。 View V ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/model-view-architecture/</link>
                <guid isPermaLink="false">6029f1316183a705401568ac</guid>
                
                    <category>
                        <![CDATA[ 软件架构 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chengjun.L ]]>
                </dc:creator>
                <pubDate>Sun, 14 Feb 2021 02:30:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/02/Pink-Cute-Chic-Vintage-90s-Virtual-Trivia-Quiz-Presentations--15--1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>在过去的 20 年中，网站已经从带有 CSS 的简单页面变成了更加复杂和功能强大的应用程序。</p><p>为了使这些应用程序更易于开发，程序员们使用了不同的模式和软件架构，以减少代码的复杂性。</p><h3 id="-">但是，首先，什么是软件架构？</h3><p>架构是描述软件的系统方法，它还描述了软件与其他软件的关系，以及软件之间如何相互影响。</p><p>软件架构还包括其他因素，例如业务策略、质量属性、人员动态、设计和 IT 环境。</p><p>换句话说，软件架构是<strong>系统的蓝图</strong>。</p><h2 id="model-view-controller-mvc-"><strong>Model-View-Controller（<strong> MVC</strong>）<strong>架构</strong></strong></h2><p>目前最流行的软件架构是 Model-View-Controller<strong> </strong>架构，简称 MVC。</p><p>MVC 将任何大型应用程序分为三个部分：</p><ul><li>Model（模型）</li><li>View（视图）</li><li>Controller（控制器）</li></ul><p>这些组件中的每个组件都是为处理应用程序的特定方面而构建的，并且具有不同的用途。</p><h3 id="model">Model</h3><p>Model 包含用户使用的所有与数据相关的逻辑，例如项目的模式和接口、数据库及其字段。</p><p>例如，客户对象将从数据库中检索客户信息，操纵或更新他们在数据库中的记录，或使用它来呈现数据。</p><h3 id="view">View</h3><p>View 包含应用程序的 UI 和呈现。</p><p>例如，View 将包括所有 UI 组件，例如文本框、下拉菜单以及用户与之交互的其他内容。</p><h3 id="controller">Controller</h3><p>最后，Controller 包含所有与业务相关的逻辑并处理传入的请求。它是 Model 和 View 之间的接口。</p><p>例如，Controller 将处理来自 View 的所有交互和输入，并使用 Model 更新数据库。使用同一 Controller 查看用户数据。</p><p>这个示意图将 MVC 架构可视化，并展示如何协同工作：</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2021/02/Pink-Cute-Chic-Vintage-90s-Virtual-Trivia-Quiz-Presentations--15-.png" class="kg-image" alt="Pink-Cute-Chic-Vintage-90s-Virtual-Trivia-Quiz-Presentations--15-" width="600" height="400" loading="lazy"><figcaption>Model-View-Controller 示意图</figcaption></figure><h2 id="mvc-"><strong><strong>MVC 架构如何运行</strong></strong></h2><p>首先，浏览器向 Controller 发送请求。 然后，Controller 与 Model 进行交互以发送和接收数据。</p><p>然后，Controller 与 View 交互以呈现数据。View 仅关注如何呈现信息，而不关注最终呈现。这将是一个动态 HTML 文件，它根据 Controller 发送的数据呈现数据。</p><p>最终，View 将其最终呈现发送到 Controller，而 Controller 将该最终数据发送到用户输出。</p><p>重要的是 View 和 Model 永远不会相互影响。它们之间唯一发生的交互是通过 Controller。</p><p>这意味着应用程序和接口的逻辑永远不会相互影响，这使得编写复杂的应用程序更加容易。</p><p>让我们看一个简单的例子：</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/02/Pink-Cute-Chic-Vintage-90s-Virtual-Trivia-Quiz-Presentations--16-.png" class="kg-image" alt="Pink-Cute-Chic-Vintage-90s-Virtual-Trivia-Quiz-Presentations--16-" width="600" height="400" loading="lazy"></figure><p>让我们看看这里发生了什么。首先，用户通过 Web 浏览器或移动应用程序输入他们想要的电影列表。</p><p>然后，浏览器将请求发送到 Controller 以获取电影列表。</p><p>接下来，Controller 将要求模型从数据库中查找电影列表。</p><figure class="kg-card kg-code-card"><pre><code class="language-express">router.get('/',ensureAuth, async (req,res)=&gt;{ 
	try{ 
		const movies = await Movies.find() (*) 
		res.render('movies/index',{ movies }) 
    } 
    
	catch(err){ console.error(err) 
		res.render('error/500') } })     </code></pre><figcaption>Controller 发送请求，获得电影列表</figcaption></figure><p>然后，Model 搜索数据库，并将电影列表返回给 Controller。</p><figure class="kg-card kg-code-card"><pre><code class="language-express">const mongoose = require('mongoose') 
const MovieSchema = new mongoose.Schema
({ 
	name:{ 
        type:String, 
        required:true 
    }, 
	description:{ 
    	type:String 
    } 
}) 

module.exports = mongoose.model('Movies',MovieSchema)</code></pre><figcaption>电影 Model</figcaption></figure><p>如果 Controller 从 Model 中获取电影列表，则 Controller 将要求 View 显示电影列表。</p><figure class="kg-card kg-code-card"><pre><code class="language-express">router.get('/',ensureAuth, async (req,res)=&gt;{ 
	try{ const movies = await Movies.find() 
		res.render('movies/index', { movies (*) }) } 

	catch(err){ 
    console.error(err) res.render('error/500') } 
})</code></pre><figcaption>Controller 发送电影列表给 View，渲染电影列表</figcaption></figure><p>‌然后，View 将接收请求，并将渲染的电影列表以 HTML 格式返回给 Controller。</p><figure class="kg-card kg-code-card"><pre><code class="language-html">&lt;div class="col" style="margin-top:20px;padding-bottom:20px"&gt;
    &lt;div class="ui fluid card"&gt; 
        &lt;div class="content"&gt; 
        &lt;div class="header"&gt;{{movie.title}}&lt;/div&gt; 
        	&lt;/div&gt; &lt;div class="extra content"&gt; 
            &lt;a href="/movies/{{movie._id}}" class="ui blue button"&gt; More from {{movie.description}} &lt;/a&gt; 
        &lt;/div&gt; 
    &lt;/div&gt;
&lt;/div&gt;</code></pre><figcaption>View 以 HTML 格式返回电影列表</figcaption></figure><p>最后，Controller 将获取该 HTML 文件，并将其返回给用户，从而获得电影列表作为输出。</p><h2 id="--1">总结</h2><p>有很多不同的软件架构，但是 Model-View-Controller 是最受欢迎的，被广泛使用。它降低了代码复杂度，使软件易于理解。</p><p>现在你明白了 Model-View-Controller 背后的概念。</p><p>祝大家学习愉快！</p><p>原文：<a href="https://www.freecodecamp.org/news/model-view-architecture/">How Model-View-Controller Architecture Works</a>，作者：<a href="https://www.freecodecamp.org/news/author/nishant-kumar/">Nishant Kumar</a><br></p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
