在 CentOS 上安装 MongoDB

一、安装 MongoDB 社区版

使用 yum 包管理器安装 MongoDB。

  1. 为 MongoDB 创建 /etc/yum.repos.d/mongodb-org-4.4.repo 文件。
    # nano /etc/yum.repos.d/mongodb-org-4.4.repo
  2. 将以下内容粘贴到编辑器中:
    [mongodb-org-4.4]
    
    name=MongoDB Repository
    
    baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/4.4/x86_64/
    
    gpgcheck=1
    
    enabled=1
    
    gpgkey=https://www.mongodb.org/static/pgp/server-4.4.asc
  3. 保存并退出文件。
  4. 要安装 MongoDB 的最新稳定版本,请执行以下操作:
    # sudo yum install -y mongodb-org

如果您需要安装特定版本或阻止自动升级。

二、设置 MongoDB

  1. 启动MongoDB。
    # systemctl start mongod.service
    
    # systemctl enable mongod.service
  2. 连接到MongoDB。
    # mongo

 三、后续步骤

默认情况下,MongoDB 并不安全。使用我们的指南来配置 MongoDB 安全性和身份验证。

注意:可以安全地忽略与 相关的任何启动警告。要防止出现这些警告,请通过运行 来验证扇区是否“如警告中所述”。如果输出大于 512,请运行以下命令:Readaheadblockdev –getra /dev/vda1

blockdev --setra 512 /dev/vda1

systemctl restart mongod

现在将解决该警告。

将 MongoDB 与 Node.js 一起使用

一、介绍

MongoDB 是一个 NoSQL数据库,将数据存储在由字段值对组成的文档中。这些是使用 BSON(JSON 的二进制表示形式)存储的。MongoDB 文档类似于关系数据库表中的行,文档字段(键值对)类似于列。MongoDB 文档是集合的一部分,一个或多个集合数据库的一部分。MongoDB API支持常规 CRUD 操作(创建,读取,更新,删除)以及聚合,地理空间查询和文本搜索功能。MongoDB 还提供高可用性和复制。您可以在多个MongoDB 服务器(称为副本集)之间分发数据,以提供冗余和分片。

本文将通过一个完整的示例引导您了解如何在 Docker 中设置 MongoDB 副本集、部署客户端应用程序以及探索更改流。它将涵盖以下内容:

  • MongoDB 和更改流的概述。
  • 如何使用 MongoDB Go 驱动程序。
  • 应用程序设置和部署
  • 测试应用程序并了解如何使用恢复令牌。

MongoDB 为许多客户端驱动程序提供官方支持,包括 Java,Python,Node.js,PHP,Ruby,Swift,C,C++,C# 和 Go。在本文中,您将使用 MongoDB 的 Go 驱动程序来构建更改流处理器和 REST API 应用程序。

1.1、MongoDB 更改流

使用更改流,您可以实时访问MongoDB数据更新。应用程序不需要担心处理 oplog 和更改流的低级操作细节。可以订阅集合、数据库或整个部署上的所有数据更改。由于更改流使用聚合框架,因此应用程序还可以筛选特定更改或转换通知。可以将更改流用于副本集(这是本文将使用的内容)和分片设置。

更改流仅在更新操作期间返回字段的差异(这是默认行为)。但您可以将其配置为返回更新文档的最新提交版本。还可以提供一个或多个管道阶段的数组来控制更改流输出。例如 -、、 等。$addFields$match$project

1.2、恢复令牌

恢复令牌允许应用程序保存进度并防止潜在的数据丢失。如果更改流应用程序崩溃,它将无法在此期间检测到数据库更改。恢复令牌可用于从应用程序停止的位置(崩溃之前)继续处理并接收所有过去的事件。更改流可以帮助构建分离且可缩放的体系结构,并使实现提取-转换-加载 (ETL)、通知、同步服务等变得更加容易。

二、应用概述

本文演示了以下应用程序:

  1. 更改流侦听器:它使用监视 API 订阅 MongoDB 集合的更改事件流。创建或更新文档后,此应用程序将立即收到实时通知。它还利用恢复令牌在重新启动或崩溃后从特定时间点继续处理。
  2. REST API:它公开了一个HTTP端点,以便在MongoDB中创建文档。

三、准备工作

  1. 使用本地 Linux 工作站,或将 Vultr 云服务器部署为工作站。
  2. 确保 Docker 已安装在工作站上。您将需要它来构建和运行 Docker 映像。
  3. 在工作站上安装 curl,一个流行的命令行 HTTP 客户端。
  4. 在工作站上安装最新版本的 Go 编程语言(版本 1.18 或更高版本)。

四、准备 MongoDB 更改流应用程序

4.1、初始化项目

创建一个目录并切换到该目录:

mkdir mongo-change-streams

cd mongo-change-streams

创建一个新的 Go 模块:

go mod init mongo-change-streams

这将创建一个新文件go.mod

创建一个新文件:main.go

touch main.go

4.2、导入库

要导入所需的 Go 模块,请将以下内容添加到文件中:main.go

package main



import (

  "context"

  "fmt"

  "log"

  "os"

  "os/signal"

  "syscall"

  "time"



  "go.mongodb.org/mongo-driver/bson"

  "go.mongodb.org/mongo-driver/mongo"

  "go.mongodb.org/mongo-driver/mongo/options"

)

除了 Go 标准库包之外,您还将从 MongoDB Go 驱动程序导入以下包:

  • go.mongodb.org/mongo-driver/mongo– 提供核心的MongoDB功能。
  • go.mongodb.org/mongo-driver/bson– 包 bson 是一个用于读取、写入和操作 BSON 的库。
  • go.mongodb.org/mongo-driver/mongo/options– 包选项定义了MongoDB Go驱动程序的可选配置。

4.3、添加函数init

将以下代码添加到文件中:main.go

var mongoConnectString string

var mongoDatabase string

var mongoCollection string

const msgFormat = "export RESUME_TOKEN=%s"



func init() {

  mongoConnectString = os.Getenv("MONGODB_URI")

  if mongoConnectString == "" {

    log.Fatal("missing environment variable", "MONGODB_URI")

  }



  mongoDatabase = os.Getenv("MONGODB_DATABASE")

  if mongoDatabase == "" {

    log.Fatal("missing environment variable", "MONGODB_DATABASE")

  }



  mongoCollection = os.Getenv("MONGODB_COLLECTION")

  if mongoCollection == "" {

    log.Fatal("missing environment variable", "MONGODB_COLLECTION")

  }

}

该函数分别从 、 和环境变量中检索 MongoDB 连接字符串、集合名称和数据库名称。initMONGODB_URIMONGODB_COLLECTIONMONGODB_DATABASE

4.4、添加函数main

将函数添加到文件:mainmain.go

func main() {

  client, err := mongo.NewClient(options.Client().ApplyURI(mongoConnectString))

  if err != nil {

    log.Fatal("failed to create mongo client", err)

  }



  fmt.Println("created client object")



  ctx, cancel := context.WithCancel(context.Background())



  err = client.Connect(ctx)

  if err != nil {

    log.Fatal("failed to connect to mongo", err)

  }



  fmt.Println("connected to mongodb")



  coll := client.Database(mongoDatabase).Collection(mongoCollection)



  defer func() {

    err = client.Disconnect(context.Background())

    if err != nil {

      fmt.Println("failed to close mongo connection")

    }

  }()



  match := bson.D{{"$match", bson.D{{"operationType", bson.D{{"$in", bson.A{"insert", "update", "replace" }}}}}}}

  project := bson.D{{"$project", bson.M{"_id": 1, "fullDocument": 1, "ns": 1, "documentKey": 1}}}

  pipeline := mongo.Pipeline{match, project}



  opts := options.ChangeStream().SetFullDocument(options.UpdateLookup)



  tokenFromEnv := os.Getenv("RESUME_TOKEN")



  if tokenFromEnv != "" {

    fmt.Println("resume token in enviroment variable", tokenFromEnv)



    t := bson.M{"_data": tokenFromEnv}

    opts.SetResumeAfter(t)



    fmt.Println("set resume token to watch client")

  }



  cs, err := coll.Watch(ctx, pipeline, opts)

  if err != nil {

    log.Fatal("failed to start change stream watch: ", err)

  }



  fmt.Println("watch established")



  defer func() {

    fmt.Println("resume token ", cs.ResumeToken().String())

    fmt.Println("use resume token in the next run with following command -", fmt.Sprintf(msgFormat, cs.ResumeToken().Lookup("_data").StringValue()))



    close, cancel := context.WithTimeout(context.Background(), 5*time.Second)

    defer cancel()



    err := cs.Close(close)

    if err != nil {

      fmt.Println("failed to close change stream")

    }



    fmt.Println("closed change stream")

  }()



  go func() {

    fmt.Println("started change stream...")



    for cs.Next(ctx) {

      re := cs.Current.Index(1)

      fmt.Println("change stream event" + re.Value().String())

    }

  }()



  exit := make(chan os.Signal, 1)

  signal.Notify(exit, syscall.SIGINT, syscall.SIGTERM)



  fmt.Println("waiting for program exit signal")



  <-exit

  fmt.Println("program exit initiated")

  cancel()

}
  • 用于创建新对象。mongo.NewClient*mongo.Client
  • 使用Connect启动与MongoDB的连接。
  • 调用数据库以获取 ,然后调用集合以获取 的句柄。mongo.Databasemongo.Collection
  • 延迟函数用于在程序结束时断开和关闭对象。*mongo.Client
  • A 是使用匹配项目阶段创建的。mongo.Pipeline
  • 如果使用环境变量提供恢复令牌,则用于配置更改流监视客户端。RESUME_TOKEN
  • 获取使用手表。作为程序退出过程的一部分,另一个 defer 函数用于提供恢复令牌信息并关闭更改流。mongo.ChangeStream
  • 更改流侦听器作为 goroutine 启动。它使用 Next 侦听更改流事件,用于获取文档,并将事件记录到控制台。Current.Index
  • 最后,设置一个 Go 通道,以便在程序中断时收到通知并干净地退出。

五、准备 MongoDB REST API 应用程序

创建一个新文件:api.go

touch api.go

5.1、导入库

要导入所需的 Go 模块,请将以下内容添加到文件中:api.go

package main



import (

  "context"

  "fmt"

  "log"

  "net/http"

  "os"

  "strconv"

  "time"



  "go.mongodb.org/mongo-driver/mongo"

  "go.mongodb.org/mongo-driver/mongo/options"

)

除了 Go 标准库包之外,我们还从 MongoDB Go 驱动程序导入以下包:

  • go.mongodb.org/mongo-driver/mongo– 提供核心的MongoDB功能。
  • go.mongodb.org/mongo-driver/mongo/options– 包选项定义了MongoDB Go驱动程序的可选配置。

5.2、添加函数init

将以下代码添加到文件中:api.go

var coll *mongo.Collection

var mongoConnectString string

var mongoDatabase string

var mongoCollection string



func init() {

  mongoConnectString = os.Getenv("MONGODB_URI")

  if mongoConnectString == "" {

    log.Fatal("missing environment variable", "MONGODB_URI")

  }



  mongoDatabase = os.Getenv("MONGODB_DATABASE")

  if mongoDatabase == "" {

    log.Fatal("missing environment variable", "MONGODB_DATABASE")

  }



  mongoCollection = os.Getenv("MONGODB_COLLECTION")

  if mongoCollection == "" {

    log.Fatal("missing environment variable", "MONGODB_COLLECTION")

  }



  client, err := mongo.NewClient(options.Client().ApplyURI(mongoConnectString))

  if err != nil {

    log.Fatal("failed to create mongo client", err)

  }



  fmt.Println("created mongo client object")



  err = client.Connect(context.Background())

  if err != nil {

    log.Fatal("failed to connect to mongo", err)

  }



  fmt.Println("connected to mongo")



  coll = client.Database(mongoDatabase).Collection(mongoCollection)

}

