Skip to content

Latest commit

 

History

History
646 lines (451 loc) · 27 KB

File metadata and controls

646 lines (451 loc) · 27 KB

九、部署

当一切都说了算,并且准备好启动 web 服务或 API 时,在启动(从代码存储库到登台、到实时环境、停止、启动和更新策略)时,始终需要考虑一些因素。

部署编译后的应用程序总是比部署解释后的应用程序要复杂一些。幸运的是,Go 被设计成一种非常现代的编译语言。这意味着,我们一直在思考传统上困扰 C 或 C++中的服务器和服务的各种问题。

考虑到这一点,在本章中,我们将介绍一些可用的工具和策略,这些工具和策略可以在最短的停机时间内轻松地部署和更新我们的应用程序。

我们还将研究一些可以降低 web 服务内部负载的方法,例如作为部署策略的一部分,卸载图像存储和消息传递。

在本章结束时,您应该有一些特定的、通用的技巧,这些技巧可以最大限度地减少部署 API 和 web 服务时特有的一些心痛,特别是那些经常更新且停机时间最少的。

在本章中,我们将了解:

  • 应用程序设计与结构
  • 云的部署选项和策略
  • 信息系统的利用
  • 将映像托管与我们的 API 服务器分离,并将其与基于云的 CDN 连接

项目结构

尽管应用程序的设计和基础设施是机构和个人偏好的问题,但您规划其架构的方式可能会对您将应用程序部署到云端或生产中任何地方的方法产生非常实际的影响。

让我们快速回顾一下我们的应用程序的结构,记住,除非我们打算生成用于大规模跨平台使用的应用程序,否则我们不需要包对象:

bin/
  api # Our API binary

pkg/

src/
  github.com/
    nkozyra/
    api/
      /api/api.go
        /interface/interface.go
        /password/password.go
        /pseudoauth/pseudoauth.go
        /services/services.go
        /specification/specification.go
        /v1/v1.go
        /v2/v2.go

我们的应用程序的结构可能值得注意,这取决于我们如何将其部署到云中。

如果在部署之前有一个管道流程来处理构建、依赖关系管理和推送到活动服务器,那么这个结构是不相关的,因为可以避免源和 Go 包依赖关系而不是二进制文件。

但是,在将整个项目推送到每个应用程序服务器或 NFS/文件服务器的场景中,结构仍然至关重要。此外,如前所述,任何需要考虑跨平台分布的地方,都应该保留 Go 项目的整个结构。

即使这不是关键问题,如果构建机器(或多台机器)与目标机器不完全相同,这也会影响您构建二进制文件的过程,尽管这并不排除单独处理该二进制文件。

在一个示例 GitHub 存储库中,如果存在任何开放目录访问,它可能还需要混淆非二进制代码,类似于我们的interface.go应用程序。

使用过程控制保持 API 运行

处理版本控制和开发过程的方法超出了本书的范围,但构建和部署 Web 编译代码的一个相当常见的问题是安装和重新启动所述过程的过程。

管理更新的发生方式,同时最大限度地减少或消除停机时间,对于实时应用程序来说至关重要。

对于脚本语言和依赖外部 web 服务器通过 web 公开应用程序的语言,此过程很简单。脚本要么侦听更改并重新启动其内部 web 服务,要么在取消缓存且更改立即生效时对其进行解释。

对于长时间运行的二进制文件,这个过程变得更加复杂,这不仅是为了更新和部署应用程序,也是为了确保应用程序处于活动状态,并且在服务停止时不需要手动干预。

幸运的是,有两种简单的方法可以解决这个问题。首先是严格的过程管理,实现自动维护。第二种是特定于 Go 的工具。让我们先看看流程管理器,以及它们如何使用 Go web 服务。

使用主管

这里有一些针对*nix 服务器的大型解决方案,从极其简单的[T1]到更加复杂和细粒度的解决方案。它们的操作方式没有太大区别,因此我们将简要地研究如何使用一个管理器来管理我们的 web 服务:Supervisor。Supervisor 在大多数 Linux 发行版和 OS X 上都可以使用,因此它是本地测试的一个很好的例子。

其他一些值得注意的流程经理如下:

这些直接监视 init 守护进程监视进程管理器的基本前提是,如果没有根据一组配置的规则重新启动应用程序的实时尝试,则侦听正在运行的应用程序。

