Redis之秒杀系统

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
简介: 秒杀是一种高并发场景,通常指的是在短时间内(秒级别)有大量用户同时访问某个商品或服务,争相抢购的情景。在这种情况下,系统需要处理大量并发请求,确保公平性、一致性,并防止因并发而导致的问题,例如超卖、恶意请求等。以下是在高并发秒杀场景下需要考虑的一些关键问题和解决方案:

秒杀是一种高并发场景,通常指的是在短时间内(秒级别)有大量用户同时访问某个商品或服务,争相抢购的情景。在这种情况下,系统需要处理大量并发请求,确保公平性、一致性,并防止因并发而导致的问题,例如超卖、恶意请求等。以下是在高并发秒杀场景下需要考虑的一些关键问题和解决方案:


超卖问题: 大量用户同时抢购同一商品可能导致超卖(卖出超过库存数量)的问题。为了解决这个问题,可以采用悲观锁或乐观锁的方式来控制库存的访问。数据库的行级锁、分布式锁等技术都可以用来防止超卖。


性能优化: 高并发场景下,系统性能是关键。使用缓存、异步处理、CDN 加速等手段可以显著提升系统的性能。缓存可以存储商品信息、用户状态等,减轻数据库压力。异步处理可以将一些不需要即时返回结果的操作异步执行,减轻请求的响应时间。


并发控制: 在高并发场景下,为了防止系统崩溃或服务不可用,需要对并发进行控制。可以使用队列、限流等技术,确保系统在承受能力范围内处理请求,防止系统超负荷崩溃。


秒杀令牌和时间窗口: 可以在系统中引入秒杀令牌,只有携带有效令牌的用户才能参与秒杀。同时,可以设置一个时间窗口,只在特定的时间范围内允许秒杀操作,有效控制请求的涌入。


用户鉴权和防刷: 针对恶意请求,需要进行用户鉴权,并采用防刷策略。例如,限制同一用户在短时间内的请求次数,通过验证码等方式增加用户请求的成本,防止恶意请求。


队列和异步处理: 使用消息队列将用户的秒杀请求进行排队,然后异步处理。这样可以有效地削峰填谷,减轻系统瞬时的压力,提高系统的容错能力。


分布式事务: 如果系统是分布式的,需要考虑分布式事务的问题。确保在秒杀过程中的各个阶段,包括扣减库存、生成订单等,能够保持事务的一致性。


实时监控和日志记录: 在高并发场景下,实时监控是及时发现问题、解决问题的关键。记录详细的日志信息,包括用户请求日志、系统性能日志等,便于事后分析和优化。


Redis 秒杀


Mysql数据库设计