该函数分别从 、 和环境变量中检索 MongoDB 连接字符串、集合名称和数据库名称。它调用数据库方法来获取 ,然后调用集合以获取 .initMONGODB_URIMONGODB_COLLECTIONMONGODB_DATABASEmongo.Databasemongo.Collection

5.3、添加函数main

将函数添加到文件:mainapi.go

func main() {

  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {



    res, err := coll.InsertOne(context.Background(), map[string]string{"user": "user-" + strconv.Itoa(int(time.Now().Unix()))})

    if err != nil {

      log.Fatal("mongo insert failed", err)

    }



    fmt.Println("created record", res.InsertedID)

  })



  fmt.Println("started http server...")

  log.Fatal(http.ListenAndServe(":8080", nil))

}

main 函数注册一个 HTTP 处理程序,以便在调用时将记录插入到 MongoDB 集合中。HTTP 服务器在端口 8080 上启动。

六、准备 Docker 映像

6.1、添加 Docker 文件

创建一个名为 的文件:Dockerfile.change_stream_app

touch Dockerfile.change_stream_app

在 中输入以下内容:Dockerfile.change_stream_app

FROM golang:1.18-buster AS build



WORKDIR /app

COPY go.mod ./

COPY go.sum ./



RUN go mod download



COPY main.go ./

RUN go build -o /mongodb-app



FROM gcr.io/distroless/base-debian10

WORKDIR /

COPY --from=build /mongodb-app /mongodb-app

EXPOSE 8080

USER nonroot:nonroot

ENTRYPOINT ["/mongodb-app"]
  • 这是一个多阶段,用作第一阶段的基础映像。Dockerfilegolang:1.18-buster
  • 复制应用程序文件,运行 ,然后生成应用程序二进制文件。go mod download
  • 在第二阶段,用作基础映像。gcr.io/distroless/base-debian10
  • 从第一阶段复制二进制文件,并将其配置为运行应用程序。ENTRYPOINT

创建一个名为 的文件:Dockerfile.rest_api

touch Dockerfile.rest_api

在 中输入以下内容:Dockerfile.rest_api

FROM golang:1.18-buster AS build



WORKDIR /app

COPY go.mod ./

COPY go.sum ./



RUN go mod download



COPY api.go ./

RUN go build -o /mongodb-api



FROM gcr.io/distroless/base-debian10

WORKDIR /

COPY --from=build /mongodb-api /mongodb-api

EXPOSE 8080

USER nonroot:nonroot

ENTRYPOINT ["/mongodb-api"]
  • 这是一个多阶段,用作第一阶段的基础映像。Dockerfilegolang:1.18-buster
  • 复制应用程序文件,运行 ,然后生成应用程序二进制文件。go mod download
  • 在第二阶段,用作基础映像。gcr.io/distroless/base-debian10
  • 从第一阶段复制二进制文件,并将其配置为运行应用程序。ENTRYPOINT

6.2、构建 Docker 镜像

拉动模块:

go mod tidy

为更改流应用程序构建 Docker 映像:

docker build -t mongo-change-streams -f Dockerfile.change_stream_app .

为 REST API 应用程序构建 Docker 镜像:

docker build -t mongo-rest-api -f Dockerfile.rest_api .

 七、在 Docker 中启动 MongoDB 集群

创建一个 Docker 网络:

docker network create mongodb-cluster

启动第一个节点 mongo1

docker run -d --rm -p 27017:27017 --name mongo1 --network mongodb-cluster mongo mongod --replSet myReplicaSet --bind_ip localhost,mongo1

启动第二个节点 mongo2

docker run -d --rm -p 27018:27017 --name mongo2 --network mongodb-cluster mongo mongod --replSet myReplicaSet --bind_ip localhost,mongo2

启动第三个节点 mongo3

docker run -d --rm -p 27019:27017 --name mongo3 --network mongodb-cluster mongo mongod --replSet myReplicaSet --bind_ip localhost,mongo3

配置副本集:

docker exec -it mongo1 mongosh --eval "rs.initiate({

_id: \"myReplicaSet\",

