Docker + Nodejs + Kafka + Redis + MySQL搭建简单秒杀环境

下面我将详细讲解“Docker + Nodejs + Kafka + Redis + MySQL搭建简单秒杀环境”的完整攻略。

1. 前置条件

在开始搭建秒杀环境之前,需要先安装Docker和Docker Compose,并确保已经熟悉Docker和Docker Compose的基本使用。

2. 搭建过程

2.1 新建项目目录

首先,新建一个项目目录,比如seckill

$ mkdir seckill
$ cd seckill

2.2 新建Docker Compose文件

在项目目录中新建一个名为docker-compose.yml的文件,内容如下:

version: '3'

services:
  mysql:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: seckill
  redis:
    image: redis:latest
  kafka:
    image: spotify/kafka
    ports:
      - "2181:2181"
      - "9092:9092"
    environment:
      ADVERTISED_HOST: localhost
      ADVERTISED_PORT: "9092"
  node:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      - kafka
      - redis
      - mysql
    environment:
      MYSQL_HOST: mysql
      MYSQL_PORT: 3306
      MYSQL_USER: root
      MYSQL_PASSWORD: root
      MYSQL_DATABASE: seckill
      REDIS_HOST: redis
      REDIS_PORT: 6379
      KAFKA_HOST: kafka
      KAFKA_PORT: 9092

该文件定义了5个服务:

  • mysql:使用MySQL 5.7的镜像,设置了root用户的密码为root,创建了一个名为seckill的数据库。
  • redis:使用最新版的Redis镜像。
  • kafka:使用Spotify的Kafka镜像,暴露了2181和9092两个端口,设置了Kafka的ADVERTISED_HOSTlocalhostADVERTISED_PORT9092
  • node:基于项目目录下的Dockerfile进行构建,暴露3000端口,依赖于mysql、redis和kafka三个服务,并设置了环境变量以供应用程序使用。

2.3 新建Dockerfile

在项目目录中新建一个名为Dockerfile的文件,内容如下:

FROM node:latest

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 3000

CMD [ "npm", "run", "start:docker" ]

该文件定义了一个基于最新版的Node.js镜像的Dockerfile,其中:

  • 使用WORKDIR指令创建了一个/app目录并将其作为工作目录。
  • 使用COPY指令将package*.json文件复制到工作目录中。
  • 使用RUN指令执行npm install命令安装应用程序的依赖。
  • 使用COPY指令将当前目录下的所有文件复制到工作目录中。
  • 使用EXPOSE指令暴露了应用程序监听的端口。
  • 使用CMD指令指定容器启动时运行的命令。

2.4 编写应用程序

在项目目录下创建一个src目录,并在该目录下创建一个名为index.js的文件,内容如下:

const kafka = require('kafka-node')
const redis = require('redis')
const mysql = require('mysql')
const express = require('express')

const app = express()

const mysqlHost = process.env.MYSQL_HOST
const mysqlPort = process.env.MYSQL_PORT || 3306
const mysqlUser = process.env.MYSQL_USER || 'root'
const mysqlPassword = process.env.MYSQL_PASSWORD
const mysqlDatabase = process.env.MYSQL_DATABASE

const redisHost = process.env.REDIS_HOST || 'localhost'
const redisPort = process.env.REDIS_PORT || 6379

const kafkaHost = process.env.KAFKA_HOST || 'localhost'
const kafkaPort = process.env.KAFKA_PORT || 9092

const redisClient = redis.createClient(redisPort, redisHost)
const kafkaClient = new kafka.KafkaClient({ kafkaHost: `${kafkaHost}:${kafkaPort}` })
const producer = new kafka.Producer(kafkaClient)

producer.on('ready', () => {
  console.log('Kafka producer is ready')
})

producer.on('error', (error) => {
  console.error('Kafka producer encountered error:', error)
})

const connection = mysql.createConnection({
  host: mysqlHost,
  port: mysqlPort,
  user: mysqlUser,
  password: mysqlPassword,
  database: mysqlDatabase,
})

connection.connect((error) => {
  if (error) {
    console.error('MySQL connection failed:', error)
  } else {
    console.log('MySQL connection is ready')
  }
})

const getRemainingSeconds = (endTime) => {
  const remainingSeconds = endTime - Math.floor(Date.now() / 1000)
  return remainingSeconds > 0 ? remainingSeconds : 0
}

