<!DOCTYPE html><html lang="zh" data-theme="dark"><head><meta charset="utf-8"><meta name="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><title>IBM 全栈开发【10】:微服务和无服务器 · Cytrogen 的个人博客</title><meta name="description" content="本文是 IBM 全栈开发课程的第十篇学习笔记,深入探讨了微服务与无服务器这两种现代云原生架构。笔记首先介绍了构建可扩展应用的十二要素方法论,并详细对比了单体、SOA与微服务的架构差异,涵盖了常见的设计模式与反模式。接着,文章转向无服务器计算,解释了其核心理念(FaaS)、优势与挑战。最后,笔记通过一个完整的流程,演示了如何为微服务构建 Docker 容器镜像,并使用 IBM Cloud Code Engine 这一全托管平台,轻松地部署和更新应用。这为理解和实践现代分布式系统的构建与部署提供了全面的理论指导。"><link rel="icon" href="../favicon.png"><link rel="canonical" href="https://cytrogen.icu/posts/52000.html"><link rel="webmention" href="https://webmention.io/cytrogen.icu/webmention"><link rel="me" href="https://m.otter.homes/@Cytrogen"><link rel="me" href="https://github.com/cytrogen"><meta name="fediverse:creator" content="@Cytrogen@m.otter.homes"><link rel="preload" href="../fonts/opensans-regular-latin.woff2" as="font" type="font/woff2" crossorigin="anonymous"><style>@font-face {
font-family: 'Open Sans';
src: url('../fonts/opensans-regular-latin.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
size-adjust: 107%;
ascent-override: 97%;
descent-override: 25%;
line-gap-override: 0%;
}
</style><script>(function() {
try {
// 优先级:用户选择 > 系统偏好 > 默认浅色
const saved = localStorage.getItem('theme');
const theme = saved ||
(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
document.documentElement.style.colorScheme = theme;
} catch (error) {
// 失败时使用默认主题,不阻塞渲染
document.documentElement.setAttribute('data-theme', 'light');
}
})();
</script><link rel="stylesheet" href="../css/ares.css"><script data-netlify-skip-bundle="true">(function() {
document.addEventListener('DOMContentLoaded', function() {
const theme = document.documentElement.getAttribute('data-theme');
const pageWrapper = document.getElementById('page-wrapper');
if (pageWrapper && theme) {
pageWrapper.setAttribute('data-theme', theme);
}
});
})();
</script><!-- hexo injector head_end start -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hexo-math@4.0.0/dist/style.css">
<!-- hexo injector head_end end --><meta name="generator" content="Hexo 8.1.1"><link rel="alternate" href="atom.xml" title="Cytrogen 的个人博客" type="application/atom+xml">
</head><body><div id="page-wrapper"><a class="skip-link" href="#main-content">跳到主要内容</a><div class="wrap"><header><a class="logo-link" href="../index.html"><img src="../favicon.png" alt="logo"></a><div class="h-card visually-hidden"><img class="u-photo" src="https://cytrogen.icu/favicon.png" alt="Cytrogen"><a class="p-name u-url u-uid" href="https://cytrogen.icu">Cytrogen</a><p class="p-note">Cytrogen 的个人博客,Cytrogen's Blog</p><a class="u-url" rel="me noopener" target="_blank" href="https://m.otter.homes/@Cytrogen">Mastodon</a><a class="u-url" rel="me noopener" target="_blank" href="https://github.com/cytrogen">GitHub</a></div><nav class="site-nav"><div class="nav-main"><div class="nav-primary"><ul class="nav-list hidden-mobile"><li class="nav-item"><a class="nav-link" href="../index.html">首页</a></li></ul><div class="nav-tools"><div class="language-menu"><button class="language-toggle" type="button"><svg class="icon icon-globe" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855A7.97 7.97 0 0 0 5.145 4H7.5V1.077zM4.09 4a9.267 9.267 0 0 1 .64-1.539 6.7 6.7 0 0 1 .597-.933A7.025 7.025 0 0 0 2.255 4H4.09zm-.582 3.5c.03-.877.138-1.718.312-2.5H1.674a6.958 6.958 0 0 0-.656 2.5h2.49zM4.847 5a12.5 12.5 0 0 0-.338 2.5H7.5V5H4.847zM8.5 5v2.5h2.99a12.495 12.495 0 0 0-.337-2.5H8.5zM4.51 8.5a12.5 12.5 0 0 0 .337 2.5H7.5V8.5H4.51zm3.99 0V11h2.653c.187-.765.306-1.608.338-2.5H8.5zM5.145 12c.138.386.295.744.468 1.068.552 1.035 1.218 1.65 1.887 1.855V12H5.145zm.182 2.472a6.696 6.696 0 0 1-.597-.933A9.268 9.268 0 0 1 4.09 12H2.255a7.024 7.024 0 0 0 3.072 2.472zM3.82 11a13.652 13.652 0 0 1-.312-2.5h-2.49c.062.89.291 1.733.656 2.5H3.82zm6.853 3.472A7.024 7.024 0 0 0 13.745 12H11.91a9.27 9.27 0 0 1-.64 1.539 6.688 6.688 0 0 1-.597.933zM8.5 12v2.923c.67-.204 1.335-.82 1.887-1.855A7.97 7.97 0 0 0 10.855 12H8.5zm3.68-1h2.146c.365-.767.594-1.61.656-2.5h-2.49a13.65 13.65 0 0 1-.312 2.5zm2.802-3.5a6.959 6.959 0 0 0-.656-2.5H12.18c.174.782.282 1.623.312 2.5h2.49zM11.27 2.461c.247.464.462.98.64 1.539h1.835a7.024 7.024 0 0 0-3.072-2.472c.218.284.418.598.597.933zM10.855 4a7.966 7.966 0 0 0-.468-1.068C9.835 1.897 9.17 1.282 8.5 1.077V4h2.355z"></path></svg><span>中文</span></button><div class="language-dropdown"></div></div></div><div class="nav-controls"><div class="more-menu hidden-mobile"><button class="more-toggle" type="button"><span>更多</span><svg class="icon icon-chevron-down" width="12" height="12" viewBox="0 0 12 12" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 8.825c-.2 0-.4-.1-.5-.2l-3.3-3.3c-.3-.3-.3-.8 0-1.1s.8-.3 1.1 0l2.7 2.7 2.7-2.7c.3-.3.8-.3 1.1 0s.3.8 0 1.1l-3.3 3.3c-.1.1-.3.2-.5.2z"></path></svg></button><div class="more-dropdown"><ul class="dropdown-list"><li class="dropdown-item"><a class="nav-link" href="../archives/index.html">归档</a></li><li class="dropdown-item"><a class="nav-link" href="../categories/index.html">分类</a></li><li class="dropdown-item"><a class="nav-link" href="../tags/index.html">标签</a></li><li class="dropdown-item"><a class="nav-link" href="../about/index.html">关于</a></li><li class="dropdown-item"><a class="nav-link" href="../sitemap/index.html">领地地图</a></li></ul></div></div><div class="theme-switcher"><button class="theme-toggle" type="button" role="switch" aria-pressed="false" aria-label="切换主题"><div class="theme-icon moon-icon"><svg class="icon icon-moon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"></path></svg></div><div class="theme-icon sun-icon"><svg class="icon icon-sun" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"></path></svg></div></button></div><details class="mobile-menu-details hidden-desktop"><summary class="hamburger-menu" aria-label="nav.menu"><svg class="icon icon-bars" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"></path></svg><span class="menu-text">nav.menu</span></summary><div class="mobile-menu-dropdown"><ul class="mobile-nav-list"><li class="mobile-nav-item"><a class="mobile-nav-link" href="../index.html">首页</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../archives/index.html">归档</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../categories/index.html">分类</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../tags/index.html">标签</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../about/index.html">关于</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../sitemap/index.html">领地地图</a></li></ul></div></details></div></div></div></nav></header><main class="container" id="main-content" tabindex="-1"><div class="post"><article class="post-block h-entry"><div class="post-meta p-author h-card visually-hidden"><img class="author-avatar u-photo" src="../favicon.png" alt="Cytrogen"><span class="p-name">Cytrogen</span><a class="u-url" href="https://cytrogen.icu">https://cytrogen.icu</a></div><a class="post-permalink u-url u-uid visually-hidden" href="https://cytrogen.icu/posts/52000.html">永久链接</a><div class="p-summary visually-hidden"><p>近期在学习 IBM 全栈应用开发微学士课程,故此记录学习笔记。</p></div><div class="visually-hidden"><a class="p-category" href="../categories/%E7%BC%96%E7%A8%8B%E7%AC%94%E8%AE%B0/">编程笔记</a><a class="p-category" href="../tags/IBM/">IBM</a><a class="p-category" href="../tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/">微服务</a><a class="p-category" href="../tags/%E6%97%A0%E6%9C%8D%E5%8A%A1%E5%99%A8/">无服务器</a></div><h1 class="post-title p-name">IBM 全栈开发【10】:微服务和无服务器</h1><div class="post-info"><time class="post-date dt-published" datetime="2024-06-11T02:32:00.000Z">6/10/2024</time><time class="dt-updated visually-hidden" datetime="2026-02-09T17:16:54.717Z"></time></div><div class="post-content e-content"><html><head></head><body><p>近期在学习 IBM 全栈应用开发微学士课程,故此记录学习笔记。</p>
<span id="more"></span>
<h1 id="1-微服务入门"><a class="markdownIt-Anchor" href="#1-微服务入门"></a> 1. 微服务入门</h1>
<h2 id="11-十二要素应用程序方法论"><a class="markdownIt-Anchor" href="#11-十二要素应用程序方法论"></a> 1.1. 十二要素应用程序方法论</h2>
<p>在现代软件开发中,软件通常以服务的形式交付。软件通过互联网集中托管和访问。这种软件通常被称为网络应用程序或软件即服务(简称 SaaS)。十二要素应用程序方法论适合这类应用程序。微服务不是十二要素应用程序的必要条件。不过,微服务通常与十二要素应用程序方法论结合使用。</p>
<p>十二要素可分为软件交付生命周期中的代码、部署和运维三个阶段。</p>
<h4 id="111-代码阶段"><a class="markdownIt-Anchor" href="#111-代码阶段"></a> 1.1.1. 代码阶段</h4>
<p>首先是 <strong>要素一</strong>:<em>代码库</em>。您应始终在版本控制系统(如 Git)中跟踪应用程序的代码库。代码库和应用程序之间是一一对应的关系。一个应用程序应包含在一个单一的代码库中。但是,应用程序会有多个部署或实例。虽然这些部署中的代码库是相同的,但每个部署中都可能存在不同的应用程序版本。例如,开发或测试环境中可能会有尚未到达生产环境的变更。</p>
<p>接下来是 <strong>要素五</strong>:<em>构建、发布和运行</em>。这一阶段展示了代码库如何成为生产部署。构建阶段编译代码、收集依赖关系,然后将代码库转化为称为构建的可执行单元。发布阶段将构建与部署的当前配置相结合,这样代码就可以运行了。然后,在运行阶段实现应用程序。这三个阶段应严格分开。例如,代码不应在运行时进行更改,因为这样会导致这些更改无法纳入构建阶段。</p>
<p><strong>要素十</strong> 是 <em>开发与开发的一致性</em>。这一要素最大限度地减少了开发环境和生产环境之间的差异,这对于持续交付以快速将变更推广到生产环境是必不可少的。此举可减少代码在一个环境中运行正常、而在另一个环境中运行不正常的情况。奇偶校验对于后端服务尤为重要:如果在生产中使用 MySQL 数据库,则应在开发环境中使用相同版本的 MySQL 数据库。奇偶校验有助于在开发过程中尽早发现故障。</p>
<p>我们要讨论的最后一个代码要素是 <strong>要素二</strong>:<em>依赖</em>。应用程序的可靠性取决于其最不可靠的依赖关系。因此,十二要素应用程序不依赖于任何软件包、依赖项或工具的隐式存在。您必须明确声明所有依赖关系。这样,当新开发人员抓取代码库时,就不会假设他们的机器上已经存在任何依赖关系。</p>
<h4 id="112-部署阶段"><a class="markdownIt-Anchor" href="#112-部署阶段"></a> 1.1.2. 部署阶段</h4>
<p>第一个部署要素是 <strong>要素三</strong>,即 <em>配置</em>。配置是指不同部署之间可能存在差异的一切。暂存和生产环境可能使用不同的数据库,因此开发人员应根据部署情况配置数据库的凭据和位置。应避免将配置存储为常量,因为不同环境下的配置可能不同。将配置存储在环境变量中,这样就可以在部署过程中轻松更改,而无需更改代码。</p>
<blockquote>
<p>暂存(也称为预生产,Staging)复制了一个完整的生产环境,以便在生产变更之前对其进行测试。自动化和公共云平台可按需提供暂存环境,以便对生产变更进行最终测试。</p>
</blockquote>
<p><strong>要素四</strong> 是 <em>后台服务</em>。十二要素应用程序不应区分本地服务和第三方服务。两者都应可通过 URL 或其他定位器信息以及任何凭据进行访问,这样开发人员就可以在不更改代码的情况下轻松更换后端服务。例如,如果数据库出现问题,就可以启动一个新的数据库,并在无需更改代码的情况下将其替换进来。</p>
<p><strong>要素六</strong> 是 <em>进程</em>。应用程序在环境中以一个或多个进程的形式执行。进程应该是无状态的,不共享任何东西。您应将持久数据存储在数据库等后端服务中,因为内存和文件系统不会在进程间共享。如果另一个进程处理后续事务,后续事务将无法访问前一个进程中的数据。因此,数据需要集中存储。</p>
<blockquote>
<p>如果进程是无状态的,不共享任何东西,那么应用程序就可以启动额外的进程进行横向扩展,处理额外的工作量,而不会在进程间产生相互依赖关系。</p>
</blockquote>
<p><em>端口绑定</em> 是 <strong>要素七</strong>。创建面向网络的服务时,不应在运行时将网络服务器注入应用程序。相反,网络应用程序应通过绑定端口并监听该端口上的传入请求来导出 HTTP。您可以将端口绑定用于 HTTP 和其他服务。绑定端口通常是在代码中通过声明网络服务器库为依赖关系来实现的。随后,由于这些应用程序可以通过 URL 访问,因此它们可以成为其他应用程序的后端服务。</p>
<h4 id="113-运维阶段"><a class="markdownIt-Anchor" href="#113-运维阶段"></a> 1.1.3. 运维阶段</h4>
<p>让我们从 <strong>要素八</strong>「<em>并发性</em>」开始。应用程序运行并发进程来处理不断增加的负载。由于进程是无状态的,不共享任何内容,因此应用程序可以启动更多进程进行横向扩展,处理更多传入请求,而不会在进程间产生相互依赖关系。</p>
<p><strong>要素九</strong> 是 <em>可处置性</em>,它规定应用程序进程需要最短的启动时间,并应在终止时优雅地结束。快速启动可让您快速部署代码或配置变更。您还可以轻松扩展应用程序,因为新的部署可以快速启动。</p>
<p><strong>要素十一</strong> 决定了 <em>如何处理日志</em>。日志可提供应用程序性能的可见性,因此应用程序不应关注日志的存储。相反,应用环境应将日志作为写入标准输出的事件流来处理。执行环境可以捕获所有运行应用程序的日志流,将其汇总并路由至目的地。当目的地是日志分析工具时,这一操作尤其有用。</p>
<p>最后一个要素是 <strong>要素十二</strong>:<em>管理流程</em>。管理流程是管理应用程序的一次性流程,例如数据库迁移。管理流程使用相同的代码库和配置针对发布版本运行。此外,应用程序代码应包含管理流程,使其与应用程序保持同步。</p>
<h2 id="12-微服务"><a class="markdownIt-Anchor" href="#12-微服务"></a> 1.2. 微服务</h2>
<p>微服务架构是一种方法,在这种方法中,单个应用程序由许多松散耦合且可独立部署的小型服务组成。这些服务通常有自己的技术栈,包括数据库和数据管理模式。团队甚至可以为不同的组件选择不同的编程语言,因为它们通过 API 端点相互依赖。</p>
<p>微服务组件通过 REST API、事件流和消息代理的组合相互通信,并根据业务功能进行隔离和组织,服务之间的分隔线称为有界上下文。</p>
<p>由于服务之间不存在相互依赖关系,因此您可以更轻松地更新代码,添加新特性或功能,而无需触及整个应用程序。每个团队都可以选择适合自身需求和专长的技术栈,构建团队负责的组件。这些组件可以相互独立地扩展,从而减少了因单一功能可能面临过多负载而不得不扩展整个应用程序所带来的浪费和成本。</p>
<p>无论你在哪里看到微服务的扩展,通常都涉及水平扩展。水平扩展是指通过添加更多的资源实例来进行扩展,也被称为「向外扩展」。有了微服务,单个服务可以单独部署和扩展。如果操作得当,微服务所需的基础架构会更少,因为它们可以只对需要的组件进行精确扩展,而不是像单体应用程序那样对整个应用程序进行扩展。</p>
<p>调用应用程序接口通常是建立特定服务状态的有效方法。然而,这并不是一种特别有效的了解最新进展的方式。此时,事件流可以帮助广播状态变化,从而通过引入消息代理帮助扩展微服务。</p>
<h4 id="121-单体应用程序-vs-soa-vs-微服务"><a class="markdownIt-Anchor" href="#121-单体应用程序-vs-soa-vs-微服务"></a> 1.2.1. 单体应用程序 VS SOA VS 微服务</h4>
<p>单体应用程序的所有或大部分功能都在一个进程中完成。应用程序由内部层或库来管理。这些层可用于安全、报告、分析、用户界面或数据访问。各层之间紧密相连,相互依赖。这正是单体设计的优势所在;因为一切都在同一个代码库中,所以简单、功能和特性之间的交叉较少。</p>
<p>然而,随着时间的推移,大多数产品都在不断发展,范围也在不断扩大,设计变得越来越复杂,修改起来也越来越困难。这也成为适应新技术的障碍,因为这意味着要重新编写整个应用程序。</p>
<p>Windows 窗体应用程序是单体设计的一个典型例子。在这里,我们将用户界面、业务逻辑和数据访问全部嵌入到一个代码库中,从而构成了整个应用程序。只有数据库不在其中。</p>
<p>面向服务架构(SOA)是以服务提供者和消费者的方式设计和构建的。所提供的服务作为一个离散的功能单元,无缝集成,易于重复使用。每个 SOA 服务都由三个部分组成。</p>
<ol>
<li>接口(interface)定义了服务提供商如何执行服务消费者的请求。</li>
<li>合约(contract)定义了服务提供商和服务消费者应如何交互。</li>
<li>实现(implementation)则是服务代码。</li>
</ol>
<p>拥有这三个独立组件的好处是,它们有助于提高可靠性,并由于服务之间的固定合约而支持并行开发。</p>
<p>然而,由于这些期望和要求,SOA 设计可能会变得复杂,阻碍快速应用开发。而且,SOA 设计是一项昂贵的投资,因此通常只适合那些能够投入所需资源和专业知识的企业团队。SOA 是一个企业范围的概念。银行系统就是一个例子。它使现有的应用程序能够通过松散耦合的接口进行公开,每个接口都与业务功能相对应,从而使扩展企业一部分的应用程序能够在其他应用程序中重复使用功能。银行的每个接口都提供服务,以完成其职责。</p>
<p>与 SOA 一样,微服务架构由松散耦合、可重用和专用的组件组成,这些组件通常彼此独立工作。即使是微服务中的数据也不会与其他服务共享,这有助于您独立扩展单个微服务,而独立也意味着您可以自由选择底层技术。这种架构的好处是易于开发,可以将单个工作单元定义为一项服务。由于每个服务都有指定的职责,因此可以灵活地添加新技术。</p>
<p>不过,虽然使用微服务有其令人信服的理由,但也存在一些相关的问题。首先是安全问题。现在有这么多不同的服务独立托管,每个服务都需要有自己的安全模式。例如,一个简单的要求,如传输层安全(或 TLS),以确保网络通信的安全。其次,调试和隔离问题也会变得更加困难,因为每个服务都是独立运行的,这意味着你可能会发现找到根本原因很有难度。</p>
<p>微服务架构是一个应用范围概念。例如,电子商务应用程序可以有独立的微服务来处理订单、安全和分析。微服务设计可将单个应用程序的内部结构分割成小块,这些小块可独立更改、扩展和管理。但是,微服务并没有定义应用程序之间的对话方式。</p>
<h4 id="122-微服务模式"><a class="markdownIt-Anchor" href="#122-微服务模式"></a> 1.2.2. 微服务模式</h4>
<p>微服务有许多可用的模式,可以应对一些更常见的问题和机遇。例如,单页面应用程序(或 SPA)模式、后端换前端(或 BFF)模式、Strangler 模式和服务发现模式。</p>
<ul>
<li>
<p>SPA 模式:<br>
随着功能更强大的浏览器、速度更快的网络和客户端语言的融合,许多网络界面开始将所有功能整合到单页应用程序中。用户通过一个界面进入,不会重新加载登陆页面,也不会脱离最初的体验。<br>
这些应用程序结合使用 HTML、CSS 和 JavaScript,通过动态服务调用后端基于 REST 的服务来响应用户输入,更新屏幕的部分内容,而不是重定向到一个全新的页面。<br>
这种应用架构通常简化了前端体验,但后端服务需要承担更多责任。</p>
</li>
<li>
<p>BFF 模式:<br>
虽然单页面应用程序能很好地满足单渠道用户体验的需求,但它在不同渠道(如移动和网络)的用户体验方面效果不佳。BFF 模式在用户体验和体验所调用的资源之间插入了一个层。这种设计可以在不同渠道之间实现定制的用户体验。<br>
例如,在台式机上使用的应用程序与在移动设备上使用的应用程序在屏幕尺寸、显示效果和性能限制方面都有所不同。BFF 模式允许开发人员为每个用户界面创建和支持一种后端类型,并使用该界面的最佳选项,而不是试图支持一种通用后端,这种后端适用于任何界面,但可能会对前端性能产生负面影响。<br>
假设用户可以通过移动应用程序或桌面上的网络应用程序访问应用程序。在应用 BFF 模式时,您需要开发一个专门用于移动体验的后端和另一个用于网络体验的后端。每个后端都知道如何调用正确的服务和协调代码,以优化所请求的渠道体验。<br>
移动应用程序可能会显示更有限的数据子集,屏幕尺寸也会与网络体验不同。每个后端都是一个微服务。您可以应用微服务架构,将单体后端分离成不同的服务,执行各自特定的必要任务,而不是让单体应用程序先检查需要哪个通道,然后再包含所有逻辑,为该通道准备用户体验。</p>
</li>
<li>
<p>Strangler 模式:<br>
有助于分阶段管理单体应用程序的重构。该模式的隐喻名称来源于花园中藤蔓缠绕树木的现象。试想一下,一个网络应用程序是通过单个 URL 构建的,这些 URL 的功能映射到业务域的不同方面。<br>
利用 Strangler 模式,您可以使用网络应用程序的结构,将应用程序拆分成多个功能域,并一次针对一个域使用基于微服务的新实现来取代这些域。这两个方面形成了独立的应用程序,它们并排存在于同一个 URL 空间中。随着时间的推移,新重构的应用会取代原来的应用,直到最后可以关闭单体应用。<br>
Strangler 模式包括以下步骤:</p>
<ol>
<li>转换。这将在云平台或现有环境中创建一个并行的新网站。</li>
<li>共存。这将使现有网站在指定时间内正常运行。它会逐步从当前位置重定向到新网站,以获得新实施的功能。</li>
<li>消除。这将删除现有网站上过时的功能,或在从原网站重定向流量时停止维护该功能。</li>
</ol>
</li>
<li>
<p>服务发现模式:<br>
可帮助应用程序和服务相互发现。之所以需要这种模式,是因为在微服务架构中,服务实例会因扩展、升级、服务故障甚至服务终止而发生动态变化。负载平衡器也可以使用这种模式进行健康检查,并在服务失败时重新平衡流量。其他模式包括实体和聚合模式,这种模式可用于电子商务网站,在该网站上,订单将是由买方分组的产品的聚合。</p>
</li>
<li>
<p>适配器模式:<br>
就像你去另一个国家旅行时想到插头适配器一样。适配器模式的目的是帮助转换原本不兼容的对象之间的关系。例如,如果您集成了第三方应用程序接口。</p>
</li>
</ul>
<h4 id="123-微服务反模式"><a class="markdownIt-Anchor" href="#123-微服务反模式"></a> 1.2.3. 微服务反模式</h4>
<p>虽然有很多模式可以很好地开发微服务,但同样也有很多模式会让开发团队很快陷入困境。以下是开发微服务时的一些禁忌:</p>
<ol>
<li>
<p><strong>不要构建微服务。</strong><br>
微服务的第一条规则是不要从微服务开始。当您确定单体应用程序的复杂性会对应用程序的开发和维护产生负面影响时,请考虑将该应用程序重构为更小的服务。<br>
当应用程序变得过于庞大,无法轻松更新和维护时,这些微服务将成为分解复杂性的理想选择,使应用程序更易于管理。<br>
然而,在你感受到这种痛苦之前,你甚至还没有一个需要重构的单体。</p>
</li>
<li>
<p><strong>不重视自动化。</strong><br>
如果您有一个单体应用程序,您只需要部署一个软件。一旦转向微服务架构,您将拥有不止一个应用程序,每个应用程序都有不同的代码、测试和部署周期。<br>
试图构建微服务时,必须同时具备以下两个条件:</p>
<ul>
<li>适当的自动化部署和监控,或……</li>
<li>管理云服务,以支持您现在庞大的异构基础设施。</li>
</ul>
<p>会带来很多不必要的麻烦。<br>
因此,在构建微服务时,一定要使用 DevOps 或云服务。</p>
</li>
<li>
<p><strong>不要构建纳米服务。</strong><br>
如果过分追求微服务中的 <strong>微</strong>,就很容易发现自己正在构建纳米服务!其复杂性将超过微服务架构的总体收益。<br>
倾向于创建大型服务,并在适当的时候创建小型服务:</p>
<ul>
<li>部署更改变得困难。</li>
<li>通用数据模型变得过于复杂。</li>
<li>加载和扩展要求不再同步并影响应用程序性能。</li>
</ul>
</li>
<li>
<p><strong>不要变成 SOA。</strong><br>
微服务和面向服务架构(SOA)这两个概念经常被混为一谈,因为在最基本的层面上,两者都是构建可被其他应用程序使用的可重用的单个组件。<br>
然而,微服务是细粒度的,每个微服务都有独立的数据存储,也就是有边界的上下文。<br>
微服务项目如果演变成 SOA 项目,很可能会不堪重负。</p>
</li>
<li>
<p><strong>不要为每种服务建立网关。</strong><br>
您应该使用 API 网关,而不是在每个服务中实施终端用户身份验证、节流、协调、转换、路由和分析。<br>
API 网关是一种 API 管理工具,位于客户端和后端服务集合之间。<br>
这将成为上述非功能性问题的核心,并避免在每个服务中重新设计这些问题。</p>
</li>
</ol>
<p>微服务的目的是解决三个最常见的挑战,即通过提供细粒度服务的业务功能来提升客户体验、灵活应对新需求和降低成本。</p>
<p>但是,在这样做的同时,你应该避免上述反模式的陷阱,以免微服务对你的开发、交付和管理要求造成困扰。</p>
<h1 id="2-web-api-要素rest-api-和-graphql"><a class="markdownIt-Anchor" href="#2-web-api-要素rest-api-和-graphql"></a> 2. Web API 要素:REST API 和 GraphQL</h1>
<h2 id="21-rest"><a class="markdownIt-Anchor" href="#21-rest"></a> 2.1. REST</h2>
<p>REST 是「表征状态传输」(Representational State Transfer)的缩写。REST API 为集成应用程序提供了一种灵活、轻量级的方式,并已成为微服务架构中连接组件的最常用方法。它是一种定义应用程序如何在网络中相互通信的架构风格。</p>
<p>应用程序接口(API)需具有三个特征才可将其归类为 RESTful(REST 式的):</p>
<ol>
<li>它通过 HTTP 管理所有请求。</li>
<li>它提供无状态的客户端 - 服务器通信。</li>
<li>它由组件之间的统一接口组成。</li>
</ol>
<p>REST API 通过 HTTP 请求进行通信,以执行标准功能,如在资源中创建、读取、更新和删除记录(也称为 CRUD)。例如,REST API 使用 <code>POST</code> 请求创建记录,使用 <code>GET</code> 请求检索记录,使用 <code>PUT</code> 请求更新记录,使用 <code>DELETE</code> 请求删除记录。</p>
<p>REST 应用程序接口是无状态的,这意味着每个请求都包含处理该请求所需的全部信息。REST 的创始人罗伊・菲尔丁(Roy Fielding)在他的论文中说道:「<em>从客户端到服务器的每个请求都必须包含理解请求所需的全部信息,而不能利用服务器上的任何存储上下文。因此,会话状态完全保存在客户端。</em>」REST API 的这种无状态特性也使其具有可扩展性。</p>
<p>RESTful API 的主要优点是接口统一,无论请求来自何处。REST API 应确保同一块数据(如产品 ID)只属于一个统一资源标识符(或 URI)。资源应包含客户可能需要的所有信息。例如,客户可能需要产品的名称和价格。</p>
<p>让我们来看一个 REST 的例子:<a target="_blank" rel="noopener" href="http://CEX.IO">CEX.IO</a> 是一个加密货币交易所。它通过 REST API 为开发者提供比特币、其他加密货币的价格和市场数据。CEX 在其开发人员部分记录了所有 API 调用,包括请求和响应参数的详细信息,全部采用简单的 JSON 格式,以及 JavaScript、Python、C# 和 Java 示例请求和代码片段。例如,您可以通过其公共 API 请求货币对的最后价格。</p>
<h4 id="211-创建-rest-api"><a class="markdownIt-Anchor" href="#211-创建-rest-api"></a> 2.1.1. 创建 REST API</h4>
<p>Python 是一种编程语言,能让您更快速地工作,更有效地集成系统。Python 可用于许多应用领域。它借助 Flask 等框架支持网络开发。借助数学、科学和工程领域的 SciPy 等库,Python 被广泛应用于科学和数值计算领域。Python 在人工智能和机器学习领域也很受欢迎。Flask 被归类为微型网络框架,因为它不需要特定的工具或库。它的构建考虑到了可扩展性和简单性。与其他框架相比,Flask 应用程序以轻量级著称。由于 Flask 对使用什么数据库或模板引擎没有意见,因此它是 RESTful API 的不错选择。</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># hello.py</span></span><br><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask</span><br><span class="line">app = Flask(__name__)</span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">'/'</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello_world</span>():</span><br><span class="line"> <span class="keyword">return</span> <span class="string">'Hello World!'</span></span><br></pre></td></tr></tbody></table></figure>
<p>使用以下命令:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">flask --app hello run</span><br></pre></td></tr></tbody></table></figure>
<p>从浏览器中打开 <code>127.0.0.1:5000</code> 便能看到响应。</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># products.py</span></span><br><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, jsonify, request</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line">app = Flask(__name__)</span><br><span class="line">products = [</span><br><span class="line"> {<span class="string">'id'</span>: <span class="number">143</span>, <span class="string">'name'</span>: <span class="string">'Notebook'</span>, <span class="string">'price'</span>: <span class="number">5.49</span>},</span><br><span class="line"> {<span class="string">'id'</span>: <span class="number">144</span>, <span class="string">'name'</span>: <span class="string">'Black Marker'</span>, <span class="string">'price'</span>: <span class="number">1.99</span>}</span><br><span class="line">]</span><br></pre></td></tr></tbody></table></figure>
<p>现在,您将创建一个名为 <code>products.py</code> 的新 Python 文件,该文件将为产品微服务提供所有端点。首先定义导入并创建默认产品列表。我们不会使用任何数据库来持久化这些产品,因此每次重启 API 时,产品列表都是一样的。接下来,定义 <code>GET</code> 方法来检索所有产品。该方法隐式返回 <code>200</code>,在 HTTP 中表示 <strong>0K</strong>。</p>
<h4 id="212-使用-curl-和-postman-发送应用程序接口请求"><a class="markdownIt-Anchor" href="#212-使用-curl-和-postman-发送应用程序接口请求"></a> 2.1.2. 使用 cURL 和 Postman 发送应用程序接口请求</h4>
<p>与服务器之间的数据传输需要支持必要网络协议的工具。Linux 上有多种用于此目的的工具,其中最流行的是 curl。curl 是 Client URL 的缩写,是一种命令行工具,可以通过各种网络协议传输数据。它开发于 1998 年,通过指定相关的 URL 和需要发送或接收的数据,可用于与网络或应用程序服务器进行通信。你可以运行一个简单的 curl 命令(如本例所示),然后查看输出结果:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl https://www.example.com/</span><br></pre></td></tr></tbody></table></figure>
<p>curl 最常见的用例包括从互联网下载文件、端点测试、调试和错误记录。curl 支持的一些常见协议包括 HTTP、HTTPS、FTP 和 IMAP。</p>
<p>如果运行这个示例的 curl 命令:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -X <span class="string">'GET'</span> <span class="string">'http://127.0.0.1:5000/products'</span> -H <span class="string">'accept: application/json'</span></span><br></pre></td></tr></tbody></table></figure>
<p>你将得到如图所示的输出结果:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[</span><br><span class="line"> {"id":143,"name":"Notebook","price":5.49},</span><br><span class="line"> {"id":144,"name":"Black Marker","price":1.99}</span><br><span class="line">]</span><br></pre></td></tr></tbody></table></figure>
<p>curl 接受多种选项,这使它成为一个用途极为广泛的命令。选项以一个或两个破折号开头,如果不需要附加值,单破折号选项可以写在一起。那么,让我们来分解一下命令和输出:连字符 <code>X</code> 表示明确指定 HTTP 函数,在本例中就是 <code>GET</code>。然后,我们指定要评估的 URL。连字符 <code>H</code> 允许您定义标题,在本例中就是告诉网络服务器我们要使用 JSON。我们的输出是以 JSON 表示的产品微服务返回的产品列表。</p>
<p>Postman 是一个用于构建和使用 API 的 API 平台,它基于一系列用户友好的强大工具,使开发人员能够轻松地创建、测试、共享和记录 API。Postman 简化了 API 生命周期的每个步骤,允许协调多个请求,这些请求可以在多次重复或迭代中执行,并帮助您简化协作,从而更快地创建更好的 API。由于其简单易用,它是最流行、最方便的工具之一,可用于测试支持多种协议的各种 API(例如 <code>GET</code>、<code>POST</code>、<code>PUT</code> 和 <code>PATCH</code> 等 HTTP 请求),然后将 API 转换为 JavaScript 和 Python 等语言的代码。</p>
<p>WhatsApp 业务平台云 API 就是一个广受欢迎的例子,它利用 Postman 创建了一种简化的、开发人员友好的体验。有了 Postman,使用自动化和预先填入的数据,入职时间缩短到几分钟,而其他需要手动设置的方法则无法做到这一点。从注册 WhatsApp 商务平台到测试电话号码和建立信息通话,开发人员只需几分钟就能完成。</p>
<p>您可以将 Postman 下载到电脑,也可以使用在线版本。</p>
<h4 id="213-记录和测试-rest-api-swagger"><a class="markdownIt-Anchor" href="#213-记录和测试-rest-api-swagger"></a> 2.1.3. 记录和测试 REST API Swagger</h4>
<p>API 指定了一个接口,并连接不同的系统,为它们提供一致的通信。API 文档就像一本参考手册,包含有效使用和集成系统的说明。Swagger 通过运行 OpenAPI 规范来确保您符合指导原则,从而节省了编写 API 文档的时间。</p>
<p>Swagger 允许您描述 API 的结构,以便机器可以读取它们。有了 API 的结构,Swagger 就能自动构建引人入胜的交互式 API 文档。该结构定义在符合 OpenAPl 规范的 JSON 或 YAML 文件中。</p>
<p>最初,API 并不是为了自我服务消费。它们由数据驱动,解决了连接和通信方面的一些特殊用例。而 OpenAPl 规范为 RESTful APIs 定义了一个标准的、与语言无关的接口 -- 该规范与语言无关,可由人类和机器读取。此外,它还允许人们和计算机发现并了解服务的功能,而无需访问源代码、额外的文档或检查网络流量。它定义了 API 支持的所有操作、所需参数和预期返回值,以及所需的 API 身份验证。它甚至还定义了服务条款、联系信息和暴露的 API 的许可证信息等内容。</p>
<p>Flask 支持将 Python 函数作为 API 暴露。Flask Swagger UI 允许您描述和可视化 REST API,从而改进了 Flask 的功能。要在应用程序中引入 Swagger UI,您需要使用 flask 蓝图 <code>flask-swagger-ui</code>:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install flask-swagger-ui</span><br></pre></td></tr></tbody></table></figure>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, jsonify, request</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">from</span> flask_swagger_ui <span class="keyword">import</span> get_swaggerui_blueprint</span><br><span class="line"></span><br><span class="line">swaggerui_blueprint = get_swaggerui_blueprint(</span><br><span class="line"> <span class="string">'/products/docs'</span>,</span><br><span class="line"> <span class="string">'https://{HOSTNAME}/swagger.json'</span>,</span><br><span class="line"> config={<span class="string">'app_name'</span>: <span class="string">"Products microservice"</span>}</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">app.register_blueprint(swaggerui_blueprint)</span><br></pre></td></tr></tbody></table></figure>
<p><code>Swagger.json</code> 以 JSON 文件的形式保存了 API 的定义和特征。您需要将此文件与您的 API 一起公开。因此,您需要一个路由来提供静态 <code>Swagger.json</code> 文件。</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">'/swagger.json'</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">static_file</span>():</span><br><span class="line"> <span class="keyword">return</span> app.send_static_file(<span class="string">'swagger.json'</span>)</span><br></pre></td></tr></tbody></table></figure>
<p>您现在有了 API 定义和 Swagger。您可以使用 UI 测试您的 API,这是使用 Swagger 的主要好处。此外,它还能让消费者获得有关您的 API、支持的功能、请求和响应的更多详细信息。它还显示产品信息部分的内容。它显示了唯一可用的方法,即检索所有产品的 <code>GET</code> 方法。然后,您可以执行该端点并查看结果,在本例中,结果就是产品列表。</p>
<h2 id="22-api-网关"><a class="markdownIt-Anchor" href="#22-api-网关"></a> 2.2. API 网关</h2>
<p>API 网关是一种 API 管理工具,位于客户端和后台服务集合之间。它汇集了满足客户需求所需的各种服务,并返回相应的结果。为什么要使用 API 网关呢?因为 API 网关可以帮助您保护 API 免受恶意使用或过度使用。因此,您可以使用具有速率限制的验证服务。使用分析和监控服务还有助于了解 API 的使用情况。此外,您还可以使用计费系统将 API 货币化。网关还能为您的各种微服务提供单一接触点,并对请求作出单一响应。最后,您可以无缝添加或移除 API,而客户对后端运行的服务一无所知。</p>
<p>设您的网店采用的是微服务架构,因此其中的一些服务将包括:产品信息服务,用于共享产品的基本信息(如白痴名称和价格);库存服务,用于显示现有库存;订单服务,用于让客户下订单购买产品;以及验证服务,用于验证平台上的用户。那么,客户端如何访问微服务呢?当您需要与多个应用程序接口交互时,这是一个问题。API 网关可以消除这种复杂性,并允许您:更改主机及其位置、增加或减少服务实例的数量,以及用新服务替换现有服务(例如订购服务)。客户对服务的访问不受影响。</p>
<p>使用 API 网关的好处如下:</p>
<ul>
<li>它能使客户端免受应用程序如何分割成微服务的影响。换句话说,它将调用多个服务的逻辑从客户端转移到网关,从而简化了客户端。</li>
<li>它还能为每个客户端提供最佳的应用程序接口,无论客户端是谁。</li>
<li>它减少了请求或往返的次数。</li>
</ul>
<p>例如,API Gateway 可以让客户只需往返一次,就能从多个服务中检索数据。无论您的微服务如何进行内部通信,API 网关都将提供与外界通信的标准协议。虽然 API 网关有很多好处,但它也有一些缺点:</p>
<ul>
<li>它是另一个需要开发和维护的组件。</li>
<li>如果设计不慎,它还可能成为应用程序的单点故障。</li>
<li>由于在执行应用程序时增加了网络步骤,网关会增加响应时间。</li>
</ul>
<p>市场上有很多 API 网关产品。您可以选择托管或开源方案。来自 IBM 的业界领先的高安全性应用网关 IBM DataPower Gateway 可根据您的需求提供两种产品:Apigee 或 Cloud Endpoints。</p>
<p>微软 Azure 和亚马逊 AWS 也在其平台上提供网关。虽然这些都是托管产品,但在开放源代码领域,一些著名的产品有:</p>
<ul>
<li>最受欢迎的 Kong</li>
<li>Apache APISIX</li>
<li>Tyk(也有托管版本)</li>
<li>Gloo(也有企业版本)</li>
</ul>
<h1 id="3-无服务器概述"><a class="markdownIt-Anchor" href="#3-无服务器概述"></a> 3. 无服务器概述</h1>
<h2 id="31-无服务计算"><a class="markdownIt-Anchor" href="#31-无服务计算"></a> 3.1. 无服务计算</h2>
<p>云原生计算基金会(或 CNCF)将无服务器定义为「<em><strong>构建和运行无需服务器管理的应用程序的概念</strong></em>」。它描述了一种更细粒度的部署模式,在这种模式下,捆绑为一个或多个功能的应用程序被上传到一个平台,然后根据当下所需的确切需求来执行、扩展和计费。换句话说,无服务器计算将基础设施管理的责任卸载给了云提供商,使开发人员能够专注于应用程序的业务逻辑。</p>
<p>将无服务器计算视为功能即服务(或 FaaS)平台和后台即服务(或 BaaS)服务的组合。FaaS 平台用于运行功能。BaaS 代表后端云服务,如云数据库、对象存储服务和消息队列。现在,IT 计算的历史显示了从传统计算到无服务器计算的渐进趋势,从而加快了部署速度、缩短了生命周期并提高了生产率。</p>
<p>这一演变趋势有几个里程碑:</p>
<ol>
<li>传统计算使用物理机,但受制于前期投资、容量规划等因素。物理机的部署需要数周或数月的时间,使用寿命长达数年。</li>
<li>在云计算中采用虚拟化技术,可实现更快的配置、高可扩展性和可用性。虚拟化有助于创建多个虚拟机(VM)或容器。虚拟机管理系统的部署只需几分钟,寿命为数天或数周。</li>
<li>基于操作系统虚拟化的容器只需几秒钟即可部署,并可存活几分钟或几小时。</li>
<li>而无服务器应用程序只需要无服务器架构的核心代码。无服务器功能的部署只需几毫秒,生命周期仅几秒钟。</li>
</ol>
<p>无服务器计算是标准云计算的进步,它抽象了基础设施和软件环境。它是一种架构风格,代码在云平台上运行,由云提供商管理硬件和软件设置、安全性、可扩展性等。客户只需为使用付费,而无需为 CPU 空闲时间付费。而开发人员只需关注函数形式的应用代码。</p>
<p>无服务器计算具有以下特点:</p>
<ul>
<li>它是无主机的,这意味着开发人员无需采购、管理和维护服务器。</li>
<li>它是弹性的,因为自动伸缩是无服务器的即时和固有特性。</li>
<li>它提供自动负载均衡,可在多个后端系统之间分配输入流量。</li>
<li>它是无状态的,因此性能更快,可扩展性更高。</li>
<li>它是事件驱动的,这意味着只有当事件发生时才会触发功能。</li>
<li>它提供高可用性,无需额外的工作或成本。</li>
<li>而且,它是基于使用量的细粒度计费。</li>
</ul>
<p>那么,无服务器世界中的功能是如何工作的呢?以创建 Docker 或 Kubernetes 容器所需的步骤为例。开发人员使用云提供商支持的语言(如 Python、Java、Node.JS 或 Go)编写代码,创建一个函数。然后,开发人员将函数上传到云。然后,定义触发函数的事件,如用户点击。一旦事件发生,触发器就会被调用,云提供商就会运行函数,从而生成容器对象。</p>
<p>让开发人员花更多时间创建高质量和优化的应用程序对企业大有裨益。开发人员可以使用任何流行的编程语言构建功能,通过添加其他功能扩展应用程序的功能,执行更好的测试(因为功能一次只执行一项任务),优化应用程序和功能,并改善用户体验。</p>
<p>为了实现零运行成本的目标,云提供商负责日常基础设施的管理和维护工作,如最大限度地提高计算机内存和网络利用率,同时最大限度地降低成本,提供服务器管理(包括操作系统更新和安全补丁),启用自动扩展,维护高可用性,实施安全,配置高性能(或低延迟),以及设置监控和日志。</p>
<h4 id="311-无服务器责任模型"><a class="markdownIt-Anchor" href="#311-无服务器责任模型"></a> 3.1.1. 无服务器责任模型</h4>
<p><img src="/posts/52000/1.png" alt="1.png"></p>
<p>在比较图中所示的不同云服务模式时,让我们来看看您与云提供商的责任,尤其是无服务器服务模式。我们从最左侧的堆栈(传统堆栈)开始,完全由您来管理。在 IaaS 模式中,您负责管理从操作系统到顶层的各层,而云提供商负责管理较低的四层。在 PaaS 模式中,您管理前两层,即应用层和数据层,而云提供商管理其余各层。在无服务器模式中,用户只管理应用程序层,而云提供商管理其余各层。而在 SaaS 模式中,云提供商负责管理整个堆栈。</p>
<h4 id="312-无服务的利与弊"><a class="markdownIt-Anchor" href="#312-无服务的利与弊"></a> 3.1.2. 无服务的利与弊</h4>
<p>让我们来了解一下传统计算所面临的挑战,以及无服务器计算的出现是如何克服这些挑战的。</p>
<p>在传统计算中,开发和运营团队需要建立和维护自己的基础设施。这些过程耗费大量时间,非常复杂,而且需要资金投入。然而,随着云、容器和无服务器计算的出现,开发团队可以专注于编写高质量代码,并在毫秒级的时间内构建和运行应用程序,而无需担心基础设施、可扩展性和容错问题。无服务器计算也面临着一些挑战,包括供应商锁定、第三方依赖性和网络。</p>
<p>也让我们来看看无服务器计算的优势和限制,然后将其与容器和传统计算进行比较。</p>
<p>在无服务器计算中,云提供商承担了大部分工作,这带来了许多好处。由于云提供商负责管理基础设施,因此无需进行基础设施设置和维护,从而降低了成本。云提供商可确保可靠性,从而实现高可用性和容错性。开发人员受益匪浅,因为他们可以专注于应用程序,做自己最喜欢的事情。如果没有无服务器,应用程序的所有部分都会持续运行,从而造成资源浪费。</p>
<p>无服务器功能允许您对应用程序进行配置,使其仅在需要时运行某些部分。例如,应用程序的前端必须持续运行,以保持用户登录。而身份验证服务只需偶尔调用,因此可以节省资源和成本。函数的运行时间以毫秒为单位,而容器或虚拟机(VM)的运行时间则分别以秒和分钟为单位。</p>
<p>许多云提供商都内置了代码编辑器,称为集成开发环境(IDE),可加快开发、部署和更新。云服务提供商按请求付费,不收取任何闲置资源的费用。您可以使用任何流行的编程语言进行开发。在身份验证、数据库和其他后端服务方面有大量第三方支持。由于开发人员只专注于开发,因此企业可以专注于业务,以比竞争对手更快的速度发布产品。</p>
<p>无服务器环境允许更多的创新和实验,即使会有失败。由于没有基础设施需要管理,绿色计算也成为一种必然的可能。</p>
<p>然而,无服务器计算并非最适合每种情况,因为它确实存在一些限制:</p>
<ul>
<li>许多企业在处理突发性工作负载时都能节省大量成本,但对于以长期运行流程为特征的工作负载,按使用付费模式带来的好处就会减少。对于这类应用,传统环境可能更具成本效益。</li>
<li>对云提供商技术或环境的依赖会导致供应商锁定风险。</li>
<li>如果隔了很长时间才收到请求,应用程序通常必须重新启动所有进程,即所谓的冷启动。这会增加功能运行时间。对于银行、医疗保健或边缘相关应用等时间关键型应用而言,无服务器延迟是不可接受的。</li>
<li>由于从端点到源代码的攻击面发生了变化,加上提供商的安全实施存在局限性,安全问题日益突出。</li>
<li>在任何分布式系统中,监控和调试都很复杂。</li>
<li>由于无法在本地系统中模仿后端即服务(或 BaaS)环境,因此测试完整功能和调试应用程序问题具有挑战性。</li>
<li>语言支持取决于云提供商。并非所有云提供商都支持所有编程语言,因此您只能使用云提供商支持的语言。</li>
<li>没有服务器可优化利用率或性能,因为您无法控制服务器。</li>
<li>没有状态持久性。例如,在下一次调用同一函数时,将无法使用之前的运行状态。由于本地缓存只能持续几个小时,因此最好使用低延迟的外部缓存,如 Redis 或 Memcached。</li>
</ul>
<h4 id="313-无服务器-vs-容器"><a class="markdownIt-Anchor" href="#313-无服务器-vs-容器"></a> 3.1.3. 无服务器 VS 容器</h4>
<p>无服务器和容器能一起工作吗?当然可以!无服务器和容器并不相互排斥。它们在混合解决方案中配合得最好。如果你在无服务器或容器之间难以取舍,请遵循标准的行业建议:「首先构建无服务器。如果需要,再转向容器」。</p>
<p>让我们比较一下无服务器计算和容器化,从无服务器的优点和容器的缺点开始:</p>
<ul>
<li>无服务器计算更具成本效益,因为您只需为使用的内容付费。</li>
<li>对于无服务器计算,可扩展性完全由云提供商负责。</li>
<li>云提供商管理所有基础设施。部署时间只需几毫秒,而不是几秒钟。</li>
<li>就上市速度而言,由于开发速度快,企业可以专注于核心业务,而不必担心基础设施。</li>
</ul>
<p>现在,让我们来看看容器的优点和无服务器的缺点:</p>
<ul>
<li>有了容器,在本地环境或云中进行测试就更容易了。</li>
<li>移植容器更容易,因为它们不受操作系统、语言和提供商的影响。</li>
<li>容器的延迟非常低,因此即使是时间紧迫的工作负载也适用。</li>
<li>容器也非常适合长期运行的应用程序,因为完成批处理作业没有时间限制。</li>
<li>使用容器,你可以同时配置应用程序和资源。</li>
<li>在语言支持方面,容器化支持任何语言。</li>
</ul>
<p>让我们比较一下无服务器计算和传统计算,从无服务器计算的优点和传统计算的缺点开始:</p>
<ul>
<li>无服务器架构是一种云计算模式,开发人员专注于编写高质量代码。</li>
<li>无服务器计算更具成本效益,因为你只需为使用的内容付费。</li>
<li>可扩展性完全由云提供商负责。</li>
<li>库和集成可在应用程序中使用。</li>
</ul>
<p>最后,让我们来看看传统计算的优点和无服务器计算的缺点:</p>
<ul>
<li>在传统计算中,数据由您控制。</li>
<li>在网络方面,您可以通过常规的互联网协议(或 IP)地址访问代码,而无需设置专用的应用程序编程接口(或 API)。</li>
<li>只需在组织的网络边界内实施安全防护。</li>
<li>由于整个设置都由您来管理,因此很少出现供应商锁定的情况。</li>
</ul>
<h4 id="314-无服务器框架"><a class="markdownIt-Anchor" href="#314-无服务器框架"></a> 3.1.4. 无服务器框架</h4>
<p>无服务器框架(Serverless Framework)是一个使用 Node.JS 编写的免费开源 Web 框架。它最初设计用于安全、快速地配置亚马逊网络服务或 AWS Lambda 函数、事件和基础设施资源。但它并不局限于 AWS。其他支持的提供商包括微软 Azure、谷歌云平台和 Apache OpenWhisk。</p>
<p>无服务器框架是一个命令行界面或 CLI,提供开箱即用的结构、自动化和最佳实践,让您可以专注于构建复杂的事件驱动型无服务器架构,包括函数、事件、资源和服务。</p>
<ul>
<li>功能只是部署在云中的代码,通常是为执行单个任务而编写的。每个功能都是一个独立的执行和部署单元,就像一个微服务。</li>
<li>任务可以是将用户保存到数据库,也可以是在指定时间执行任务。</li>
<li>功能由事件触发,而事件则来自其他资源,例如:API 网关、URL 上的 HTTP 请求或 S3 存储桶中上传的新文件。</li>
<li>资源是功能使用的基础设施组件,例如云提供商作为服务提供给您的数据库,或用于存储文件的 S3 存储桶。</li>
<li>服务是框架的组织单位。您可以将其视为一个项目文件,尽管您可以为一个应用程序提供多个服务。服务通过 <code>serverless.yml</code> 文件进行配置,您可以在其中定义要部署的功能、事件和资源。<br>
使用框架 CLL 部署时,配置文件中的所有内容都会一次性部署。<code>serverless.yml</code> 文件控制着服务中的所有内容。该文件有专门的章节用于指定函数、事件和资源。<figure class="highlight yaml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">service:</span> <span class="string">products</span></span><br><span class="line"></span><br><span class="line"><span class="attr">functions:</span></span><br><span class="line"> <span class="attr">productsCreate:</span></span><br><span class="line"> <span class="attr">events:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">httpApi:</span> <span class="string">'POST /products/create'</span></span><br><span class="line"> <span class="attr">productsDelete:</span></span><br><span class="line"> <span class="attr">events:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">httpApi:</span> <span class="string">'DELETE /products/delete'</span></span><br><span class="line"></span><br><span class="line"><span class="attr">resources:</span></span><br></pre></td></tr></tbody></table></figure>
</li>
</ul>
<p>让我们在 AWS 上使用无服务器框架构建并部署一个 <code>Hello World</code> 应用程序。</p>
<p>首先,您需要 serverless CLI,可以使用 npm 全局安装:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -g serverless</span><br></pre></td></tr></tbody></table></figure>
<p>运行 <code>serverless</code> 命令:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">serverless</span><br></pre></td></tr></tbody></table></figure>
<p>使用 Python 创建第一个 AWS HTTP API。该命令将带您完成一个向导,部署完成后,将为您提供一个 URL。如果在浏览器中打开,就会看到如图所示的结果。</p>
<p>接着更改函数代码,以在请求时返回 <code>Hello World</code>:</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">hello</span>(<span class="params">event, context</span>):</span><br><span class="line"> body = <span class="string">'Hello World!'</span></span><br><span class="line"> response = {<span class="string">'statusCode'</span>: <span class="number">200</span>, <span class="string">'body'</span>: body}</span><br><span class="line"> <span class="keyword">return</span> response</span><br></pre></td></tr></tbody></table></figure>
<p>然后就只需重新部署并再次测试即可。</p>
<h4 id="315-无服务器参考架构和用例"><a class="markdownIt-Anchor" href="#315-无服务器参考架构和用例"></a> 3.1.5. 无服务器参考架构和用例</h4>
<p>让我们来看一个实现简单 TODO 应用程序的 Web 应用程序,其中注册用户可以创建、读取、更新和删除项目。</p>
<p>Web 应用程序参考架构是一个通用的、事件驱动的后端,使用 AWS Lambda 和 Amazon API Gateway 实现其业务逻辑。它还使用 Amazon DynamoDB 作为数据库,并使用 Amazon Cognito 进行用户管理。应用程序中的所有静态内容都使用 AWS Amplify Console 托管。</p>
<p>基本上,Web 应用程序包括 3 个不同的组件。前端应用程序包含使用创建 React 应用程序生成的所有静态内容,而创建 React 应用程序则借助 HTML 文件、用于样式设计的 CSS、在客户端运行的 JavaScript 以及图像来工作。所有这些对象都托管在 AWS Amplify Console 上。当用户连接到网站时,所需的资源就会下载到他们的浏览器并开始运行。当应用程序需要与后端通信时,就会向后端发出 REST API 调用。</p>
<p>后端应用程序是实现实际业务逻辑的地方。逻辑托管在 Lambda 函数中,前端通过 API Gateway 使用 REST API 进行访问。数据随后存储在 DynamoDB 中。TODO 应用程序将用户限制在自己的待办事项中。因此,用户必须经过注册和身份验证才能访问自己的待办事项。为此,您可以使用 Cognito 用户池,让用户注册应用程序并进行身份验证。</p>
<p>无服务器应用程序的一个常见用例是事件流。这些应用无需设置前期基础设施即可编写和部署。它们可以从发布者、订阅者主题或事件日志中触发,为您提供弹性、可扩展的事件管道,而无需维护复杂的集群。这些事件流管道可为分析系统提供动力,更新二级数据存储和缓存,或为监控系统提供信息。后处理的例子包括图像和视频处理,您可以针对不同的目标设备动态调整图像大小或更改视频转码。后期处理还可用于图像识别或人工智能目的,以检测护照照片上是否有阴影。</p>
<p>在构建应用程序时,您需要决定在应用程序中使用哪种语言。无服务器应用程序可以使用多种语言,从而防止团队被语言锁定,即不得不无限期地使用与传统软件相同的语言。通常,选择哪种语言来构建应用程序并不是最适合项目的,而是取决于可用资源。</p>
<h4 id="316-流行的无服务器平台"><a class="markdownIt-Anchor" href="#316-流行的无服务器平台"></a> 3.1.6. 流行的无服务器平台</h4>
<p>亚马逊网络服务(AWS)Lambda 是亚马逊提供的一种无服务器、事件驱动的计算服务。该服务可让您为任何应用程序或后端服务运行代码,而无需配置或管理服务器。它可以响应任何规模的执行请求,从每天十几个事件到每秒数十万个自动执行请求。</p>
<p>AWS 平台提供两百多种服务。您只需支付运行功能的费用以及在 Lambda 和其他 AWS 服务之间传输数据的费用。此外,它还可用于各种用例,例如文件处理、网络应用、物联网(IoT)和移动后端。其次,谷歌云功能具有简单直观的开发人员体验。只需编写代码,然后让谷歌云处理运行基础架构。它可以根据流量自动将所有基础架构管理抽象为零,并即时进行扩展和缩减。数据更新时,Google Firebase 会立即提醒开发人员。轻量级提取、转换和加载(ETL)功能等异步工作负载不需要单独的服务器。您可以直接部署与所选事件相关联的函数,然后执行该函数。</p>
<p>Azure 是一种无服务器解决方案,可让您编写更少的代码、维护更少的基础设施并节省成本。使用 C#、Java、JavaScript、PowerShell 或 Python 编写函数,或使用自定义处理程序来使用几乎任何其他语言。Microsoft Azure 具有出色的开发人员操作或 DevOps 功能,可为持续集成和交付提供简便的设置。这一功能也适用于 Azure 函数。</p>
<p>使用 Consumption 计划,您只需在函数运行时支付费用,而高级计划和应用程序服务计划则提供满足特殊需求的功能。例如,Azure Functions 有许多不同的用例:当文件上传或 blob 存储发生变化时运行代码,收集和处理来自物联网设备的数据,或在预先定义的定时间隔执行代码。</p>
<p>IBM 云函数允许您从事件或直接通过 REST API 触发操作,从而促进与其他服务的轻松集成。使用 IBM Cloud Functions,您只需为您使用的时间支付费用,甚至低至十分之一秒。</p>
<p>IBM 云功能的一部分是 IBM Watson 强大的认知服务。例如,检测图像或视频中出现的物体或人物。云功能可在多个 IBM 云区域内使用。它支持高可用性,以避免单点故障。客户数据(如操作代码)会在不同区域自动同步。在生产中调试无服务器应用程序可能很困难,因为无法访问运行代码的运行时环境和基础架构。为了访问环境,IBM 提供了 LogDNA 的日志分析和 IBM Cloud Monitoring 指标。</p>
<p>大多数平台都有特定的供应商。您还可以选择开源开发。Knative 就是这样一个选择。Knative 基于通过 Kubernetes 运行的容器,Kubernetes 是容器的协调框架。与其他无服务器平台相比,Knative 的优势在于它提供了一个基于 Kubernetes 开发的平台,从而避免了供应商锁定。您可以在任何支持 Kubernetes 的云中部署该平台,而无需做任何更改。这也使得它与平台无关。此外,它还允许您根据自己的需求制定推广策略,通过将流量逐步转移到无服务器组件的最新版本来实现。</p>
<h2 id="32-faas"><a class="markdownIt-Anchor" href="#32-faas"></a> 3.2. FaaS</h2>
<p>功能即服务或 FaaS 是一种云计算服务,可让您根据事件执行代码,而无需通常与构建和启动微服务应用程序相关的复杂基础设施。FaaS 具有以下特点:</p>
<ul>
<li>它是无服务器计算的一个子集。</li>
<li>它以多个函数的形式创建应用程序,其中一个函数是用任何编程语言编写的软件。</li>
<li>FaaS 既可以部署在混合云上,也可以部署在企业内部环境中。</li>
<li>它是无状态的,但可以使用外部缓存来维护状态。</li>
<li>函数以毫秒为单位执行,并行处理单个请求,因此具有瞬时可扩展性。</li>
<li>FaaS 按运行功能所需的时间计费,而不是按服务器实例大小计费。</li>
<li>使用 FaaS,您可以将服务器划分为可自动独立扩展的功能,这样您就不必管理基础设施,也就可以去专注于应用程序代码,从而缩短产品上市时间。</li>
<li>FaaS 模式的最大优势之一是成本。您只需在发生操作时支付费用。当操作完成时,一切都会停止 -- 没有代码运行,服务器不会闲置,也不会产生任何费用。</li>
<li>由于函数是无状态的、独立的小代码块,因此可以根据需要自动、独立、即时地扩展。如果需求下降,它们会自动缩减。</li>
<li>FaaS 本身具有高可用性,因为它分布在不同地区和可用区,部署时无需增加成本。</li>
</ul>
<p>无服务器堆栈由三个主要部分组成,即功能即服务(FaaS)、后台即服务(BaaS)和 API 网关。让我们来看看无服务器堆栈中的组件是如何工作的:</p>
<ol>
<li>事件请求来自不同的渠道,如 HTTP 请求、来自 GitHub 和 Docker Hub 等资源库的网络钩子以及计划作业。</li>
<li>这些请求会经过 API 网关,由该网关识别并转发给相应的功能。</li>
<li>然后,这些功能会处理这些请求,并进一步(如有必要)将它们导向后端服务(如文件和对象存储、块存储、通知服务等),以便进一步处理和 / 或存储。</li>
<li>然后通过 FaaS 组件和 API 网关将输出或响应发送回客户端。</li>
</ol>
<p>举个现实世界中的例子,您需要将个人资料图片上传到网站。网站可能还需要该图片的缩略图,以便在某些网页上显示。这是使用功能即服务的常见情况。用户选择一张个人照片。图片被上传到对象存储桶。该事件触发一个 IBM 云函数,该函数处理个人资料照片并创建缩略图。然后,该函数将缩略图存储在对象存储中,以便网站在需要时可以访问缩略图。</p>
<p>FaaS 函数应设计为响应事件时执行单个工作。因此,要限制代码范围、提高效率并减轻重量,以便函数能快速加载和执行。</p>
<p>FaaS 的价值在于功能的隔离。过多的函数会增加成本,并降低函数隔离的价值。使用过多的第三方库会减慢函数的初始化速度,使其难以扩展。</p>
<p>一些常见的托管 FaaS 提供商包括:</p>
<ul>
<li>亚马逊的 AWS Lambda</li>
<li>谷歌云功能</li>
<li>微软的 Azure 功能</li>
<li>IBM 的云功能</li>
<li>Red Hat 的 OpenShift 云功能</li>
<li>Netlify、Oracle 和 Twilio 等其他一些提供商</li>
</ul>
<p>如果不想依赖第三方管理平台,也有许多自我管理的 FaaS 可供选择。其中包括:</p>
<ul>
<li>Fission 是 Kubernetes 上的无服务器功能框架</li>
<li>Fn Project 是一个容器原生无服务器平台</li>
<li>Knative 是一个基于 Kubernetes 的平台,用于构建、部署和管理无服务器工作负载</li>
<li>OpenFaaS 允许您将任何 Linux 或 Windows 进程转化为函数</li>
</ul>
<h1 id="4-创建和部署微服务"><a class="markdownIt-Anchor" href="#4-创建和部署微服务"></a> 4. 创建和部署微服务</h1>
<p>在本地成功构建和测试微服务后,您需要部署微服务。如果选择自托管微服务,则需要进行详细调整,并认真应对许多非同小可的挑战。</p>
<p>首先,你需要特意配置和构建你的微服务,使它们为生产做好准备,包括必要的资产,如库依赖关系、资源、凭据等。然后将它们编译并构建成一个可执行的二进制文件,以便在托管环境中运行。接下来,您需要仔细选择运行微服务的基础设施,如网络服务器、操作系统、网络、数据库等等。您的团队需要从众多选项中谨慎选择。由于微服务的流量会发生波动,因此需要动态地扩大或缩小规模。例如,电子商务网站在节假日总是会出现流量高峰,而几天或一周后流量就会大幅下降。在大多数情况下,您需要部署多个相关的微服务来工作和通信。微服务之间的通信需要可靠和安全。最后,还需要进行日志记录、监控和仪表板工作等活动,以确保所有微服务的稳定性,并可识别甚至预见任何生产问题。自助托管还可能面临其他挑战,这些挑战取决于微服务的具体实施和构建。</p>
<p>接下来,我们将举例说明如何部署基于 Python 的微服务。假设您构建了一个基于 Python 的微服务,它可能是一个 Flask、Django 或任何其他 Python 网络应用程序。微服务无法直接开始提供所需的服务。它需要一个网络服务器接口或入口点来调用你的微服务。对于基于 Python 的微服务,主要有两种可用的接口:网络服务器网关接口(或 WSGI)是网络服务器与网络应用程序或微服务之间通信的主要 Python 标准。顾名思义,它只支持同步服务调用。</p>
<p>目前有许多流行的 WSGI 网络服务器,如 Gunicorn 和 uWSGI。你需要决定哪一种最适合你的需求。</p>
<p>异步服务器网关接口(ASGI)是另一种网络服务器接口。它与 WSGI 的主要区别在于它支持异步代码,因此你的微服务可以被异步调用。一些流行的 ASGI 网络服务器包括 Daphne 和 Hypercorn。</p>
<p>无论是 WSGI 还是 ASGI 网络服务器,都需要在特定类型的基础设施上运行。根据您的服务要求或协议,基础设施可以是一台笔记本电脑、一台专用工作站,也可以是一个拥有数百个计算和数据节点的复杂集群。</p>
<h2 id="41-ibm-云代码引擎"><a class="markdownIt-Anchor" href="#41-ibm-云代码引擎"></a> 4.1. IBM 云代码引擎</h2>
<p>可以看出,要在生产中部署微服务,您需要做出许多权衡和努力。值得庆幸的是,您现在可以在云平台上部署微服务,只需付出极少的努力。现在,让我们来介绍一下 IBM 云代码引擎(IBM Cloud Code Engine),这是一个可轻松部署微服务的全面而强大的平台。IBM 云代码引擎,简称「代码引擎」,抽象了构建、部署和管理工作负载的操作负担,使开发人员可以专注于代码开发。IBM Cloud Code Engine 的主要目标是减轻开发人员的部署负担。在它的帮助下,开发人员可以将代码推送到云平台,而无需考虑基础架构。</p>
<p>IBM 云代码引擎可以看作是一个完全托管的无服务器平台。它结合了平台即服务(PaaS)、容器即服务(CaaS)和无服务器部署模型所需的所有功能。IBM 云代码引擎可运行您的工作负载,包括微服务、Web 应用程序、事件驱动函数或批处理作业。如果您已构建了微服务,您可以将其作为 IBM Cloud Code Engine 应用程序运行,以直观的用户体验为传入的 HTTPS 请求提供服务,而无需轻松管理您的基础架构。</p>
<p>IBM 云代码引擎有三种主要用例或部署模式:第一种用例涉及将构建的应用程序部署到代码引擎。这里的应用程序可以是微服务、Web 应用程序或控制台应用程序。第二种用例是直接推送源代码。代码引擎可以从 GitHub repo 等远程仓库或本地工作区的源代码构建应用程序。构建好的应用程序可以自动部署,无需担心构建过程,既方便又省时。第三种用例是创建和运行批处理作业,如数据处理或分析任务。例如,如果您的一个微服务需要分析结果,您可以部署一个批处理作业,在同一平台上执行分析任务。因此,您部署的所有微服务和作业都可以无缝地协同工作,因为它们都托管在同一个基础架构中。</p>
<h2 id="411-术语"><a class="markdownIt-Anchor" href="#411-术语"></a> 4.1.1. 术语</h2>
<p>代码引擎中的「项目」一词代表一个包含并管理其资源和实体的组。代码引擎中的分组包含的实体有构建、应用程序、作业、传输层服务(或 TLS)HTTP 连接证书等。</p>
<p>项目的一个重要功能是为其实体提供命名空间。命名空间提供了一种在单个组内隔离实体组和资源的机制。在命名空间内,实体名称必须是唯一的,但在不同命名空间之间则不需要。另一个重要功能是管理资源和提供访问控制。</p>
<p>在代码引擎中,您的代码将运行应用程序。与常规部署的网络应用程序一样,运行中的应用程序可以提供 HTTP 请求或 REST API。除了传统的 HTTP 请求,代码引擎还支持使用 WebSockets 的应用程序。WebSocket 是一种基于传输控制协议(或 TCP)的通信协议。它主要用于客户端和服务器之间的长期运行和基于会话的通信,如聊天应用程序。代码引擎可通过创建或销毁应用程序运行实例来扩展应用程序。应用程序运行实例的数量会根据接收到的工作负载和您的配置设置自动增加或减少(至零)。</p>
<p>在代码引擎上下文中,构建或镜像构建(image build)是一种可用于从源代码创建容器镜像的机制。容器镜像包括容器运行所需的所有资产,如可执行源代码、依赖项、资源、容器引擎、系统库、配置设置等。</p>
<p>代码引擎支持从 Dockerfile 构建,Dockerfile 是一个文本文件,其中包含构建 Docker 容器镜像的所有命令。此外,它还可以使用云原生构建包(Cloud Native Buildpack)。构建包是另一种常用的容器镜像构建方式。它包含用于执行检查源代码、创建构建计划或执行构建计划以生成镜像等任务的可执行文件。从源代码构建容器镜像后,您可以将构建的容器镜像部署到代码引擎,并创建相应的应用程序。</p>
<p>作业是代码的一次性执行。与应用程序一样,作业也会运行可执行代码。根据工作量的不同,代码引擎会创建一个或多个作业实例。与主要处理 HTTP 请求或 WebSocket 会话的应用程序不同,作业旨在一次性运行并退出。在代码引擎中运行作业前,您可以指定作业每次运行时使用的工作负载配置。一些典型的作业包括批量查询和转换数据的数据处理作业、机器学习模型训练作业、根据预设计划生成报告的报告作业,以及创建和发送账单的计费作业。</p>
<h2 id="412-为微服务构建容器镜像"><a class="markdownIt-Anchor" href="#412-为微服务构建容器镜像"></a> 4.1.2. 为微服务构建容器镜像</h2>
<p>容器是一个独立的、包含所有内容的可执行软件单元。应用程序的源代码与其库、依赖项和运行时一起打包在容器中。容器构建完成后,可在任何设备上运行,如笔记本电脑、台式电脑或内部服务器。它还可以在云中运行。容器体积小、速度快、便于携带,因为与虚拟机不同,容器的每个实例都不需要客户操作系统,而是可以利用主机操作系统的功能和资源。微服务的关键要素之一是,就每个运行实例的计算资源而言,它通常规模较小且相互隔离。这使得容器与微服务架构中的这种小型轻量级服务完美匹配。</p>
<p>Docker 是一个软件平台,用于构建和运行作为容器的应用程序。此外,大量的互补工具、技术和开发方法仍在不断增长,并形成了庞大的 Docker 和容器化经济。Docker 提供了一种通过开源平台构建和运行容器的简单方法。Docker 是 IBM 云引擎中使用的主要容器技术。容器镜像是云引擎应用程序的基础。</p>
<p>现在让我们讨论使用 Docker 文件从源代码构建容器的典型方法。假设您已经完成了应用程序开发,并在集成开发环境中使用源代码、依赖项和库对其进行了本地测试。现在,你想构建一个 docker 容器。第一步就是创建一个 Dockerfile。Dockerfile 就像一个蓝图,通过它可以构建一个镜像。它概述了构建所需容器的所有说明。容器镜像是在创建 Dockerfile 之后构建的。</p>
<blockquote>
<p>请注意,容器镜像和容器是两种不同的东西。镜像是一个不可更改的文件,其中包含应用程序运行所需的所有应用程序资产,如源代码、库和依赖项。镜像是只读的;如果更改镜像,就需要创建一个新的镜像。与面向对象编程一样,容器镜像就像一个类,它就像一个正在运行的容器的模板。因此,如果我们实例化一个容器镜像,就会得到一个名为容器的运行镜像。</p>
</blockquote>
<p>因此,基本上你只需编写一个合适的 Dockerfile,剩下的就交给 Docker 来处理吧。</p>
<p>接下来,让我们看看构建基于 Flask 的微服务的 Dockerfile 示例。</p>
<figure class="highlight dockerfile"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> python:<span class="number">3.8</span></span><br></pre></td></tr></tbody></table></figure>
<p>Dockerfile 的第一行以 <code>FROM</code> 指令开始,用于指定后续指令将构建的基础镜像。该基础镜像通常来自公共资源库,比如操作系统,或者来自特定语言的基础镜像,比如 Python。你也可以选择添加一个更具体、更高级的基础镜像,以更好地服务于你的微服务,而不是一般的 Python docker 镜像。</p>
<figure class="highlight dockerfile"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Or import a 3rd party image such as tiangolo/uwsgi-nginx-flask:python3.8</span></span><br></pre></td></tr></tbody></table></figure>
<p>你可以添加更具体、更先进的基础镜像,以便更好地为你的微服务服务。例如,<code>uwsgi-nginx-flask</code> 可让你创建 Python 语言的 Flask 微服务,并在单个容器中与 uWSGl 和 Nginx 一起运行。</p>
<figure class="highlight dockerfile"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">COPY</span><span class="language-bash"> . /app</span></span><br></pre></td></tr></tbody></table></figure>
<p>下一条 <code>COPY</code> 指令会将所有文件复制到镜像中的 <code>/app</code> 文件夹。</p>
<figure class="highlight dockerfile"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ENV</span> LISTEN_PORT <span class="number">8080</span></span><br></pre></td></tr></tbody></table></figure>
<p>你可以使用 <code>ENV</code> 指令设置环境变量。在这里,你将 <code>LISTEN PORT</code> 设置为 <code>8080</code>,这意味着用该镜像制作的容器将监听 <code>8080</code> 端口。</p>
<figure class="highlight dockerfile"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">EXPOSE</span> <span class="number">8080</span></span><br></pre></td></tr></tbody></table></figure>
<p>Dockerfile 中的 <code>EXPOSE</code> 关键字向 Docker 表明,容器将监听 <code>8080</code> 端口的流量。</p>
<figure class="highlight dockerfile"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">RUN</span><span class="language-bash"> pip install -r requirements.txt</span></span><br></pre></td></tr></tbody></table></figure>
<p><code>RUN</code> 指令执行命令。</p>
<figure class="highlight dockerfile"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">"python"</span>, <span class="string">"-m"</span>, <span class="string">"flask"</span>, <span class="string">"run"</span>]</span></span><br></pre></td></tr></tbody></table></figure>
<p>最后一个是 <code>CMD</code>。该指令的主要目的是提供执行容器的默认值。该指令通常定义了应在容器中运行的可执行文件。在示例中,你运行的是 flask 微服务。但请注意,一个 Dockerfile 中只能有一条 CMD 指令。</p>
<p>容器镜像构建完成后,可以将其推送到容器存储库,以便更好地管理。这个容器存储库称为容器注册中心。常见的注册表有 Docker Hub 和 IBM Cloud Container Registry。注册表中的容器镜像可以通过唯一的镜像名称轻松提取和使用。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">hostname:repository:tag</span><br><span class="line">e.g., icr.io/codeengine/hello</span><br></pre></td></tr></tbody></table></figure>
<p>镜像名称由主机名、存储库和标记组成。主机名标识了该镜像应推送到的注册中心。例如,<code>icr.io</code> 表示 IBM 容器注册中心。存储库是一组相关的容器镜像。通常,这些镜像是同一应用程序或服务的不同版本,因此应用程序或服务的名称就是一个好的资源库名称。最后,标签提供了有关镜像的特定版本或变体的信息。</p>
<h4 id="413-部署和运行应用程序"><a class="markdownIt-Anchor" href="#413-部署和运行应用程序"></a> 4.1.3. 部署和运行应用程序</h4>
<p>您可以使用两种模式之一将基于容器映像的应用程序部署到 IBM Cloud Code:</p>
<ol>
<li>首先,您可以构建容器映像并将其推送到私有或公共容器注册中心。云代码引擎可以使用唯一的映像名称提取映像,然后通过授予注册表访问权限自动进行部署。</li>
<li>或者,如果您不想手动构建映像,您可以指定一个 dockerfile 或一个 buildpack 以及您的代码,然后向云代码引擎发出指令,让它从源代码中构建您的应用程序,然后进行部署。因此,这两种模式本质上是一样的。</li>
</ol>
<p>唯一的区别是谁负责构建和推送容器映像。IBM 云控制台是一个精心设计的网络门户,供最终用户方便地管理他们的 IBM 云服务,包括代码引擎。在 IBM 云控制台中,只需点击几下,您就可以轻松创建和部署应用程序。如果您熟悉命令行界面(CLI),并希望执行更精确的部署,可以选择 IBM Cloud CLI 来创建和部署应用程序。</p>
<p>您的应用程序部署到代码引擎后,引擎将提供应用程序的端点 URL,该 URL 可指向应用程序的主页面或微服务的入口端点。如上所述,IBM 云控制台提供了一个精心设计的用户界面,可帮助您轻松创建和部署应用程序。</p>
<p>现在让我们看看创建应用程序的主要步骤:首先,您需要指定应用程序的名称。然后,您可以选择从容器映像或源代码进行部署。这里您可以选择默认的容器映像选项。接下来,您需要提供图像的引用,还可选择提供注册表访问权限,以便代码引擎从注册表中提取图像。但请注意,这些只是主要步骤。您还可以根据应用程序的需要配置其他高级设置。创建应用后,云引擎将自动部署应用 如果应用运行无误,应在几分钟内准备就绪。应用程序就绪后,您可以单击「Test application」,使用 URL 进行测试。</p>
<p>如果您喜欢命令行界面,也可以使用 IBM Cloud CLI 部署应用程序。创建和部署应用程序的主要命令是 <code>ibmcloud ce app create</code>:它有三个主要参数:应用程序名称 <code>--name</code>、容器注册表中的映像引用 <code>--image</code> 以及注册表访问(如果容器注册表不是公开的)<code>--registry-secret</code>。</p>
<p>我们来看一个示例:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ibmcloud ce app create --name helloworldapp --image us.icr.io/mynamespace/hello_repo --registry-secret myregistry</span><br></pre></td></tr></tbody></table></figure>
<p>在这里,您想通过 <code>us.icr.io</code> 注册服务器中的映像创建一个名为 <code>helloworldapp</code> 的应用程序。为了让代码引擎提取映像,您创建并提供了名为 <code>myregistry</code> 的注册表访问权限。</p>
<p>创建并部署应用程序后,可以使用 <code>ibmcloud ce app get</code> 命令运行并测试应用程序,该命令有两个主要参数:应用程序名称 <code>--name</code> 和应用程序的输出格式(如 URL)<code>--output</code>。下面,我们以获取先前部署的应用程序 <code>helloworldapp</code> 的输出为例进行说明。</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ibmcloud ce app get --name helloworldapp --output url</span><br></pre></td></tr></tbody></table></figure>
<p>使用应用程序名称调用 <code>ibmcloud ce app get</code>,并指定输出格式为 URL。然后,您就可以从命令输出中看到应用程序的公开访问 URL。</p>
<h4 id="414-更新已部署的应用程序"><a class="markdownIt-Anchor" href="#414-更新已部署的应用程序"></a> 4.1.4. 更新已部署的应用程序</h4>
<p>假设您为宠物业务开发了基于微服务的 PetShop 网络应用程序,并将每个微服务作为应用程序部署在代码引擎项目中。您的 PetShop 应用程序的网络流量很大,因此您决定将部分数据从 SQL DB 迁移到非 SQL DB。因此,您需要为新数据库服务开发和构建另一个容器映像,并根据新映像更新您的宠物数据库服务。此外,您还需要为应用程序添加一些新的环境变量,并需要更多计算资源来处理对 NoSQL DB 的请求。</p>
<p>代码引擎会管理已部署应用程序的每个修订版,因此您无需删除应用程序并部署新的应用程序。您只需更新现有应用程序,代码引擎就会为您创建和管理新版本。更新代码引擎应用程序有以下四种常见情况:</p>
<ol>
<li>更新环境变量,如数据库位置或秘钥</li>
<li>更新应用程序可见性,如将应用程序的 URL 从公开更改为私有或项目专用</li>
<li>更新应用程序的图片参考或 GitHub repo</li>
<li>更新应用程序的运行时资源</li>
</ol>
<p>与应用程序部署一样,您也可以使用 IBM 云代码引擎控制台或 CLI 更新应用程序。如果是添加环境变量之类的简单更新,使用精心设计的控制台 UI 会更方便快捷。对于更复杂、更精确的应用程序更新,代码引擎 CLI 可能更合适。执行所有应用程序更新相关操作的主要命令是 <code>ibmcloud ce application update</code>。</p>
<p>在应用程序控制台页面,点击环境变量表,可以找到所有环境变量。要添加或更新环境变量,您可以单击「Add environment variable(添加环境变量)」按钮。另外,如果您喜欢使用命令行界面(IBM Cloud CLI),则添加或更新环境变量的主要命令是 <code>ibmcloud ce app update</code>,其中包含两个主要参数:应用程序名称 <code>--name</code> 以及环境变量的名称和值 <code>--env</code>。</p>
<p>让我们来看一个快速示例:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ibmcloud ce app update --name pet_db_service --<span class="built_in">env</span> DB_HOST=localhost</span><br></pre></td></tr></tbody></table></figure>
<p>首先,在应用程序中添加一个名为 <code>DB_HOST</code> 的环境变量,其值为 <code>localhost</code>。<code>update</code> 命令会返回已更新到最新版本的信息。</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ibmcloud ce app get --name pet_db_service</span><br></pre></td></tr></tbody></table></figure>
<p>要仔细检查环境变量是否添加成功,可以使用 <code>app get</code> 命令显示 <code>pet_db_service</code> 应用程序的详细信息。现在可以看到环境变量 <code>DB_HOST</code> 已按预期添加。</p>
<p>部署应用程序时,会分配两种类型的 URL:内部 URL 和外部 URL。内部 URL 用于与应用程序内的其他应用程序通信。外部 URL 可以是公共 URL、外部 URL 或仅限 IBM 专用网络的 URL。选择 URL 类型可定义应用程序的可见性。</p>
<p>在应用程序控制台页面,单击系统域映射选项卡更新应用程序的可见性。选择「No external system domain mapping(无外部系统域映射)」时,此应用程序无法从公共互联网访问,只能从本项目内的组件(群集 - 本地)进行网络访问。选择此选项时,将显示群集本地 URL。选择私有时,可通过 IBM 云虚拟私有端点访问此应用程序。选择公开时,您可以查看应用程序的公开和群集本地 URL。</p>
<p>同样,您也可以通过 CLI 更新应用程序的可见性:主要命令仍然是 <code>ibmcloud ce app update</code>,有两个主要参数:应用程序名称 <code>--name</code> 和应用程序的可见性 <code>--visibility</code>。让我们来看一个快速示例:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ibmcloud ce app update --name pet_db_service --visibility private</span><br></pre></td></tr></tbody></table></figure>
<p>首先将 <code>pet_db_service</code> 应用程序的可见性更新为 <code>private</code>。然后,如果您获取应用程序的详细信息,就会看到两个 URL。外部 URL 现在包含一个私有子域,这意味着现在只能通过 IBM 虚拟专用网络访问 <code>pet_db_service</code>。</p>
<p>现在,您可以通过控制台用户界面更新图像引用,方法如下:</p>
<ol>
<li>在应用程序控制台页面上,单击「Code」选项卡。</li>
<li>然后为应用程序指定新的映像引用。</li>
</ol>
<p>在 CLI 上,<code>ibmcloud ce app update</code> 命令有三个参数用于更新映像引用:应用程序名称 <code>--name</code>、映像引用的名称和值 <code>--image</code>,以及用于访问非公开容器注册表的注册表秘密 <code>--secret</code>。</p>
<p>例子:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ibmcloud ce app update --name pet_db_service --image us.icr.io/petshop/no_sql_pet_db_service --registry-secret myregistry</span><br></pre></td></tr></tbody></table></figure>
<p>如果应用程序实例的响应时间过长或 CPU 和内存使用率过高,可以增加应用程序的运行时资源。为此,请进入应用程序页面并单击运行时选项卡。然后就可以根据需要更新实例的 CPU、内存和短暂存储。如果您想让应用程序扩大或缩小规模,也可以在同一用户界面上更新扩展和并发设置。</p>
<p>要通过 CLI 更新 CPU 或 GPU,需要使用相同的 <code>ibmcloud ce app update</code> 命令,其中包含三个主要参数:应用程序名称 <code>--name</code>、为实例设置的 CPU 数量 <code>--cpu</code> 和为实例设置的内存数量 <code>--memory</code>。</p>
<p>让我们看看示例:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ibmcloud ce app update --name pet_db_service --cpu 2 --memory 16GB</span><br></pre></td></tr></tbody></table></figure>
<p>您将 <code>pet_db_service</code> 运行时资源的每个实例增加到 <code>2</code> 个 CPU 和 <code>16GB</code> 内存。</p>
</body></html></div></article></div></main><footer><div class="paginator"><a class="prev" href="fa4d.html">上一篇</a><a class="next" href="407.html">下一篇</a></div><!-- Webmention 显示区域--><div class="webmention-section webmention-empty" data-page-url="posts/52000.html" data-full-url="https://cytrogen.icu/posts/52000.html" data-mode="static">
<h3 class="webmention-title">Webmentions (<span class="webmention-count">0</span>)</h3>
<div class="webmention-list"></div>
<span>暂无 Webmentions</span>
</div><div class="copyright"><p class="footer-links"><a href="../friends/index.html">友链</a><span class="footer-separator"> ·</span><a href="../links/index.html">邻邦</a><span class="footer-separator"> ·</span><a href="../contact/index.html">联络</a><span class="footer-separator"> ·</span><a href="../colophon/index.html">营造记</a><span class="footer-separator"> ·</span><a href="../atom.xml">RSS订阅</a></p><p>© 2025 - 2026 <a href="https://cytrogen.icu">Cytrogen</a>, powered by <a href="https://hexo.io/" target="_blank">Hexo</a> and <a href="https://github.com/cytrogen/hexo-theme-ares" target="_blank">hexo-theme-ares</a>.</p><p><a href="https://blogscn.fun" target="_blank" rel="noopener">BLOGS·CN</a></p></div></footer></div></div><a class="back-to-top" href="#top" aria-label="返回顶部"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path d="M3.293 9.707a1 1 0 010-1.414L9.586 2a2 2 0 012.828 0l6.293 6.293a1 1 0 01-1.414 1.414L11 3.414V17a1 1 0 11-2 0V3.414L2.707 9.707a1 1 0 01-1.414 0z"></path></svg></a><script>document.addEventListener('DOMContentLoaded', function() {
const codeBlocks = document.querySelectorAll('figure.highlight');
codeBlocks.forEach(block => {
let caption = block.querySelector('figcaption');
if (!caption) {
caption = document.createElement('figcaption');
block.insertBefore(caption, block.firstChild);
}
const info = document.createElement('div');
info.className = 'info';
const filename = caption.querySelector('span');
if (filename) {
filename.className = 'filename';
info.appendChild(filename);
}
const lang = block.className.split(' ')[1];
if (lang) {
const langSpan = document.createElement('span');
langSpan.className = 'lang-name';
langSpan.textContent = lang;
info.appendChild(langSpan);
}
const sourceLink = caption.querySelector('a');
if (sourceLink) {
sourceLink.className = 'source-link';
info.appendChild(sourceLink);
}
const actions = document.createElement('div');
actions.className = 'actions';
const codeHeight = block.scrollHeight;
const threshold = 300;
if (codeHeight > threshold) {
block.classList.add('folded');
const toggleBtn = document.createElement('button');
toggleBtn.textContent = '展开';
toggleBtn.addEventListener('click', () => {
block.classList.toggle('folded');
toggleBtn.textContent = block.classList.contains('folded') ? '展开' : '折叠';
});
actions.appendChild(toggleBtn);
}
const copyBtn = document.createElement('button');
copyBtn.textContent = '复制';
copyBtn.addEventListener('click', async () => {
const codeLines = block.querySelectorAll('.code .line');
const code = Array.from(codeLines)
.map(line => line.textContent)
.join('\n')
.replace(/\n\n/g, '\n');
try {
await navigator.clipboard.writeText(code);
copyBtn.textContent = '已复制';
copyBtn.classList.add('copied');
setTimeout(() => {
copyBtn.textContent = '复制';
copyBtn.classList.remove('copied');
}, 3000);
} catch (err) {
console.error('复制失败:', err);
copyBtn.textContent = '复制失败';
setTimeout(() => {
copyBtn.textContent = '复制';
}, 3000);
}
});
actions.appendChild(copyBtn);
caption.innerHTML = '';
caption.appendChild(info);
caption.appendChild(actions);
const markedLines = block.getAttribute('data-marked-lines');
if (markedLines) {
const lines = markedLines.split(',');
lines.forEach(range => {
if (range.includes('-')) {
const [start, end] = range.split('-').map(Number);
for (let i = start; i <= end; i++) {
const line = block.querySelector(`.line-${i}`);
if (line) line.classList.add('marked');
}
} else {
const line = block.querySelector(`.line-${range}`);
if (line) line.classList.add('marked');
}
});
}
});
});</script><script async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" id="MathJax-script"></script><script>(function() {
document.addEventListener('DOMContentLoaded', function() {
const themeToggle = document.querySelector('.theme-toggle');
if (!themeToggle) return;
const getCurrentTheme = () => {
return document.documentElement.getAttribute('data-theme') || 'light';
};
const updateUI = (theme) => {
const isDark = theme === 'dark';
themeToggle.setAttribute('aria-pressed', isDark.toString());
};
const setTheme = (theme) => {
document.documentElement.setAttribute('data-theme', theme);
document.documentElement.style.colorScheme = theme;
const pageWrapper = document.getElementById('page-wrapper');
if (pageWrapper) {
pageWrapper.setAttribute('data-theme', theme);
}
// Find and remove the temporary anti-flicker style tag if it exists.
// This ensures the main stylesheet takes full control after the initial load.
const antiFlickerStyle = document.getElementById('anti-flicker-style');
if (antiFlickerStyle) {
antiFlickerStyle.remove();
}
localStorage.setItem('theme', theme);
updateUI(theme);
};
const toggleTheme = () => {
const current = getCurrentTheme();
const newTheme = current === 'light' ? 'dark' : 'light';
setTheme(newTheme);
};
updateUI(getCurrentTheme());
themeToggle.addEventListener('click', toggleTheme);
if (window.matchMedia) {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', function(e) {
if (!localStorage.getItem('theme')) {
const theme = e.matches ? 'dark' : 'light';
setTheme(theme);
}
});
}
});
})();
</script><script src="../js/details-toggle.js" defer></script><script>(function() {
document.addEventListener('DOMContentLoaded', function() {
const backToTopBtn = document.querySelector('.back-to-top');
if (!backToTopBtn) return;
const toggleButtonVisibility = () => {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const shouldShow = scrollTop > 200;
if (shouldShow) {
backToTopBtn.classList.add('is-visible');
} else {
backToTopBtn.classList.remove('is-visible');
}
};
let ticking = false;
const handleScroll = () => {
if (!ticking) {
requestAnimationFrame(() => {
toggleButtonVisibility();
ticking = false;
});
ticking = true;
}
};
const scrollToTop = (event) => {
event.preventDefault();
window.scrollTo({
top: 0,
behavior: 'smooth'
});
};
window.addEventListener('scroll', handleScroll);
backToTopBtn.addEventListener('click', scrollToTop);
toggleButtonVisibility();
});
})();</script></body></html>