members: [

  {_id: 0, host: \"mongo1\"},

  {_id: 1, host: \"mongo2\"},

  {_id: 2, host: \"mongo3\"}

]

})"

您应该看到以下输出:

{ ok: 1 }

 八、启动两个应用程序

在终端中,启动更改流应用程序:

export MONGODB_URI=mongodb://mongo1:27017,mongo2:27017,mongo3:27017/?replicaSet=myReplicaSet



docker run --network mongodb-cluster -e MONGODB_URI=$MONGODB_URI -e MONGODB_DATABASE=test_db -e MONGODB_COLLECTION=test_collection -e RESUME_TOKEN=$RESUME_TOKEN mongo-change-streams

您将看到类似于以下内容的输出:

created client object

connected to mongodb

watch established

started change stream...

waiting for program exit signal

在另一个终端中,启动 REST API 应用程序:

export MONGODB_URI=mongodb://mongo1:27017,mongo2:27017,mongo3:27017/?replicaSet=myReplicaSet



docker run --network mongodb-cluster -p 8080:8080 -e MONGODB_URI=$MONGODB_URI -e MONGODB_DATABASE=test_db -e MONGODB_COLLECTION=test_collection mongo-rest-api

您将看到类似于以下内容的输出:

created mongo client object

connected to mongo

started http server...

 九、测试应用程序

在MongoDB中创建一些记录。为此,调用 REST API 应用程序(从另一个终端)公开的 HTTP 端点:

curl -i localhost:8080

重复上述过程两到三次。在所有情况下,您都应该得到HTTP / 1.1 200 OK响应。

导航到终端并检查 REST API 应用程序日志。您应该会看到已创建的记录。请注意,在您的情况下,对象 ID 可能有所不同:

created record ObjectID("639c092160078afff212209b")

created record ObjectID("639c097660078afff212209c")

created record ObjectID("639c097760078afff212209d")

导航到终端并检查更改流应用程序日志。您应该看到与上述相同的记录(由 REST API 应用程序创建)。这些是由更改流侦听器进程自动检测到的。请注意,在您的情况下,对象 ID 可能有所不同:

change stream event{"_id": {"$oid":"639c092160078afff212209b"},"user": "user-1671170337"}

change stream event{"_id": {"$oid":"639c097660078afff212209c"},"user": "user-1671170422"}

change stream event{"_id": {"$oid":"639c097760078afff212209d"},"user": "user-1671170423"}

9.1、使用更改流恢复令牌

首先,关闭更改流应用程序 – 在运行该应用程序的相应终端上按 +。CTRLC

您应该会看到与此类似的日志。请注意,令牌可能因您的情况而异。

resume token  {"_data": "82639C09A4000000012B0229296E04"}

use this token in the next run with following command - export RESUME_TOKEN=82639C09A4000000012B0229296E04

日志消息突出显示了如果要利用 Resume 令牌时应使用的命令。记下该命令。

通过调用 REST API 应用程序公开的 HTTP 端点向 MongoDB 添加一些记录:

curl -i localhost:8080

重复几次。

导航到终端并检查 REST API 应用程序日志。您应该会看到已创建的记录。请注意,在您的情况下,对象 ID 可能有所不同:

created record ObjectID("639c09ed60078afff212209e")

created record ObjectID("639c09ee60078afff212209f")

重新启动更改流应用程序。这一次,通过将 Resume 令牌作为环境变量传递来使用它。您可以使用上面的日志输出中的命令:

export RESUME_TOKEN=82639C09A4000000012B0229296E04



export MONGODB_URI=mongodb://mongo1:27017,mongo2:27017,mongo3:27017/?replicaSet=myReplicaSet



docker run --network mongodb-cluster -e MONGODB_URI=$MONGODB_URI -e MONGODB_DATABASE=test_db -e MONGODB_COLLECTION=test_collection -e RESUME_TOKEN=$RESUME_TOKEN mongo-change-streams

您应该会看到与此类似的日志。请注意,令牌和对象 ID 在您的情况下可能会有所不同。

token passed in as enviroment variable 82639C09A4000000012B0229296E04

set token to watch client option

watch established

started change stream...

change stream event{"_id": {"$oid":"639c09ed60078afff212209e"},"user": "user-1671170541"}

change stream event{"_id": {"$oid":"639c09ee60078afff212209f"},"user": "user-1671170542"}

验证您收到的记录是否与关闭更改流应用程序时添加的记录相同。

您可以通过调用 REST API 应用程序公开的 HTTP 端点来继续向 MongoDB 添加记录:

curl -i localhost:8080

正如预期的那样,更改流应用程序将实时检测和记录这些:

您应该看到类似于以下内容的日志:

change stream event{"_id": {"$oid":"639c0a5c60078afff21220a0"},"user": "user-1671170652"}

change stream event{"_id": {"$oid":"639c0a5d60078afff21220a1"},"user": "user-1671170653"}

 十、收尾

最后,要停止这两个应用程序,请在各自的终端中按 +。CTRLC

同时删除 MongoDB 集群实例和 Docker 网络:

docker rm -f mongo1 mongo2 mongo3

docker network rm mongodb-cluster

 十一、结论

在本文中,您将了解 MongoDB 和更改流的概述。您在 Docker 中设置了一个 MongoDB 副本集,部署了客户端应用程序,并通过端到端示例探索了更改流和恢复令牌。

如何在 Python 中使用 MongoDB 与 MongoEngine

一、介绍

MongoDB是一个免费的开源NoSQL数据库程序。它将数据存储在文档集合中,其中文档类似于传统关系数据库系统中的一行。它没有用于在集合中存储数据的固定架构;您可以将数据存储在键值对中,就像 Python 字典一样。灵活的数据模型和对水平扩展的支持等功能允许您在需求更改时进行更改或缩放数据库。

MongoEngine是一个对象文档映射器(ODM),它允许你的Python应用程序与MongoDB数据库进行交互。它提供了一个声明性 API,您可以在其中使用 Python 类和对象与数据库中的文档进行交互。这个抽象层使开发人员更容易与MongoDB数据库进行交互,有助于减少开发时间,并使数据库逻辑不易出错。

本文介绍 MongoEngine ODM 的安装和配置,定义文档架构、执行创建、读取、更新和删除 (CRUD) 操作以及从数据库中查询筛选数据的步骤。它还介绍了文档引用、迁移和元选项的基础知识。

二、准备工作

  • 在 Vultr 部署 Ubuntu 20.04 实例。
  • 创建具有 sudo 权限的非 root 用户。
  • 以非根用户身份登录您的实例。
  • 在实例上安装 MongoDB 数据库服务器。

三、设置环境

安装 MongoEngine 库。

$ pip install mongoengine

设置新的项目目录。

$ mkdir ~/mongoengine_demo

切换到项目目录。

$ cd ~/mongoengine_demo

 四、连接到数据库

您可以使用 MongoEngine 模块中的函数与 MongoDB 服务器建立连接。必须将主机名、端口、数据库等值作为参数传递。connect()

使用文本编辑器创建一个名为 的新 Python 文件。app.py

$ nano app.py

将以下内容添加到文件中,并使用 + 然后保存文件。CTRLXENTER

from mongoengine import *

client = connect('mongoengine_demo')

上面的代码使用默认主机和端口与MongoDB服务器建立连接。作为参数传递的字符串引用数据库的名称。如果数据库不存在,它会尝试使用传递给函数的名称创建新数据库。127.0.0.127017connect()

以下示例演示如何使用非默认值连接到 MongoDB 服务器。

connect('database_name', username='username', password='password', authentication_source='admin')

还可以使用该参数与多个数据库或数据库服务器建立连接。有关详细信息,请参阅 MongoEngine 文档中的多个数据库部分。alias

五、定义文档架构

存储在MongoDB数据库中的文档没有任何固定的模式。但是,定义文档架构可确保数据结构和验证,从而使数据库逻辑不易出错。

MongoEngine ODM允许您创建固定或动态文档架构。您可以从 MongoEngine 模块继承 or 类以创建新的文档类并使用字段对象定义每个字段,请参阅 MongoEngine 文档中的字段部分以查找所有可用的字段类型。DocumentDynamicDocument

编辑在上一节中创建的 Python 文件。

$ nano app.py

将以下内容添加到文件中,并使用 + 然后保存文件。CTRLXENTER

class User(Document):
    name = StringField(required=True)
    email = EmailField(required=True)
    age = IntField(required=True)

    def __repr__(self):
        return f'<User name="{self.name}">'

上面的代码创建了一个名为继承自 MongoEngine 模块中的类的文档类。它将 Python 类映射到数据库中的集合。默认情况下,它使用转换为蛇大小写的 Python 类名作为集合名称。此文档类使用固定架构,其中对象只能包含名称、电子邮件和期限。UserDocument

六、执行 CRUD 操作

MongoEngine 将每个文档类映射到数据库中的集合。创建文档类的新实例以在集合中创建新文档。本节介绍如何使用上一节中创建的文档类执行 CRUD 操作。

进入Python控制台。

$ python

导入所需的模块。

>>> from app import User

上面的命令从您在上一节中创建的 Python 文件中导入类。User

创建新条目。

>>> user1 = User(name='Example User', email=f'user@example.com', age=21)
>>> user1.save()

上面的命令从类创建一个新对象并调用该方法,该方法启动集合并在数据库中创建新文档。Usersave()

阅读第一个条目。

>>> User.objects.first()

上面的命令返回一个字典,其中包含存储在集合中的第一个文档的值。user

更新条目。

>>> user1.age = 22
>>> user1.save()

上面的命令更改对象中 age 属性的值并调用该方法,该方法将更新数据库中的文档。user1save()

验证更改。

>>> User.objects.first().age

删除条目。

>>> user1.delete()

验证更改。

>>> User.objects

退出 Python 控制台。

>>> exit()

 七、查询筛选后的数据

文档类具有允许访问存储在集合中的对象的属性。该属性是一个接受条件并返回包含筛选的文档对象的对象。本节介绍使用文档类查询筛选数据的基础知识。objectsobjectsQuerySetManagerQuerySet

进入Python控制台。

$ python

导入所需的模块。

>>> from app import User

填充数据库。

>>> user_objects = [User(name=f'Person {i}', email=f'person{i}@example.com', age=i+18) for i in range(10)]
>>> User.objects.insert(user_objects)

上述命令使用枚举值创建 10 个不同对象的列表,并将其插入到数据库中。User

使用单个条件查询文档。

>>> User.objects(age__gt=20)

上面的命令返回年龄超过 20 的文档列表。

输出

[<User name="Person 3">, <User name="Person 4">, <User name="Person 5">, <User name="Person 6">, <User name="Person 7">, <User name="Person 8">, <User name="Person 9">]

使用多个条件查询文档。

>>> User.objects(age__gt=20, age__lt=25)

上面的命令返回年龄大于 20 且小于 25 的文档列表。

输出

[<User name="Person 3">, <User name="Person 4">, <User name="Person 5">, <User name="Person 6">]

获取单个文档。

>>> User.objects(age__=19).first()

上面的命令返回一个生存期为 19 的文档对象。该方法返回单个文档对象,而不是包含文档对象的列表。first()

输出

<User name="Person 1">

退出 Python 控制台。

>>> exit()

请参阅 MongoEngine 文档中的查询数据库,以查找 中的所有可用过滤选项。QuerySetManager

 八、文档参考

MongoEngine ODM允许链接到文档架构中的其他文档。它使您能够在文档之间创建关系;与传统的关系数据库系统一样,它们将链接行的主键存储为外键。

您可以使用文档架构中的字段对象链接到其他文档。MongoEngine ODM 还支持 和 字段对象的组合以形成多对一关系。ReferenceFieldListFieldReferenceField

本节介绍如何使用文档类中的字段对象链接到不同的文档。ReferenceField

编辑 Python 文件。

$ nano app.py

将以下内容添加到 User 文档类上方的文件中,并使用 + 然后保存文件。CTRLXENTER

class Video(Document):
    title = StringField(required=True)

    def __repr__(self):
        return f'<Video title="{self.title}">'

class Course(Document):
    name = StringField(required=True)
    price = IntField(required=True)
    videos = ListField(ReferenceField(Video))

    def __repr__(self):
        return f'<Course name="{self.name}">'

上面的代码创建 Python 类,并从在数据库中创建两个新集合的类继承。文档类使用 and 字段对象在 videos 属性中存储视频对象引用的列表。VideoCourseDocumentCourseReferenceFieldListField

进入 Python 控制台。

$ python

导入所需的模块。

>>> from app import Course, Video

上面的命令从文件中导入 and 类。CourseVideoapp.py

视频集合中创建新条目。

>>> video1 = Video(title='Example Video 1').save()
>>> video2 = Video(title='Example Video 2').save()

上述命令创建文档类的两个新实例,该实例在数据库中启动并创建两个新文档。Video

课程集合中创建新条目。

>>> course1 = Course(name='Example Course 1', price=100, videos=[video1, video2]).save()

上面的命令创建文档类的新实例,该实例初始化并在数据库中创建新文档。它将文档与视频集合中的其他两个文档链接。Course

验证更改。

>>> Course.objects.first().videos
>>> Course.objects.first().videos[0].title
>>> Course.objects.first().videos[1].title

退出 Python 控制台。

>>> exit()

 九、文档迁移

NoSQL 数据库的灵活性使您可以轻松地进行文档架构更改。本节介绍如何在文档架构中进行结构更改。

编辑 Python 文件

$ nano app.py

将以下内容添加到文件中,并使用 + 然后保存文件。CTRLXENTER

class User(Document):
    name = StringField(required=True)
    email = EmailField(required=True)
    age = IntField(required=True)
    enrolled_courses = ListField(ReferenceField(Course))

    def __repr__(self):
        return f'<User name="{self.name}">'

上面的代码修改在用户集合中添加了一个名为 enrolled_courses 的新属性。和字段对象的组合允许用户文档引用多个课程文档。ListFieldReferenceField

进入Python控制台。

$ python

导入所需的模块。

>>> from app import User, Course

上面的命令从文件中导入 and 类。UserCourseapp.py

获取文档对象。

>>> user1 = User.objects.first()
>>> course1 = Course.objects.first()

上述命令从用户课程集合中获取第一个文档对象。

更新用户文档对象。

>>> user1.enrolled_courses = [course1]
>>> user1.save()

上述命令将 enrolled_courses 属性的值设置为包含文档引用对象的 Python 列表。Course

验证更改。

>>> User.objects.first().enrolled_courses
>>> User.objects.first().enrolled_courses[0].name

退出 Python 控制台。

>>> exit()

文档架构中的更改不会影响现有文档。如果要将更改应用于所有文档,则必须对集合使用该方法。有关更多信息,请参阅 MongoEngine 文档中的文档迁移部分。update_many()

十、文档元选项

文档类中的字典允许您向集合添加元数据,例如集合名称、文档索引列表、默认排序和继承选项、分片键等。本节介绍字典在文档类中的用法。metameta

编辑 Python 文件

$ nano app.py

将以下内容添加到 User 文档类下面的文件中,并使用 + then 保存文件。CTRLXENTER

class MetaExample(Document):
    age = IntField()

    meta = {
        'collection': 'example_collection',
        'indexes': ['age']
    }

    def __repr__(self):
        return f'<MetaExample age={self.age}>'

上面的代码创建了一个名为从该类继承的新 Python 类。它会创建一个名为 example_collection 的新集合,并在集合索引中添加年龄字段。MetaExampleDocument

索引是一种特殊的数据结构,它存储存储在数据库中的数据子集,使数据更易于以编程方式横向。如果没有索引,服务器必须执行完全集合扫描。如果查询存在适当的索引,则服务器可以限制必须扫描的文档数。

进入Python控制台。

$ python

导入所需的模块。

>>> from app import client, MetaExample

上面的命令从文件中导入对象和类。clientMetaExampleapp.py

初始化集合。

>>> MetaExample(age=999).save()

验证自定义集合名称。

>>> client.get_database('mongoengine_demo').list_collection_names()

验证集合索引。

>>> MetaExample.list_indexes()

退出 Python 控制台。

>>> exit()

请参阅 MongoEngine 文档中的文档集合部分,以查找可用元选项的完整列表。

十一、结论

您安装了 MongoEngine 库,与 MongoDB 数据库服务器建立了连接,定义了文档架构,执行了 CRUD 操作,并从数据库中查询了过滤的数据。您还探索了文档引用、迁移和元选项的基础知识。

如何创建 MongoDB 副本集

一.介绍

在任务关键型应用程序中部署 MongoDB 数据库时,可以配置副本集以实现高可用性。副本集提供冗余和高可用性,由于没有单点故障,因此可减少灾难期间的停机时间。MongoDB 副本集的最低建议配置是一个主节点和两个辅助节点,但副本集最多可以有 50 个成员。应用程序仅写入主节点,辅助节点复制数据。如果主节点发生故障,副本集将举行选举以选择新的主节点。应用程序可以从辅助数据库读取数据,但不能写入它们。

本指南介绍如何创建 MongoDB 副本集。它在 Ubuntu 20.04 上进行了测试,但其他 Linux 发行版的步骤类似。

二.准备工作

您需要将三台服务器连接到同一个 Vultr VPC。每个服务器都应具有:

  • 配置了 sudo 权限的非 root 用户
  • MongoDB安装并使用密码保护
  • 为清楚起见,本指南对服务器使用以下主机名和专用 IP 地址。您应该用您的值替换这些值。
    • 主:服务器-1 – 10.0.0.1
    • 辅助:服务器-2 – 10.0.0.2
    • 辅助:服务器-3 – 10.0.0.3

三 配置主机文件

MongoDB建议使用DNS主机名而不是副本成员的IP地址。由于 Vultr VPC 是没有 DNS 的私有网络,因此请将私有 IP 地址和主机名添加到主机文件中。在每台服务器上重复这些步骤。

  1. 以非 root 用户身份通过 SSH 连接到服务器。
  2. 编辑主机文件。
    $ sudo nano /etc/hosts
  3. 找到下面的行。
    127.0.0.1 localhost
  4. 输入 IP 地址和主机名,如该行下所示。
    127.0.0.1 localhost
    10.0.0.1 server-1
    10.0.0.2 server-2
    10.0.0.3 server-3
  5. 保存并关闭文件。

四. 设置复制密钥

副本集中的所有服务器共享一个 base64 密钥。请按照以下步骤安装密钥。

使用该命令在其中一台服务器上生成新密钥。openssl

$ openssl rand -base64 756

你应该得到这样的块。复制此 base64 密钥。您将在以下步骤中使用它。

Yga80PbkHKptRRoONFCPaPzOCFpySgpwNHMA3JS179wyGCOIOYg/FUnDyiIhGe5D
YVQF3o+SliscBiKftsPZ5WBojRREcefAUHOqK7pVBOjT+oYuH6ltMGiDtH26XjVB
... truncated ...
yxJm+UjpN0n8V1pH1LrMJT4FC4Bw3L7vqSnxVbLRnQIiO2Y0ECfyPgepCCNIyuaP
mMSUJ8mmlq4jdfoAKvCspeliSQ/cqaxKfqaTWjzhsLk8eHbU

在每台服务器上重复这些步骤。

  1. 创建新的auth_key文件。
    $ sudo nano /var/lib/mongodb/auth_key
  2. 粘贴您的 base64 密钥。
  3. 保存并关闭文件。
  4. 将权限设置为 400,使文件对文件所有者只读,对所有其他文件都被拒绝访问。
    $ sudo chmod 400 /var/lib/mongodb/auth_key
  5. 将所有者和组更改为 mongodb
    $ sudo chown mongodb:mongodb /var/lib/mongodb/auth_key

 五. 配置 MongoDB

在本部分中,你将配置共享密钥、网络接口和副本集名称。在每台服务器上重复这些小节。

5.1. 配置共享密钥

  1. 在编辑器中打开 mongod.conf 以执行以下步骤。
    $ sudo nano /etc/mongod.conf
  2. 找到安全部分。
    security:
      authorization: enabled
    #operationProfiling:
  3. 授权:行下,添加键文件值,如下所示。
    security:
      authorization: enabled
      keyFile: /var/lib/mongodb/auth_key
    #operationProfiling:

5.2. 配置网络接口

  1. 找到网络接口部分。
    # network interfaces
    net:
      port: 27017
      bindIp: 127.0.0.1
  2. 将环路接口 (127.0.0.1) 之后的相应服务器名称添加到每个服务器。例如:在服务器 1 上:
    # network interfaces
    net:
      port: 27017
      bindIp: 127.0.0.1, server-1

    在服务器 2 上:

    # network interfaces
    net:
      port: 27017
      bindIp: 127.0.0.1, server-2

    在服务器 3 上:

    # network interfaces
    net:
      port: 27017
      bindIp: 127.0.0.1, server-3

5.3. 配置副本集名称

  1. 找到复制部分。
    #replication:
  2. 从复制行中删除注释。在此下方,按所示添加。#replSetName: "rs0"
    replication:
      replSetName: "rs0"
  3. 在主节点上重新启动 MongoDB。
    $ sudo systemctl restart mongod
  4. 在辅助节点上重新启动 MongoDB。
    $ sudo systemctl restart mongod

 六. 引导副本集

在本部分中,你将节点添加到副本集并引导复制过程。

  1. 在主节点上,登录到MongoDB。
    $ mongosh -u your_admin_name -p --authenticationDatabase admin
  2. 输入您的管理员帐户的密码,然后按继续。ENTER
  3. 运行以下命令以添加副本集成员。
    test> rs.initiate(
          {
          _id: "rs0",
          members: [
              { _id: 0, host: "server-1" },
              { _id: 1, host: "server-2" },
              { _id: 2, host: "server-3" }
              ]
          })
  4. 当副本集启动时,应收到以下响应。请注意,提示符更改为 。rs0 [direct: secondary] test>
    { ok: 1 }
    rs0 [direct: secondary] test>
  5. 创建示例company_db数据库。
    rs0 [direct: secondary] test> use company_db
  6. 您应该会收到以下响应,提示更改为 。此成员现在是主节点。rs0 [direct: primary] company_db>
    switched to db company_db
    rs0 [direct: primary] company_db>
  7. company_db数据库的新员工集合中插入示例记录。
    rs0 [direct: primary] company_db> db.employees.insertOne({   
                                      "staff_id" : 1,
                                      "staff_name" : "JOHN DOE",
                                      "phone" : "11111111"  
                                      })
  8. 您应该得到下面的输出。
    {
      acknowledged: true,
      insertedId: ObjectId("621dcf1abdb5b0c5e59294d9")
    }
  9. 在每个辅助节点上,登录到MongoDB。
    $ mongosh -u your_admin_name -p --authenticationDatabase admin
  10. 输入您的管理员帐户的密码,然后按继续。ENTER
  11. 您应该会看到下面的提示,显示成员是辅助节点。
    rs0 [direct: secondary] test>
  12. 在每个辅助节点上,切换到company_db
    rs0 [direct: secondary] test> use company_db
  13. 应得到以下输出。
    switched to db company_db
  14. 在每个辅助节点上运行以下命令,以允许它们接受读取命令。
    rs0 [direct: secondary] company_db> db.getMongo().setReadPref('primaryPreferred')
  15. 列出员工集合中的文档。
    rs0 [direct: secondary] company_db> db.employees.find()
  16. 您应该在每个辅助节点上获得以下输出,该输出显示副本集将数据复制到每个节点。
    [
      {
        _id: ObjectId("621dcf1abdb5b0c5e59294d9"),
        staff_id: 1,
        staff_name: 'JOHN DOE',
        phone: '11111111'
      }
    ]
  17. 尝试在任何辅助节点上添加新的员工记录。
    rs0 [direct: secondary] company_db> db.employees.insertOne({   
                                      "staff_id" : 2,
                                      "staff_name" : "MARY ROE",
                                      "phone" : "22222222"  
                                      })
  18. 该命令应失败。辅助节点是只读的。
    MongoServerError: not primary
  19. 如果停止主服务器或主服务器脱机,副本集将选择其中一个辅助节点作为新的主节点。
    $ sudo systemctl stop mongod

在 Ubuntu 20.04 上使用 Golang 和 MongoDB 创建 CRUD 应用程序

1.介绍

MongoDB是最好的基于文档的开源数据库管理系统之一。由于其灵活的设计模式,您可以在各种业务系统中使用该应用程序,这是传统 SQL 数据库无法满足的。

凭借其强大的数据库模式,MongoDB平台适用于设置用于管理产品数据,创建内容管理系统(CMS)的系统,以及在大多数情况下存储和查询大数据的系统。

另一方面,Golang 是一种快速、可扩展且易于学习的现代编程语言,用于编码高级软件应用程序。由于MongoDB为Golang语言提供了全面的API,因此您可以将这两个应用程序一起使用,为金融,电子商务,研究等提供解决方案。

在本指南中,您将使用 MongoDB 设置一个数据库,并从自定义 Golang 应用程序与其通信,以在 Ubuntu 20.04 服务器上执行基本的创建、读取、更新和删除 (CRUD) 操作。

2.准备工作

要继续执行本指南,请确保您已获得以下各项:

  • Ubuntu 20.04 服务器。
  • 具有 sudo 权限的非 root 用户。
  • 使用用户帐户和密码配置的 MongoDB 数据库。
  • 一个戈朗包。

3. 创建 MongoDB 数据库

数据驱动应用程序必须能够存储、检索、更新和删除记录。只有在为软件设置数据库后,才能执行所有这些操作。通过 SSH 连接到您的服务器,然后按照以下步骤初始化 MongoDB 数据库。

  1. 登录到您的MongoDB数据库。替换为数据库的管理员帐户。mongo_db_admin
    $ mongosh -u mongo_db_admin -p --authenticationDatabase admin
  2. 出现提示时,输入MongoDB帐户的密码,然后按继续。接下来,运行下面的语句以创建数据库。ENTERshop_db
    test> use shop_db
  3. 确认已切换到新数据库。shop_db
    switched to db shop_db
  4. 接下来,使用 MongoDB 函数在新集合中插入三个文档。productsinsertMany()
    shop_db> db.products.insertMany([
    
                {"product_id" : 1,
    
                 "product_name" : "LEATHER BELT",
    
                 "retail_price" : 24.35   
    
                },
    
                {"product_id" : 2,
    
                 "product_name" : "WINTER JACKET",
    
                 "retail_price" : 99.95    
    
                },
    
                {"product_id" : 3,
    
                 "product_name" : "WOOLEN SWEATER",
    
                 "retail_price" : 43.20
    
                }  
    
               ]);
  5. 通过确认下面的输出来确保命令已成功。
    {
    
      acknowledged: true,
    
      insertedIds: {
    
        '0': ObjectId("62188b2358979df39bbcf178"),
    
        '1': ObjectId("62188b2358979df39bbcf179"),
    
        '2': ObjectId("62188b2358979df39bbcf17a")
    
      }
    
    }
  6. 接下来,使用以下语句查询集合以确保数据已到位。products
    shop_db> db.products.find()
  7. 您应该获得所有产品的列表以及相关的产品,如下所示。_ids
    [
    
      {
    
        _id: ObjectId("62188b2358979df39bbcf178"),
    
        product_id: 1,
    
        product_name: 'LEATHER BELT',
    
        retail_price: 24.35
    
      },
    
      {
    
        _id: ObjectId("62188b2358979df39bbcf179"),
    
        product_id: 2,
    
        product_name: 'WINTER JACKET',
    
        retail_price: 99.95
    
      },
    
      {
    
        _id: ObjectId("62188b2358979df39bbcf17a"),
    
        product_id: 3,
    
        product_name: 'WOOLEN SWEATER',
    
        retail_price: 43.2
    
      }
    
    ]
  8. 从 MongoDB 服务器注销。
    shop_db> quit
  9. 现在,您已经设置了数据库集合和示例文档。在接下来的步骤中,您将使用 Golang 语言创建一些脚本来操作您的 MongoDB 集合。shop_dbproducts

4. 创建文件main.go

该文件将保存应用程序的函数。这是执行应用程序时触发的主要方法。main.gomain()

  1. 在创建文件的源代码之前,请创建一个目录以将源代码与其余 Linux 文件分开。main.goproject
    $ mkdir project
  2. 然后,切换到新目录。project
    $ cd project
  3. 接下来,使用文本编辑器打开一个新文件以进行编辑。nanomain.go
    $ nano main.go
  4. 打开文件后,在文件中输入以下信息。将 替换为 MongoDB 用户帐户的正确值。main.gomongo_db_adminEXAMPLE_PASSWORD
    package main
    
    
    
    import (
    
        "context"
    
        "net/http"
    
        "encoding/json" 
    
        _"log" 
    
        "fmt" 
    
        "go.mongodb.org/mongo-driver/mongo" 
    
    "go.mongodb.org/mongo-driver/mongo/options"         
    
    )
    
    
    
    const (  
    
        dbUser = "mongo_db_admin"
    
        dbPass = "EXAMPLE_PASSWORD"
    
        dbName = "shop_db"
    
    )
    
    
    
    func main() {
    
         http.HandleFunc("/api/v1/products", requestHandler)
    
         http.ListenAndServe(":8080", nil)
    
    }
    
    
    
    func requestHandler(w http.ResponseWriter, req *http.Request) {
    
    
    
        w.Header().Set("Content-Type", "application/json")
    
    
    
        response := map[string]interface{}{}
    
    
    
        ctx := context.Background()
    
    
    
        client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://" + dbUser + ":" + dbPass + "@localhost:27017"))
    
    
    
        if err != nil { 
    
            fmt.Println(err.Error())
    
        } 
    
    
    
        collection := client.Database(dbName).Collection("products")  
    
    
    
        data := map[string]interface{}{} 
    
    
    
        err = json.NewDecoder(req.Body).Decode(&data)
    
    
    
        if err != nil { 
    
            fmt.Println(err.Error())
    
        }
    
    
    
        switch req.Method {
    
            case "POST":
    
                response, err = createRecord(collection, ctx, data)
    
            case "GET":
    
                response, err = getRecords(collection, ctx)
    
            case "PUT":
    
                response, err = updateRecord(collection, ctx, data)
    
            case "DELETE":
    
                response, err = deleteRecord(collection, ctx, data)
    
        }
    
    
    
        if err != nil { 
    
            response = map[string]interface{}{"error": err.Error(),}  
    
        } 
    
    
    
        enc := json.NewEncoder(w)
    
        enc.SetIndent("", "  ")
    
    
    
        if err := enc.Encode(response); err != nil {
    
            fmt.Println(err.Error())
    
        }   
    
    }
  5. 完成编辑后保存并关闭文件。
  6. 在上面的文件中,您将创建一个 Web 服务器,该服务器使用语句和 .8080http.HandleFunc("/api/v1/products", requestHandler)http.ListenAndServe(":8080", nil)
  7. 在该函数下,您将连接到之前创建的 MongoDB 实例。接下来,使用 Golang 语句通过传递集合引用将 HTTP 请求路由到相应的 CRUD 函数。最后,您将使用 JSON 函数以人类可读的格式格式化和输出数据。requestHandler()switchproducts
  8. 创建文件后,现在将在不同的文件上设置单独的函数,以处理应用程序的所有 CRUD 操作。main.go

5. 设置新文件create_record.go

要为 CRUD 操作设置的第一个文件是该文件。此文件包含用于将文档插入集合的功能。create_record.goproducts

  1. 运行以下命令以设置文件。create_record.go
    $ nano create_record.go
  2. 接下来,在文件中输入以下信息。
    package main
    
    
    
    import (
    
        "context"      
    
        "go.mongodb.org/mongo-driver/mongo"       
    
    )
    
    
    
    func createRecord(collection *mongo.Collection, ctx context.Context, data map[string]interface{})(map[string]interface{}, error){     
    
    
    
        req, err := collection.InsertOne(ctx, data)
    
    
    
        if err != nil { 
    
            return nil, err                    
    
        }
    
    
    
        insertedId := req.InsertedID
    
    
    
        res := map[string]interface{}{
    
                   "data" : map[string]interface{}{                            
    
                        "insertedId": insertedId,
    
                    },
    
               } 
    
    
    
        return res, nil
    
    }
  3. 保存并关闭文件。
  4. 上面文件中的主要功能是将BSON有效负载从请求HTTP客户端保存到MongoDB数据库。在上述文件下,如果语句执行没有任何错误,则返回新文档的 。collection.InsertOne(ctx, data)insertedId
  5. 接下来,您将设置一个函数来从 MongoDB 集合中删除文档。

6. 创建文件delete_record.go

与任何其他应用程序一样,如果不再需要记录,则必须提供从集合中删除记录的功能。products

  1. 使用 打开一个新文件 。delete_record.gonano
    $ nano delete_record.go
  2. 接下来,在文件中输入以下信息。delete_record.go
    package main
    
    
    
    import (
    
        "context"          
    
        "go.mongodb.org/mongo-driver/mongo"     
    
        "go.mongodb.org/mongo-driver/bson"
    
    )
    
    
    
    func deleteRecord(collection *mongo.Collection, ctx context.Context, data map[string]interface{})(map[string]interface{}, error){
    
    
    
        _, err := collection.DeleteOne(ctx, bson.M{"product_id": data["product_id"]})
    
    
    
        if err != nil { 
    
            return nil, err                    
    
        }     
    
    
    
        res := map[string]interface{}{
    
                   "data" : "Document deleted successfully.",   
    
               } 
    
    
    
        return res, nil
    
    }
  3. 保存并关闭文件。
  4. 在上面的文件中,您正在使用该函数从MongoDB数据库中删除文档。为了确保删除正确的文档,请使用语句检索要删除的项目。换句话说,在向应用程序提交请求时,您应该在 HTTP 有效负载中传递 a。collection.DeleteOne(...)product_idbson.M{"product_id": data["product_id"]}product_idDELETE
  5. 接下来,您将设置一个函数来更新文档。

7. 创建一个新文件update_record.go

您将使用该文件对文档进行更改。此文件下的 function() 依赖于包含要更新的字段的有效负载以及文档的唯一性。update_record.goupdateRecord()product_id

  1. 用于打开新文件。nanoupdate_record.go
    $ nano update_record.go
  2. 接下来,在文件中输入以下信息。update_record.go
    package main
    
    
    
    import (
    
        "context"
    
        "go.mongodb.org/mongo-driver/bson"     
    
        "go.mongodb.org/mongo-driver/mongo" 
    
    )
    
    
    
    func updateRecord(collection *mongo.Collection, ctx context.Context, data map[string]interface{})(map[string]interface{}, error){            
    
    
    
        filter := bson.M{"product_id": data["product_id"]}
    
        fields := bson.M{"$set": data}
    
    
    
        _, err := collection.UpdateOne(ctx, filter, fields)
    
    
    
        if err != nil { 
    
            return nil, err                    
    
        }
    
    
    
        res := map[string]interface{}{
    
                   "data" : "Document updated successfully.",   
    
               } 
    
    
    
        return res, nil
    
    }
  3. 在上面的文件中,您首先使用语句为要更新的文档提供一个参数。然后,您将使用语句提交新的文档值。此处的值来自请求客户端提交的 HTTP 有效负载。filterfilter := bson.M{"product_id": data["product_id"]}fields := bson.M{"$set": data}data
  4. 接下来,使用该函数向集合提交更新请求。在下一步中,您将创建一个函数,用于从 MongoDB 集合中检索记录。collection.UpdateOne(ctx, filter, fields)

8. 创建一个新文件get_records.go

MongoDB API for Golang具有非常直观的功能,用于以地图的形式从数据库中检索文档。您将使用这些函数查询数据库集合并将文档返回到之前创建的文件。main.go

  1. 用于创建新文件。nanoget_records.go
    $ nano get_records.go
  2. 然后,在文件中输入以下信息。get_records.go
    package main
    
    
    
    import (
    
        "context"          
    
        "go.mongodb.org/mongo-driver/bson"
    
        "go.mongodb.org/mongo-driver/mongo" 
    
    )
    
    
    
    func getRecords(collection *mongo.Collection, ctx context.Context)(map[string]interface{}, error){ 
    
    
    
        cur, err := collection.Find(ctx, bson.D{})
    
    
    
        if err != nil { 
    
            return nil, err
    
        }
    
    
    
        defer cur.Close(ctx) 
    
    
    
        var products []bson.M           
    
    
    
        for cur.Next(ctx) {
    
    
    
            var product bson.M
    
    
    
            if err = cur.Decode(&product); err != nil {
    
                return nil, err
    
            }
    
    
    
            products = append(products, product)
    
    
    
        }
    
    
    
        res := map[string]interface{}{}
    
    
    
        res = map[string]interface{}{
    
                  "data" : products,   
    
              }             
    
    
    
        return res, nil
    
    }
  3. 保存并关闭文件。
  4. 在上面的文件中,您将使用该函数返回已保存在集合中的文档的光标。然后,您将使用循环循环访问稍后附加到数组的文档。cur, err := collection.Find(ctx, bson.D{})productsfor cur.Next(ctx) {...}products []bson.M
  5. 最后,您将数据作为映射返回到调用函数。现在,你已为应用设置了所有 CRUD 函数。在下一步中,您将测试应用程序以确保一切按预期工作。[string]interface{}

9. 测试 Golang 应用程序

在此步骤中,你将测试应用程序,以确保它可以处理所有 CRUD 操作,而不会出现任何错误。

  1. 导入 Golang 应用程序的 MongoDB 驱动程序。
    $ go get go.mongodb.org/mongo-driver/mongo
  2. 接下来,执行以下命令以运行应用程序。以下命令允许应用程序启动 Web 服务器并侦听端口上的传入 HTTP 连接,并具有阻止功能。不要在此 SSH 终端窗口上运行任何其他命令。8080
    $ go run ./
  3. 接下来,在单独的终端窗口中建立与服务器的新 SSH 会话。
  4. 尝试通过运行以下命令创建新文档。curl
    $ curl -X POST localhost:8080/api/v1/products -H "Content-Type: application/json" -d '{"product_id": 4, "product_name": "WIRELESS KEYBOARD",  "retail_price": 45.30}'
  5. 您应该获得新记录,如下所示。insertedId
    {
    
      "data": {
    
        "insertedId": "621c9acf3f4e8882c3eeabef"
    
      }
    
    }
  6. 接下来,使用以下命令从集合中检索所有文档。products
    $ curl -X GET localhost:8080/api/v1/products
  7. 现在,您应该会看到四个文档的列表。前三个是首次初始化数据库时设置的文档,最后一个 record() 是您刚刚使用该命令插入的文档。WIRELESS KEYBOARDcurl
    {
    
      "data": [
    
        {
    
          "_id": "621c9aaf35ece941bcc5b80d",
    
          "product_id": 1,
    
          "product_name": "LEATHER BELT",
    
          "retail_price": 24.35
    
        },
    
        {
    
          "_id": "621c9aaf35ece941bcc5b80e",
    
          "product_id": 2,
    
          "product_name": "WINTER JACKET",
    
          "retail_price": 99.95
    
        },
    
        {
    
          "_id": "621c9aaf35ece941bcc5b80f",
    
          "product_id": 3,
    
          "product_name": "WOOLEN SWEATER",
    
          "retail_price": 43.2
    
        },
    
        {
    
          "_id": "621c9acf3f4e8882c3eeabef",
    
          "product_id": 4,
    
          "product_name": "WIRELESS KEYBOARD",
    
          "retail_price": 45.3
    
        }
    
      ]
    
    }
  8. 接下来,运行以下命令以使用 of 更新文档并将其 from 更改为 。product_id1product_nameLEATHER BELTMETAL BUCKLE LEATHER BELT
    $ curl -X PUT localhost:8080/api/v1/products -H "Content-Type: application/json" -d '{"product_id": 1, "product_name": "METAL BUCKLE LEATHER BELT",  "retail_price": 45.30}'
  9. 以下输出确认你已成功更新产品详细信息。
    {
    
      "data": "Document updated successfully."
    
    }
  10. 通过运行以下命令删除带有 of () 的文档。product_id4WIRELESS KEYBOARD
    $ curl -X DELETE localhost:8080/api/v1/products -H "Content-Type: application/json" -d '{"product_id": 4}'
  11. 您应该会收到以下确认消息。
    {
    
      "data": "Document deleted successfully."
    
    }
  12. 接下来,再次检索记录,以确保已在集合中执行 和 操作。UPDATEDELETEproducts
    $ curl -X GET localhost:8080/api/v1/products
  13. 从下面的输出中可以看到,您已经删除了带有 of 的文档,并且您还成功地将文档的值更新为 of 到 。product_id4product_id1METAL BUCKLE LEATHER BELT
    {
    
      "data": [
    
        {
    
          "_id": "621c9aaf35ece941bcc5b80d",
    
          "product_id": 1,
    
          "product_name": "METAL BUCKLE LEATHER BELT",
    
          "retail_price": 45.3
    
        },
    
        {
    
          "_id": "621c9aaf35ece941bcc5b80e",
    
          "product_id": 2,
    
          "product_name": "WINTER JACKET",
    
          "retail_price": 99.95
    
        },
    
        {
    
          "_id": "621c9aaf35ece941bcc5b80f",
    
          "product_id": 3,
    
          "product_name": "WOOLEN SWEATER",
    
          "retail_price": 43.2
    
        }
    
      ]        
    
    }
  14. 您的代码现在按预期工作,并且能够处理所有 CRUD 操作。

10.结论

在本指南中,您使用Golang编程语言在Ubuntu 20.04服务器上连接和操作MongoDB集合中的数据。使用 Golang 设计下一个数据驱动的 MongoDB 应用程序时,请使用本指南中的函数。

如何在 Debian 11 上安装 MongoDB

一、介绍

MongoDB是一个NoSQL数据库应用程序,用于将数据作为类似JSON的文档存储在服务器上。MongoDB拥有现代化的基础设施,具有各种服务,同时具有延展性和友好性,为开发人员及其代码提供了自由。

  • 跨平台
  • 可选架构
  • 各种安全和加密功能
  • 易于实施

本指南将解释如何在 Debian 11 实例上安装和配置 MongoDB 基础知识。

二、准备工作

  • 部署 Debian 11 实例。
  • 使用 sudo 用户登录到实例。

三、安装 MongoDB

安装用于安装 MongoDB 的其他软件包。

$ sudo apt install gnupg2 wget

添加 MongoDB 公共 GPG 密钥。

$ wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | sudo apt-key add -

将 MongoDB 的存储库添加到您的实例中。

$ echo "deb http://repo.mongodb.org/apt/debian buster/mongodb-org/5.0 main" | sudo tee /etc/apt/sources.list.d/mongodb-org-5.0.list

更新您的实例。

$ sudo apt update

安装 MongoDB。

$ sudo apt install mongodb-org

验证 MongoDB 是否已成功安装。

$ mongod --version



db version v5.0.6

    Build Info: {

        "version": "5.0.6",

        "gitVersion": "212a8dbb47f07427dae194a9c75baec1d81d9259",

        "openSSLVersion": "OpenSSL 1.1.1k  25 Mar 2021",

        "modules": [],

        "allocator": "tcmalloc",

        "environment": {

        "distmod": "debian10",

        "distarch": "x86_64",

        "target_arch": "x86_64"

    }

}

启动 MongoDB 服务。

$ sudo systemctl start mongod

启用 MongoDB 以在实例启动时启动。

$ sudo systemctl enable mongod

 四、创建新用户

在MongoDB的环境中创建用户对于在特定数据库中授权自己和访问MongoDB中的功能非常简单有用。

输入MongoDB的shell,Mongosh。您可以连接到MongoDB部署。但是,在本指南中,我们将连接到在本地主机上运行的MongoDB实例。

$ sudo mongosh

默认情况下,您将使用测试数据库。您可以通过输入Mongosh来显示您正在使用的数据库。让我们将数据库切换到管理数据库。db

$ use admin

要创建新用户,请在 Mongosh 中输入此代码块。您可以添加自己唯一的用户名和密码

db.createUser(

    {

        user: "Vultr",

        pwd:  "topSecret1",

        roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]

    }

)

