使用 Redis 实现秒杀系统

这篇具有很好参考价值的文章主要介绍了使用 Redis 实现秒杀系统。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、简介

1 秒杀系统

秒杀系统是指在一个非常短的时间内(通常是几十秒钟),将某种商品或服务以极低的价格进行销售。这种销售方式需要保证高并发和高可用性,同时防止超卖和恶意攻击等问题。秒杀系统的特点是大量的用户在同一时间瞬间涌入服务器,该类型的高并发读写操作对系统性能提出了较高的要求。

2 常见问题

在秒杀场景下,会遇到以下常见问题:

  • 高并发(每秒新建的TCP连接数非常高)
  • 超卖(由于网页刷新频率过快,导致用户可购买数量超出实际剩余数量)
  • 恶意攻击(攻击者通过机器人、脚本等手段进行抢购,从而瘫痪系统)

二、Redis 简介

Redis(Remote Dictionary Server)是一个开源、支持网络、基于内存、键值对存储数据库。它支持多种数据结构,如字符串(String)、哈希(Hash)、列表(List)、集合(Set)和有序集合(Sorted Set)。Redis 的访问速度非常快,在存储海量数据时,丝毫不会影响系统性能,所以 Redis 被广泛应用于高并发的互联网项目中。

1 Redis基本概念

  • 单线程:Redis 采用单线程模型进行工作,避免了线程切换带来的上下文切换开销,因此速度非常快。
  • 持久化存储:Redis 中支持 RDB 持久化和 AOF 持久化,可以将内存中的数据保留到磁盘上,防止服务器崩溃时数据的丢失。
  • 丰富的数据类型:Redis 支持多种数据类型,如字符串、列表、集合、哈希表等,方便用户根据不同的业务需求选择合适的数据类型。
  • 高性能:Redis 是一个基于内存的数据库,它的读写速度都非常快,同时也因为是基于内存,所以 Redis 的存储容量受限,不适用于存储大量的数据。
  • 分布式:Redis 支持分布式集群,可以将数据进行分片存储,提高了系统的并发处理能力,同时增加了系统的可扩展性,保证了高可用性。

2 Redis 作为秒杀系统的优点

  • 高效读写:Redis 的读写性能非常快,能够满足秒杀系统的高并发读写需求,保证了系统的高效运作。
  • 数据持久化:Redis 支持数据的持久化存储,可以将内存中的数据保留到磁盘上,防止服务器崩溃时数据的丢失,减小对系统的影响。
  • 分布式特性:Redis 支持分布式集群,可以将缓存分片存储,避免单节点压力过大,保证了系统的可扩展性和高可用性。
  • 原子操作:Redis 支持多个操作的原子性,如事务处理、CAS 等,保证了数据的一致性和安全性,有效地防止了超卖等问题。

三、Redis 在秒杀系统中的应用

1 数据存储中的应用

Redis 的快速读写操作使得它成为二级缓存的首选,常用于缓存不经常变更或者不经常使用的数据。在秒杀系统中,Redis 可以用来缓存商品名称、库存数量、是否售罄等信息,减少数据库的访问量,提高数据读写效率和系统的响应速度。

// jedis 是 Redis 的 Java 客户端

// 设置 key-value 对
jedis.set("product:001:name", "iPhone 12");
jedis.set("product:001:stock", "1000");

// 获取 key-value 对
String name = jedis.get("product:001:name");
String stock = jedis.get("product:001:stock");

2 在分布式锁中的应用

在秒杀场景中为了防止商品超卖,通常需要引入分布式锁机制。Redis 提供了一种简单有效的分布式锁实现方式,通过抢占 key 来实现锁,避免了多个系统同时修改数据的情况。


// 尝试获取锁
boolean lockResult = jedis.setnx("lock:product:001", "value");
if(lockResult) {
    // 获取锁成功,执行业务逻辑...
    // 释放锁
    jedis.del("lock:product:001");
} else {
    // 获取锁失败,等待重试...
}

3 在消息队列中的应用

在秒杀场景中系统需要处理大量并发请求,为了避免请求在瞬间涌入服务器导致系统崩溃,可以使用消息队列来对用户的请求进行排队,这样可以有效地缓解系统压力。


// 将秒杀请求加入消息队列
jedis.lpush("seckill:requests", "request001");

// 从消息队列中获取请求
String request = jedis.brpop("seckill:requests", 10).get(1);

四、Redis秒杀系统设计

1 数据库表设计

秒杀系统一般需要两个表:商品表和订单表。商品表用于存储商品信息,订单表用于存储订单信息。

商品表设计

在商品表中需要包含以下字段:

字段名 类型 描述
id int 商品id
name varchar 商品名称
description text 商品描述
price decimal 商品单价
stock int 商品库存

