构建 NodeJS 影院预订微服务并使用 docker 部署(04/4)

一、说明
构建一个微服务的电影网站,需要Docker、NodeJS、MongoDB,这样的案例您见过吗?如果对此有兴趣,您就继续往下看吧。
我们前几章的快速回顾
- 第一篇文章介绍了微服务架构模式,并讨论了使用微服务的优缺点。
- 第二篇文章我们讨论了使用 HTTP/2 协议的微服务安全性。
- 本系列的第三篇文章描述了微服务架构中通信的不同方面,我们解释了 NodeJS 中的设计模式,如依赖注入、控制反转和 SOLID 原则。
- 我们已经制作了 3 个 API,并将其运行到 Docker 容器中
- 我们已经进行了单元,集成和压力测试。
如果你还没有读过前面的章节,你错过了一些很棒的东西🤘🏽,我会把链接放在下面,所以你可以看看👀。
构建 NodeJS 影院微服务并使用 docker 部署【01/4】
构建 NodeJS 影院微服务并使用 docker 部署它(02/4)
构建 NodeJS 影院预订微服务并使用 docker 部署(03/4)
这是🏛“构建 NodeJS 影院微服务”系列的第四篇文章。本系列文章演示如何使用 ES6、¿ES7 ...8?,连接到 MongoDB 副本集,本文还演示了如何将其部署到 docker 容器中,并模拟此微服务在云环境中的运行方式。
好的,在这一点上,我们将完成下一个图表:

