基于MySQL数据库和Koa2框架解决抢票遇到的高并发问题
学了这么久nodejs,一直说NodeJS适合运用在高并发、I/O密集、少量业务逻辑的场景,今天终于派上了用场,现在面临的问题是如何处理新生们听我们宣讲会抢票的问题。
一开始的思路
因为抢票肯定要控制票的数量,所以,我一开始的思路是每次抢票之后,获取数据库中票的总数量,然后跟后台写的票的常量做对比,如果数据库中存的票的数量小于自定义票的常量,那就返回抢票结果和(数据库中票的数量+1)就是抢票者抢到的票编号,同时将票和编号和用户id存入数据库。
下面是封装的数据库的操作
/* 高并发接口测试 */
const config = require('../config/Database');
// sql查询表中数据总条:SELECT COUNT(*) FROM 表名称。
// 返回大于 20 岁的人数:
// SELECT COUNT(*) FROM Personsinfo WHERE Age>20
// 增
exports.add = function (addSqlParams) {
let addSql = 'INSERT INTO briefing(brief_session,brief_time,user_cloudId,brief_sign,brief_number) VALUES(?,?,?,?,?)';
return new Promise(function (resolve, reject) {
config.query(addSql, addSqlParams, function (err, result) {
resolve(result);
if (err) {
console.log(err);
reject(err.message);
}
console.log('INSERT ID:', result);
});
});
}
// 获得数据库中的总票数
exports.get_ticket = function (addSqlParams) {
let addSql = 'SELECT count(*) AS count FROM briefing WHERE brief_session=' + "'" + addSqlParams + "'";
return new Promise(function (resolve, reject) {
config.query(addSql, addSqlParams, function (err, result) {
resolve(result[0]);
if (err) {
console.log(err);
reject(err.message);
}
console.log('INSERT ID:', result);
});
});
}
下面是业务逻辑层的操作
/* 高并发接口测试 */
const DB = require("../dbhelper/db_high");
const uuid = require('uuid-v4');
exports.high = async (ctx, next) => {
// 这是票的个数,从数据库里面获取
let ticket_count = 30;
// 前两个参数要从数据库中获取
let brief_session = "第三场"
let brief_time = "2019-07-15 15:16:45"
// 后三个参数,第一个是前端传回来的,后两个是自己定的
let user_cloudId = uuid().replace(/-/g, '')
let brief_sign = 0;
// 获取数据库中现在有的票数
let result1 = await DB.get_ticket(brief_session);
console.log(result1.count)
if (result1.count < ticket_count) {
result1.count = result1.count + 1;
let addparams = [brief_session, brief_time, user_cloudId, brief_sign, result1.count];
console.log(result1.count)
let result = await DB.add(addparams);
console.log(result);
ctx.response.body = {
brief_number: result1.count,
state: '抢票成功',
code: '1'
}
} else {
ctx.response.body = {
state: '抢票失败',
code: '0'
}
}
}
// 关闭抢票接口的同时 将brief_number清零
我用网页进行单独测试的时候,能够一张一张的返回正确结果
网页测试
可是当用高并发测试工具时候
高并发测试
高并发测试结果
就会发现,数据库中会出现重复的数据
一张票被好多个人同时抢上,我突然意识到了自己的问题,当用户抢票时直接对数据库进行数量的读取操作,不仅耗费了时间,而且,会出现逻辑上来不及判断,导致一张票被好多人抢的问题
正确的思路
正确的思路应该是设置一个全局变量brief_number 来控制票的编号
/* 高并发接口测试 */
const DB = require("../dbhelper/db_high");
const uuid = require('uuid-v4');
let brief_number = 0;
exports.high = async (ctx, next) => {
// 这是票的个数,从数据库里面获取
let ticket_count = 10;
// 前两个参数要从数据库中获取
let brief_session = "第三场"
let brief_time = "2019-07-15 15:16:45"
// 后三个参数,第一个是前端传回来的,后两个是自己定的
let user_cloudId = uuid().replace(/-/g, '')
let brief_sign = 0;
// 获取数据库中现在有的票数
// let result1 = await DB.get_ticket(brief_session);
// console.log(result1.count)
if (brief_number < ticket_count) {
brief_number = brief_number + 1;
let addparams = [brief_session, brief_time, user_cloudId, brief_sign, brief_number];
console.log(brief_number)
let result = await DB.add(addparams);
console.log(result);
ctx.response.body = {
brief_number: brief_number,
state: '抢票成功',
code: '1'
}
} else {
ctx.response.body = {
state: '抢票失败',
code: '0'
}
}
}
// 关闭抢票接口的同时 将brief_number清零
如图所示,当处理5000个人的并发抢100张票时候可以将响应时间控制在两秒左右,四秒以下
高并发测试
正宗的解决思路
上面那个用全局变量的方法虽然快,但是有一个问题,如果并发量到达一定程度的话,服务器崩掉重启之后,会将原来的抢票信息清空,不过一般配一个性能好一点的服务器不会出现这样的问题。为了保险起见,还是使用redis数据库。
解决思路:将票的总数存入redis数据库,每次用户抢票比较redis数据库中的数据总量,并将抢票信息存入数据库。速度上虽然比不上内存,但是一定比存入MySQL数据库比较强多了。
网友评论