Go 部署我们的 REST 服务详解

在本章中,我们将了解如何使用一些工具(如 Nohup 和 Nginx)部署 Go 应用程序。为了让网站在互联网上可见,我们需要一个虚拟专用服务器VPS和部署工具。我们将首先看到如何运行 Go 可执行文件,并使用 Nohup 使其成为后台进程。接下来,我们将安装 Nginx 并将其配置为代理 Go 服务器。

在本章中,我们将介绍以下主题:

本章代码见https://github.com/narenaryan/gorestful/tree/master/chapter10 。将其复制到GOPATH并按照章节中给出的说明运行。

Nginx 是一种高性能的 web 服务器和负载均衡器,非常适合部署高流量网站。即使这个决定是自以为是的,Python 和节点开发人员通常也会使用它。

Nginx 还可以充当上游代理服务器,允许我们将 HTTP 请求重定向到运行在同一服务器上的多个应用程序服务器。Nginx 的主要竞争者是 Apache 的 httpd。Nginx 是一款优秀的静态文件服务器,可供 web 客户端使用。由于我们正在处理 API,我们将研究处理 HTTP 请求的各个方面

**在 Ubuntu 16.04 上,使用以下命令安装 Nginx:

sudo apt-get update
sudo apt-get install nginx

在 macOS X 上,您可以使用brew进行安装:

brew install nginx

https://brew.sh/ 是 macOS X 用户非常有用的软件打包系统。我的建议是使用它来安装软件。成功安装后,您可以通过在浏览器中打开机器 IP 进行检查。在 web 浏览器上打开http://localhost/。您将看到:

这意味着 Nginx 已成功安装。服务于80端口,服务于默认页面。在 macOS 上,默认的 Nginx 监听端口为8000

sudo vi /usr/local/etc/nginx/nginx.conf

在 Ubuntu(Linux)上,文件将位于以下路径:

sudo vi /etc/nginx/nginx.conf

打开文件,搜索服务器,将端口80修改为8000

server {
        listen 8080; # Change this to 80 
        server_name localhost;
        #charset koi8-r;
        #access_log logs/host.access.log main;
        location / {
            root html;
            index index.html index.htm;
        }

        ... 
}

