nodejs之簡單的秒殺系統實現(mysql、redis、kafka、zookeeper、postman或docker)

nodejs之簡單的秒殺系統實現(mysql、redis、kafka、zookeeper或docker)

一:環境

1.一臺阿里雲服務器
2.雲服務器需要安裝redis、kafka、mysql、zookeeper環境
3.一臺本地電腦需要安裝postman
注:kafka與zookeeper的安裝和配置請參考:https://blog.csdn.net/wushichao0325/article/details/84993081 或者使用docker直接拉去鏡像安裝。

二:工具說明

1.postman:用於使用其中的(Run)實現併發測試
2.redis:用於存放商品信息,這裏只創建一個counter字段,使用redis的命令set設置個初始值。100,即商品總量。模擬數據爲:counter=100
3.kafka:結合zookeeper使用,將秒殺成功的結果生成一個producer,待consumer去消費及同步數據到mysql。模擬數據爲:創建一個topic名爲PRODUCT_NUMBER。
4.mysql:存放最終的秒殺結果。模擬數據爲:創建一個seckill數據庫,再創建一個seckill表,表字段爲id自增和date存放秒殺時間和uid存放用戶id。

三:代碼實現

1.代碼目錄
在這裏插入圖片描述
2.node依賴包
在這裏插入圖片描述
3.seckillService.js
接收秒殺請求,並將秒殺結果及用戶信息發送個kafka的producer。

var express=require("express"),
    redis=require("redis"),
    kafka=require('kafka-node'),
    Producer=kafka.Producer,
    kafkaClient=new kafka.Client(),
    producer=new Producer(kafkaClient);
    count=0;
    app=express();
    config=require("./config/redis.json");
var bodyParser=require('body-parser');
app.use(express.json());
app.use(bodyParser.json({limit: '1mb'}));  //body-parser 解析json格式數據
app.use(bodyParser.urlencoded({            //此項必須在 bodyParser.json 下面,爲參數編碼
    extended: true
}));
app.all("*", function (req, res, next) {
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
    res.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
    res.setHeader("X-Powered-By",' 3.2.1');
    next();
});
app.post('/seckill',function(req,res){
    console.log('count='+count++);
    let fn=function(optionalClient){
        let client;
        if(optionalClient=='undefined'||optionalClient==null){
            client=redis.createClient(config);
        }else{
            client=optionalClient;
        }
        client.on('error',function(er){
            console.error(er.stack);
            client.end(true);
        });
        client.watch("counter");//監聽counter字段
        client.get("counter",function(err,reply){
            if(parseInt(reply)>0){
                let multi=client.multi();
                multi.decr('counter');//更新redis的counter數量減一。
                multi.exec(function(err,replies){
                    if(replies==null){//counter字段正在操作中,等待counter被其他釋放
                        console.log("counter被使用");
                        fn(client);
                    }else{
                        var args = {
                            openid: 'b05NZ2Y1WjbE9fRV9MZTBWWQ==',
                            seckillTime: '2018-12-12 00:00:01',
                        }
                        let payload=[{
                            topic:'PROUDCT_NUMBER',
                            messages:[JSON.stringify(args)],
                            key:"seckill",
                            partition:0
                        }];
                        console.log("payload:",payload);
                        producer.send(payload,function(err,data){
                            console.log(data);
                        });
                        res.send(replies);
                        client.end(true);
                    }
                })

            }else{
                console.log("已經賣光了");
                res.send("已經賣光了");
                client.end(true);
            }
        });
    }
    fn(null);
});
app.listen(8888, "0.0.0.0",function () {
    console.log(`Please link connectorServer http://localhost:8888`);
});

4.seckill_kafka_consumer.js
消費者消費生產者的數據,並將數據同步到mysql中。

var kafka=require('kafka-node'),
    Consumer=kafka.Consumer,
    client=new kafka.Client(),
    consumer=new Consumer(
        client,
        [
            {topic:'CAR_NUMBER',partition:0}
        ],
        {
            autoCommit:false,
            fetchMaxWaitMs: 1000,
            fetchMaxBytes: 1024 * 1024,
            fromOffset: true
        }
    );
var mysql=require('mysql');
var connection=mysql.createConnection({
    "host":"**.***.***.**",
    "user":"root",
    "password":"password",
    "database":"seckill"
});
connection.connect();
consumer.on('message',function(message){
    console.log("得到的生產者的數據爲:",message);
    let value=JSON.parse(message.value);
    connection.query('insert into seckill set ?',{date:new Date(),uid:value.openid},function(error,results,fields){
        if(error){
            console.error(error);
        }
        console.log("插入數據庫成功");
    });
})

四:啓動項目

1.啓動雲服務器的redis
2.啓動雲服務器的zookeeper
3.啓動雲服務器的kafka
4.將項目部署到雲服務器中。
5.進入項目根目錄,啓動seckillService.js和seckill_kafka_consumer.js,我是使用pm2去管理這兩個服務。

五:測試

打開postman如圖:
在這裏插入圖片描述
先將這個鏈接保存一個文件,然後點擊Runner按鈕。
如圖:
在這裏插入圖片描述
可以使用post請求:http://seckill.ykplay.com/seckill 測試。
服務器打印信息如下:
seckillService.js
在這裏插入圖片描述
seckill_kafka_consumer.js
在這裏插入圖片描述
有這些信息證明秒殺功能成功實現了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章