子影院微服务架构
notification.api.js hosted with ❤ by GitHub剩下的是为我们构建的是支付服务和 通知服务,这次我们将非常快速地开发它们,以专注于我们在此架构中没有讨论过的东西,从一开始就存在,API 网关,所以请和我在一起,让我们开始制作一些有趣的东西😎。
我们将在本文中使用的是:
- NodeJS 版本 7.5.0(本地安装)
- MongoDB 3.4.1
- Docker for Mac 1.13.0(已安装,1.13.1 破坏了东西)
跟进文章的先决条件:
- 已完成上一章中的示例。
如果你还没有,我已经上传了一个 github 存储库,所以你可以在分支步骤 3 上获得最新的存储库链接。
二、支付和通知服务
由于本文用于构建 API 网关,因此这次我不会花太多时间来描述下一个服务,我将只强调有趣的部分。对于此服务,我们将继续使用相同的项目和应用程序结构,对此会略有变化,因此让我们看看此服务是如何组成的👀。
2.1 支付服务
要使支付服务正常工作,您可能知道有一堆库供节点进行信用卡收费,此时我将使用一个名为 stripe 的库,但在构建我们的支付服务之前,您应该访问 stripe 网站,并创建一个帐户才能使用此库, 因为我们需要一个条纹代币来进行支付测试。
# Then we need to install stripe in our project
cinema-microservice/payment-service $ npm i -S stripe --silent 那么我们如何使用条纹,首先让我们在我们的文件中注册我们的条纹依赖项:di.js
const { createContainer, asValue } = require('awilix')
const stripe = require('stripe')// here we include the stripeSettings
function initDI ({serverSettings, dbSettings, database, models, stripeSettings}, mediator) {mediator.once('init', () => {mediator.on('db.ready', (db) => {const container = createContainer()container.register({database: asValue(db),validate: asValue(models.validate),ObjectID: asValue(database.ObjectID),serverSettings: asValue(serverSettings),// and here we register our stripe modulestripe: asValue(stripe(stripeSettings.secret))})mediator.emit('di.ready', container)})
// more code ..., check the cinema microservice repository for to see the full code 接下来,我们将看到我们的文件如何:api/payment.js
'use strict'
const status = require('http-status')module.exports = ({repo}, app) => {app.post('/payment/makePurchase', (req, res, next) => {const {validate} = req.container.cradlevalidate(req.body.paymentOrder, 'payment').then(payment => {return repo.registerPurchase(payment)}).then(paid => {res.status(status.OK).json({paid})}).catch(next)})app.get('/payment/getPurchaseById/:id', (req, res, next) => {repo.getPurchaseById(req.params.id).then(payment => {res.status(status.OK).json({payment})}).catch(next)})
} 最后让我们检查一下我们的文件:repository.js
// this the function that makes the charge, when it's done// returns the charge object returned by stripeconst makePurchase = (payment) => {return new Promise((resolve, reject) => {// here we retrieve or stripe dependecyconst {stripe} = container.cradle// we create the chargestripe.charges.create({amount: Math.ceil(payment.amount * 100),currency: payment.currency,source: {number: payment.number,cvc: payment.cvc,exp_month: payment.exp_month,exp_year: payment.exp_year},description: payment.description}, (err, charge) => {if (err && err.type === 'StripeCardError') {reject(new Error('An error occuered procesing payment with stripe, err: ' + err))} else {const paid = Object.assign({}, {user: payment.userName, amount: payment.amount, charge})resolve(paid)}})})}// this the function that our API calls firstconst registerPurchase = (payment) => {return new Promise((resolve, reject) => {// and here we call the function to execute stripemakePurchase(payment).then(paid => {// if every thing is succesfull, we make the registry at our db, for the record onlydb.collection('payments').insertOne(paid, (err, result) => {if (err) {reject(new Error('an error occuered registring payment at db, err:' + err))}resolve(paid)})}).catch(err => reject(err))})}const getPurchaseById = (paymentId) => {... more code, where we only query our database for the payment with the id}// more code... visit the repository to see the complete code 我想在这里强调一些事情,我们在这里使用一些指导方针,就像我们一样优秀的开发人员,在函数和 ,指导方针是:repository.jsregisterPurchase()makePurchase()
做一件事(DOT)
“每个函数应该只做一件事,并尽可能做那一件事。少即是多
“F项应尽可能短:如果它们运行的时间更长,请考虑将子任务和数据分解为单独的函数和对象。——摘自《Programming Apps with Javascript》一书,
埃里克·艾略特
2.2 通知服务
好的,现在,在我们的通知服务中,再次有一些非常好的库用于发送电子邮件,短信,彩信等,您可以查看twilio或sendgrid,以更深入地了解通知服务,但是这次我将向您展示一个非常简单的服务,使用nodemailer。
# So we need to install nodemailer in our project
notification-service$ npm i -S nodemailer nodemailer-smtp-transport --silent 现在让我们看看我们的js文件如何,首先是我们的,然后是我们的 api/notification.jsrepository.js
module.exports = ({repo}, app) => {// this our endpoint where is going to validate our email, and the create and finally send itapp.post('/notifiaction/sendEmail', (req, res, next) => {const {validate} = req.container.cradlevalidate(req.body.payload, 'notification').then(payload => {return repo.sendEmail(payload)}).then(ok => {res.status(status.OK).json({msg: 'ok'})}).catch(next)})
} notification.api.js hosted with ❤ by GitHub
const sendEmail = (payload) => {return new Promise((resolve, reject) => {const {smtpSettings, smtpTransport, nodemailer} = container.cradleconst transporter = nodemailer.createTransport(smtpTransport({service: smtpSettings.service,auth: {user: smtpSettings.user,pass: smtpSettings.pass}}))const mailOptions = {from: '"Do Not Reply, Cinemas Company 👥" <no-replay@cinemas.com>',to: `${payload.user.email}`,subject: `Tickects for movie ${payload.movie.title}`,html: `<h1>Tickest for ${payload.movie.title}</h1><p>Cinem: ${payload.cinema.name}</p><p>Room: ${payload.cinema.room}</p><p>Seats: ${payload.cinema.seats}</p><p>description: ${payload.description}</p><p>Total: ${payload.totalAmount}</p><p>Total: ${payload.orderId}</p><h3>Cinemas Microserivce 2017, Enjoy your movie !</h3>`}transporter.sendMail(mailOptions, (err, info) => {if (err) {reject(new Error('An error occured sending an email, err:' + err))}transporter.close()resolve(info)})})} 要一如既往地查看完整配置,欢迎您在分支步骤 4 的 github 上查看影院微服务存储库。
如果我们设置一切正常,并运行集成测试,我们的通知服务可以发送如下图所示的电子邮件:

三、 结论 支付和通知服务
如果您认为我一如既往地使用这两项服务进行快速访问,欢迎您向我发送推文或在下面🤓发表评论,以便我们可以更详细地讨论这里发生的事情。
有一件重要的事情我没有提到,当我们用钱(信用卡、账户)处理时,我们需要确保我们的数据被加密只是为了增加另一层安全性,而在支付服务内部,我们需要解密用户信息,继续进行条纹结账。
最后在存储库中,每个服务上都有我们的 bash 文件,如果我们执行它,我们会将我们的服务放入 docker 容器中。start_service.sh
$ bash < start_service.sh 如果我们在docker机器管理器1中运行它,我们应该有这样的东西:

码头工人容器列表 === 码头工人 PS :D
四、接口网关
迁移到微服务时,应用程序设计和体系结构的最大变化之一是 使用网络在应用程序的功能组件之间进行通信。在整体式应用中,应用程序组件在内存中进行通信。在微服务应用中,这种通信是通过网络进行的,因此网络设计和实现变得至关重要。— @Nginx 互认协议文件
4.1 但首先¿什么是API网关?¿ 我们需要它吗?
API 网关是作为进入系统的单一入口点的服务器。它类似于面向对象设计中的立面模式。— 克里斯·理查森
API 网关封装了内部系统架构,并为每个客户端提供了量身定制的 API。它可能具有其他职责,例如身份验证、监视、负载平衡、缓存、请求整形和管理以及静态响应处理。
下图向我们展示了 API 网关如何适应我们的影院架构:

影院微服务 API 网关示例
API 网关负责请求路由、组合和协议转换。来自客户端的所有请求首先通过 API 网关。然后,它将请求路由到相应的微服务。
4.2 我们为什么需要它?(优点和缺点)
因为使用 API 网关封装了应用程序的内部结构,这减少了客户端和应用程序之间的往返次数,并且还简化了我们的代码。实现 API 网关以两种方式之一处理请求。某些请求只是代理/路由到相应的服务。它通过扇出到多个服务来处理其他请求。
但是 API 网关也有一些缺点。它是另一个“必须开发、部署和管理的高可用性组件”。还存在 API 网关成为开发瓶颈的风险,因此作为优秀的开发人员,我们必须更新 API 网关,更新 API 网关的过程必须尽可能轻量级。
跨领域关注点的实现方式必须使微服务无需处理有关其特定范围之外的问题的详细信息。例如,身份验证可以作为任何 API 网关或代理的一部分实现。— @authO
API 网关处理的常见问题列表:
- 认证
- 运输安全
- 负载平衡
- 请求调度(包括容错和服务发现)
- 依赖关系解析
- 传输转换
五、构建微服务
好的,现在我们已经湿透了,我们已经阅读📖了 API 网关的功能,让我们开始构建我们的影院微服务 API 网关 👩🏻 🔬👨🏾 🔬
在知道我们一直在使用 http/2 协议构建微服务之前,为了启动和运行该协议,我们需要满足一些规则,我们需要创建一些 SSL 证书,为了简单起见,我们创建了自签名证书,但这将成为在我们的 API 网关中代理我们的路由的问题。http-proxy nodejs 模块有一个建议,告诉我们以下内容:
您可以在选项中设置安全 SSL 证书到目标连接(避免自签名证书)的验证。
secure: true
我们不能将代理为自签名证书,我们需要将微服务 HTTP/2 协议回滚到前一个协议,但并非一切都是坏消息,因为 API 网关有很多问题,我们现在将使用 HTTP/2 协议,仅在 API 网关中,这就是我们如何激活网关的传输安全问题。
那么让我们开始吧,让我们开始动手🖐🏽一些<编码/>👩🏻💻👨🏻💻,但首先我们需要进行一些审查并查看我们的微服务定义,这在我们的raml中文件然后我们需要检查我们的 api 文件并确认我们的 api 路由定义良好。这很重要,因为这些路由是我们要代理的路由。
因此,让我们从raml files
booking.raml hosted with ❤ by GitHub
#%RAML 1.0
title: Booking Service
version: v1
baseUri: /booking/:type: { POST: {item : Booking, item2 : User, item3: Ticket} }/verify/{orderId}:type: { GET: {item : Ticket} } view rawcatalog.raml hosted with ❤ by GitHub
#%RAML 1.0
title: Cinema Catalog Service
version: v1
baseUri: /cinemas/:type: { GET: {item : Cinemas } }/{cinema_id}:type: { GET: {item : Movies } }/{city_id}/{movie_id}:type: { GET: {item : Schedules } }
movies.raml hosted with ❤ by GitHub
#%RAML 1.0
title: Movies Service
version: v1
baseUri: /movies/:/premieres:type: { GET: {item : MoviePremieres } }/{id}:type: { GET: {item : Movie } } notification.raml hosted with ❤ by GitHub
#%RAML 1.0
title: Notification Service
version: v1
baseUri: /notification/sendEmail:type: { POST: {item : Payload} }/sendSMS:type: { POST: {item : Payload} } payment.raml hosted with ❤ by GitHub
#%RAML 1.0
title: Payment Service
version: v1
baseUri: /payment/makePurchase:type: { POST: {item : PaymentOrder} }/getPruchaseById/{orderId}:type: { GET: {item : Payment} } 因此,现在我们已经很好地定义了端点,是时候重构我们的微服务了,仅使用 http 协议,并在代码中检查我们的 api 端点。
现在看🤴🏽👸🏻,最有趣的部分还没有到来 😁
由于我们一直在对我们所有的微服务进行码头化,因此知道哪些容器正在运行, 并且如果我们发出以下命令:docker-machine manger1
$ docker inspect <containerName | containerId > 这将返回我们有关该容器的作用的信息,前提是我们在容器创建时刻指定该信息,如果您一直在关注本系列,您已经注意到每个服务都有一个 所以现在我们需要停止并删除我们的服务,为什么因为我们要在标签标志中添加一个新标志,如下所示:start-service.shdocker run command.
$ docker run --name {service} -l=apiRoute='{route}' -p {host-port}:{container-port} --env-file env -d {service} 让我们看看为什么我们需要这个标志,为此,我的朋友们让我们深入了解 api-gateway 源代码,所以我们要看到👀的第一个文件是:api-gateway/config.js
const fs = require('fs')const serverSettings = {port: process.env.PORT || 8080,ssl: require('./ssl')
}const machine = process.env.DOCKER_HOST
const tls = process.env.DOCKER_TLS_VERIFY
const certDir = process.env.DOCKER_CERT_PATHif (!machine) {throw new Error('You must set the DOCKER_HOST environment variable')
}
if (tls === 1) {throw new Error('When using DOCKER_TLS_VERIFY=1 you must specify the property DOCKER_CERT_PATH for certificates')
}
if (!certDir) {throw new Error('You must set the DOCKER_CERT_PATH environment variable')
}const dockerSettings = {protocol: 'https',host: machine.substr(machine.indexOf(':', 0) + 3, machine.indexOf(':', 6) - 6),port: parseInt(machine.substr(-4), 10),checkServerIdentity: false,ca: fs.readFileSync(certDir + '/ca.pem'),cert: fs.readFileSync(certDir + '/cert.pem'),key: fs.readFileSync(certDir + '/key.pem'),version: 'v1.25'
}module.exports = Object.assign({}, { serverSettings, dockerSettings }) 在这里,我们设置 docker-settings 变量,因为我们将与我们的通信,但为了能够与该机器通信,我们需要将我们的环境正确设置为 manger1 docker 机器,我们可以在我们的终端中执行此操作执行以下命令:manager1 docker-machine
$ eval `docker-machine env manager1 一旦设置了我们的环境,我们将直接从nodejs连接到我们的docker-machie,为什么?因为我们正在获取正在运行的容器的信息,并且能够正确地将请求从我们的 API 网关代理到微服务。
那么我们如何从nodejs连接到我们的docker机器,首先我们需要安装一个调用到我们项目的nodejs模块,如下所示:dockerode
$ npm i -S dockerode --silent 在我们继续查看 API 网关的代码之前,首先让我们弄清楚什么是代理。
在计算机网络中,代理服务器是充当客户端请求的中介的服务器,这些请求从其他服务器寻求资源。— 维基百科
好的,现在让我们看看什么是 ES6 代理。
代理是 ES6 中一个有趣而强大的功能,它充当 API 使用者和对象之间的中介。简而言之,每当访问基础对象的属性时,都可以使用 来确定所需的行为。对象可用于为您的 配置陷阱,这些陷阱定义和限制访问底层对象的方式 — 来自《实用 ES6》一书,作者:尼古拉斯·贝瓦夸
ProxytargethandlerProxy
因此,现在我们知道代理在计算机网络中和作为 ES6 对象的含义,让我们看看docker.js
在我们的文件中,发生了很多魔术🔮✨,所以让我们看看发生了什么。docker.js
'use strict'
const Docker = require('dockerode')const discoverRoutes = (container) => {return new Promise((resolve, reject) => {// here we retrieve our dockerSettingsconst dockerSettings = container.resolve('dockerSettings')// we instatiate our docker object, that will communicate with our docker-machineconst docker = new Docker(dockerSettings)// function to avoid registering our database route and api route const avoidContainers = (name) => {if (/mongo/.test(name) || /api/.test(name)) {return false}return true}// here we register our routes in our ES6 proxy objectconst addRoute = (routes, details) => {routes[details.Id] = {id: details.Id,name: details.Names[0].split('').splice(1).join(''),route: details.Labels.apiRoute,target: getUpstreamUrl(details)}}// we generate the container url to be proxyconst getUpstreamUrl = (containerDetails) => {const {PublicPort} = containerDetails.Ports[0]return `http://${dockerSettings.host}:${PublicPort}`}// here we list the our running containersdocker.listContainers((err, containers) => {if (err) {reject(new Error('an error occured listing containers, err: ' + err))}const routes = new Proxy({}, {get (target, key) {console.log(`Get properties from -> "${key}" container`)return Reflect.get(target, key)},set (target, key, value) {console.log('Setting properties', key, value)return Reflect.set(target, key, value)}})containers.forEach((containerInfo) => {if (avoidContainers(containerInfo.Names[0])) {addRoute(routes, containerInfo)}})// and finally we resolve our routesresolve(routes)})})
}module.exports = Object.assign({}, {discoverRoutes}) 因此,首先,我们实例化我们的对象以便能够与我们的 docker 机器通信,然后我们创建我们的来存储我们的对象发现的所有路由并列出它,然后我们遍历发现的容器并使用容器详细信息注册我们的 对象,这就是为什么我们需要在启动 docker 容器时添加标签标志,因为,它可以为我们提供更多信息,而这种操作系统是向容器添加信息的一种方式,因此对我们来说,它可以帮助我们了解容器的用途。dockerproxy routes objectdockerroute
所以最后我们解析或路由对象在 .你可能会问我为什么使用 ES6 代理对象,这是因为我认为这可能是使用 ES6 代理对象的🤓😄一个很好的例子(因为我还没有看到很多 ES6 代理的例子),我们可以使用任何类型的对象来存储我们的路由,但代理对象可以帮助我们做更多的事情, 我们可以在 JavaScript 对象中看到中间件等代理,但这超出了本文的范围。server.js
所以现在让我们看看我们的文件,看看我们如何实现我们的路由:server.js
'use strict'
const express = require('express')
const proxy = require('http-proxy-middleware')
const spdy = require('spdy')
const morgan = require('morgan')
const helmet = require('helmet')
const cors = require('cors')
const status = require('http-status')const start = (container) => {return new Promise((resolve, reject) => {const {port, ssl} = container.resolve('serverSettings')const routes = container.resolve('routes')if (!routes) {reject(new Error('The server must be started with routes discovered'))}if (!port) {reject(new Error('The server must be started with an available port'))}const app = express()app.use(morgan('dev'))app.use(bodyparser.json())app.use(cors())app.use(helmet())app.use((err, req, res, next) => {reject(new Error('Bad Gateway!, err:' + err))res.status(status.BAD_GATEWAY).send('url not found!')next()})for (let id of Reflect.ownKeys(routes)) {const {route, target} = routes[id]app.use(route, proxy({target,changeOrigin: true,logLevel: 'debug'}))}if (process.env.NODE === 'test') {const server = app.listen(port, () => resolve(server))} else {const server = spdy.createServer(ssl, app).listen(port, () => resolve(server))}})
}module.exports = Object.assign({}, {start}) 在这里,我们要做的是创建一个然后我们循环我们的路由,并将其注册到应用程序中作为中间件,在该中间件中,我们正在应用另一个称为将请求代理到正确的微服务的中间件,最后我们启动我们的服务器。express apphttp-proxy-middleware
所以现在我们已经准备好在本地运行我们的 api-gateway 并运行超级集成测试,我们将调用 raml 文件中声明的所有端点,并通过 api-gateway 调用它。
但首先让我们重新创建我们的容器,为此,让我们创建一个名为的自动化脚本,该脚本为我们完成所有工作,如下所示:start_all_microservices.sh
#!/usr/bin/env basheval `docker-machine env manager1`array=('./movies-service''./cinema-catalog-service''./booking-service''./payment-service''./notification-service'
)# we go to the root of the project
cd ..for ((i = 0; i < ${#array[@]}; ++i)); do# we go to each foldercd ${array[$i]}sh ./start-service.sh# and we go back to the root again :Dcd ..
done 但是我们需要使用新的标签标志修改每个微服务,如下所示:start-service.sh
docker run --name booking-service -l=apiRoute='/booking' -p 3002:3000 --env-file env -d booking-service
docker run --name catalog-service -l=apiRoute='/cinemas' -p 3001:3000 --env-file env -d catalog-service
// and so on
# and once we have it, let's run the command
$ bash < start_all_microservice.sh 我们需要有这样的东西

带有新标签标志的新容器和新创建的容器,以便能够在我们的nodejs api-gateway中发现它,所以现在让我们使用以下命令运行我们的api-gateway:
$ npm start 这将在我们的“没有类似的地方”中启动一个服务器:127.0.0.1:8080,(我们心爱的本地主机💕)我们应该看到这样的东西:

API 网关控制台输出
接下来我们需要打开另一个终端并将其放置在我们的影院微服务项目中,并运行以下命令来执行我们的超大型集成测试 👩🏻 🔬👨🏾 🔬
$ npm run int-test 我们将有一些如下所示的输出:

在上控制台中,我们可以看到代理如何调度并重定向到相应的url:port,在下控制台中,我们可以看到所有测试是如何正确通过的。
我们需要对我们的 api-gateway 做一件事,那就是 dockerize 我们的 api-gateway,但让我告诉你,我无法 dockerize 它,我还在弄清楚如何从容器到 docker-machine(host)说话,如果你能弄清楚,欢迎你在下面发表评论, 与我们分享您发现并应用于Dockerize我们的API网关的内容,因此,与此同时,我仍在研究如何完成此任务,因此这就是为什么我们在“没有像127.0.0.1这样的地方”地址而不是Docker机器IP地址中进行集成测试的原因,所以请继续关注,我会告诉您我所发现的。
六、更新 API 网关docker化解决方案
再次您好,让我告诉您我发现了什么,以及如何解决此问题,因此让我们开始对 API 网关进行 docker化。
因此,按照我们的模式,让我们看一下我们的内部 api-gateway 文件夹,这个文件应该包含这样的内容:start-service.sh
#!/usr/bin/env bash
eval `docker-machine env manager1`
docker rm -f api-gateway-service
docker rmi api-gateway-service
docker image prune
docker volume prune
docker build -t api-gateway-service .
docker run --name api-gateway-service -v /Users/Cramirez/.docker/machine/machines/manager1:/certs --net='host' --env-file env -d api-gateway-service 在这里,我们添加了一些标志,所以让我们看看它们。
- 卷标志 在这里,我们将 docker 机器证书文件夹绑定到我们的容器,以便它能够读取这些证书
-v - net 标志在这里,我们告诉容器连接主机网络适配器,这会将 docker 机器 IP 绑定到我们的 API 网关容器,我们唯一需要关心的是可用的端口,以便我们可以运行我们的 API 网关,就我而言,我正在端口 8080 上执行 API 网关
--net
所以现在我们可以再次调用我们的超级骗子测试,但知道我们的 docker 机器 ip + api 网关端口,例如https://192.168.99.100:8080
七、是时候回顾一下了
我们做了什么...? 我们总结了下图:

我们在本文中只构建了 3 个服务:支付服务、通知服务和超级骗子 API 网关 😎 .
我们已经集成了微服务的所有功能,所以现在我们可以发出 GET 和 POST 请求,在这些请求中,我们的一些微服务具有数据验证、数据库交互、第三方服务交互等......总而言之,我们已经做了很多工作,但让我告诉你亲爱的读者,这不是该系列的结束,也许这可能会开始像权力的游戏一样,没有结局(冬天来了❄️,对我来说很多电视节目),但与我们的电影微服务系统有很大关系, 所以请继续关注一些有趣的东西。
我们通过 API 网关🤔实现了什么?
我们提供微服务安全性,仅在我们的API网关服务器中处理,这称为传输安全性 ✔️
我们做了一个半动态的容器服务发现,为什么是半?因为我们使用添加到容器中的 label 标志对路由进行硬编码,并且我们一个接一个地创建容器,但这有助于我们将请求重定向到正确的目标,这称为请求调度✔️和依赖关系解析 ✔️
我们已经在 NodeJS 中看到了很多开发,但我们可以做和学习的东西还有很多,这只是更高级编程的先睹为快。我希望这已经展示了一些有趣和有用的东西,你可以在你的工作流程中用于Docker和NodeJS。
相关文章:
构建 NodeJS 影院预订微服务并使用 docker 部署(04/4)
一、说明 构建一个微服务的电影网站,需要Docker、NodeJS、MongoDB,这样的案例您见过吗?如果对此有兴趣,您就继续往下看吧。 我们前几章的快速回顾 第一篇文章介绍了微服务架构模式,并讨论了使用微服务的优缺点。第二篇…...
SpringBootWeb案例 Part3
目录 1. 新增员工 1.1 需求 1.2 接口文档 1.3 思路分析 PostMapping RequestBody //把前端传递的JSON数据填充到实体类中 1.4 功能开发 1.5 功能测试 1.6 前后端联调 2. 文件上传 2.1 文件上传简介 Spring中提供了一个API:MultipartFile,使…...
C++中using 用法
C中的 using 关键字用于引入命名空间、类型别名和模板别名。以下是 using 关键字的几种常见用法及其中文解析: 1. 引入命名空间: using namespace std; 中文解析:引入 std 命名空间,使得命名空间中的成员在当前作用域内可直接使…...
window下jdk安装及更换jdk版本的一些问题。
目录 jdk安装jdk的选择。oracle的jdk怎么安装。openjdk怎么安装。 jdk的版本控制。更换jdk的一些问题。 jdk安装 jdk的选择。 目前有两种可选的jdk,oracle的和开源的Openjdk,这两种jdk的区别可以自行查阅,就结果而言,openjdk开源…...
GPT4模型架构的泄漏与分析
迄今为止,GPT4 模型是突破性的模型,可以免费或通过其商业门户(供公开测试版使用)向公众提供。它为许多企业家激发了新的项目想法和用例,但对参数数量和模型的保密却扼杀了所有押注于第一个 1 万亿参数模型到 100 万亿参…...
GEE/PIE遥感大数据处理与典型案例丨数据整合Reduce、云端数据可视化、数据导入导出及资产管理、机器学习算法等
目录 专题一:初识GEE和PIE遥感云平台 专题二:GEE和PIE影像大数据处理基础 专题三:数据整合Reduce 专题四:云端数据可视化 专题五:数据导入导出及资产管理 专题六:机器学习算法 专题七:…...
STM32--DMA
文章目录 DMA简介DMA特性 DMA框图DMA基本结构DMA请求数据宽度对齐DMA数据转运工程DMAADC多通道 DMA简介 直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的…...
mongodb和redis的用途
MongoDB和Redis都是常见的NoSQL数据库,它们有不同的特点和用途。 MongoDB的主要特点和用途: 数据存储:MongoDB是一种面向文档的数据库,以JSON样式的BSON文档(二进制JSON)的形式存储数据。它支持复杂的数据…...
【动手学深度学习】--18.图像增广
文章目录 图像增广1.常用的图像增广方法1.1翻转和裁剪1.2改变颜色1.3结合多种图像增广方法 2.使用图像增广进行训练3.训练 图像增广 官方笔记:图像增广 学习视频:数据增广【动手学深度学习v2】 图像增广在对训练图像进行一系列的随机变化之后ÿ…...
数据分析--统计学知识
描述型统计 描述统计 1.集中趋势 :众数、平均数、分位数 2.离散趋势: 极值(max)、极差(max-min)、平均差、方差、标准差、分位差 3.分布:峰泰、偏度 推理型统计 概率分布:离散型…...
matlab 计算点云协方差矩阵
目录 一、概述1、算法概述2、主要函数二、代码示例三、结果展示四、参数解析输入参数输出参数五、参考链接本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、概述...
python进阶之图像编程 pillow扩展库
一、概述 1.1pillow简介 Python Imaging Library (PIL)是python 下的图像处理模块,支持多种格式,并提供强大的图像处理功能,可以通过pip进行安装后使用。 1.2pillow具体应用 Pillow 库是 Python3 最常用的图像处理库,它支持多种图像格式&a…...
TiCDC Canal-JSON 消息接收示例(Java 版)
1.引言 业务程序经常会通过各式各样的缓存来提升用户的访问速度。 由于存在缓存,在一些实时性要求较高的场景中,需要在数据变更的同时将数据缓存进行更新或删除。 如果数据本身由其他业务部门提供,就无法在写入的同时做缓存的一致性处理。…...
SQLite、MySQL、PostgreSQL3个关系数据库之间的对比
引言 关系数据模型以行和列的表格形式组织数据,在数据库管理工具中占主导地位。今天还有其他数据模型,包括NoSQL和NewSQL,但是关系数据库管理系统(RDBMS)仍然占主导地位用于存储和管理全球数据。 本文比较了三种实现最…...
开源容灾备份软件,开源cdp备份软件
数据的安全性和完整性面临着硬件问题、黑客攻击、人为错误等各种威胁。在这种环境下,开源容灾备份软件应运而生,通过提供自动数据备份和恢复,有效地保证了公司的数据安全。 一、开源容灾备份软件的定义和作用 开源容灾备份软件是一种基于开源…...
Java合并区间
问题: 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。 示例: 示例 1ÿ…...
前端面试:【代码质量与工程实践】单元测试、集成测试和持续集成
在现代软件开发中,确保代码质量是至关重要的。单元测试、集成测试和持续集成是关键的工程实践,用于提高代码的可靠性和可维护性。本文将深入探讨这些概念,以及它们如何在软件开发中发挥作用。 1. 单元测试(Unit Testing࿰…...
2023/8/17总结
项目完善: 算法推荐 item-CF 算法推荐我主要写的是协同过滤算法,然后协同过滤算法分成俩种—— 基于用户的 user-CF 基于物品的 item-CF 因为害怕用户冷启动,和数据量的原因 我选择了 item-CF 主要思路是——根据用户的点赞列表&…...
REDIS 7 教程 数据类型-进阶篇
⑥ *位图 bitmap 1. 理论 由0和1 状态表现的二进制位的bit 数组。 说明:用String 类型作为底层数据结构实现的一种统计二值状态的数据类型 位图本质是数组,它是基于String 数据类型的按位操作。该数组由多个二进制位组成,每个二进制位都对应一个偏…...
图文并茂:Python Tkinter从入门到高级实战全解析
目录 介绍什么是Tkinter?准备工作第一个Tkinter程序界面布局事件处理补充知识点 文本输入框复选框和单选框列表框弹出对话框 综合案例:待办事项列表总结 介绍 欢迎来到本篇文章,我们将带您深入了解如何在Python中使用Tkinter库来创建图形用…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
恶补电源:1.电桥
一、元器件的选择 搜索并选择电桥,再multisim中选择FWB,就有各种型号的电桥: 电桥是用来干嘛的呢? 它是一个由四个二极管搭成的“桥梁”形状的电路,用来把交流电(AC)变成直流电(DC)。…...
AxureRP-Pro-Beta-Setup_114413.exe (6.0.0.2887)
Name:3ddown Serial:FiCGEezgdGoYILo8U/2MFyCWj0jZoJc/sziRRj2/ENvtEq7w1RH97k5MWctqVHA 注册用户名:Axure 序列号:8t3Yk/zu4cX601/seX6wBZgYRVj/lkC2PICCdO4sFKCCLx8mcCnccoylVb40lP...
Qt的学习(一)
1.什么是Qt Qt特指用来进行桌面应用开发(电脑上写的程序)涉及到的一套技术Qt无法开发网页前端,也不能开发移动应用。 客户端开发的重要任务:编写和用户交互的界面。一般来说和用户交互的界面,有两种典型风格&…...
字符串哈希+KMP
P10468 兔子与兔子 #include<bits/stdc.h> using namespace std; typedef unsigned long long ull; const int N 1000010; ull a[N], pw[N]; int n; ull gethash(int l, int r){return a[r] - a[l - 1] * pw[r - l 1]; } signed main(){ios::sync_with_stdio(false), …...
验证redis数据结构
一、功能验证 1.验证redis的数据结构(如字符串、列表、哈希、集合、有序集合等)是否按照预期工作。 2、常见的数据结构验证方法: ①字符串(string) 测试基本操作 set、get、incr、decr 验证字符串的长度和内容是否正…...
FTXUI::Dom 模块
DOM 模块定义了分层的 FTXUI::Element 树,可用于构建复杂的终端界面,支持响应终端尺寸变化。 namespace ftxui {...// 定义文档 定义布局盒子 Element document vbox({// 设置文本 设置加粗 设置文本颜色text("The window") | bold | color(…...