现在一切都准备好了。服务器在80HTTP 端口上运行,这意味着客户端可以使用 URL(http://localhost/)和无端口(http://localhost:3000访问服务器。此基本服务器提供名为html的目录中的静态文件。root参数可以修改为我们放置 web 资产的任何目录。您可以使用以下命令检查 Nginx 的状态:

service nginx status

针对 Windows 操作系统的 Nginx 非常基本,并不真正用于生产级部署。开源开发人员通常更喜欢使用 Debian 或 Ubuntu 服务器来部署 Nginx 的 API 服务器。

代理服务器是保存其中原始服务器信息的服务器。它充当客户端请求的前端块。每当客户端发出 HTTP 请求时,它都可以直接转到应用程序服务器。但是,如果应用程序服务器是用编程语言编写的,那么您需要一个能够将应用程序响应转换为客户端可以理解的响应的转换器。公共网关接口CGI也做同样的事情。对于 Go,我们可以运行一个简单的 HTTP 服务器,它可以像普通服务器一样工作(不需要翻译)。那么,我们为什么要使用另一个名为 Nginx 的服务器呢?我们使用 Nginx 是因为它带来了很多东西。

拥有代理服务器(Nginx)的好处:

  • 它可以充当负载平衡器
  • 它可以坐在应用程序集群的前面,重定向 HTTP 请求
  • 它可以为具有良好性能的文件系统服务
  • 它可以很好地流媒体

如果同一台机器运行多个应用程序,那么我们可以将所有这些应用程序放在一个保护伞下。Nginx 还可以充当 API 网关,可以作为多个 API 端点的起点。我们将在下一章中看到一个专门的 API 网关,但 Nginx 也可以作为一个网关使用。请参阅下图:

如果您看到,插图客户端直接与 Nginx 通信,而不是与运行其他应用程序的端口通信。在图中,Go 在端口8000上运行,其他应用程序在不同的端口上运行。这意味着不同的服务器提供不同的 API 端点。如果客户端希望调用这些 API,它需要访问三个端口。相反,如果我们有 Nginx,它可以充当这三个服务器的代理服务器,并简化客户端请求-响应周期。

Nginx 也被称为上游服务器,因为它服务于来自其他服务器的请求。从图中可以看出,Python 应用程序可以顺利地从 Go 应用程序请求 API 端点。

要使用代理服务器,我们需要了解一些重要的 Nginx 路径。在 Nginx 中,我们可以同时托管多个站点(www.example1.comwww.exampl2.com等)。请看下表:

| | 路径 | 说明 | | 配置 | /etc/nginx/nginx.con | 这是基本 Nginx 配置文件。它可以用作默认文件。 | | 配置 | /etc/nginx/sites-available/ | 如果我们在 Nginx 中运行多个站点,那么我们可以有多个配置文件。 | | 配置 | /etc/nginx/sites-enabled/ | 这些是 Nginx 上当前激活的站点。 | | 日志 | /var/log/nginx/access.log | 此日志文件记录服务器活动,例如时间戳和 API 端点 | | 日志 | /var/log/nginx/error.log | 此日志文件记录所有与代理服务器相关的错误,如磁盘空间、文件系统权限等。 |

这些路径位于 Linux 操作系统中。对于 macOS X,使用/usr/local/nginx作为基本路径。

服务器块是实际的配置块,它告诉服务器要服务什么以及在哪个端口上侦听。我们可以在sites-available文件夹中定义多个服务器块。在 Ubuntu 上,位置将是:

/etc/nginx/sites-available

在 macOS X 上,位置将为:

/usr/local/etc/nginx/sites-avaiable

直到我们将sites-available复制到sites-enabled目录,配置才生效。因此,对于您创建的每个新配置,始终为sites-availablesites-enabled创建一个软链接。

现在,让我们在日志记录中创建一个裸应用程序服务器:

mkdir -p $GOPATH/src/github.com/narenaryan/basicServer
vi $GOPATH/src/github.com/narenaryan/basicServer/main.go

此文件是一个基本 Go 服务器,用于说明代理服务器的功能。然后,我们向 Nginx 添加一个配置,将代理端口8000(Go running port)添加到 HTTP 端口(80)。现在,让我们编写代码:

package main
import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "os"
    "time"
)
// Book holds data of a book
type Book struct {
    ID int
    ISBN string
    Author string
    PublishedYear string
}
func main() {
    // File open for reading, writing and appending
    f, err := os.OpenFile("app.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
    if err != nil {
        fmt.Printf("error opening file: %v", err)
    }
    defer f.Close()
    // This attache sprogram logs to file
    log.SetOutput(f)
    // Function handler for handling requests
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        log.Printf("%q", r.UserAgent())
        // Fill the book details
        book := Book{
            ID: 123,
            ISBN: "0-201-03801-3",
            Author: "Donald Knuth",
            PublishedYear: "1968",
        }
        // Convert struct to JSON using Marshal
        jsonData, _ := json.Marshal(book)
        w.Header().Set("Content-Type", "application/json")
        w.Write(jsonData)
    })
    s := &http.Server{
        Addr: ":8000",
        ReadTimeout: 10 * time.Second,
        WriteTimeout: 10 * time.Second,
        MaxHeaderBytes: 1 << 20,
    }
    log.Fatal(s.ListenAndServe())
}

这是一个简单的服务器,它以 API(此处为虚拟数据)的形式返回书籍详细信息。运行程序并在端口8000上运行。现在,打开一个壳并执行 CURL 命令:

CURL -X GET "http://localhost:8000"

它返回以下数据:

{
  "ID":123,
  "ISBN":"0-201-03801-3",
  "Author":"Donald Knuth",
  "PublishedYear":"1968"
}

但是客户端需要在这里请求到8000端口。我们如何使用 Nginx 代理此服务器?如前所述,我们需要编辑名为default的默认站点可用服务器块:

vi /etc/nginx/sites-available/default

编辑此文件,找到服务器块,然后向其中添加一行:

server {
        listen 80 default_server;
        listen [::]:80 default_server ipv6only=on;

        root /usr/share/nginx/html;
        index index.html index.htm;

        # Make site accessible from http://localhost/
        server_name localhost;

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                try_files $uri $uri/ =404;
                # Uncomment to enable naxsi on this location
                # include /etc/nginx/naxsi.rules
                proxy_pass http://127.0.0.1:8000;
        }
}

config文件的这一部分称为服务器块。这控制代理服务器的设置,listen表示nginx应该在哪里侦听。rootindex如果我们需要提供任何服务,请指向静态文件。server_name是您的域名。因为我们没有准备好域,所以它只是本地主机。location是这里的重点章节。在location中,我们可以定义我们的proxy_pass,它可以代理给定的URL:PORT。因为我们的 Go 应用程序运行在8000端口上,所以我们在这里提到了它。如果我们在不同的机器上运行,例如:****

**```go http://example.com:8000


我们可以将相同的东西作为参数赋给`proxy_pass`。为了使此配置生效,我们需要重新启动 Nginx 服务器。使用以下方法执行此操作:

```go
service nginx restart

现在,向http://localhost发出 CURL 请求,您将看到 Go 应用程序的输出:

CURL -X GET "http://localhost"
{
  "ID":123,
  "ISBN":"0-201-03801-3",
  "Author":"Donald Knuth",
  "PublishedYear":"1968"
}

location是定义统一资源标识符URI)的指令,可以代理给定的server:port组合。这意味着通过定义各种 URI,我们可以代理运行在同一服务器上的多个应用程序。它看起来像:

server {
    listen ...;
    ...
    location / {
        proxy_pass http://127.0.0.1:8000;
    }

    location /api {
        proxy_pass http://127.0.0.1:8001;
    }
    location /mail {
        proxy_pass http://127.0.0.1:8002;
    }
    ...
}

这里,三个应用程序在不同的端口上运行。这些文件添加到配置文件后,客户端可以通过以下方式访问:

http://localhost/
http://localhost/api/
http://localhost/mail/

在实际情况中,我们使用多个服务器而不是一个服务器来处理大量传入的 API 请求。但谁需要将传入的客户端请求转发到服务器实例?负载平衡是一个过程,其中中央服务器根据特定标准将负载分配给各个服务器。请参阅下图:

那些请求条件的方法称为负载平衡方法。让我们在一个简单的表格中看看每种方法的作用:

| 负载均衡方法 | 说明 | | 循环赛 | 请求在服务器上均匀分布,并考虑服务器权重。 | | 最小连接 | 请求被发送到当前为最少数量的客户端提供服务的服务器。 | | IP 哈希 | 这用于将来自给定客户端 IP 的请求发送到给定服务器。只有当该服务器不可用时,才会将其提供给另一台服务器 | | 最短时间 | 来自客户端的请求以最低的平均延迟(服务客户端的时间)和最少的活动连接数发送到计算机。 |

现在,我们了解了 Nginx 中如何为我们的 Go-API 服务器实现负载平衡。此过程的第一步是在 Nginx 配置文件的http部分创建一个upstream

http {
    upstream cluster {
        server site1.mysite.com weight=5;
        server site2.mysite.com weight=2;
        server backup.mysite.com backup;
    }
}

这里,服务器是运行相同代码的服务器的 IP 地址或域名。我们在这里定义一个名为backendupstream。它是我们可以在 location 指令中引用的服务器组。权重应与可用资源成比例。在前面的代码中,site1被赋予了更高的权重,因为它可能是一个更大的实例(内存和磁盘)。现在,在 location 指令中,我们可以使用proxy_pass命令指定服务器组:

server {
    location / {
        proxy_pass http://cluster;
    }
}

现在,正在运行的代理服务器将向集群中的机器传递所有 API 端点命中/的请求。默认的请求路由算法将是循环路由,这意味着所有服务器的轮次将一个接一个地重复。如果我们需要改变它,我们在上游定义中提到了这一点。请看以下代码段:

http {
    upstream cluster {
        least_conn;
        server site1.mysite.com weight=5;
        server site2.mysite.com;
        server backup.mysite.com backup;
    }
}

server {
    location / {
        proxy_pass http://cluster;
    }
}