成功创建新用户后,Mongosh 会提示以下输出。

{ ok: 1 }

显示数据库上的可用用户。

> show users

[

    {

        _id: 'admin.Vultr',

        userId: UUID("697f43de-6542-4a3c-b846-fbb3ad9f766b"),

        user: 'Vultr',

        db: 'admin',

        roles: [ { role: 'userAdminAnyDatabase', db: 'admin' } ],

        mechanisms: [ 'SCRAM-SHA-1', 'SCRAM-SHA-256' ]

    }

 ]

您可以通过按 + 两次、+ 或输入 Mongosh 来退出 Mongosh。CTRLCCTRLD.exit

五、启用数据库身份验证

MongoDB的标准是在使用数据库时禁用身份验证。为了保护您的数据库,在MongoDB中启用用户身份验证至关重要。

使用所需的文本编辑器输入MongoDB配置文件。

$ sudo nano /etc/mongod.conf

将此代码块粘贴到 MongoDB 配置文件中以启用身份验证。

security:

 authorization: enabled

保存并退出配置文件,然后重新启动MongoDB。

$ sudo systemctl restart mongod

输入Mongosh与您的用户名和密码。

$ sudo mongosh -u Vultr -p topSecret1

成功进行身份验证后,Mongosh 会提示以下输出。