/*
SQLyog Community v11.26 (32 bit)
MySQL - 8.0.33 : Database - test
*********************************************************************
*/
/*!40101 SET NAMES utf8 */;
/*!40101 SET SQL_MODE=''*/;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`test` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `test`;
/*Table structure for table `stock` */
DROP TABLE IF EXISTS `stock`;
CREATE TABLE `stock` (
  `id` BIGINT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(20) DEFAULT NULL,
  `count` INT DEFAULT NULL,
  `create_time` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*Data for the table `stock` */
INSERT  INTO `stock`(`id`,`name`,`count`,`create_time`) VALUES (1,'apple',500,'2023-11-28 19:02:04'),(2,'huawei',500,'2023-11-28 19:02:26');
/*Table structure for table `stock_order` */
DROP TABLE IF EXISTS `stock_order`;
CREATE TABLE `stock_order` (
  `id` BIGINT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(20) DEFAULT NULL,
  `price` INT DEFAULT NULL,
  `create_time` TIMESTAMP NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1729467951815541250 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*Data for the table `stock_order` */
/*Table structure for table `article_select` */
DROP TABLE IF EXISTS `article_select`;
/*!50001 DROP VIEW IF EXISTS `article_select` */;
/*!50001 DROP TABLE IF EXISTS `article_select` */;
/*!50001 CREATE TABLE  `article_select`(
 `a` bigint ,
 `b` varchar(11) ,
 `c` varchar(20) ,
 `d` bigint 
)*/;
/*View structure for view article_select */
/*!50001 DROP TABLE IF EXISTS `article_select` */;
/*!50001 DROP VIEW IF EXISTS `article_select` */;
/*!50001 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `article_select` (`a`,`b`,`c`,`d`) AS select `article`.`id` AS `id`,`article`.`name` AS `name`,`article`.`des` AS `des`,`article`.`categoryid` AS `categoryid` from `article` */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;


Mysql秒杀实现

秒杀代码设计初步代码如下:


@RestController
public class MyController {
    @Autowired
    StockMapper stockMapper;
    @Autowired
    StockOrderMapper stockOrderMapper;
    @Transactional
    @GetMapping("/order/{id}")
    public String order(@PathVariable("id") Long id){
        Stock stock = stockMapper.selectById(id);
        Integer count = stock.getCount();
        if(count<=0){
            throw new RuntimeException("库存不足");
        }
        StockOrder stockOrder=new StockOrder();
        stockOrder.setName(stock.getName());
        stockOrderMapper.insert(stockOrder);
        UpdateWrapper<Stock> updateWrapper=new UpdateWrapper<>();
        updateWrapper.setSql("count = count - 1 where count > 0 and id ="+id); //在mysql这里执行的时候,数据库会加行锁,所以相对是安全的
        int update = stockMapper.update(null, updateWrapper);
        if(update<=0){
            throw new RuntimeException("库存不足");
        }
        return "success";
    }
}


由于业务代码直接与mysql数据库进行交互,mysql一秒支持的并发量低,性能较低,然后下面进行压测:

压测得到的汇总报告如下图:



Mysql+Redis秒杀实现


使用redis修改代码如下:

@RestController
public class MyController {
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    StockMapper stockMapper;
    @Autowired
    StockOrderMapper stockOrderMapper;
    @PostConstruct
    public void init(){
        List<Stock> stocks = stockMapper.selectList(null);
        for (Stock stock : stocks) {
            stringRedisTemplate.opsForValue().set("product_"+stock.getId(),stock.getCount()+"");
        }
    }
    @GetMapping("/order/{id}")
    public String order(@PathVariable("id") Long id){
        Long decrement = stringRedisTemplate.opsForValue().decrement("product_" + id);
        if(decrement<0){
            stringRedisTemplate.opsForValue().increment("product_"+id);
            return "库存不足";
        }
        try {
           ((MyController)AopContext.currentProxy()).mys_order(id);
        }catch (Exception e){
            stringRedisTemplate.opsForValue().increment("product_"+id);
            return "库存不足";
        }
        return "购买成功";
    }
    @Transactional
    public void mys_order(Long id){
        Stock stock = stockMapper.selectById(id);
        if(stock.getCount()<=0){
            throw new RuntimeException("库存不足");
        }
        StockOrder stockOrder=new StockOrder();
        stockOrder.setName(stock.getName());
        stockOrderMapper.insert(stockOrder);
        UpdateWrapper<Stock> updateWrapper=new UpdateWrapper<>();
        updateWrapper.setSql("count = count - 1 where count > 0 and id ="+id); //在mysql这里执行的时候,数据库会加行锁,所以相对是安全的
        int update = stockMapper.update(null, updateWrapper);
        if(update<=0){
            throw new RuntimeException("库存不足");
        }
    }
}


压测结果吞吐量如下图,使用redis作为缓存相对于仅仅使用mysql数据库吞吐量提升了不少,性能得到了提升。


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
18小时前
|
NoSQL Redis 数据安全/隐私保护
macos系统中redis如何设置密码
以上步骤应该可以帮助你在macOS系统的Redis服务中设置密码,确保你的数据存储更加安全。此外,确保你定期检查Redis安全性相关的最佳实践和更新,以保持你的服务安全可靠。
8 3
|
1月前
|
存储 消息中间件 缓存
Redis:内存数据存储与缓存系统的技术探索
**Redis 概述与最佳实践** Redis,全称Remote Dictionary Server,是流行的内存数据结构存储系统,常用于数据库、缓存和消息中介。它支持字符串、哈希、列表等数据结构,并具备持久化、主从复制、集群部署及发布/订阅功能。Redis适用于缓存系统、计数器、消息队列、分布式锁和实时系统等场景。最佳实践包括选择合适的数据结构、优化缓存策略、监控调优、主从复制与集群部署以及确保安全配置。
29 3
|
1月前
|
NoSQL Java 网络安全
Redis在java(Maven)中使用,常用基本命令大全,最全Termius怎么进行端口映射(MAC系统最简单教学)
Redis在java(Maven)中使用,常用基本命令大全,最全Termius怎么进行端口映射(MAC系统最简单教学)
|
2月前
|
存储 缓存 监控
利用Redis构建高性能的缓存系统
在现代Web应用中,性能优化是提升用户体验和响应速度的关键。Redis作为一款开源的内存数据结构存储系统,因其出色的性能、丰富的数据结构和灵活的使用方式,成为了构建高性能缓存系统的首选工具。本文将探讨Redis在缓存系统中的应用,分析其优势,并通过实例展示如何结合Redis构建高效、可靠的缓存系统,以应对高并发、大数据量等挑战。
|
2月前
|
存储 缓存 监控
利用Redis构建高性能的缓存系统
在现今高负载、高并发的互联网应用中,缓存系统的重要性不言而喻。Redis,作为一款开源的、内存中的数据结构存储系统,它可以用作数据库、缓存和消息代理。本文将深入探讨Redis的核心特性,以及如何利用Redis构建高性能的缓存系统,并通过实际案例展示Redis在提升系统性能方面的巨大潜力。
|
2月前
|
存储 NoSQL 测试技术
Redis数据存储系统为什么快?
Redis的快速并非偶然,而是深思熟虑的设计理念的结果。通过将数据存储于内存、采用单线程模型、实现非阻塞I/O等独特的技术选择,Redis在高并发和低延迟方面展现了卓越的表现。
45 16
|
2月前
|
缓存 NoSQL Java
【亮剑】分布式锁是保证多服务实例同步的关键机制,常用于互斥访问共享资源、控制访问顺序和系统保护,如何使用注解来实现 Redis 分布式锁的功能?
【4月更文挑战第30天】分布式锁是保证多服务实例同步的关键机制,常用于互斥访问共享资源、控制访问顺序和系统保护。基于 Redis 的分布式锁利用 SETNX 或 SET 命令实现,并考虑自动过期、可重入及原子性以确保可靠性。在 Java Spring Boot 中,可通过 `@EnableCaching`、`@Cacheable` 和 `@CacheEvict` 注解轻松实现 Redis 分布式锁功能。
|
2月前
|
开发框架 前端开发 JavaScript
JavaScript云LIS系统源码ASP.NET CORE 3.1 MVC + SQLserver + Redis医院实验室信息系统源码 医院云LIS系统源码
实验室信息系统(Laboratory Information System,缩写LIS)是一类用来处理实验室过程信息的软件,云LIS系统围绕临床,云LIS系统将与云HIS系统建立起高度的业务整合,以体现“以病人为中心”的设计理念,优化就诊流程,方便患者就医。
44 0
|
2月前
|
人工智能 前端开发 Java
Java语言开发的AI智慧导诊系统源码springboot+redis 3D互联网智导诊系统源码
智慧导诊解决盲目就诊问题,减轻分诊工作压力。降低挂错号比例,优化就诊流程,有效提高线上线下医疗机构接诊效率。可通过人体画像选择症状部位,了解对应病症信息和推荐就医科室。
255 10
|
2月前
|
存储 缓存 NoSQL
深入解析Redis:一种快速、高效的键值存储系统
**Redis** 是一款高性能的键值存储系统,以其内存数据、高效数据结构、持久化机制和丰富的功能在现代应用中占有一席之地。支持字符串、哈希、列表、集合和有序集合等多种数据结构,适用于缓存、计数、分布式锁和消息队列等场景。安装Redis涉及下载、编译和配置`redis.conf`。基本操作包括键值对的设置与获取,以及哈希、列表、集合和有序集合的操作。高级特性涵盖发布/订阅、事务处理和Lua脚本。优化策略包括选择合适数据结构、配置缓存和使用Pipeline。注意安全、监控和备份策略,以确保系统稳定和数据安全。
338 1