前面的配置是创建一个三台机器的集群,并添加负载平衡方法作为最少连接least_conn是我们提到负载平衡方法的字符串。其他值可以是ip_hashleast_time。您可以通过在局域网局域网中安装一组机器来尝试。或者,我们可以让 Docker 安装多个虚拟容器作为不同的机器来测试负载平衡。

我们需要在/etc/nginx/nginx.conf文件中添加http块,而服务器块在/etc/nginx/sites-enabled/default中。最好将这两种设置分开。

**# 速率限制我们的 restapi

我们还可以通过速率限制来限制对 Nginx 代理服务器的访问速率。它提供了一个名为limit_conn_zone的指令 http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html#limit_conn_zone 。其格式如下:

limit_conn_zone client_type zone=zone_type:size;

client_type可分为两种类型:

  • IP 地址(限制来自给定 IP 地址的请求)
  • 服务器名称(限制来自服务器的请求)

zone_typeclient_type的对应关系也发生变化。其取值如下表所示:

| 客户类型 | 区域类型** | | $binary_remote_address | addr | | $server_name | servers |

Nginx 需要将一些内容保存到内存中,以便记住 IP 地址和服务器以进行速率限制。size是我们分配给 Nginx 执行记忆的存储。它采用 8m(8MB)或 16m(16MB)等值。现在,让我们看看在哪里添加这些设置。前一项应作为全局设置添加到nginx.conf文件中的http指令中:

http {
    limit_conn_zone $server_name zone=servers:10m;
}

这将为 Nginx 分配要使用的共享内存。现在,在 sites available/default 的 server 指令中,添加以下内容:

server {
   limit_conn servers 1000;
}

在前面使用limit_conn的配置中,给定服务器的连接总数不会超过 1K。如果我们试图从给定 IP 地址向客户端设置速率限制,则使用以下方法:

server {
  location /api {
      limit_conn addr 1;
  }
}

此设置阻止客户端(IP 地址)打开多个到服务器的连接(例如,在线铁路预订)。如果我们有客户端下载的文件,需要设置带宽限制,请使用limit_rate

server {
  location /download {
      limit_conn addr 10;
      limit_rate 50k;
  }
}

通过这种方式,我们可以控制客户端与 Nginx 下代理的服务的交互。如果我们直接使用 Go binary 来运行服务,我们将失去所有这些功能。

这是 Nginx 设置中最重要的部分。在本节中,我们将了解如何使用基本认证限制对服务器的访问。这对于我们的 RESTAPI 服务器非常重要,因为假设我们有相互通信的服务器 X、Y 和 Z。X 可以直接为客户端服务,但 X 通过调用内部 API 与 Y 和 Z 对话以获取一些信息。因为我们知道客户端不应该访问 Y 或 Z,所以我们可以使它只允许 X 访问资源。我们可以使用nginx访问模块允许或拒绝 IP 地址。看起来是这样的:

location /api {
    ...
    deny 192.168.1.2;
    allow 192.168.1.1/24;
    allow 127.0.0.1;
    deny all;
}

此配置告诉 Nginx 允许来自192.168.1.1/24范围内的客户端的请求,不包括192.168.1.2。下一行表示允许来自同一主机的请求,并阻止来自任何其他客户端的所有其他请求。完整的服务器块如下所示:

server {
    listen 80 default_server;
    root /usr/share/nginx/html;

    location /api {

        deny 192.168.1.2;
        allow 192.168.1.1/24;
        allow 127.0.0.1;
        deny all;
    }
}

有关这方面的更多信息,请参阅nginx_httpaccess 模块中的文档。我们还可以为我们的 Nginx 服务的静态文件添加密码安全访问。它主要不适用于 API,因为在 API 中,应用程序负责对用户进行认证。

Nginx 位于我们的 Go-API 服务器前面,这很好,它只是代理一个端口。但是,有时该 web 应用程序可能会由于操作系统重新启动或崩溃而停止。每当您的 web 服务器被杀死时,有人的工作就是自动恢复它的生命。督导员就是这样一个任务执行者。为了使我们的 API 服务器一直运行,我们需要监视它。Supervisord 是一种可以监视正在运行的进程(系统)并在进程终止时重新启动它们的工具。