这里值得指出的是,这些系统没有真正的分布式方法,允许您集中管理多个服务器的进程,因此您通常必须使用负载平衡器和网络监控来获得此类反馈。

对于 Supervisor,在安装它之后,我们只需要一个简单的配置文件,通常可以通过导航到/etc/supervisor/conf.d/on*nix 发行版来定位它。下面是我们应用程序中此类文件的一个示例:

[program:socialnetwork]
command=/var/app/api
autostart=true
autorestart=true
stderr_logfile=/var/log/api.log
stdout_logfile=/var/log/api.log

虽然您可能会变得更复杂,例如,将多个应用程序分组在一起,以允许同步重新启动,这对于升级非常有用,这是保持我们长期运行的 API 正常运行所需的全部。

当需要更新时,例如从 GIT 到 staging 到 live,可以手动或通过以下命令以编程方式触发重新启动服务的进程:

supervisorctl restart program:socialnetwork

这不仅可以让您的应用程序保持运行,还可以强制执行一个完整的更新过程,该过程将激活您的代码并触发该过程的重新启动。这确保了尽可能少的停机时间。

为更优雅的服务器使用礼仪

虽然 init replacement 流程管理器自己工作得很好,但他们确实缺乏来自应用程序内部的一些控制。例如,简单地终止或重新启动 web 服务器几乎肯定会删除任何活动请求。

就其本身而言,Patterns 缺少一些进程的侦听控制,例如[T0]goagain[T1],这是一个库,它将 TCP 侦听器存储在 goroutines 中,并允许外部通过 SIGUSR1/SIGUSR2 进程间自定义信号控制重启。

但是,您可以将两者结合使用来创建这样一个流程。或者,您也可以直接编写内部侦听器,因为为了优雅地重新启动 web 服务器,goagain 可能会有点过头。

使用方式作为net/http周围的替代品/包装物的示例如下所示:

package main

import
(
  "github.com/braintree/manners"
  "net/http"
  "os"
  "os/signal"
)

var Server *GracefulServer

func SignalListener() {
  sC := make(chan os.signal, 1)
  signal.Notify(sC, syscall.SIGUSR1, syscall.SIGUSR2)
  s := <- sC
  Server.Shutdown <- true
}

在 goroutine 中运行并使用侦听 SIGUSR1 或 SIGUSR2 的通道阻塞后,我们将在收到此类信号时沿Server.Shutdown通道传递布尔值。

func Init(allowedDomains []string) {
  for _, domain := range allowedDomains {
    PermittedDomains = append(PermittedDomains, domain)
  }
  Routes = mux.NewRouter()
  Routes.HandleFunc("/interface", APIInterface).Methods("GET", "POST", "PUT", "UPDATE")
  Routes.HandleFunc("/api/user",UserLogin).Methods("POST","GET")
  ...
}

这只是我们在api.go中的Init()函数的一个重演。这将注册我们需要的 Gorilla 路由器。

func main() {

  go func() {
    SignalListener()
  }()
  Server = manners.NewServer()
  Server.ListenAndServe(HTTPport, Routes)
}

main()函数中,我们不只是启动http.ListenAndServe()函数,而是使用方式服务器。

这将防止在发送关机信号时断开连接。

与 Docker 一起部署

在过去的几年里,很少有服务器端产品像 Docker 一样在科技界掀起了巨大的浪潮。

Docker 创建了类似于易于部署、预配置的虚拟机的东西,与传统 VM 软件(如 VirtualBox、VMWare 等)相比,这些虚拟机对主机的影响要小得多。

通过使用 Linux 容器,它能够以比虚拟机少得多的总重量完成这一任务,这允许在保留对许多操作系统本身的访问权限的同时容纳用户空间。这就避免了每个虚拟机在实际应用中都需要成为操作系统和应用程序的完整映像。

为了在 Go 中使用,这通常是一个很好的选择,特别是如果我们为多个目标处理器创建构建,并且希望轻松地为其中任何一个或所有处理器部署 Docker 容器。更好的是,安装方面现在基本上是开箱即用的,因为 Docker 已经创建了语言堆栈,并将 Go 包含在其中。

虽然 Docker 的核心本质上只是一个典型 Linux 发行版映像的抽象,但使用它可以轻松地进行升级和快速资源调配,甚至可以提供额外的安全好处。最后一点取决于您的应用程序及其依赖关系。

Docker 通过使用非常简单的配置文件进行操作,并使用语言堆栈,您可以轻松创建一个可以启动的容器,该容器包含我们 API 所需的所有内容。

