本文構建一個簡易秒殺系統,展示高併發下庫存扣減、訊息佇列解耦、異步落庫的核心流程。技術棧:
counter 存儲庫存,WATCH/MULTI/EXEC 樂觀鎖保證原子性CAR_NUMBER 接收成功扣減事件/seckill,目標 Host/Port 配置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
# 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);
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);
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())');
});
專案原始碼:MockSecKill