订单表设计

在订单表中需要包含以下字段:

字段名 类型 描述
id int 订单id
user_id int 用户id
goods_id int 商品id
create_time datetime 创建时间
status int 订单状态,0表示未支付,1表示已支付

2 接口设计

秒杀系统需要以下几个接口:

  • 商品列表接口:用于获取商品列表。
  • 商品详情接口:用于获取指定商品的详细信息。
  • 下单接口:用于下单操作。
  • 订单列表接口:用于获取对应用户的订单列表。

3 队列设计

秒杀系统需要一个队列用于处理订单的下单请求,可以选用Redis作为队列。在Redis中使用list数据结构作为队列,在多个服务器下运行多个相同的消费者程序,以实现分布式处理订单请求。

4 Redis 优化策略

为了保证秒杀系统的高并发和性能,需要对Redis进行优化。优化策略包括:

  • 增加Redis的内存大小,以缓存更多的商品和订单信息。
  • 合理设置Redis的过期时间,避免Redis中的数据一直占用内存。
  • 使用Redis集群模式或主从复制模式,以提高Redis的可用性和性能。

五、秒杀系统的实现流程

1 商品初始化

在秒杀系统中首先需要进行商品初始化。具体实现流程如下:

// 定义商品实体类
public class Goods {
  private int id;
  private String name;
  private int stock;
  private double price;
  // 省略 getter 和 setter 方法
}

// 在系统启动时,从数据库中读取所有秒杀商品信息
List<Goods> goodsList = goodsDAO.queryAllSeckillGoods();
for (Goods goods : goodsList) {
  // 将商品信息存入到 Redis 中,以便后续操作使用
  redisService.set("seckill:good:" + goods.getId(), JSON.toJSONString(goods));
  // 将商品库存数量存入到 Redis 中,以便进行库存的修改操作
  redisService.set("seckill:stock:" + goods.getId(), goods.getStock());
}

5.2 前端页面限流

在秒杀系统中,为了避免瞬间大量用户访问导致系统崩溃,需要对前端页面进行限流。具体实现流程如下:

// 在前端页面中加入验证码或者滑动验证等机制
public class SeckillController {
  @PostMapping("/seckill")
  public String seckill(@RequestParam("goodsId") int goodsId,
                        @RequestParam("userId") int userId,
                        @RequestParam("verifyCode") String verifyCode) {
    // 验证码通过之后再执行秒杀操作
    if (verifyCodeIsValid(userId, verifyCode)) {
      // 秒杀操作
      seckillService.seckill(goodsId, userId);
    }
  }
}

5.3 后端请求接口限流

在秒杀系统中,同样需要对后端请求接口进行限流,以避免恶意攻击。具体实现流程如下:

// 使用限流工具对后端接口进行限流
public class SeckillController {
  @PostMapping("/seckill")
  public String seckill(@RequestParam("goodsId") int goodsId,
                        @RequestParam("userId") int userId) {
    if (rateLimiter.tryAcquire()) { // 使用 Guava RateLimiter 进行限流
      // 秒杀操作
      seckillService.seckill(goodsId, userId);
    } else {
      return "请求过于频繁,请稍后再试!";
    }
  }
}

5.4 分布式锁控制全局唯一性

在秒杀系统中由于多个用户同时访问同一个商品,需要对商品进行加锁,保证全局唯一性。具体实现流程如下:

// 使用 Redis 的分布式锁实现秒杀商品的唯一性
public class SeckillServiceImpl implements SeckillService {

  @Override
  public void seckill(int goodsId, int userId) {
    // 加锁操作
    String lockKey = "seckill:lock:" + goodsId;
    String requestId = UUID.randomUUID().toString();
    long expireTime = 3000; // 锁过期时间设置为 3 秒钟
    boolean isSuccess = redisService.tryLock(lockKey, requestId, expireTime);
    if (isSuccess) {
      try {
        // 秒杀操作
        int stock = redisService.get("seckill:stock:" + goodsId, Integer.class);
        if (stock > 0) {
          redisService.decr("seckill:stock:" + goodsId); // 减库存
          seckillDAO.insertOrder(goodsId, userId); // 写入订单记录
          notificationService.sendSeckillSuccessMsg(userId, goodsId); // 发送通知消息
        }
      } finally {
        // 释放锁操作
        redisService.releaseLock(lockKey, requestId);
      }
    }
  }

}

5.5 Redis 减库存

在秒杀系统中对商品的操作都是基于 Redis 获取和修改的,包括商品库存数量。具体实现流程如下:

// Redis 减库存操作
public class SeckillServiceImpl implements SeckillService {

