本文是「使用 NodeJS 构建影院微服务」系列的第 二篇文章。
如果你还没有阅读过第一篇文章,你可以去这里看看👀。
在本篇中我们将继续构建我们的影院微服务,这次我们将进行影院目录服务的开发,以完成下图中的功能。
我们将使用到以下技术:
要跟上本文的进度有以下要求:
如果你还没有完成这些代码,我已经将代码传到了 github 上,你可以直接使用代码库分支 step-1。
在第一章中,我们实现了一个简单的微服务,它使用的是 HTTP/1.1 协议。对于已有 15 年历史的 HTTP 协议来说,HTTP/2 是第一次大升级,它被高度优化且效率更高。HTTP/2 是最新的网络标准,它起源于谷歌的 SPDY 协议。现在已有许多热门站点在使用它,并且几乎所有主流浏览器都支持该协议。
要实现 HTTP/2,只需满足以下一些规则。
这意味着我们要在客户端与服务端之间启用一个单独的连接,然后在“网络中”利用类似 Y 轴分片( Y-axis sharding 会在本系列文章的动态扩容篇讲到更多)的方式享受 HTTP/2 带来的性能提升,同时还能享受微服务架构在运营和开发上带来的好处。
为什么我们要使用最新的 HTTP/2 协议?因为作为一名好的开发者,我们不仅要保障应用,基础设施和通信的安全,尽最大努力来防止恶意攻击;而且作为一名好的开发者,我们还应该采用对我们有利的最佳实践方式,比如使用 HTTP/2。
微服务安全的一些好的实践方式如下:
安全,是微服务应用生产环境部署中的一个重要组成部分。根据 451 Research 的一项名为2016 软件定义设施展望的调查,在未来的 12 月内,近 45% 的企业已经或计划实现基于容器的应用部署。由于 DevOps 实践在企业中取得了稳固地位,容器应用变得更加流行,安全顾问们需要知道如何保障这些应用的安全。—— @Ranga Rajagopalan
我们将对微服务通信进行加密以实现安全通信,并提升公网流量的安全性,这是我们使用 HTTP/2 的原因之一,为了更好的性能以及安全提升。
首先,让我们更新下上一章的电影服务,以支持 HTTP/2 协议,在此之前我们在 config 目录下新建一个 ssl 目录。
movies-service/config:/ $ mkdir ssl
movies-service/config:/ $ cd ssl
进入 ssl 目录后,我们创建一个自签名的 SSL 证书,用于实现 HTTP/2 协议。
# Let's generate the server pass key
ssl/: $ openssl genrsa -des3 -passout pass:x -out server.pass.key 2048
# now generate the server key from the pass key
ssl/: $ openssl rsa -passin pass:x -in server.pass.key -out server.key
# we remove the pass key
ssl/: $ rm server.pass.key
# now let's create the .csr file
ssl/: $ openssl req -new -key server.key -out server.csr
...
Country Name (2 letter code) [AU]:MX
State or Province Name (full name) [Some-State]:Michoacan
...
A challenge password []:
...
# now let's create the .crt file
ssl/: $ openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt
然后我们使用下面的命令安装 SPDY 包:
cinema-catalog-service/: $ npm i -S spdy --silent
首先我们在 ssl/
目录下创建一个 index.js
文件,代码如下,我们在代码中加载 key 和 cert 文件,这里是我们为数不多地使用 fs.readFileSync()
的地方之一:
const fs = require('fs')
module.exports = {
key: fs.readFileSync(`${__dirname}/server.key`),
cert: fs.readFileSync(`${__dirname}/server.crt`)
}
然后我们还要修改一些文件,第一个文件是 config.js
:
const dbSettings = { ... }
// The first modification is adding the ssl certificates to the
// serverSettings
const serverSettings = {
port: process.env.PORT || 3000,
ssl: require('./ssl')
}
module.exports = Object.assign({}, { dbSettings, serverSettings })
接下来修改 server.js
,如下:
...
const spdy = require('spdy')
const api = require('../api/movies')
const start = (options) => {
...
const app = express()
app.use(morgan('dev'))
app.use(helmet())
app.use((err, req, res, next) => {
reject(new Error('Something went wrong!, err:' + err))
res.status(500).send('Something went wrong!')
})
api(app, options)
// here is where we made the modifications, we create a spdy
// server, then we pass the ssl certs, and the express app
const server = spdy.createServer(options.ssl, app)
.listen(options.port, () => resolve(server))
})
}
module.exports = Object.assign({}, {start})
最后修改 index.js
这个主文件:
'use strict'
const {EventEmitter} = require('events')
const server = require('./server/server')
const repository = require('./repository/repository')
const config = require('./config/')
const mediator = new EventEmitter()
...
mediator.on('db.ready', (db) => {
let rep
repository.connect(db)
.then(repo => {
console.log('Connected. Starting Server')
rep = repo
return server.start({
port: config.serverSettings.port,
// here we pass the ssl options to the server.js file
ssl: config.serverSettings.ssl,
repo
})
})
.then(app => { ... })
})
...
现在我们使用下面的指令重建 docker 镜像:
$ docker build -t movies-service .
再使用以下参数运行我们的 docker 镜像 moives-service
:
$ docker run --name movies-service -p 443:3000 -d movies-service
最终我们用 chrome 浏览器测试一下,可以确认我们的 HTTP/2 协议已经完全生效了。
而且我们还可以使用 wireshark 之类的抓包工具确认 ssl 确实生效了。
另外一种加密并保障我们的微服务通信安全的方式是使用 json web token
协议,我们将在本系列后面的文章中讲到🚉。
到此为止我们已经知道了如何实现 HTTP/2 协议,接下来让我们继续构建影院目录服务 。我们会使用与电影服务一样的项目结构,让我们少讲话🗣多编码👨🏻💻👩🏻💻这就干起来。
在开始设计 API 之前,这次我们要先为数据库设计 Mongo Schemas,我们将使用到以下数据库:
本文重点关注于构建微服务,故不会在“模型数据设计”上花太多的时间,只会强调某些部分。
# posible collections for the cinemas db.
# For locations
- countries
- states
- cities
# For cinemas
- cinemas
- cinemaRooms
- schedules
对于我们的 Locations 库来说,一个国家可以有多个州而一个州则对应一个国家,所以这是一对多的关系,对于州和城市来说也是一样的关系,让我们看一下关系图:
这种关系还适用于很多情况。一个城市有多个电影院,一个电影院属于一个城市;一个放映室有许多放映计划,一个放映计划属于一个放映室,我们看一下它们之间的关系。
如果电影院数组或者放映计划数据增长比较有限,我们可以采用上图中的引用关系。假设一个放映室每天最多只有 5 个放映计划,我们也可以将放映计划文档直接嵌入到影院文档中去。
内嵌数据模型允许应用将相关性数据片断存放在同一条数据库记录中。这样做可使得应用的常用操作需要更少的查询和更新。—— MongoDB Docs
这就是我们数据库结构设计的最终结果。