Current Mongosh Log ID: 61fff39bf56ab6dd6b893038

Connecting to: mongodb://127.0.0.1:27017/directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.1.9

Using MongoDB:          5.0.6

Using Mongosh:          1.1.9



For mongosh info see: https://docs.mongodb.com/mongodb-shell/



> 

在 Linux 服务器上使用 Golang 和 MySQL 实现 Redis 事务和锁

一、介绍

在 Redis 中,事务是由必须以原子方式提交的多个命令组成的单个工作单元。也就是说,要么执行所有命令,要么不执行任何命令。Redis 使用 、、 和 函数来实现此功能。MULTIEXECDISCARDWATCH

要通过该工具创建事务,您只需先运行命令,然后运行其他后续命令。最后,应执行命令来处理事务或命令刷新排队的命令。redis-cliMULTIEXECDISCARD

该命令允许您在事务的生存期内实现锁定机制,如果您的密钥被另一个会话修改,该命令应无法避免将 Redis 数据库置于不一致状态。WATCHWATCHedEXEC

在本指南中,您将使用 Redis 事务函数在 Linux 服务器上使用 Golang 和 MySQL 创建抢票应用程序。

二、准备工作

若要继续本教程,请确保具有以下各项:

  • 一个 Linux 服务器。
  • 具有 sudo 权限的非 root 用户。
  • 一个MySQL服务器。
  • 一个 Redis 服务器。
  • 一个戈朗包。