  @Override
  public void seckill(int goodsId, int userId) {
    // 加锁和减库存操作
    int stock = redisService.get("seckill:stock:" + goodsId, Integer.class);
    if (stock > 0) {
      redisService.decr("seckill:stock:" + goodsId);
      // 省略其他业务逻辑操作
    }
  }
  
}

5.6 MySQL 写入订单记录

在秒杀系统中,需要将成功秒杀的订单信息记录到 MySQL 数据库中。具体实现流程如下:

// MySQL 写入订单记录操作
public class SeckillDAOImpl implements SeckillDAO {

  @Override
  public void insertOrder(int goodsId, int userId) {
    String sql = "INSERT INTO seckill_order (goods_id, user_id, create_time) VALUES (?, ?, ?)";
    jdbcTemplate.update(sql, goodsId, userId, new Date());
  }
  
}

5.7 消息通知用户秒杀成功

在秒杀系统中可以通过消息队列等方式,对用户进行秒杀成功的通知。具体实现流程如下:

// 消息通知用户秒杀成功
public class NotificationServiceImpl implements NotificationService {

  private static final Logger logger = LoggerFactory.getLogger(NotificationServiceImpl.class);

  @Override
  public void sendSeckillSuccessMsg(int userId, int goodsId) {
    // 使用消息队列对用户进行通知
    Message message = new Message();
    message.setUserId(userId);
    message.setGoodsId(goodsId);
    rocketMQTemplate.convertAndSend("seckill-success-topic", message);
    logger.info("通知消息已发送:{}", message);
  }
  
}

六、安全策略

秒杀系统是一个高并发业务,为了保证系统的安全性和稳定性,在使用Redis做缓存的同时,需要针对以下两个方面进行安全策略的设计:

1 防止超卖

在秒杀活动中,一件商品仅有有限的数量,当超过了这个数量之后就不能再销售,此时需要采取防止超卖的措施。

实现方式

  • 基于Redis的单线程机制,把减库存操作原子化执行,并且需要锁住对应的商品id。
  • 针对锁定商品的情况,使用 Redis 的分布式锁机制。以此来保证一次只有一个请求能够成功地请求到库存锁,并且持有锁的时间应尽量短。

下面是Java代码实现:文章来源地址https://www.toymoban.com/news/detail-510479.html

public boolean decrementStock(String key) {
    String lockKey = "LOCK_" + key;
    try (Jedis jedis = jedisPool.getResource()) {
        //加锁
        String lockValue = UUID.randomUUID().toString();
        String result;
        while (true) {
            result = jedis.set(lockKey, lockValue, "NX", "PX", 3000);
            if ("OK".equals(result)) {
                break;
            }
            Thread.sleep(100);
        }
        //判断是否加锁成功
        if (!lockValue.equals(jedis.get(lockKey))) {
            return false;
        }

        try {
            //操作库存
            int stock = Integer.parseInt(jedis.get(key));
            if (stock > 0) {
                jedis.decr(key);
                return true;
            }
            return false;
        } finally {
            //释放锁
            jedis.del(lockKey);
        }
    } catch (Exception e) {
        log.error("decrementStock failed, key:{}", key, e);
        return false;
    }
}

2 防止恶意刷单

恶意用户通过程序模拟大量请求,从而导致服务器无法响应正常用户的请求。为了解决这个问题,需要加入防止恶意刷单的策略。

实现方式

  • 对每个用户IP进行限流,设置每分钟能够请求的次数。
  • 设置人机验证,如图形验证码或者短信验证等机制,让恶意用户成本太高从而放弃攻击。

下面是Java代码实现:

public boolean checkUserRequest(String ip) {
    // 检查ip对应的请求数是否超过最大允许请求次数
    String requestCountKey = "REQUEST_COUNT_" + ip;
    try (Jedis jedis = jedisPool.getResource()) {
        long currentCount = jedis.incr(requestCountKey);
        if (currentCount == 1) {
            // 第一次计数,设置过期时间为60s
            jedis.expire(requestCountKey, 60);
        }

        if (currentCount > maxRequestPerMinute) {
            // 超过最大允许请求次数,返回false
            return false;
        }
        return true;
    }
}

七、部署方案

1 安全性优化

  • 部署到专门的CDN缓存服务器,减小服务器带宽压力,保护服务器和数据库。
  • 设置服务器防火墙,禁止外部访问 Redis 和 数据库等敏感资源。
  • 开启Redis的持久化,以防止内存数据丢失或者意外宕机等情况。

2 性能优化

  • 提高Redis性能,采用集群方式,增加机器和配置Redis相关参数等。
  • 使用高效的缓存查询方式,避免频繁查询数据库,如使用Redis自带的哈希表来存储秒杀商品信息。