看看这个 Docker 文件示例,看看我们如何获得社交网络 web 服务所需的所有软件包:

FROM golang:1.3.1-onbuild

RUN go install github.com/go-sql-driver/mysql
RUN go install github.com/gorilla/mux
RUN go install github.com/gorilla/sessions
RUN go install github.com/nkozyra/api/password
RUN go install github.com/nkozyra/api/pseudoauth
RUN go install github.com/nkozyra/api/services
RUN go install github.com/nkozyra/api/specification
RUN go install github.com/nkozyra/api/api

EXPOSE 80 443

然后,可以使用简单的命令构建和运行该文件:

docker build -t api .
docker run --name api-running api -it --rm

您可以看到,至少这将大大加快跨多个实例(本例中为容器)的 Go 更新过程。

完整的 Docker 基本图像也可用于 Google 云平台。如果您使用或想要测试 Google Cloud,这些工具对于快速部署最新版本的 Go 非常有用。

在云环境中部署

对于那些还记得房间里满是物理单用途服务器、毁灭性的硬件故障以及极其缓慢的重建和备份时间的人来说,云托管的出现很可能是天赐良机。

如今,通常可以在短时间内从模板构建完整的体系结构,并且自动缩放和监视比以往任何时候都更容易。现在,市场上也有很多参与者,从谷歌、微软和亚马逊到专注于简单、节约和易用性的小型公司,如 Linode 和 Digital Ocean。

每个 web 服务都有自己的功能集和缺点,但大多数都有一个非常通用的工作流。为了探索 Golang 自身通过 API 提供的其他功能,我们将研究 Amazon Web 服务。

请注意,Go 中的其他云平台也有类似的工具。甚至微软的平台 Azure 也有一个为 Go 编写的客户端库。

亚马逊网络服务

与前面提到的许多云服务一样,部署到 AmazonWeb 服务或 AWS 与部署到任何标准物理服务器的基础设施大体上没有什么不同。

不过,与 AWS 有一些不同之处。首先是 it 提供的服务的广度。Amazon 并不严格地只处理静态虚拟服务器。它还处理一系列支持性服务,如 DNS、电子邮件和 SMS 服务(通过其 SNS 服务)、长期存储等。

尽管到目前为止已经说了这么多,但请注意,许多替代云服务提供了类似的功能,这些功能可能与以下示例提供的功能类似。

直接与 AWS 使用 Go to 接口

虽然一些云服务确实在其服务中提供了某种形式的 API,但没有一个服务像 Amazon Web 服务那样健壮。

AWS API 提供了对其环境中所有可能操作的直接访问,从添加实例到提供 IP 地址,再到添加 DNS 条目等等。

正如您所期望的,直接与此 API 接口可以打开很多可能性,因为它涉及到自动化应用程序的运行状况以及管理更新和错误修复。

为了直接与 AWS 接口,我们将使用goamz包启动我们的应用程序:

package main
import (
    "launchpad.net/goamz/aws"
    "launchpad.net/goamz/ec2"
)

提示

要获取运行本例的两个依赖项,请运行go get launchpad.net/goamz/aws命令和go get launchpad.net/goamz/ec2命令。

您可以在上找到关于此的其他文档 http://godoc.org/launchpad.net/goamzgoamz包还包括一个 Amazon S3 存储服务包,以及一些 Amazon SNS 服务和简单数据库服务的附加实验包。

基于图像启动新实例很简单。如果您习惯于手动或通过受控、自动或自动缩放的流程部署它,那么它可能太简单了。

    AWSAuth, err := aws.EnvAuth()
    if err != nil {
        fmt.Println(err.Error())
    }
    instance := ec2.New(AWSAuth, aws.USEast)
    instanceOptions := ec2.RunInstances({
        ImageId:      "ami-9eaa1cf6",
        InstanceType: "t2.micro",
    })

在本例中,ami-9eaa1cf6指的是 Ubuntu 服务器 14.04。

在下一节中,有一个与 Amazon API 的接口将非常重要,我们将把图像数据从关系数据库中移到 CDN 中。

处理二进制数据和 CDN

您可能还记得早在第 3 章路由和引导中,我们研究了如何将二进制数据,特别是图像数据以 BLOB 格式存储在数据库中。

当时,我们以一种非常简单的方式处理这个问题,将二进制图像数据简单地放入某种存储系统中。