三、 创建 MySQL 数据库、用户帐户和表

Redis 是一个内存数据库,虽然它可以将数据持久保存到磁盘,但它不是为此目的而设计的,可能无法以最佳方式执行。因此,在本指南中,您将使用 MySQL 数据库在 Redis 服务器生成票证信息后将其永久存储到 MySQL 表中。

通过 SSH 连接到您的服务器,然后按照以下步骤创建数据库。

    1. 以 身份登录到 MySQL 服务器。root
      $ sudo mysql -uroot -p
    2. 出现提示时输入您的 MySQL 密码,然后按继续。然后,执行以下命令以创建数据库和帐户。替换为强值。rootENTERbookingsbookings_userEXAMPLE_PASSWORD
      mysql> CREATE DATABASE bookings DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
      
             CREATE USER 'bookings_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'EXAMPLE_PASSWORD';
      
             GRANT ALL PRIVILEGES ON bookings.* TO 'bookings_user'@'localhost';
      
             FLUSH PRIVILEGES;
    3. 切换到新数据库。
      mysql> USE bookings;
    4. 接下来,创建一个表。在此示例应用程序中,您将使用 Redis 服务器从可用座位池中抓取乘客的座位。然后,您将在表中永久存储分配的信息和信息。ticketsseat_no'sticket_id'stickets
      mysql> CREATE TABLE tickets (
      
                 ticket_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
      
                 seat_no BIGINT    
      
             ) ENGINE = InnoDB;
    5. 您的数据库、用户帐户和表现已就位。从 MySQL 服务器注销。
      mysql> QUIT;

      在下一步中,您将创建一个 Golang 脚本来接受通过 HTTPS 传入的工单请求。

四、创建文件main.go

要将此应用程序与其他 Linux 文件分开,您需要一个单独的源代码目录。

  1. 创建目录.project
    $ mkdir project
  2. 然后,切换到新目录.project
    $ cd project
  3. 接下来,用于创建文件。此文件包含运行应用程序时触发的主脚本.nanomain.go
    $ nano main.go
  4. With the file opened, paste the following information into the file.main.go
    package main
    
    
    
    import (
    
    "encoding/json"
    
    "fmt"
    
    "net/http"
    
    "strconv"
    
    )
    
    
    
    func main() {
    
        http.HandleFunc("/tickets", httpHandler)            
    
        http.ListenAndServe(":8080", nil)
    
    }
    
    
    
    func httpHandler(w http.ResponseWriter, req *http.Request) { 
    
    
    
        var err error
    
        resp := map[string]interface{}{}
    
    
    
        resp, err = newTicket() 
    
    
    
        enc := json.NewEncoder(w)
    
        enc.SetIndent("", "  ") 
    
    
    
        if err != nil {
    
            resp = map[string]interface{}{"error": err.Error(),}     
    
        }
    
    
    
        if err := enc.Encode(resp); err != nil {
    
            fmt.Println(err.Error())
    
        }
    
    
    
    }
    
    
    
    func newTicket() (map[string]interface{}, error) {
    
    
    
        seatNo, err := createTicket("test")
    
    
    
        if err != nil {
    
            return nil, err
    
        }
    
    
    
        resp := map[string]interface{}{"Response" : "Seat # " + strconv.FormatInt(seatNo, 10) + " booked successfully.",}
    
    
    
        return resp, nil          
    
    
    
    }
  5. Save and close the file when you’re through with editing.
  6. 在上面的文件中,你将导入包,该包允许你格式化 JSON 数据。接下来,你已包含用于格式化和输出字符串的包。该包允许您将其他数据类型转换为字符串格式,同时库提供 HTTP 实现。main.goencoding/jsonfmtstrconvnet/http
  7. 在 main 函数 () 下,您正在侦听 URL 中端口上的传入 HTTP 请求。然后,您将 HTTP 请求重定向到函数,该函数又使用该语句调用该函数。func main() {...}8080/ticketsfunc httpHandler(){...}newTicket()resp, err = newTicket()
  8. 在该函数下,您将使用该语句调用该函数,以从 Redis 服务器获取乘客的座位号。在下一步中,您将在新文件中创建函数。func newTicket(){}createTicket(...)seatNo, err := createTicket("test")createTicket(...)tickets.go

五、 创建文件tickets.go

在此步骤中,您将创建一个连接到 Redis 服务器的 Golang 脚本。首先,脚本将读取一个键,用于检查可供预订的座位总数。然后,如果剩余席位数大于或等于 1,则脚本将保留一个席位号,将剩余席位数减 1 并返回分配给调用脚本的席位。testseat_no

  1. 用于创建文件。nanotickets.go
    $ nano tickets.go
  2. 然后,在文件中输入以下信息。tickets.go
    package main
    
    
    
    import (
    
    "context"
    
    "errors"
    
    "strconv"
    
    "github.com/go-redis/redis"
    
    )
    
    
    
    func createTicket(key string) (int64, error) {
    
    
    
        ctx := context.Background()
    
    
    
        redisClient := redis.NewClient(&redis.Options{
    
            Addr: "localhost:6379",
    
            Password: "",
    
            DB: 0,
    
        })
    
    
    
        var seatNo int64
    
    
    
        err := redisClient.Watch(ctx, func(tx *redis.Tx) error {
    
    
    
            val, err := tx.Get(ctx, key).Int64()
    
    
    
            if err != nil && err != redis.Nil {
    
                return err
    
            }
    
    
    
            seatNo = val
    
    
    
            if (seatNo - 1) < 0 {
    
                return errors.New("Unable to secure a seat.\r\n")
    
            }
    
    
    
            _, err = tx.Pipelined(ctx, func(pipe redis.Pipeliner) error {
    
    
    
                pipe.Set(ctx, key, strconv.FormatInt(seatNo - 1, 10), 0)    
    
    
    
                return nil
    
            })
    
    
    
            if err == nil {
    
                insertRecord(seatNo)
    
            }
    
    
    
    return err
    
    
    
    }, key)
    
    
    
        if err == redis.TxFailedErr {               
    
            return createTicket(key)
    
    }
    
    
    
    return seatNo, err
    
    }
  3. 保存并关闭文件。
  4. 在上面的文件中,您已导入包,以使用语句为 Redis 调用提供不受限制的截止时间。然后,使用包将自定义错误返回到调用函数。该软件包允许您在 Golang 脚本中实现 Redis 函数。contextctx := context.Background()errorsgithub.com/go-redis/redis
  5. 在 中,您接受 1 个参数。这是您用于在应用程序中保留可用席位的名称。在本教程中,你将用作键名称。在生产环境中,您可以考虑使用更有意义/更具描述性的名称,例如 。func createTicket(key string) (int64, error){}keytestavailable_seats
  6. 该语句允许您连接并创建新的 Redis 客户端实例。然后,您使用语句初始化一个空变量。脚本分配座位号后,您将填充此变量。redisClient := redis.NewClient(...)seatNovar seatNo int64
  7. 接下来,您将使用 Redis 函数,该语句在事务的生存期内监视密钥。如果另一个会话以任何方式修改了密钥,则整个事务应中止,并且您已对脚本进行了编码,以便使用语句重试脚本。请记住,在生产环境中,客户可以从不同的应用程序(例如移动应用程序、API、桌面应用程序、门户等)购买票证。这里的想法是一次发行一张票以避免超额预订。WATCHerr := redisClient.Watch(ctx, func()...{...}, key)testtestif err == redis.TxFailedErr { return createTicket(key) }
  8. 在函数内部,您可以使用语句检索剩余席位的值。如果没有剩余席位,您将使用 语句 抛出自定义错误。WATCHval, err := tx.Get(ctx, key).Int64()if (seatNo - 1) < 0 { return errors.New("Unable to secure a seat.\r\n") }
  9. 接下来,预订座位后,您将使用语句减少可用座位数。Redis 管道允许您在一次网络调用中将多个命令传输到 Redis 服务器。在本教程中仅执行一个命令时,应始终使用管道模型,以便在应用程序逻辑发生更改时更轻松地进行修改。pipe.Set(ctx, key, strconv.FormatInt(seatNo - 1, 10), 0)
  10. 然后,您调用该函数以将票证信息保存到 MySQL 数据库,以防使用该语句执行流水线命令时没有错误。一旦整个函数运行,它应该返回文件或任何错误,以防遇到任何错误。insertRecord()if err == nil { insertRecord(seatNo) }createTicket()seatNomain.go
  11. 在下一步中,您将在不同的文件中创建要在此调用的函数。insertRecord()tickets.godatabase.go

六、创建文件database.go