到了这里,关于使用 Redis 实现秒杀系统的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 秒杀抢购案例,基于 Redis 实现

    目录 1、关于全局唯一 ID 生成器 1.1 需要满足的特性 1.2 代码实现 1.3 其他的唯一 ID 生成策略 2、实现秒杀下单 2.1 超卖问题的产生 2.2 超卖问题的分析与解决 2.21 悲观锁与乐观锁  2.22 乐观锁中的两种常用方案        ▶️version 版本控制方案 ▶️CAS方案 2.3 实现一人一单 2.4

    2024年02月08日
    浏览(44)
  • SpringBoot+RabbitMQ+Redis实现秒杀功能

    传统处理:如果不涉及到redis的话,最初的用户请求进来的流程大概是先去数据库判断下当前用户是否已经秒杀过当前商品,如果秒杀过的话则返回秒杀失败不能重复秒杀,否则的话则执行减库存,下订单等步骤。 然而秒杀场景下,用户量非常庞大直接访问数据库的可能会使

    2024年02月02日
    浏览(37)
  • Golang实现Redis分布式锁解决秒杀问题

    先写一个脚本sql,插入2000个用户 登录是通过2个字段,一个是mobile,一个是password,生成了mobile从1到2000,密码默认是123456 然后写一个单元测试,实现新注册的2000个用户登录,然后获取token 我们使用有缓冲的通道和sync.WaitGroup信号量,来控制协程的数量,经过测试,发现limi

    2024年02月14日
    浏览(42)
  • Redis分布式锁原理之实现秒杀抢优惠卷业务

    背景 优惠券秒杀有两个业务涉及线程并发问题,第一个是库存超卖,第二个是一人一单,这就必须采取锁的方案了。下面根据优惠券秒杀功能一步一步进行展开,利用悲观锁、同步锁、分布式锁等方案循序渐进解决各种问题。 下单核心思路:当我们点击抢购时,会触发右侧

    2024年02月03日
    浏览(50)
  • 微服务 Spring Boot 整合Redis 实现优惠卷秒杀 一人一单

    CSDN话题挑战赛第2期 参赛话题:Java技术分享 在分布式系统中,经常需要使用 全局唯一ID 查找对应的数据。产生这种ID需要保证系统全局唯一,而且要高性能以及占用相对较少的空间。 全局唯一ID在数据库中一般会被设成 主键 ,这样为了保证数据插入时索引的快速建立,还需

    2024年02月03日
    浏览(47)
  • 微服务---Redis实用篇-黑马头条项目-优惠卷秒杀功能(使用java阻塞队列对秒杀进行异步优化)

    1.1 秒杀优化-异步秒杀思路 我们来回顾一下下单流程 当用户发起请求,此时会请求nginx,nginx会访问到tomcat,而tomcat中的程序,会进行串行操作,分成如下几个步骤 1、查询优惠卷 2、判断秒杀库存是否足够 3、查询订单 4、校验是否是一人一单 5、扣减库存 6、创建订单 在这六

    2024年02月05日
    浏览(52)
  • 华为云应用中间件DCS系列—Redis实现(电商网站)秒杀抢购示例

    云服务、API、SDK,调试,查看,我都行 阅读短文您可以学习到:应用中间件系列之Redis实现(电商网站)秒杀抢购示例 华为云开发者插件(Huawei Cloud Toolkit),作为华为云围绕其产品能力向开发者桌面上的延伸,帮助开发者快速在本地连接华为云,打通华为云到开发者的最后

    2024年02月07日
    浏览(41)
  • 秒杀系统的业务流程以及优化方案(实现异步秒杀)

    先看基本的业务流程  那么我们可以看到整个流程都是一个线程来完成的,这样的话耗时还是很长的,那么可不可以采用多线程去实现呢? 首先我们要思考怎么对业务进行拆分,可以想象一个我们去饭店点餐,会有前台接待,询问订单,之后将小票传给后厨去做饭,这样就会

    2024年02月11日
    浏览(43)
  • Redis-秒杀

    唉 就记得当时抢冰墩墩的时候的秒杀了 我们要注意什么问题呢? 1.几百万人在这个瞬间抢冰墩墩 这个瞬间会有大量的请求 服务器要能抗的住 2.不能超卖,就那些冰墩墩 卖多了压根没有 好不容易抢到你说没货了怕不是要被冲烂 3.避免少卖 拢共就那些 你再少卖点 没屁了

    2024年02月12日
    浏览(23)
  • Redis专题-秒杀

    开局一张图,内容全靠“编”。 昨天晚上在群友里看到有人在讨论库存并发的问题,看到这里我就决定写一篇关于redis秒杀的文章。 我们看看一般我们库存是怎么出问题的 其实redis提供了两种解决方案: 加锁和原子操作 。 1.1、加锁 加锁:其实非常常见,读取数据前,客户

    2024年02月12日
    浏览(30)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包