AmazonS3 是 AWS 内容分发/交付网络方面的一部分,它将 bucket 视为数据集合,每个 bucket 都有自己的访问控制权限集。应该注意的是,AWS 还提供了一个真正的 CDN,称为 Cloudfront,但 S3 可以作为存储服务用于此目的。

让我们首先看看如何使用goamz包列出给定存储桶中最多 100 个项目:

提示

用您的凭据替换代码中的----------------。

package main

import
(
  "fmt"
    "launchpad.net/goamz/aws"
    "launchpad.net/goamz/s3"
)

func main() {
  Auth := aws.Auth { AccessKey: `-----------`, SecretKey: `-----------`, }
  AWSConnection := s3.New(Auth, aws.USEast)

  Bucket := AWSConnection.Bucket("social-images")

    bucketList, err := Bucket.List("", "", "", 100)
    fmt.Println(AWSConnection,Bucket,bucketList,err)  
    if err != nil {
        fmt.Println(err.Error())
    }
    for _, item := range bucketList.Contents {
        fmt.Println(item.Key)
    }
}

在我们的社交网络示例中,我们将此作为/api/user/:id:端点的一部分进行处理。

 func UsersUpdate(w http.ResponseWriter, r *http.Request) {
  Response := UpdateResponse{}
  params := mux.Vars(r)
  uid := params["id"]
  email := r.FormValue("email")
  img, _, err := r.FormFile("user_image")
  if err != nil {
    fmt.Println("Image error:")
    fmt.Println(err.Error())

返回上传,我们要么检查错误并继续尝试处理图像,要么继续。稍后我们将演示如何处理空值:

  }
  imageData, ierr := ioutil.ReadAll(img)
  if err != nil {
    fmt.Println("Error reading image:")
    fmt.Println(err.Error())

此时,我们已经尝试读取图像并提取数据,如果无法,我们将通过fmt.Printlnlog.Println打印响应并跳过其余步骤,但不要惊慌,因为我们可以通过其他方式继续编辑。

  } else {
    mimeType, _, mimerr := mime.ParseMediaType(string(imageData))
    if mimerr != nil {
      fmt.Println("Error detecting mime:")
      fmt.Println(mimerr.Error())
    } else {
      Auth := aws.Auth { AccessKey: `-----------`, SecretKey: `-----------`, }
      AWSConnection := s3.New(Auth, aws.USEast)
      Bucket := AWSConnection.Bucket("social-images")
      berr := Bucket.Put("FILENAME-HERE", imageData, "", "READ")
      if berr != nil {
        fmt.Println("Error saving to bucket:")
        fmt.Println(berr.Error())
      }
    }
  }

第 3 章路由和引导中,我们将上传的数据以我们的形式进行处理,将其转换为 Base64 编码字符串,并保存在我们的数据库中。

因为我们现在要直接保存图像数据,所以可以跳过最后一步。我们可以从请求中的FormFile函数中读取任何内容,并获取整个数据并将其发送到 S3 存储桶,如下所示:

    f, _, err := r.FormFile("image1")
    if err != nil {
      fmt.Println(err.Error())
    }
    fileData,_ := ioutil.ReadAll(f)

这对我们来说是有意义的,以确保我们为这张图片有一个唯一的标识符,以避免竞争条件。

检查是否存在文件上传

FormFile()函数实际上在引擎盖下调用ParseMultipartForm(),并返回文件、文件头的默认值,如果不存在,则返回标准错误。

使用 net/smtp 发送电子邮件

将我们的 API 和社交网络与辅助工具分离是一个好主意,可以在我们的系统中创建一种特殊性,减少这些系统之间的冲突,并为每个系统提供更合适的系统和维护规则。

为我们的电子邮件系统配备一个 socket 客户端将非常简单,该客户端允许系统直接侦听来自 API 的消息。事实上,这只需几行代码即可完成:

package main

import
(
  "encoding/json"
  "fmt"
  "net"
)

const
(
  port = ":9000"
)

type Message struct {
  Title string `json:"title"`
  Body string `json:"body"`
  To string `json:"recipient"`
  From string `json:"sender"`
}

func (m Message) Send() {

}
func main() {

  emailQueue,_ := net.Listen("tcp",port)
  for {
    conn, err := emailQueue.Accept()
    if err != nil {

    }
    var message []byte
    var NewEmail Message
    fmt.Fscan(conn,message)
    json.Unmarshal(message,NewEmail)
    NewEmail.Send()
  }

}

让我们看看实际的发送函数,它将把我们的消息从 API 中的注册过程传递到电子邮件服务器:

func (m Message) Send() {
  mailServer := "mail.example.com"
  mailServerQualified := mailServer + ":25"
  mailAuth := smtp.PlainAuth(
        "",
        "[email]",
        "[password]",
        mailServer,
      )
  recip := mail.Address("Nathan Kozyra","nkozyra@gmail.com")
  body := m.Body

  mailHeaders := make(map[string] string)
  mailHeaders["From"] = m.From
  mailHeaders["To"] = recip.toString()
  mailHeaders["Subject"] = m.Title
  mailHeaders["Content-Type"] = "text/plain; charset=\"utf-8\""
  mailHeaders["Content-Transfer-Encoding"] = "base64"
  fullEmailHeader := ""
  for k, v := range mailHeaders {
    fullEmailHeader += base64.StdEncoding.EncodeToString([]byte(body))
  }

  err := smtp.SendMail( mailServerQualified, mailAuth, m.From, m.To, []byte(fullEmailHeader))
  if err != nil {
    fmt.Println("could not send email")
    fmt.Println(err.Error())
  }
}

虽然这个系统可以很好地工作,因为我们可以监听 TCP 并接收消息,告诉我们发送什么以及发送到什么地址,但它本身并不是特别容错的。

我们可以通过使用消息队列系统轻松地解决这个问题,接下来我们将使用 RabbitMQ 来研究这个系统。

兔子带 Go

web 设计的一个方面与 API 特别相关,但几乎是任何 web 堆栈的一部分,这就是在服务器和其他系统之间传递消息的思想。

通常称为高级消息队列协议AMQP。它可以是 API/web 服务的一个基本部分,因为它允许以其他方式分离的服务在不使用其他 API 的情况下相互通信。

通过消息传递,我们在这里讨论的是可以或应该在不协调的系统之间共享的通用内容,当重要的事情发生时,这些内容会转移到相关的接收者那里。

再打个比方,它就像手机上的推送通知。当后台应用程序有消息要通知您时,它会生成警报并通过消息传递系统传递。

下图是该系统的基本表示。发送方(在本例中为 API)将向堆栈中添加消息,然后接收方(R)或电子邮件发送过程将检索这些消息:

RabbitMQ with Go

我们认为这些过程对 API 特别重要,因为通常,机构希望将 API 与基础设施的其余部分隔离开来。尽管这样做是为了防止 API 资源影响活动站点,或者允许两个不同的应用程序安全地对同一数据进行操作,但它也可以用于允许一个服务接受多个请求,同时允许第二个服务或系统在资源允许的情况下处理这些请求。

这也为用不同编程语言编写的应用程序提供了非常基本的数据粘合剂。

在我们的 web 服务中,我们可以使用 AMQP 解决方案通知我们的电子邮件系统在成功注册后生成欢迎电子邮件。这使我们的核心 API 不必担心这样做,相反,它可以专注于我们系统的核心。

我们可以通过多种方式将系统 a 和系统 B 之间的请求形式化,但演示简单电子邮件的最简单方式是设置标准消息和标题并以 JSON 格式传递:

type EmailMessage struct {
  Recipient string `json:"to"`
  Sender string `json:"from"`
  Title string `json:"title"`
  Body string `json:"body"`
  SendTime time.Time `json:"sendtime"`
  ContentType string `json:"content-type"`
}

通过这种方式而不是通过开放的 TCP 连接接收电子邮件,使我们能够保护消息的完整性。在前面的示例中,由于故障、崩溃或关机而丢失的任何消息都将永远丢失。

另一方面,消息队列的操作与邮箱类似,具有可配置的耐久性级别,允许我们指定消息应如何保存、消息何时过期以及哪些进程或用户应有权访问它们。

在本例中,我们使用作为包的一部分传递的文本消息,该包将由我们的邮件服务通过队列接收。在发生灾难性故障的情况下,邮件仍将在那里等待 SMTP 服务器处理。

另一个重要特性是它能够向消息发起人发送“回执”。在这种情况下,电子邮件系统将告诉 API 或 web 服务电子邮件进程已成功从队列中获取电子邮件。

在我们简单的 TCP 过程中复制这一点并非无关紧要。我们必须内置的故障保险和意外事件的数量将使它成为一个非常沉重的独立产品。

幸运的是,在 Go 中集成消息队列非常简单:

func Listen() {

  qConn, err := amqp.Dial("amqp://user:pass@domain:port/")
  if err != nil {
    log.Fatal(err)
  }

这只是我们与 RabbitMQ 服务器的连接。如果检测到任何连接错误,我们将停止该过程。

  qC,err := qConn.Channel()
  if err != nil {
    log.Fatal(err)
  }

  queue, err := qC.QueueDeclare("messages", false, false, false, false, nil)
  if err != nil {
    log.Fatal(err)
  }

这里的队列名称有点像 memcache 键或数据库名称一样任意。关键是确保发送和接收机制都搜索相同的队列名称:

  messages, err := qC.Consume( queue.Name, "", true, false, false, false, nil)
  waitChan := make(chan int)
  go func() {
    for m := range messages {
      var tmpM Message
      json.Unmarshal(d.Body,tmpM)
      log.Println(tmpM.Title,"message received")
      tmpM.Send()
    }

在这里的循环中,我们侦听消息,并在收到消息时调用Send()方法。在本例中,我们将传递 JSON,然后将其解组为一个Message结构,但此格式完全取决于您:

  }()

  <- waitChan

}

在我们的main()函数中,我们需要确保用调用 AMQP 侦听器的Listen()函数替换无限 TCP 侦听器:

func main() {

  Listen()

现在,我们能够从消息队列(消息队列)中获取消息(在电子邮件意义上),这意味着我们只需要在 web 服务中包含此功能。

在我们讨论的示例用法中,新注册的用户将收到一封电子邮件,提示激活帐户。这样做通常是为了防止注册时使用伪造的电子邮件地址。这无论如何都不是一个严密的安全机制,但它确保我们的应用程序可以与表面上可以访问真实电子邮件地址的人通信。

发送到队列也很容易。

鉴于我们在两个不同的应用程序之间共享凭据,将其形式化为一个单独的包是有意义的:

package emailQueue

import
(
  "fmt"
  "log"
  "github.com/streadway/amqp"
)

const
(
  QueueCredentials = "amqp://user:pass@host:port/"
  QueueName = "email"
)

func Listen() {

}

func Send(Recipient string, EmailSubject string, EmailBody string) {

}

这样,我们的 API 和侦听器都可以导入emailQueue包并共享这些凭证。在我们的api.go文件中,添加以下代码:

func UserCreate(w http.ResponseWriter, r *http.Request) {

  ...

  q, err := Database.Exec("INSERT INTO users set user_nickname=?, user_first=?, user_last=?, user_email=?, user_password=?, user_salt=?",NewUser.Name,NewUser.First, NewUser.Last,NewUser.Email,hash,salt)
  if err != nil {
    errorMessage, errorCode := dbErrorParse(err.Error())
    fmt.Println(errorMessage)
    error, httpCode, msg := ErrorMessages(errorCode)
    Response.Error = msg
        Response.ErrorCode = error
    http.Error(w, "Conflict", httpCode)
  } else {

    emailQueue.Send(NewUser.Email,"Welcome to the Social Network","Thanks for joining the Social Network!  Your personal data will help us become billionaires!")

  }

在我们的e-mail.go流程中的:

emailQueue.Listen()

AMQP 是具有 RabbitMQ 扩展的更通用的消息传递接口。您可以在上了解更多信息 https://github.com/streadway/amqp

更多关于抓兔洞的信息,请访问https://github.com/michaelklishin/rabbit-hole 或可使用go get github.com/michaelklishin/rabbit-hole命令下载。

总结

通过将 API 的逻辑与托管环境和辅助支持服务分离,我们可以减少由于非基本功能而导致的功能爬行和崩溃的机会。

在本章中,我们将图像托管从数据库移到云中,并存储原始图像数据和对 S3 的引用,S3 是一个经常用作 CDN 的服务。然后,我们使用 RabbitMQ 演示如何在部署中使用消息传递。

此时,您应该掌握如何卸载这些服务,并更好地了解部署、更新和正常重启的可用策略。

在我们的下一章中,我们将开始对社交网络的最终必要需求进行总结,并在此过程中探索一些提高 web 服务速度、可靠性和总体性能的方法。

我们还将推出一项二级服务,允许我们从 SPA 界面在社交网络中聊天,并将我们的图像扩展到 CDN 工作流,允许用户创建画廊。我们将研究通过接口和 API 直接最大化图像显示和获取的方法。