app.get('/seckill/:itemId', async (req, res, next) => {
  const itemId = req.params.itemId

  // check if the item is in stock
  const stockName = `stock:${itemId}`
  redisClient.get(stockName, async (error, stock) => {
    if (error) {
      console.error('Failed to retrieve stock information from Redis:', error)
      res.status(500).send('Internal Server Error')
      return
    }

    if (!stock) {
      console.log(`Item ${itemId} is out of stock`)
      res.status(404).send('Not Found')
      return
    }

    // check if the item is being snapped up
    const snapUpName = `snap_up:${itemId}`
    redisClient.get(snapUpName, async (error, snapUp) => {
      if (error) {
        console.error('Failed to retrieve snap-up information from Redis:', error)
        res.status(500).send('Internal Server Error')
        return
      }

      if (snapUp) {
        console.log(`Item ${itemId} is being snapped up`)
        res.status(409).send('Conflict')
        return
      }

      // prepare to snap up the item
      redisClient.incr(snapUpName, async (error, snapUp) => {
        if (error) {
          console.error('Failed to increment snap-up information in Redis:', error)
          res.status(500).send('Internal Server Error')
          return
        }

        if (snapUp > 1) {
          console.log(`Item ${itemId} has already been snapped up`)
          res.status(409).send('Conflict')
          return
        }

        // decrement the item's available stock
        redisClient.decr(stockName, async (error, stock) => {
          if (error) {
            console.error('Failed to decrement stock information in Redis:', error)
            res.status(500).send('Internal Server Error')
            return
          }

          if (stock < 0) {
            // roll back the snap-up
            redisClient.del(snapUpName, async (error) => {
              if (error) {
                console.error('Failed to delete snap-up information from Redis:', error)
              }

              console.log(`Item ${itemId} has run out of stock`)
              res.status(404).send('Not Found')
            })
          } else {
            // create the order in MySQL
            const order = { item_id: itemId }
            connection.query('INSERT INTO orders SET ?', order, async (error) => {
              if (error) {
                console.error('Failed to create order in MySQL:', error)

                // roll back the snap-up
                redisClient.del(snapUpName, async (error) => {
                  if (error) {
                    console.error('Failed to delete snap-up information from Redis:', error)
                  }

                  // restore the item's stock
                  redisClient.incr(stockName, async (error) => {
                    if (error) {
                      console.error('Failed to increment stock information in Redis:', error)
                    }
                  })

                  res.status(500).send('Internal Server Error')
                })
              } else {
                // publish the order information to Kafka
                producer.send([{ topic: 'orders', messages: JSON.stringify(order) }], (error) => {
                  if (error) {
                    console.error('Failed to publish order information to Kafka:', error)
                  }

                  // set the remaining seconds of snap-up time to Redis
                  const remainingSeconds = getRemainingSeconds(1619799120) // 2021/5/1 0:12:00
                  redisClient.set(snapUpName, remainingSeconds, async (error) => {
                    if (error) {
                      console.error('Failed to set snap-up information to Redis:', error)
                    }

                    console.log(`Item ${itemId} has been snapped up`)
                    res.status(200).send('OK')
                  })
                })
              }
            })
          }
        })
      })
    })
  })
})

app.listen(3000, () => {
  console.log('Server is listening on port 3000')
})

该文件是一个基于Node.js的Express应用程序,使用了Kafka、Redis和MySQL的模块,并暴露了一个名为/seckill/:itemId的GET接口,该接口接受一个itemId参数表示秒杀的商品ID。在接口执行过程中,它会首先从Redis中查询商品的库存信息,如果库存不足,就会返回404 Not Found;如果商品正在被抢购中,则返回409 Conflict;如果商品可以抢购,则在Redis中标记该商品正在被抢购,然后将商品的库存量减少1,最后将抢购订单记录到MySQL中,同时将订单信息发布到Kafka中进行异步处理,返回200 OK

2.5 启动容器

在项目根目录下使用以下命令启动Docker容器:

$ docker-compose up

2.6 示例

上述过程完成后,可以使用一些工具来模拟抢购行为。比如使用curl命令,执行以下步骤:

  1. 查询商品1的库存信息:
$ curl http://localhost:3000/seckill/1
Not Found
  1. 查询商品1的库存信息(再次):
$ curl http://localhost:3000/seckill/1
Not Found
  1. 查询商品2的库存信息:
$ curl http://localhost:3000/seckill/2
Not Found
  1. 查询商品1的库存信息:
$ curl http://localhost:3000/seckill/1
Not Found
  1. 查询商品2的库存信息:
$ curl http://localhost:3000/seckill/2
Not Found
  1. 发起商品1的抢购请求:
$ curl http://localhost:3000/seckill/1
OK
  1. 再次发起商品1的抢购请求:
$ curl http://localhost:3000/seckill/1
Conflict
  1. 发起商品2的抢购请求:
$ curl http://localhost:3000/seckill/2
OK
  1. 查询商品1的库存信息:
$ curl http://localhost:3000/seckill/1
Not Found
  1. 查询商品2的库存信息:
$ curl http://localhost:3000/seckill/2
Not Found

以上步骤可以验证秒杀环境的正常工作。

总结

以上就是使用Docker和Node.js等技术搭建简单秒杀环境的完整攻略。在搭建过程中,我们通过Docker Compose统一管理了MySQL、Redis、Kafka和Node.js等服务的启动和配置,使得整个环境的部署和维护非常方便。同时,我们还演示了如何通过Node.js编写一个模拟秒杀的应用程序,使用了Kafka、Redis和MySQL等技术,并利用Docker Compose将这些组件一起协同工作,以供开发、测试和演示等目的使用。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Docker + Nodejs + Kafka + Redis + MySQL搭建简单秒杀环境 - Python技术站

(0)
上一篇 2023年6月8日
下一篇 2023年6月8日

相关文章

  • 前端MVVM框架解析之双向绑定

    前端MVVM框架是现代化Web开发过程中不可或缺的一部分。其中MVVM中的双向绑定技术同样非常重要,可以显著提高前端开发的效率和可维护性。本文将对前端MVVM框架中双向绑定的原理和实现进行详细解析,同时提供两个示例以供参考。 双向绑定的基本原理 双向绑定的基本思想是将数据和UI双向绑定,使得UI的变化能够自动更新数据,而数据的变化也能够自动更新UI。简单来说…

    node js 2023年6月8日
    00
  • nodejs socket实现的服务端和客户端功能示例

    我来为您讲解一下“nodejs socket实现的服务端和客户端功能示例”的完整攻略,希望能对您有所帮助。 简介 Node.js是一种基于Chrome V8引擎的JavaScript运行环境,它是一个开放源代码、跨平台的服务器端运行环境。在Node.js中,socket可以用来实现服务器与客户端之间的数据传输。本文将介绍Node.js中如何使用socket实…

    node js 2023年6月8日
    00
  • Linux编程之ICMP洪水攻击

    ICMP洪水攻击是一种利用大量ICMP数据包使目标主机网络资源占用充足而导致服务不可用的攻击方式。在Linux系统中使用C语言编写程序实现ICMP洪水攻击主要包含以下步骤: 1. 准备工作 首先需要安装libpcap开发环境,libpcap提供了底层操作网络数据包的接口。在Ubuntu上,可以通过下面的命令安装: sudo apt-get install l…

    node js 2023年6月8日
    00
  • npm报错:无法将”npm”项识别为cmdlet、函数、脚本文件或可运行程序的名称

    当我们在使用npm命令时,有时可能会遇到以下报错: 无法将"npm"项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。 这个错误是因为电脑没有安装npm或npm没有配置到环境变量中所致。 以下是解决这个问题的方法: 方法一:安装Node.js npm是随Node.js一…

    node js 2023年6月8日
    00
  • node.js中Socket.IO的进阶使用技巧

    下面是“node.js中Socket.IO的进阶使用技巧”的完整攻略,包含两条示例说明。 Socket.IO概述 Socket.IO是一个实时应用程序框架,它使得在Web浏览器和服务器之间进行实时双向通信变得非常容易。它允许在混合Websockets、HTTP请求和轮询之间动态选择最佳的通信通道。在Node.js中,Socket.IO利用了底层的EventE…

    node js 2023年6月8日
    00
  • 在nodejs中创建child process的方法

    当我们需要在Node.js应用程序中执行一些长时间运行的任务或与其他应用程序交互时,我们可以使用child process模块创建子进程。 在Node.js中创建子进程,可以使用child_process模块。该模块提供了4个不同的方法。他们分别是: exec spawn fork execFile 下面我们分别讲解这4个方法。 exec方法 exec()方…

    node js 2023年6月8日
    00
  • Nodejs极简入门教程(三):进程

    下面是Nodejs极简入门教程(三):进程的详细讲解攻略。 什么是进程 在操作系统中,进程是指正在运行的程序。它是一个独立的执行单元,一个程序会启动一个或多个进程。每个进程都是由操作系统来管理和调度的。 进程的特点: 独立性:进程的执行是互相独立的,一个进程不会影响另一个进程。 动态性:进程的创建和撤销都是动态的,一个进程可以创建另一个进程,同时也可以被终止…

    node js 2023年6月8日
    00
  • vue init webpack 建vue项目报错的解决方法

    问题描述:在使用vue init webpack命令创建vue项目时,可能会遇到以下错误提示: AssertionError [ERR_ASSERTION]: Task function must be specified TypeError: Cannot read property ‘apply’ of undefined 这种错误可能是由于 vue-c…

    node js 2023年6月8日
    00
合作推广
合作推广
分享本页
返回顶部