Redis 在电商秒杀场景中的应用

Redis 在电商秒杀场景中的应用

  • 一、简介
    • 1.1 简介
    • 1.2 场景应用
  • 二、Redis 优势与挑战
    • 2.1 优势
    • 2.2 秒杀场景的挑战
  • 三、应用场景分析
    • 3.1 库存预热
      • 代码示例
    • 3.2 分布式锁
    • 3.3 消息队列
  • 四、系统设计方案
    • 4.1 架构设计
    • 4.2 技术选型
    • 4.3 数据结构设计
  • 五、Redis 性能优化
    • 5.1 集群部署
      • 5.1.1 Redis Sentinel
      • 5.1.2 Redis Cluster
    • 5.2 缓存策略
      • 5.2.1 本地缓存
      • 5.2.2 分布式缓存
    • 5.3 流量控制

一、简介

1.1 简介

在电商平台的特定时期,如双十一、618等节日或促销活动,会有大量用户涌入该平台进行购物。其中,秒杀场景是用户数量最多的,因为消费者可在限定的时间内获得极具性价比的产品和服务。

举例来说,假设某电商平台推出了一款新品,原价为100元。但在秒杀期间,只需要支付10元便可购买该产品,而且该产品总共只有100件。那么这样的秒杀场景既能提高消费者的购买积极性,也能解决商家库存问题。

1.2 场景应用

Redis是一个基于内存的缓存数据库(In-memory data structure store),拥有快速响应和高并发等优点。在电商秒杀场景中,Redis作为数据库或缓存,发挥着极其重要的作用:

  1. 读写能力强:Redis支持多种数据类型操作,并且数据的存储和读取都非常迅速。在秒杀场景中,用户的访问需要在几毫秒内完成,否则用户就会离开。Redis的快速响应能力保证了用户能够顺利地完成秒杀操作。

  2. 分布式锁:在秒杀活动中,如果两个用户同时竞买同一件商品,将会产生资源竞争问题。为了解决这个问题,需要采用分布式锁机制来控制并发,保证数据的完整性和准确性。Redis提供了多种分布式锁的实现方案,使得开发人员在秒杀场景中等高并发下能够更精准、高效地处理业务。

二、Redis 优势与挑战

2.1 优势

  1. 快速读写:Redis的内存读写速度非常快,特别适合对单条数据进行频繁读写的情况,更是秒杀场景中所需的。

  2. 高并发:Redis拥有良好的并发支持,对于增删改查这样频繁地路由请求而言,Redis可以应对秒杀中的高并发访问。

  3. 数据类型多样:Redis支持多种数据类型,如字符串、哈希、集合、有序集合和列表等。在处理复杂的秒杀逻辑时,这些数据类型能显著提升其效率。

2.2 秒杀场景的挑战

  1. 容量不足:由于Redis使用内存来存储缓存,容量较小,可能无法容纳全部数据。

  2. 数据一致性问题:秒杀场景中,如果两个用户同时竞买同一件商品,需要使用分布式锁来解决竞争问题。而使用分布式锁可能会引起数据不一致的问题,需要在设计方案时考虑处理。

  3. 系统可用性问题:在电商秒杀场景中,系统的可用性非常重要。如果Redis服务器宕机或出现其他意外情况,会导致许多的秒杀请求失败,给用户和商家带来交易上的损失。因此,需要采取多种手段,如数据备份、灾备和监控等,保护系统稳定运行。

三、应用场景分析

3.1 库存预热

在一些特定的电商促销活动中,为了避免在秒杀开始时由于读取数据库等操作造成的系统延迟等问题,可以使用 Redis 对库存进行预热。

代码示例

