Redis应用实战 - 秒杀场景(Node.js版本)
有志者,事竟成!如果你在学习数据库,那么本文《Redis应用实战 - 秒杀场景(Node.js版本)》,就很适合你!文章讲解的知识点主要包括MySQL、分布式、Redis、Node.js、秒杀,若是你对本文感兴趣,或者是想搞懂其中某个知识点,就请你继续往下看吧~
写在前面
公司随着业务量的增加,最近用时几个月时间在项目中全面接入
CREATE TABLE `seckill_goods` (
`id` INTEGER NOT NULL auto_increment,
`fk_good_id` INTEGER,
`amount` INTEGER,
`start_time` DATETIME,
`end_time` DATETIME,
`is_valid` TINYINT ( 1 ),
`comment` VARCHAR ( 255 ),
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NOT NULL,
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;CREATE TABLE `orders` (
`id` INTEGER NOT NULL auto_increment,
`order_no` VARCHAR ( 255 ),
`good_id` INTEGER,
`user_id` INTEGER,
`status` ENUM ( '-1', '0', '1', '2' ),
`order_type` ENUM ( '1', '2' ),
`scekill_id` INTEGER,
`comment` VARCHAR ( 255 ),
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NOT NULL,
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;CREATE TABLE `goods` (
`id` INTEGER NOT NULL auto_increment,
`name` VARCHAR ( 255 ),
`thumbnail` VARCHAR ( 255 ),
`price` INTEGER,
`status` TINYINT ( 1 ),
`stock` INTEGER,
`stock_left` INTEGER,
`description` VARCHAR ( 255 ),
`comment` VARCHAR ( 255 ),
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NOT NULL,
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;产品表在此次业务中不是重点,以下逻辑都以
INSERT INTO `redis_app`.`seckill_goods` (
`id`,
`fk_good_id`,
`amount`,
`start_time`,
`end_time`,
`is_valid`,
`comment`,
`created_at`,
`updated_at`
)
VALUES
(
1,
1,
200,
'2020-06-20 00:00:00',
'2023-06-20 00:00:00',
1,
'...',
'2020-06-20 00:00:00',
'2021-06-22 10:18:16'
);秒杀接口开发
首先,说一下
// 引入moment库处理时间相关数据
const moment = require('moment');
// 引入数据库model文件
const seckillModel = require('../../dbs/mysql/models/seckill_goods');
const ordersModel = require('../../dbs/mysql/models/orders');
// 引入工具函数或工具类
const UserModule = require('../modules/user');
const { random_String } = require('../../utils/tools/funcs');
class Seckill {
/**
* 秒杀接口
*
* @method post
* @param good_id 产品id
* @param accessToken 用户Token
* @param path 秒杀完成后跳转路径
*/
async doSeckill(ctx, next) {
const body = ctx.request.body;
const accessToken = ctx.query.accessToken;
const path = body.path;
// 基本参数校验
if (!accessToken || !path) { return ctx.throwException(20001, '参数错误!'); };
// 判断此产品是否加入了抢购
const seckill = await seckillModel.findOne({
where: {
fk_good_id: ctx.params.good_id,
}
});
if (!seckill) { return ctx.throwException(30002, '该产品并未有抢购活动!'); };
// 判断是否有效
if (!seckill.is_valid) { return ctx.throwException(30003, '该活动已结束!'); };
// 判单是否开始、结束
if(moment().isBefore(moment(seckill.start_time))) {
return ctx.throwException(30004, '该抢购活动还未开始!');
}
if(moment().isAfter(moment(seckill.end_time))) {
return ctx.throwException(30005, '该抢购活动已经结束!');
}
// 判断是否卖完
if(seckill.amount 至此,秒杀接口用传统的关系型数据库就实现完成了,代码并不复杂,注释也很详细,不用特别的讲解大家也都能看懂,那它能不能正常工作呢,答案显然是否定的
通过
{
amount: 200,
start_time: '2020-06-20 00:00:00',
end_time: '2023-06-20 00:00:00',
is_valid: 1,
comment: '...',
}其次,创建
if (redis.call('hexists', KEYS[1], KEYS[2]) == 1) then
local stock = tonumber(redis.call('hget', KEYS[1], KEYS[2]));
if (stock > 0) then
redis.call('hincrby', KEYS[1], KEYS[2], -1);
return stock
end;
return 0
end;最后,完成代码,完整代码如下:
// 引入相关库
const moment = require('moment');
const Op = require('sequelize').Op;
const { v4: uuidv4 } = require('uuid');
// 引入数据库model文件
const seckillModel = require('../../dbs/mysql/models/seckill_goods');
const ordersModel = require('../../dbs/mysql/models/orders');
// 引入Redis实例
const redis = require('../../dbs/redis');
// 引入工具函数或工具类
const UserModule = require('../modules/user');
const { randomString, checkObjNull } = require('../../utils/tools/funcs');
// 引入秒杀key前缀
const { SECKILL_GOOD, LOCK_KEY } = require('../../utils/constants/redis-prefixs');
// 引入避免超卖lua脚本
const { stock, lock, unlock } = require('../../utils/scripts');
class Seckill {
async doSeckill(ctx, next) {
const body = ctx.request.body;
const goodId = ctx.params.good_id;
const accessToken = ctx.query.accessToken;
const path = body.path;
// 基本参数校验
if (!accessToken || !path) { return ctx.throwException(20001, '参数错误!'); };
// 判断此产品是否加入了抢购
const key = `${SECKILL_GOOD}${goodId}`;
const seckill = await redis.hgetall(key);
if (!checkObjNull(seckill)) { return ctx.throwException(30002, '该产品并未有抢购活动!'); };
// 判断是否有效
if (!seckill.is_valid) { return ctx.throwException(30003, '该活动已结束!'); };
// 判单是否开始、结束
if(moment().isBefore(moment(seckill.start_time))) {
return ctx.throwException(30004, '该抢购活动还未开始!');
}
if(moment().isAfter(moment(seckill.end_time))) {
return ctx.throwException(30005, '该抢购活动已经结束!');
}
// 判断是否卖完
if(seckill.amount 这里代码主要做个四个修改:
- 步骤2,判断产品是否加入了抢购,改为去
Redis
中查询 - 步骤7,判断登录用户是否已抢到,因为不在维护抢购活动
id
,所以改为使用用户id
、产品id
和状态status
判断 - 步骤8,扣库存,改为使用
lua
脚本去Redis
中扣库存 - 对扣库存和写入数据库操作进行加锁
订单的操作仍然在
Mysql数据库中进行,因为大部分的请求都在步骤5被拦截了,剩余请求
Mysql是完全有能力处理的。
再次通过
Jmeter进行测试,发现订单表正常,库存量扣减正常,说明超卖问题和限购已经解决。
其他问题
秒杀场景的其他技术
基于Redis
支持高并发、键值对型数据库和支持原子操作等特点,案例中使用Redis
来作为秒杀应对方案。在更复杂的秒杀场景下,除了使用Redis
外,在必要的的情况下还需要用到其他一些技术:- 限流,用漏斗算法、令牌桶算法等进行限流
- 缓存,把热点数据缓存到内存里,尽可能缓解数据库访问的压力
- 削峰,使用消息队列和缓存技术使瞬间高流量转变成一段时间的平稳流量,比如客户抢购成功后,立即返回响应,然后通过消息队列异步处理后续步骤,发短信,写日志,更新一致性低的数据库等等
- 异步,假设商家创建一个只针对粉丝的秒杀活动,如果商家的粉丝比较少(假设小于1000),那么秒杀活动直接推送给所有粉丝,如果用户粉丝比较多,程序立刻推送给排名前1000的用户,其余用户采用消息队列延迟推送。(1000这个数字需要根据具体情况决定,比如粉丝数2000以内的商家占99%,只有1%的用户粉丝超过2000,那么这个值就应该设置为2000)
- 分流,单台服务器不行就上集群,通过负载均衡共同去处理请求,分散压力
这些技术的应用会让整个秒杀系统更加完善,但是核心技术还是
Redis
,可以说用好Redis
实现的秒杀系统就足以应对大部分场景。Redis
健壮性
案例使用的是单机版Redis
,单节点在生产环境基本上不会使用,因为- 不能达到高可用
- 即便有着
AOF
日志和RDB
快照的解决方案以保证数据不丢失,但都只能放在master
上,一旦机器故障,服务就无法运行,而且即便采取了相应措施仍不可避免的会造成数据丢失。
因此,
Redis
的主从机制和集群机制在生产环境下是必须的。Redis
分布式锁的问题- 单点分布式锁,案例提到的分布式锁,实际上更准确的说法是单点分布式锁,是为了方便演示,但是,单点
Redis
分布式锁是肯定不能用在生产环境的,理由跟第2点类似 - 以主从机制(多机器)为基础的分布式锁,也是不够的,因为
redis
在进行主从复制时是异步完成的,比如在clientA
获取锁后,主redis
复制数据到从redis
过程中崩溃了,导致锁没有复制到从redis
中,然后从redis
选举出一个升级为主redis
,造成新的主redis
没有clientA
设置的锁,这时clientB
尝试获取锁,并且能够成功获取锁,导致互斥失效。
针对以上问题,
redis
官方设计了Redlock
,在Node.js
环境下对应的资源库为node-redlock
,可以用npm
安装,至少需要3个独立的服务器或集群才能使用,提供了非常高的容错率,在生产环境中应该优先采用此方案部署。- 单点分布式锁,案例提到的分布式锁,实际上更准确的说法是单点分布式锁,是为了方便演示,但是,单点
总结
秒杀场景的特点可以总结为瞬时并发访问、读多写少、限时和限量,开发中还要考虑避免超卖现象以及类似黄牛抢票的限购问题,针对以上特点和问题,分析得到开发的原则是:数据写入内存而不是写入硬盘,异步处理而不是同步处理,扣库存操作原子执行以及对单用户购买进行加锁,而
Redis正好是符合以上全部特点的工具,因此最终选择
Redis来解决问题。
秒杀场景是一个在电商业务中相对复杂的场景,此篇文章只是介绍了其中最核心的逻辑,实际业务可能更加复杂,但只需要在此核心基础上进行扩展和优化即可。
秒杀场景的解决方案不仅仅适合秒杀,类似的还有抢红包、抢优惠券以及抢票等等,思路都是一致的。
解决方案的思路还可以应用在单独限购、第二件半价以及控制库存等等诸多场景,大家要灵活运用。
项目地址
https://github.com/threerocks/redis-seckill
参考资料
https://time.geekbang.org/column/article/307421
https://redis.io/topics/distlock
以上就是《Redis应用实战 - 秒杀场景(Node.js版本)》的详细内容,更多关于mysql的资料请关注golang学习网公众号!
【熬夜整理近百份大厂面经】2022校招提前批面经总结分享(腾讯、字节、阿里、百度、京东等招聘信息+必考点+简历书写)
- 上一篇
- 【熬夜整理近百份大厂面经】2022校招提前批面经总结分享(腾讯、字节、阿里、百度、京东等招聘信息+必考点+简历书写)
- 下一篇
- 视频号直播自定义组件接入电商系统
-
- 数据库 · MySQL | 19小时前 |
- MySQL数值函数大全及使用技巧
- 117浏览 收藏
-
- 数据库 · MySQL | 2天前 |
- 三种登录MySQL方法详解
- 411浏览 收藏
-
- 数据库 · MySQL | 2天前 |
- MySQL数据备份方法与工具推荐
- 420浏览 收藏
-
- 数据库 · MySQL | 3天前 |
- MySQL数据备份方法与工具推荐
- 264浏览 收藏
-
- 数据库 · MySQL | 3天前 |
- MySQL索引的作用是什么?
- 266浏览 收藏
-
- 数据库 · MySQL | 4天前 |
- MySQL排序原理与实战应用
- 392浏览 收藏
-
- 数据库 · MySQL | 1星期前 |
- MySQLwhere条件查询技巧
- 333浏览 收藏
-
- 数据库 · MySQL | 1星期前 |
- MySQL常用数据类型有哪些?怎么选更合适?
- 234浏览 收藏
-
- 数据库 · MySQL | 1星期前 |
- MySQL常用命令大全管理员必学30条
- 448浏览 收藏
-
- 数据库 · MySQL | 1星期前 |
- MySQL高效批量插入数据方法大全
- 416浏览 收藏
-
- 数据库 · MySQL | 1星期前 |
- MySQL性能优化技巧大全
- 225浏览 收藏
-
- 数据库 · MySQL | 1星期前 |
- MySQL数据备份4种方法保障安全
- 145浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3160次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3373次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3401次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4505次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3783次使用
-
- golang MySQL实现对数据库表存储获取操作示例
- 2022-12-22 499浏览
-
- Go语言实战之实现一个简单分布式系统
- 2022-12-23 220浏览
-
- 分享Redis高可用架构设计实践
- 2023-01-24 286浏览
-
- 搞一个自娱自乐的博客(二) 架构搭建
- 2023-02-16 244浏览
-
- B-Tree、B+Tree以及B-link Tree
- 2023-01-19 235浏览