您将为此抢票应用程序创建的最后一个脚本是文件。此文件包含将票证信息永久存储到 MySQL 数据库的逻辑。database.go

  1. 使用 Nano 创建文件。database.go
    $ nano database.go
  2. 然后,在文件中输入以下信息。database.go
    package main
    
    
    
    import (
    
        "database/sql"
    
        _ "github.com/go-sql-driver/mysql"      
    
    )
    
    
    
    func insertRecord(seatNo int64) error {
    
    
    
        dbUser     := "bookings_user"
    
        dbPassword := "EXAMPLE_PASSWORD"
    
        dbName     := "bookings"
    
    
    
        db, err := sql.Open("mysql", dbUser + ":" + dbPassword + "@tcp(127.0.0.1:3306)/" + dbName) 
    
    
    
        if err != nil {
    
            return err
    
        }
    
    
    
        defer db.Close()
    
    
    
        queryString := "insert into tickets (seat_no) values (?)"
    
    
    
        stmt, err   := db.Prepare(queryString) 
    
    
    
        if err != nil {
    
            return err       
    
        }
    
    
    
        defer stmt.Close()     
    
    
    
        _, err = stmt.Exec(seatNo) 
    
    
    
        if err != nil {
    
            return err
    
        }
    
    
    
        return nil
    
    }
  3. 保存并关闭文件。
  4. 在上面的文件中,你正在使用 and 包来实现 Golang 中的 SQL 和 MySQL 功能。在该函数下,你将使用之前创建的凭据连接到 MySQL 数据库。然后,您将工单信息保存到表中。database/sqlgithub.com/go-sql-driver/mysqlfunc insertRecord(...) error {...}tickets
  5. 现在,您已经编写了使用 MySQL 和 Golang 运行 Redis 事务的所有脚本。在下一步中,您将测试一切是否按预期工作。

七、 测试 Redis 事务应用程序

您的 Golang 事务应用程序现在已经准备好进行测试了。

  1. 在执行应用程序之前,请导入已在应用程序中实现的所有包。
    $ go get github.com/go-redis/redis
    
    $ go get github.com/go-sql-driver/mysql
  2. 接下来,打开 Redis 命令行界面。
    $ redis-cli
  3. 通过将键的值设置为 .10test10
    $ SET test 10
  4. 从 Redis 服务器注销。
    $ QUIT
  5. 确保您仍在目录下,然后执行以下命令以运行 Golang 应用程序。project
    $ go run ./
  6. 上面的命令有一个阻塞功能,可以在端口下旋转 Web 服务器。不要在此终端窗口上运行任何其他命令。8080
  7. 接下来,在新的终端窗口中通过 SSH 连接到您的服务器并安装 Apache Bench () 软件包。你将使用此工具向应用程序发送并行票证请求命令,以查看它是否可以处理事务,而不会出现任何超额预订或争用情况。ab
    $ sudo apt install -y apache2-utils
  8. 接下来,向应用程序发送并行票证请求。请记住,您只在 Redis 服务器中使用了席位。因此,只有事务应该成功,其余事务应该失败。此外,由于您已使用该函数实现了 Redis 锁,因此不应出现不同会话具有相同情况的情况。201010seat_noWATCH
    $ ab -v 2 -n 20 -c 20 http://localhost:8080/tickets
  9. 您应该会收到以下响应。
    ...
    
    {
    
      "Response": "Seat # n booked successfully."
    
    }
    
    
    
    ...
    
    
    
    {
    
      "error": "Unable to secure a seat.\r\n"
    
    }
    
    
    
    ...
  10. 接下来,在仍然登录到第二个终端窗口的同时,登录到MySQL数据库以确认新的更改。root
    $ sudo mysql -u root -p
  11. 输入 MySQL 服务器的密码,然后按继续。然后,切换到数据库。rootENTERbooking
    mysql> USE bookings;
  12. 对表运行语句。SELECTtickets
    mysql> SELECT
    
               ticket_id,
    
               seat_no
    
           FROM tickets;
  13. 您现在应该看到以下票证和关联的 .从以下输出中可以看出,没有超额预订的情况。此外,该脚本已成功消除了任何竞争条件的机会,因为没有两张票具有相同的。seat_no'sseat_no
    +-----------+---------+
    
    | ticket_id | seat_no |
    
    +-----------+---------+
    
    |         1 |      10 |
    
    |         2 |       9 |
    
    |         3 |       7 |
    
    |         4 |       8 |
    
    |         5 |       6 |
    
    |         6 |       5 |
    
    |         7 |       3 |
    
    |         8 |       4 |
    
    |         9 |       1 |
    
    |        10 |       2 |
    
    +-----------+---------+
    
    10 rows in set (0.00 sec)
  14. 您的脚本现在按预期工作。

八、结论

在本指南中,您已经在 Linux 服务器中实现了 Redis 事务并使用 Golang 和 MySQL 数据库锁定,以创建抢票应用程序。使用本指南中的逻辑可避免在创建多用户应用程序时出现争用条件和数据库不一致。

将分布式应用程序连接到数据库:优点和缺点

一、介绍

随着公司跨不同地理区域的发展,统一业务流程的一种方法是实现与中央数据库通信的分布式应用程序。但是,此方法要求您将应用程序分解为两个单独的程序:服务器软件和客户端软件。

服务器软件应驻留在安全的云环境中,例如 Vultr 的云计算、裸机或 Vultr Managed Kubernetes (VKE)。这通常称为后端。在面向数据的应用程序中,您的后端必须绑定到关系数据库服务器,例如 MySQL 或 PostgreSQL。基于云的数据库更具弹性、可扩展性,并且可以处理大型工作负载。

另一方面,客户端软件是在用户的计算机或移动设备上运行的前端。例如,手机上的Facebook应用程序是客户端应用程序。您可以使用任何现代编程语言设计前端应用程序,包括Java,C#,VB.Net,Swift,Android等。

设计分布式应用程序时出现的唯一主要挑战是将本地客户端应用程序(例如,移动应用程序和桌面应用程序)链接到服务器软件(在线数据库)的最佳方法。在本指南中,您将学习可以实施的两种不同方法以及每种方法的优缺点。

二、直接连接方式

每个新的云计算程序员在将应用程序的后端连接到云时都会想到一个简单的想法,那就是更改数据库连接字符串。例如,如果您在本地部署了连接到本地 MySQL 实例的应用程序,则数据库连接字符串可能类似于以下代码片段。

    Server=localhost;Database=sample_database;user=sample_user;password=EXAMPLE_PASSWORD;

现在,要将应用程序的数据库移动到云中,您只需启动云服务器,安装 MySQL 服务器并将连接字符串更改为以下设置,其中 是云服务器的域名。example.com

    Server=example.com;Database=sample_database;user=sample_user;password=EXAMPLE_PASSWORD;

上述方法应该可以很好地工作,并且您需要在客户端的应用程序中更改最少的代码。但是,它具有以下缺点,应不惜一切代价避免。

  1. 直接连接方法要求软件的每个副本都将数据库凭据硬编码在某处。即使您可能尝试加密连接字符串,单个安全漏洞或漏洞也会使您的应用程序毫无价值。如果黑客设法获取连接字符串,他们可以破解应用程序中的每个用户帐户,因为他们已经可以直接访问中央数据库。
  2. 由于不同地理区域中的用户可以访问分布式应用程序,因此直接连接方法要求您向每台主机打开数据库端口。这为机器人、DDoS 和暴力攻击创造了一个非常好的途径。
  3. 紧密耦合。由于已将连接字符串保存在分发的每个软件副本中,因此数据库凭据中的单个更改将意味着锁定所有用户。

从上述缺点可以看出,将分布式应用程序连接到云数据库的直接方法并不是最佳方法。在下一步中,您将看到一种更可靠且几乎防故障的方法。

三、接口连接方式

API 代表应用程序编程接口。这是一个中间件,允许您的前端应用程序以安全的方式与后端应用程序通信。

API 层应驻留在云基础架构中,例如 Vultr 的云计算实例或 VPS。基本上,以下是通过 API 将分布式应用程序迁移并连接到云数据库的基本过程。

  1. 注册一个云计算帐户。
  2. 然后,启动一个 Linux 服务器。例如,Ubuntu 20.04 对于云计算初学者来说是一个很好的操作系统。
  3. 接下来,在 Linux 服务器中安装数据库服务器。例如,您可以安装 MySQL 或部署 PostgreSQL 数据库服务器。
  4. 在服务器上创建新数据库,然后从本地数据库导入数据。
  5. 接下来,使用您喜欢的脚本语言(例如 PHP、Golang、Python 等)创建一个 API。请参阅以下指南,了解创建 API 的基础知识。
    • 在 Linux 上使用 Golang 和 MySQL 8 设计现代 API。
    • 在 Ubuntu 20.04 上使用 PHP 和 MySQL 创建一个 JSON REST API。
  6. 您的 API 应支持身份验证、授权、排序、筛选、分页和自定义字段选择。
  7. 然后,您应该将 API 源代码放在 Web 服务器的根目录中,以便可以通过 HTTP 方法(、、 和 )公开访问它。GETPOSTPUTDELETE
  8. 接下来,重新设计应用程序(例如,桌面软件或移动应用)以使用新的 API 终结点,而不是连接字符串。现代编程语言附带了不同的库,允许您使用最少的代码执行此操作。例如,当使用Android编写应用程序时,您可以使用Volley库。同样,对于 .NET 应用程序,可以使用 WebRequest 类向 API 终结点发出 HTTP 请求。
  9. 典型的 API 终结点类似于以下 URL。
    http://www.example.com/api/v1
  10. 最后,使用新的 API 设置发布应用。

API 连接方法可能需要更长的时间才能实现。此外,它还要求您完全重组前端应用程序。但是,它具有以下优点。

  1. 数据库凭据仅保存在 API 层中。最终用户在连接到云应用程序时应通过 HTTPS 使用其帐户凭据。这种方法更安全,如果发生中间人攻击,只有少数用户帐户受到威胁,而不是整个数据库。
  2. 松耦合。使用 API 方法时,应用程序有 3 层。即前端、中间件 (API) 和后端(数据库)。这意味着单个组件中的更改可能并不总是影响其他组件的存在。
  3. 代码重用。API 方法促进代码重用。多个客户端应用程序可以使用 API 数据。例如,您可以创建一个 API,将公司门户、移动应用程序、桌面应用程序、基于 Web 的软件等连接到中央数据库。这意味着您只需要维护一个 API 端点,这大大降低了编程成本和运行 IT 基础架构所需的员工数量。

三、结论

在权衡了这两种方法之后,很明显 API 方法胜出,并且始终是推荐的方法。此外,像 Facebook 和T witter 这样的大公司使用API 方法,因为它是高度安全的。

如果你的公司正在大幅增长到不同的地理区域,则应考虑在云中设计和托管分布式应用。最后,您将拥有一个更安全、更稳定的弹性系统,最重要的是,您将拥有一个用于所有业务流程的中央数据库。

如何在 CentOS 7 上安装 Apache CouchDB

一、介绍

Apache CouchDB是一个免费的开源NoSQL数据库服务器,它将数据库存储为JSON格式的文档。它包括一个 RESTful HTTP API,可让您轻松创建、读取、编辑和删除文档。

本指南将教你如何在 CentOS 7 服务器上安装和设置 Apache CouchDB。

1.1、准备工作

  • 部署 CentOS 7 Vultr 服务器。
  • 登录到服务器。
  • 更新服务器。

二、设置 CouchDB 存储库

安装 CentOS EPEL 資料庫。

$ sudo yum install epel-release

然后,设置一个新的 CouchDB 存储库文件:

$ sudo nano /etc/yum.repos.d/apache-couchdb.repo

将以下内容粘贴到其中:

[Apache-Couchdb]

name=couchdb

baseurl=https://apache.jfrog.io/artifactory/couchdb-rpm/el$releasever/$basearch/

gpgkey=https://couchdb.apache.org/repo/keys.asc https://couchdb.apache.org/repo/rpm-package-key.asc

gpgcheck=1

repo_gpgcheck=1

enabled=1

更新服务器。

$ sudo yum install couchdb

 三、安装 CouchDB

现在,所有必要的存储库都已设置完毕,请使用以下命令安装 CouchDB:

$ sudo yum install couchdb

启用 CouchDB 以在引导时启动。

$ sudo systemctl enable couchdb

启动 CouchDB。

$ sudo systemctl start couchdb

 四、设置 CouchDB