我们可以使用 Python 的pip命令轻松安装 Supervisord。在 Ubuntu 16.04 上,只需使用apt-get命令:

sudo apt-get install -y supervisor

这将安装两个工具,supervisorsupervisorctlSupervisorctl用于控制主管并添加任务、重新启动任务等。让我们使用我们创建的示例basicServre.go程序来说明 Nginx 的功能。将二进制文件安装到$GOPATH/bin目录。这里,假设我的GOPATH/root/workspace

go install github.com/narenaryan/basicServer

始终将当前GOPATHbin文件夹添加到系统路径。无论何时安装项目二进制文件,它都可以作为整个系统环境中的正常可执行文件使用。您可以将这一行添加到~/.profile文件export PATH=$PATH:/usr/local/go/bin中。

现在,在以下位置创建一个配置文件:

/etc/supervisor/conf.d/goproject.conf

您可以添加任意数量的配置文件,supervisord将它们视为单独的进程来运行。将以下内容添加到前面的文件:

[supervisord]
logfile = /tmp/supervisord.log
[program:myserver]
command=/root/workspace/bin/basicServer
autostart=true
autorestart=true
redirect_stderr=true

默认情况下,我们在/etc/supervisor/有一个名为supervisord.conf的文件。请查看以下内容以供进一步参考:

  • [supervisord]部分给出了supervisord的日志文件的位置。
  • [program:myserver]是遍历给定目录并执行给定命令的任务块。

现在,我们可以要求我们的supervisorctl重新读取配置并重新启动任务(流程)。对此,只需说:

supervisorctl reread
supervisorctl update

然后,通过以下方式启动我们的supervisorctl

supervisorctl

您将看到如下内容:

因此,我们的图书服务受到Supervisor的监控。让我们试着终止这个过程,看看Supervisor有什么作用:

kill 6886

现在,Supervisor通过运行二进制文件,尽快启动一个新流程(不同于pid

这在生产场景中非常有用,在生产场景中,如果发生任何崩溃或操作系统重启,需要启动服务。这里有一个问题,我们如何启动/停止应用程序服务?使用supervisorctl中的startstop命令进行平滑操作:

supervisorctl> stop myserver
supervisorctl> start myserver

有关主管的更多详细信息,请访问http://supervisord.org/

本章专门介绍如何将 API 服务部署到生产环境中。一种方法是运行 Go 二进制文件,并通过IP: Port组合直接从客户端访问它。该 IP 将是虚拟专用服务器VPSIP 地址。相反,我们可以注册域名并指向 VPS。第二种也是更好的方法是将其隐藏在代理服务器后面。Nginx 就是这样一个代理服务器,使用它我们可以在一个保护伞下拥有多个应用服务器。

我们了解了如何安装 Nginx 并开始配置它。Nginx 提供了负载平衡和速率限制等功能,这在为客户端提供 api 时可能是至关重要的。负载平衡是在相似的服务器之间分配负载的过程。我们看到了可用的加载机制的类型。其中一些是循环、IP 哈希、最少连接等。然后,我们通过允许和拒绝几组 IP 地址向服务器添加了认证。

最后,我们需要一个进程监视器,它可以使崩溃的应用程序恢复生命。主管是这项工作的一个很好的工具。我们了解了如何安装 Supervisord,以及如何启动 supervisorctl,这是一个用于控制正在运行的服务器的命令行应用程序。

在下一章中,我们将看到如何使用 API 网关实现 API 生产等级。我们将深入讨论如何将 API 放在负责认证和速率限制的实体后面。****

教程来源于Github,感谢apachecn大佬的无私奉献,致敬!

技术教程推荐

深入浅出计算机组成原理 -〔徐文浩〕

从0打造音视频直播系统 -〔李超〕

消息队列高手课 -〔李玥〕

Flink核心技术与实战 -〔张利兵〕

Go 并发编程实战课 -〔晁岳攀(鸟窝)〕

搞定音频技术 -〔冯建元 〕

深入C语言和程序运行原理 -〔于航〕

Vue 3 企业级项目实战课 -〔杨文坚〕

现代C++20实战高手课 -〔卢誉声〕