萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 資料庫 >> MySQL 教學

Docker + Node.js + Kafka + Redis + MySQL 搭建秒殺系統實戰

架構概覽

本文構建一個簡易秒殺系統,展示高併發下庫存扣減、訊息佇列解耦、異步落庫的核心流程。技術棧:

  • JMeter:模擬 2000 併發請求(5 秒內)
  • Seckill Service (Node.js/Express):接收請求、Redis 扣減庫存、發送 Kafka 訊息
  • Redis:計數器 counter 存儲庫存,WATCH/MULTI/EXEC 樂觀鎖保證原子性
  • Kafka + Zookeeper:訊息佇列,Topic CAR_NUMBER 接收成功扣減事件
  • Kafka Consumer (Node.js):消費訊息、寫入 MySQL 落庫
  • MySQL:持久化秒殺成功記錄

環境搭建

1. JMeter 壓測配置

  • Thread Group:2000 執行緒,Ramp-Up 5 秒
  • HTTP Request:POST /seckill,目標 Host/Port 配置

2. Docker Compose 一鍵啟動基礎設施

version: '2'
services:
  zookeeper:
    image: zookeeper:3.4
    ports: ["2181:2181"]
  
  kafka:
    image: wurstmeister/kafka:latest
    ports: ["9092:9092"]
    environment:
      KAFKA_ADVERTISED_HOST_NAME: <本機IP>  # 必須設為實際 IP
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
  
  redis:
    image: redis:3.2
    ports: ["6379:6379"]
  
  mysql:
    image: mysql:5.7
    ports: ["3306:3306"]
    environment:
      MYSQL_ROOT_PASSWORD: <自定義密碼>

啟動:docker-compose up -d

3. 初始化資料

# Kafka 建立 Topic
docker exec -it <kafka容器> kafka-topics.sh --create --topic CAR_NUMBER --partitions 1 --replication-factor 1 --zookeeper zookeeper:2181

# Redis 設定初始庫存 100
docker exec -it <redis容器> redis-cli SET counter 100

# MySQL 建表
CREATE TABLE seckill (id BIGINT AUTO_INCREMENT PRIMARY KEY, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP);

核心代碼解析

Seckill Service (Express)

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

const app = express();
const client = redis.createClient();
const producer = new kafka.Producer(new kafka.KafkaClient({kafkaHost: 'localhost:9092'}));

app.post('/seckill', (req, res) => {
    // 1. 監視 counter
    client.watch('counter');
    
    // 2. 讀取庫存
    client.get('counter', (err, stock) => {
        if (stock > 0) {
            // 3. 啟動事務扣減
            client.multi()
                .decr('counter')
                .exec((err, replies) => {
                    if (replies === null) {
                        // 4. 衝突重試
                        return fn(); // 遞歸重試
                    }
                    // 5. 發送 Kafka 訊息
                    producer.send([{topic: 'CAR_NUMBER', messages: 'success'}], (err, data) => {});
                    res.send('success');
                });
        } else {
            res.send('sold out');
            client.quit();
        }
    });
});

app.listen(3000);

Kafka Consumer

const kafka = require('kafka-node');
const mysql = require('mysql');

const consumer = new kafka.Consumer(
    new kafka.KafkaClient({kafkaHost: 'localhost:9092'}),
    [{topic: 'CAR_NUMBER', partition: 0}],
    {autoCommit: true}
);

const db = mysql.createConnection({host: 'localhost', user: 'root', password: 'pwd', database: 'seckill'});

consumer.on('message', (message) => {
    db.query('INSERT INTO seckill (create_time) VALUES (NOW())');
});

測試結果驗證

  • JMeter:2000 請求,成功率 100%
  • Redis counter:從 100 降為 0
  • MySQL seckill 表:精確插入 100 筆記錄
  • 無超賣/少賣:樂觀鎖 + 單線程 Redis 保證一致性

生產環境擴展考量

  • Redis Cluster / Sentinel 高可用
  • Kafka 多 Partition + Consumer Group 水平擴展
  • 資料庫連接池、批次寫入優化
  • 冪等性設計(防重複消費)
  • 限流、熔斷、降級(Sentinel、Hystrix)
  • 分散式鎖(Redisson)替代樂觀鎖應對極高併發

專案原始碼:MockSecKill

copyright © 萬盛學電腦網 all rights reserved