所有 CouchDB 数据和配置文件都存储在 中,并且是位于子目录中的主配置文件。首先,通过将 CouchDB 绑定到网络地址来设置 CouchDB,然后添加新的管理用户。/opt/couchdblocal.ini/etc/

打开文件 。/opt/couchdb/etc/local.ini

$ sudo nano /opt/couchdb/local.ini

找到条目 ,并将其更改为您的首选设置。默认情况下,它设置为 ,该地址使 CouchDB 可从任何网络地址访问,并将其绑定到您的公共 Vultr IP 地址。bind_address =127.0.0.10.0.0.0global

[chttpd]

;port = 5984

bind_address = 127.0.0.1

要设置新用户,请滚动到底部或搜索关键字。在该部分下,添加一个格式为 .要加强服务器安全性,请替换为您选择的秘密用户名。adminsusername = passwordadmin

[admins]

admin = YOUR-PASSWORD

此外,添加以下条目以允许 CouchDB 在重新启动时创建所有必要的系统数据库。

[couchdb]



single_node=true

保存并关闭文件。

或者,您可以使用以下命令手动创建系统数据库:、、:_users_replicator_global_changes

$ curl -u Username:Password -X PUT http://127.0.0.1:5984/_users

$ curl -u Username:Password -X PUT http://127.0.0.1:5984/_replicator

$ curl -u Username:Password -X PUT http://127.0.0.1:5984/_global_changes

重新启动 CouchDB 以使更改生效。

$ sudo systemctl restart couchdb

 4.1、配置防火墙

首先,允许服务器上的 HTTP 流量。

$ sudo firewall-cmd --permanent --zone=public --add-service=http

如果服务器上有 SSL 证书,请允许 HTTPS 流量。

$ sudo firewall-cmd --permanent --zone=public --add-service=https 

然后,打开端口 5984。

$ sudo firewall-cmd --permanent --zone=public --add-port=5984/tcp

重新启动防火墙以使规则生效。

$ sudo systemctl reload firewalld

4.2、测试您的 CouchDB 安装

在终端窗口中,使用以下 curl 命令测试 CouchDB 安装:

$ curl 127.0.0.1:5984

您的输出应类似于以下内容:

{"couchdb":"Welcome","version":"3.2.1","git_sha":"244d428af","uuid":"99b896bf19b5b076970e12574b9b9ff8","features":["access-ready","partitioned","pluggable-storage-engines","reshard","scheduler"],"vendor":{"name":"The Apache Software Foundation"}}

访问 Vultr 服务器的公共 IP 地址在端口上,并带有 GUI 版本的端点。5984/utils

http://SERVER-IP:5984/_utils

![CouchDB 登录页面(https://i.imgur.com/RF9K8D8.png)

使用之前创建的管理员用户名和密码登录以继续。

构建 Kubernetes:如何选择集群部署策略

一、介绍

本指南将引导您完成在为 Vultr Kubernetes 引擎 (VKE) 选择集群部署策略时应考虑的一些架构决策,以及每种方法的优缺点。

VKE 群集是一组服务器,它们为以高度可用、分布式和可扩展的方式运行应用程序和服务提供基础架构。集群由一个控制平面(负责管理集群)和一个或多个工作器节点(负责运行实际应用程序和服务)组成。控制平面提供核心调度、网络和存储服务。它管理基础结构,例如虚拟机和容器。它监督工作节点之间的通信,以确保它们以最佳方式运行。工作器节点负责运行托管群集各种工作负载的容器(称为 Pod)。

有多种方法可以配置群集体系结构以托管应用程序,这些方法具有灵活扩展、成本效率、应用程序和环境隔离、易于管理等优势。幸运的是,VKE 提供了一个免费的控制平面,并且只对工作节点的总数收费,这消除了为您的应用程序选择最佳架构时的任何成本考虑因素。但是,在选择部署策略时,除了成本之外,还应考虑其他重要事项。本指南探讨了每种方法最重要的优缺点,以帮助您做出明智的选择。

二、基于大小的架构模式

组织和分组群集的一种方法是按大小。Kubernetes 集群可以通过添加和删除工作器节点或部署具有不同计算资源量(如 CPU、内存和存储)的工作器节点来扩展或缩减。这些工作器节点是运行应用程序的物理或虚拟服务器。使用 VKE,您可以轻松添加或删除节点以调整集群大小。Kubernetes 集群非常强大和高效,允许用户根据自己的需求快速扩展或缩减其计算资源。

集群的大小是工作器节点总数及其每个计算资源的组合,它们用于部署更多 Pod(水平扩展)或为每个 Pod 分配不同数量的计算资源(垂直扩展)。

2.1、几个大型集群

在此架构配置中预置一些集群来托管工作负载。您甚至可以使用单个集群。每个都相对较大,有许多工作节点和计算资源可用于在许多 Pod 中运行工作负载。

使用几个大型集群有好处:

  • 您可以优化资源利用率。在几个大型集群上托管应用程序时,工作节点会有效地与 Pod 共享计算资源,从而最大限度地减少计算能力的浪费。
  • 您可以有效地管理基础架构,因为您无需与多个集互即可执行管理或日常任务。
  • 您可以重复使用集群范围的资源,例如负载均衡器、入口控制器等,从而使其管理更简单、更高效。一个

但是,拥有几个大型集群有一些缺点:

  • 如果需要分离不同的应用程序,单个控制平面只能具有软多租户。Kubernetes 命名空间和基于角色的访问控制提供租户之间的分离,但它们共享相同的控制平面组件,例如用于服务发现的 DNS 服务器。
  • 集群越少,容错能力就越低,因为每个集群上集中的服务就越多。任何服务中断或故障都可能导致大量容量丢失。
  • 如果大型群集发生故障,则重建该群集更具挑战性。大型群集可能具有各种不同的应用程序,这些应用程序需要复杂的配置过程。
  • 在单个群集上具有太多不同的租户应用程序可能会给控制平面组件带来压力,从而导致意外错误。如果计划托管许多应用程序,最好将它们分布在多个群集中。

2.2、许多小集群

在此体系结构模式中,您将工作负载分布在具有较小工作器节点的较大集群组中。这可实现灵活的扩展、成本效益、应用程序和环境隔离以及易于管理。如果集群过载,可以将某些 Pod 移动到另一个集群,使其成为更动态、更高效的解决方案。

如果需要,使用许多小型集群是很好的:

  • 硬多租户:您可以通过将应用程序分布在不同的集群中来完全隔离应用程序,以防止共享控制平面组件。
  • 容错:此体系结构具有更强的容错能力。在发生单个集群故障时,您可以保留大量容量。
  • 降低复杂性:重建损坏的小型群集不太复杂,因为它托管的应用程序较少。

此模式有一些缺点:

  • 您必须反复与许多不同的集互,以执行管理或例行任务,例如监视、更新等。
  • 将应用程序分散到许多不同的集群可能会浪费负载均衡器和入口控制器等资源,而这些资源可以同时处理许多应用程序。

三、基于效用的架构模式

应用程序可能包含前端、数据库、业务逻辑等的多个组件。您可能会发现自己需要部署应用程序的多个实例来创建不同的环境,例如生产、开发、测试、暂存等。这些环境中的每一个都可能具有非常不同的要求来满足应用程序的需求,这可能导致资源使用效率低下。

幸运的是,可以使用其他几种体系结构模式来解决此问题。这些关注的是 Kubernetes 集群的效用,而不是它的大小。例如,您可能会发现一个应用程序占用更多 CPU,而另一个应用程序需要大量内存。此外,生产环境可能需要入口控制器,而开发不需要,依此类推。通过选择正确的配置,您可以确保应用程序尽可能高效地运行,同时满足每个环境的需求。

3.1、每个应用程序的群集数

每个应用程序群集的方法在单个群集中运行每个应用程序及其所有环境(例如开发、测试和生产)。此体系结构配置提供了许多优点,例如应用程序隔离和易于管理,因为应用程序的所有相关组件都可以一起找到。此外,它还允许更大的可扩展性,因为可以配置集群以满足应用程序的确切要求,包括不同的计算资源和 Kubernetes 版本。

但是,由于所有环境都位于同一群集上,因此这可能会对性能和可靠性产生不利影响。假设在一个环境中出现问题,例如在测试环境中执行错误的代码。在这种情况下,它可能会导致其他环境(尤其是生产环境)的服务中断。因此,在同一群集上托管多个环境时,请务必采取额外的预防措施。

3.2、每个环境的群集数

每个环境群集的方法允许您在单个群集上托管共享环境(例如测试或生产)的多个应用程序。这种方法的好处是:

  • 高效使用资源:您可以根据环境要求预置每个集群的计算资源。您的开发和测试环境可能小于生产环境。
  • 访问隔离:更容易限制对生产群集或托管敏感环境的其他内容的访问。

但是,与每个应用程序群集的方法相比,管理单个应用程序更为复杂,因为您将环境分散在不同的群集上。可能有更好的方法来对具有高度可变要求的应用程序进行分组。例如,需要不同 Kubernetes 版本的应用程序不能使用这种方法组合在一起。

此外,使用此方法时,您必须管理多个群集及其资源,这可能会成为一项艰巨的任务。这是因为必须单独预配和维护每个群集,并且必须跨所有群集复制对应用程序或环境所做的任何更改。此外,如果一个集群关闭,可能会影响其他集群及其应用程序,从而导致潜在的停机。

幸运的是,有一些方法可以最大程度地降低此方法的复杂性。例如,您可以使用自动化工具来管理集群和资源,从而简化管理多个集群的过程。此外,您可以使用容器化技术来确保应用程序和环境可以在多个集群上运行,从而提供一定程度的冗余,以帮助最大限度地降低停机风险。

四、选择正确的架构

在托管基础结构时,没有无效的方法。上面讨论的所有体系结构都是有效的。但是,两种体系结构配置之间的选择取决于您如何确定以下因素的优先级。

  • 缩放
  • 成本效益
  • 租户隔离
  • 高可用性
  • 易于管理

随着资源需求的增加,扩展一组大型集群的成本非常高,因为与启动许多小型集群相比,每个集群的成本要高得多。您可以通过向小型集群组添加更多集群来轻松扩展基础架构,从而无限增加容量。

成本效益取决于您如何利用资源。通过在几个大型群集上托管基础结构,通过重用群集范围的资源(如入口控制器、负载均衡器等),可以更高效。拥有许多集群可能会引入资源缺口,并且成本高于您的利用率。

租户隔离有两种类型:软隔离和硬隔离。通过使用 Kubernetes 命名空间、基于角色的访问控制等在大型群集上实现软租户隔离,但由于共享控制平面组件,应用程序仍然可以发现群集上运行的其他应用程序。您可以通过在许多小型集群上托管基础架构块来实现硬租户隔离,从而消除共享控制平面组件。

Kubernetes 具有安全和资源限制功能,例如用于控制流量的 NetworkPolicy 资源、用于命名空间级别隔离的 Pod 安全准入、ResourceQuota 对象或 LimitRange 资源的资源限制等等。但是,这些方法需要额外的配置而不是硬隔离,并且无法保护您的基础结构免受每个安全漏洞的影响。

高可用性很难通过少量大型集群实现,因为它的容错能力低于拥有许多小型集群。由于大型集群中的服务中断,您可能会损失大量容量。与重建大型集群相比,您可以轻松地在一组小型集群中重建损坏的集群。

易于管理是拥有几个大型集群的一个吸引人的功能。与大型集群相比,管理由许多小型集群组成的组非常复杂,因为您需要单独与每个集群进行交互。如果您管理大量集群,则必须多次执行日常任务,例如升级 Kubernetes 版本和监控集群运行状况。

五、结论

本指南引导您完成用于托管应用程序的不同体系结构配置。它还比较了不同的配置,包括扩展、成本效率、应用程序/环境隔离、易管理性等。您可以通过根据所讨论的指针分析和评估需求,为应用程序选择正确的集群体系结构。请参阅 Kubernetes 组件概述,了解有关 Kubernetes 集群中各个组件的更多信息。