public class StockPreheating {private static final String HOST = "127.0.0.1";private static final int PORT = 6379;public static void main(String[] args) {// 连接 RedisJedis jedis = new Jedis(HOST, PORT);// 设置预热初始值int stockNum = 100000;String key = "stock";jedis.set(key, String.valueOf(stockNum));// 循环生成商品预热缓存for(int i=1; i<=100000; i++){String skuKey = "sku:" + i;jedis.set(skuKey, "stock");}// 关闭 Redis 连接jedis.close();}
}

说明:
以上示例代码中,我们通过 Jedis Java 客户端连接到 Redis,并使用循环生成商品预热缓存,最后关闭 Redis 连接。

3.2 分布式锁

使用 Redis 的分布式锁可以解决分布式环境下的共享资源互斥问题。常用于秒杀、抢购等高并发场景中。

public class DistributedLock {private static final String HOST = "127.0.0.1";private static final int PORT = 6379;private static Jedis jedis = new Jedis(HOST, PORT);/*** 加锁操作* @param key 要加锁的键名* @param value 键值,在释放锁时需要校验* @param expireTime 锁过期时间,防止死锁* @return 是否加锁成功*/public static boolean lock(String key, String value, int expireTime) {// nx:key不存在时才进行设置,保证锁是互斥的// ex:键值设置后,经过 expireTime 秒自动过期释放锁,避免产生死锁return "OK".equals(jedis.set(key, value, "NX", "EX", expireTime));}/*** 解锁操作* @param key 要加锁的键名* @param value 键值,在加锁时设置的值* @return 是否解锁成功*/public static boolean unlock(String key, String value) {// lua 脚本:先判断键值是否与 value 相等,如果相等则删除 key 并返回 true,否则返回 falseString script ="if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +"then\n" +"    return redis.call(\"del\",KEYS[1])\n" +"else\n" +"    return 0\n" +"end";Object result = jedis.eval(script, Collections.singletonList(key), Collections.singletonList(value));return Long.parseLong(result.toString()) == 1;}
}

说明:
以上示例代码中,我们实现了 Redis 的加锁和解锁操作,并配合 Lua 脚本实现了多个命令的原子性,保证了加锁和解锁的一致性。

3.3 消息队列

使用 Redis 可以方便地实现消息队列,对于需要异步处理和业务解耦的场景非常有用。

public class MessageQueue {private static final String HOST = "127.0.0.1";private static final int PORT = 6379;private static Jedis jedis = new Jedis(HOST, PORT);/*** 发布订阅,实现消息队列* @param channel 订阅频道名* @param message 消息内容*/public static void sendMessage(String channel, String message) {jedis.publish(channel, message);}/*** 消息订阅者* @param channel 订阅频道名* @param listener 消息接收监听器*/public static void subscribe(String channel, JedisPubSub listener) {jedis.subscribe(listener, channel);}
}

说明:
以上示例代码中,我们实现了 Redis 的发布订阅消息队列功能,并通过 JedisPubSub 接口进行消息接收监听,实现了消费者消息异步接收处理。

四、系统设计方案

4.1 架构设计

本电商秒杀系统采用分布式架构,包含以下组件:

  • 前端负载均衡器:将用户请求均匀地分发到不同的Web服务器上,使用Nginx负责实现。
  • 应用服务器:接收用户请求并处理,使用Java + Spring Boot框架实现。
  • Redis服务器:作为主要的数据存储和缓存介质,使用单机或集群模式均可。
  • 数据库服务器:保存订单信息等长期存储的数据,使用MySQL数据库。

4.2 技术选型

  • Web服务器:Nginx
  • 后端框架:Spring Boot
  • 数据库:MySQL
  • 缓存:Redis

4.3 数据结构设计

为了保证秒杀活动的效率和可靠性,需要采用以下数据结构来支持系统的运行:

  • 商品库存队列:使用Redis的List结构来保存活动商品的库存数量,每次有人秒杀成功后,将库存队列中的商品数量减1。
  • 用户请求队列:使用Redis的List结构保存用户请求信息,每次请求进来后都先放入请求队列中,再由后台系统取出处理。如果请求队列过长,则说明系统可能已经无法处理更多的请求,需要返回秒杀失败的提示。

下面是Java代码实现:

// 库存队列操作
public class StockQueue {private static final String STOCK_QUEUE_KEY = "stock_queue";// 添加库存数量public static void addStock(int num) {RedisTemplate<String, Integer> redisTemplate = getRedisTemplate();redisTemplate.opsForList().rightPush(STOCK_QUEUE_KEY, num);}// 减少库存数量public static boolean reduceStock() {RedisTemplate<String, Integer> redisTemplate = getRedisTemplate();Long stockNum = redisTemplate.opsForList().leftPop(STOCK_QUEUE_KEY);if (stockNum == null || stockNum <= 0) {return false;}return true;}// 获取当前库存数量public static int getCurrentStock() {RedisTemplate<String, Integer> redisTemplate = getRedisTemplate();List<Integer> stockList = redisTemplate.opsForList().range(STOCK_QUEUE_KEY, 0, -1);int totalStock = 0;for (Integer num : stockList) {totalStock += num;}return totalStock;}private static RedisTemplate<String, Integer> getRedisTemplate() {// 配置Redis连接等操作return redisTemplate;}
}// 用户请求队列操作
public class RequestQueue {private static final String REQUEST_QUEUE_KEY = "request_queue";// 将用户请求添加到队列中public static void addRequest(String request) {RedisTemplate<String, String> redisTemplate = getRedisTemplate();redisTemplate.opsForList().rightPush(REQUEST_QUEUE_KEY, request);}// 从队列中获取一个请求进行处理public static String getRequest() {RedisTemplate<String, String> redisTemplate = getRedisTemplate();String request = redisTemplate.opsForList().leftPop(REQUEST_QUEUE_KEY);return request;}private static RedisTemplate<String, String> getRedisTemplate() {// 配置Redis连接等操作return redisTemplate;}
}

五、Redis 性能优化

5.1 集群部署

5.1.1 Redis Sentinel

Redis Sentinel 是 Redis 官方提供的高可用解决方案。它通过选举一个主节点以及多个备份节点的方式,实现了 Redis 服务的自动切换和故障恢复。在生产环境中建议使用 Redis Sentinel 来保证 Redis 服务的高可用性。

5.1.2 Redis Cluster

Redis Cluster 也是 Redis 官方提供的高可用解决方案。与 Redis Sentinel 不同的是,Redis Cluster 主要是通过分片来实现数据的自动平衡和故障恢复。它提供了强大的横向扩展能力,能够支持更大规模的数据存储和高并发访问。

5.2 缓存策略

5.2.1 本地缓存

本地缓存是指应用程序直接将数据存储在本地内存中,以加快数据的读取速度。应该注意的是,本地缓存一般只适用于数据量较小、相对固定且不怎么变化的情况下。当数据量过大或者经常变化时,本地缓存很容易引起内存溢出等问题。

代码示例:

// 使用 HashMap 作为本地缓存
private static Map<String, Object> localCache = new HashMap<>();public static Object getData(String key) {Object value = localCache.get(key);if (value != null) {return value;}// 从数据库中获取数据value = getDataFromDB(key);if (value != null) {localCache.put(key, value);}return value;
}

5.2.2 分布式缓存

分布式缓存是指将数据缓存在多个服务器节点上,以提高数据的读取速度和可用性。相比于本地缓存,分布式缓存具有更好的扩展性和容错性,能够适应大数据量和高并发访问的场景。

常见的分布式缓存方案包括 Memcached 和 Redis。其中 Redis 的性能和功能都要优于 Memcached,所以在选择分布式缓存方案时建议选择 Redis。

使用 Redis 进行分布式缓存代码示例:

// 创建 JedisPool 对象,用于连接 Redis
JedisPool jedisPool = new JedisPool("localhost", 6379);public static Object getData(String key) {try (Jedis jedis = jedisPool.getResource()) {// 尝试从 Redis 中获取数据byte[] value = jedis.get(key.getBytes());if (value != null) {// 如果存在则直接返回return deserialize(value);} else {// 否则从数据库中获取Object data = getDataFromDB(key);if (data != null) {// 并写入 Redis 中byte[] bytes = serialize(data);jedis.set(key.getBytes(), bytes);}return data;}}
}

5.3 流量控制

流量控制是指对系统中的请求和响应进行限制,以避免因高并发访问而造成系统崩溃或服务不可用。常用的流量控制方案包括限流和熔断。

限流是指通过设置各种限制条件,对请求进行限制的一种方式。常见的限流方式包括漏桶算法、令牌桶算法等。

熔断是指当系统出现故障或异常时,立即切断上游请求或降级处理的一种方式。在使用熔断技术时需要关注熔断恢复、熔断点、熔断时间等问题。

以下是 Java 使用 Google Guava 包进行的限流和熔断的代码示例:

// 创建 RateLimiter 对象,每秒最多处理 100 个请求
private static RateLimiter rateLimiter = RateLimiter.create(100);public static void processRequest(Request request) {if (!rateLimiter.tryAcquire()) {// 请求被拒绝,返回错误信息throw new RuntimeException("系统繁忙,请稍后再试!");}// 处理请求// ...
}// 使用 CircuitBreaker 进行熔断,最多允许出现 3 次错误
private static CircuitBreaker circuitBreaker = CircuitBreaker.create("circuitBreaker", CircuitBreakerConfig.custom().failureRateThreshold(50).ringBufferSizeInClosedState(3).waitDurationInOpenState(Duration.ofMillis(1000)).build());public static Object getData(String key) {try {// 尝试执行业务逻辑circuitBreaker.decorateSupplier(() -> getDataFromDB(key)).get();} catch (Exception e) {// 出现故障或异常,进行降级处理return fallback();}// 处理数据// ...
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/22743.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据库数据恢复-Oracle数据库文件出现坏块的数据恢复案例

Oracle数据库故障&初检&分析&#xff1a; 打开Oracle数据库时报错&#xff0c;报错信息&#xff1a;“system01.dbf需要更多的恢复来保持一致性&#xff0c;数据库无法打开”。用户急需恢复zxfg用户下的数据。 出现上述报错的可能原因包括&#xff1a;控制文件损坏、数…

C高级--day3(shell中的输入、命令置换符、数组、算数运算、分支结构)

#!/bin/bash pls ~/ -l | grep "^-" | wc -l qls ~/ -l | grep "^d" | wc -l echo "普通文件个数&#xff1a;$p" echo "目录文件个数&#xff1a;$q"#!/bin/bash read file posexpr index $file \. strexpr substr $file $((pos1)) 2…

什么是Java中的Maven?

Java中的Maven&#xff0c;可以简单理解为“一个神奇的工具”&#xff0c;它可以自动帮你管理Java项目的依赖关系&#xff0c;让你不再为手动下载、配置各种库而烦恼。想象一下&#xff0c;你正在写一个Java项目&#xff0c;突然发现需要引入一个名为"第三方库"的模块…

视频是如何做成gif动图的?1分钟快速转gif动画

常见的电影、电视剧等视频体积较大不易于传输和保存。为了方便大家使用可以将视频制作成GIF&#xff0c;可直接发送给对方非常的方便。那么&#xff0c;要怎么将视频转换成gif动画呢&#xff1f;很简单&#xff0c;使用专业的gif图片在线制作工具–GIF中文网&#xff0c;无需下…

企业电子招投标采购系统java spring cloud+spring boot功能模块功能描述+数字化采购管理 采购招投标

​功能模块&#xff1a; 待办消息&#xff0c;招标公告&#xff0c;中标公告&#xff0c;信息发布 描述&#xff1a; 全过程数字化采购管理&#xff0c;打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力&#xff0c;为外…

如何加深Java理解的思考--20230805

目录 一、Java的深度理解二、计算机科学基础三、知识系统结构化和基础化四、学习大牛的开发实践经验与思维逻辑&#xff0c;了解前沿新技术栈、新的解决方案&#xff0c;并批判性地吸收消化。注意思维逻辑很重要&#xff0c;很重要&#xff0c;很重要。很多时候这个东西需要自己…

Git 用户名邮箱的全局配置和单仓库配置(不同项目使用不同账号登录)

Git 用户名邮箱的全局配置和单仓库配置(不同项目使用不同账号登录) 需求 因工作和个人的仓库地址、用户名和邮箱都不一样,很多时候一个git账号无法满足工作和个人学习并行的需求。 全局用户名和邮箱是本地 git 客户端的变量&#xff0c;可配置&#xff0c;不随 git 库而改变…

【IDEA】常用插件清单

【IDEA】常用插件清单 arthas ideaCodeium: AI Autocomplete for xxxCommit-MessageGenerateAllSetterMaven HelperMybatisPlusOne Dark themePDF ViewerRainbow BracketsRestfulToolSequenceDiagramSonarLintTranslation arthas idea 快捷生成arthas命令 Codeium: AI Autoc…

微信云托管(本地调试)⑥:nginx、vue刷新404问题

一、nginx默认路径 1.1、默认配置文件路径&#xff1a;/etc/nginx/nginx.conf 1.2、默认资源路径&#xff1a;/usr/share/nginx/html/index.html 二、修改nginx.conf配置 &#xff08;注意配置中的&#xff1a;include /etc/nginx/conf.d/*.conf; 里面包了一个server配置文件…

大数据面试题:HBase的读写缓存

面试题来源&#xff1a; 《大数据面试题 V4.0》 大数据面试题V3.0&#xff0c;523道题&#xff0c;679页&#xff0c;46w字 参考答案&#xff1a; HBase上RegionServer的cache主要分为两个部分&#xff1a;MemStore & BlockCache。 MemStore是写缓存&#xff0c;Block…

【flink】开启savepoint

先启动一个任务 flink run -c com.yang.flink.CDCJob test-cdc.jar开启savepoint 命令&#xff1a; flink savepoint JobID 文件地址 flink savepoint e929a11d79bdc5e6f140f2cfb92e1335 file:///workspace/flinkSavepoints/backend这样就开启好了 操作中的错误 详细信…

Day10-作业(SpringBootWeb案例)

作业1&#xff1a;完成课上预留给大家自己完成的功能 【部门管理的修改功能】 注意&#xff1a; 部门管理的修改功能&#xff0c;需要开发两个接口&#xff1a; 先开发根据ID查询部门信息的接口&#xff0c;该接口用户查询数据并展示 。(一定一定先做这个功能) 再开发根据ID…

锁策略, cas 和 synchronized 优化过程总结

目录 一、锁策略 1. 乐观锁和悲观锁 2. 读写锁 3. 重量级锁和轻量级锁 4. 自旋锁 5. 公平锁和非公平锁 6.可重入锁 vs 不可重入锁 二、CAS 1. CAS 是怎么实现的 1) 实现原子类 2) 实现自旋锁 3. CAS 的 ABA 问题 三、Synchronized 原理 1.Synchronized 加锁工作过程 3.1 偏向…

Vue自定义防重复点击指令(v-repeatClick)

&#xff01;&#xff01;&#xff01;Vue防抖节流方法&#xff1a;VUE使用节流和防抖_vue防抖节流_停留的章小鱼的博客-CSDN博客 新建js文件directive.js: // directive.js // 防重复点击(指令实现) //使用&#xff1a; 在需要的按钮中加 v-repeatClick 指令即可 <el-but…

【websocket - Tornado】简易聊天应用

1、背景 项目测试的过程中需要自己搭建一个webscoket站点,确保此类服务接入后台系统后访问不受影响。python的服务框架常用的有Flask、Django、Tornado,每个框架的侧重点不同,导致使用的场景就会有所差异。 Flask轻量级,采用常规的同步编程方式,需要安装其他模块辅助,主…

逆向时如何找到MingGW(GNU)编译程序的main函数

编译器是MingGW生成的可执行文件的显著特点是, 最终运行ZwContinue后程序就莫名其妙启动了, 也找不到main函数。 为了探究里面究竟怎么回事, 我找到了wrk-v1.2的源码, 其中包含了ZwContinue的实现, 首先先看一下注释, API界面包含了2个参数, 其中让人感兴趣的是PCONTEXT, 这是…

【MySQL】仓储--维护出入库流水、库存,去重数量逻辑修正

系列文章 C#底层库–MySQLBuilder脚本构建类&#xff08;select、insert、update、in、带条件的SQL自动生成&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129179216 C#底层库–MySQL数据库操作辅助类&#xff08;推荐阅读&#xff0…

使用kickstart和anaconda自动化安装centos系统

使用kickstart和anaconda自动化安装centos系统 使用kickstart和anaconda自动化安装centos系统 anaconda 介绍 kickstart 介绍 实验过程 前提 1.已经安装好至少两台centos系统 2.需要实现自动安装的系统的光盘镜像 3.已安装的系统之间可以通讯(比如处于VMware中的NAT网络的…

数据结构【第3章】——线性表

线性表的定义 线性表&#xff1a;零个或多个数据元素的有限序列。 1&#xff09;线性表是一个序列。即元素之间是有顺序的&#xff0c;若元素存在多个&#xff0c;则第一个元素无前驱&#xff0c;最后一个元素无后继&#xff0c;其他每个元素都有且只有一个前驱和后继。 2&a…

GitHub上删除项目后,IDEA分享项目到GitHub提示Remote is already on GitHub

文章目录 一、错误信息二、解决方法1.删除GitHub上的项目2.找到项目里的.git隐藏文件3.找到config文件4.打开config文件&#xff0c;删除[remote "git-test"]及下面两行内容5.继续使用IDEA分享项目到GitHub即可 一、错误信息 二、解决方法 1.删除GitHub上的项目 2.…