场景题
阅读提示
建议先看题目目录,再按“概念 -> 原理 -> 场景 -> 优化”顺序复习。
每题先讲结论,再补关键机制和项目实践,回答会更稳。
1、如何设计一个订单号生成服务
- 看数据量的大小:在设计订单号生成服务时,需要充分考虑数据量的大小,以确定生成的ID所需的位数。这可以避免位数不足导致存储问题,同时确保足够的位数以防止影响索引检索效率。
- 不重复、唯一性:订单号必须保证唯一性,否则会出现订单冲突和数据不一致等问题。可采用UUID、Snowflake、数据库生成或者利用Redis等机制,确保订单号的全局唯一性。
- 考虑高可用:通过多节点部署、负载均衡、健康检查等技术手段,确保服务在面对故障时能够保持可用状态,避免影响其他服务。
- 易于理解:为了方便排查问题,需要考虑到订单号的格式和组成,可以使用时间戳、随机数、用户ID等信息来拼接订单号。
- 安全性:不能让别人通过订单号看出公司的经营状况,比如你的订单就是流水号的话,那么别人就可以从订单号推测出你公司的整体运营状况。
2、订单超时自动取消功能如何设计
- 定时任务:通过定时任务隔一段时间扫表,对于过期超时的订单数据更新状态为已取消。这种方式容易实现;缺点就是定时任务是固定时间间隔的,所以一些订单并不能及时到点的去立马取消,可能过期了一两分钟才被定时任务扫描到;而且这种方式也增加了数据库的压力,当表里数据越来越多之后,扫描的时间也会越来越长。所以这种方式适合对时间要求不高的,数据量也不是很多的业务场景。
- DelayQueue延迟队列:DelayQueue是JDK提供的一个无界队列,订单生成以后,设置过期时间放入定义好的DelayQueue,然后创建一个线程,在线程中通过
while(true)不断的从DelayQueue中获取过期的数据。这种方式不依赖数据库,也不依赖其他组件,实现也方便;但是DelayQueue是一个无界队列,如果放入的订单数据过多,就会造成OOM。所以这种方式适合数据量比较小的,万一丢失了影响也不大的业务场景。 - Redisson:Redisson是一个基于Java的Redis客户端和分布式锁库,它提供的功能中有一个分布式延迟队列
RDelayedQueue,是基于zset结构实现的延迟队列,实现类是RedissonDelayedQueue。这种方式使用简单,并且其实现类中大量使用lua脚本保证其原子性,不会有并发重复问题。 - MQ延迟消息:RocketMQ 提供了支持延迟消息的功能(RabbitMQ并没有直接支持延迟消息的功能,要结合插件等才行),延迟消息就是当消息写入到Broker后,不会立刻被消费者消费,需要等待指定的时长后才可被消费处理;在订单创建之后,我们就可以把订单信息作为一条消息投递到rocketmq,并设置延迟时间,等到了时间之后consumer就可以消费到这条消息,然后检查用户是否支付了这个订单,没支付就取消。这种方式可以使代码逻辑清晰,系统之间完全解耦,只需关注生产及消费消息,而且吞吐量极高,最多可以支撑万亿级的数据量;缺点就是引入MQ,要考虑消息丢失,消息积压等问题,会增加系统的复杂度。
- MQ的死信队列:因为过了存活时间(TTL)、队列长度超限、被消费者拒绝等原因无法被消费时,就会被投递到死信队列。当队列中没有被消费支付的订单到了指定时间之后,投递到死信队列,然后消费这个死信队列,对死信队列中的订单消息进行取消。
- Redis过期监听:Redis提供了过期监听的功能,在
redis.conf配置文件中配置notify-keyspace-events Ex开启监听功能,然后在代码中继承KeyspaceEventMessageListener,实现onMessage方法就可以监听过期的数据。我们对订单设置过期时间,然后放入到redis,到了时候后,就消费这个key,检查用户是否支付了。
3、如何设计一个购物车功能
- 购物车系统的主要功能是在用户选择商品后,在下单之前保存用户的意向商品、数量等信息,以便用户进行统一支付。购物车的操作主要包括加入购物车、查看购物车以及通过购物车下单等。
- 一般情况下,在存储购物车信息时,并不需要保存商品的所有详细信息,只需记录一个SKU ID,并加上数量、时间等字段即可。商品的库存、价格、介绍等信息可以在渲染购物车时实时查询和计算。
- 对于未登录用户,购物车信息可以临时缓存在客户端,使用Cookie或LocalStorage等技术进行存储。以下是一个JSON格式的未登录用户购物车存储示例:
{
"cart": [
{
"SKUID": 10086,
"timestamp": 1666513990,
"count": 2
},
{
"SKUID": 10010,
"timestamp": 1666513990,
"count": 10
}
]
}- 已登录用户的购物车需要使用持久化存储,可以选择数据库或Redis缓存。如果使用数据库,可以创建表格存储
user_id、sku_id、count、time_stamp等业务字段。如果使用Redis,可以在未登录用户购物车的基础上添加user_id作为key,如下所示:
{
"KEY": 12343123,
"VALUE": [
{
"SKUID": 10086,
"timestamp": 1666513990,
"count": 2
},
{
"SKUID": 10010,
"timestamp": 1666513990,
"count": 10
}
]
}- 使用Redis和数据库各有优势:Redis性能更高,响应时间更短,能支撑更多并发请求;而MySQL具有较高的数据可靠性和支持丰富的查询方式和事务机制。
4、如果让你设计一个秒杀系统,怎么设计?
一个秒杀系统面临着多个独特的挑战,设计的时候应该考虑以下问题:
- 高并发瞬时流量:
在处理秒杀场景的高并发瞬时流量时,系统的稳定性和可用性成为关键挑战。通常,我们会在整体架构设计上采取逐层的流量过滤策略,确保系统能够有效处理巨大的请求压力。
首先,从客户端开始,可以在这一层实施一些请求的随机丢弃,以降低服务端的压力。被丢弃的请求可以直接返回失败或系统繁忙的提示,引导用户重试。这种方式可以在离用户更近的地方进行流量过滤。
接下来,通过CDN(内容分发网络)和Nginx进行统一接入,不仅可以实现负载均衡和流量分发,还可以配置黑白名单、IP限流等策略,进行更细粒度的流量过滤。
在服务端,通过Nginx配置的流量过滤和前端的丢弃策略,可以减轻服务端的压力。服务器层面也可以配置各种限流策略,例如基于Sentinel的动态限流,自定义的限流算法等。
此外,针对一些查询操作和写操作,可以使用缓存来提高性能。在缓存方面,本地缓存的响应速度更快,近端缓存效果优于远端缓存,可以有效抵挡部分请求。
由于秒杀业务本身允许一定的失败率,通过层层过滤,我们可以在前端、Nginx、服务器等层次上过滤更多的流量,提升系统的稳定性,确保在高并发情况下仍能提供可用的服务。
- 热点数据:
秒杀系统常面临热点数据问题,特别是在大家抢购同一商品时,该商品成为热点数据。这带来了高频的查询请求和非常频繁的写请求,尤其是在库存扣减等操作上。
针对热点数据,主要的解决方案包括拆分和缓存。
首先是拆分,即将热点数据分割成多个相对冷门的数据,并通过负载均衡将不同请求分散到不同的数据上,以降低单一数据的请求压力。
其次是利用缓存。通常,对于秒杀业务,可以提前预知哪些数据会成为热点,因此可以提前进行缓存的预热。这不仅包括在Redis等缓存中的预热,还应在本地缓存上执行预热,以避免Redis的热key问题。在处理热点数据时,充分利用缓存可以有效减轻数据库的负担,提高系统的响应速度。
通过拆分和缓存的组合应用,能够有效缓解热点数据带来的高并发压力,提升系统的稳定性和性能。
- 数据量大:
秒杀系统经常面临高频的下单操作,导致最终产生的订单量巨大,进而带来了查询效率低的问题。
针对这个挑战,有几种可行的解决方案:
- 加缓存: 在查询订单时可以引入缓存,将常被查询的订单数据缓存在内存中,以提高查询效率。可以使用分布式缓存如Redis等。
- 使用Elasticsearch(ES): 当订单数据量庞大时,可以考虑使用Elasticsearch进行存储和检索。ES对于大规模文档的全文搜索和分析非常高效,适用于订单数据的查询。
- 分库分表: 当单一数据库无法承载巨大的订单数据时,可以考虑采用分库分表的方式,将订单数据分散存储在多个数据库或表中,提高查询性能。
- 数据归档: 对于历史订单数据,可以实施数据归档策略,将过早的历史数据归档到冷数据存储中,减轻热数据存储的压力。这样可以保证查询性能,同时降低存储成本。
选择适当的方案取决于具体业务需求和系统架构。可能需要综合考虑缓存的实时性、ES的搜索能力、分库分表的扩展性以及数据归档的存储成本等因素。
- 库存的正确扣减:
在秒杀业务中,由于高频的并发库存扣减操作,提升库存扣减的性能并确保准确性是一项极为重要的任务。稍有不慎可能导致超卖、少卖等严重问题。
为了解决这个问题,可以采取以下策略:
- 优化数据库事务: 通过合理设置数据库事务隔离级别,减小事务的范围,降低锁竞争,提高并发处理能力。合理使用事务的提交点,确保扣减库存和生成订单等关键操作的原子性。
- 使用乐观锁: 在数据库层面引入乐观锁机制,通过版本号或时间戳等方式进行标记,减少锁的争用,提高并发性能。乐观锁对于高并发场景有较好的适应性。
- 分布式锁: 对于分布式系统,可以引入分布式锁,确保同一时刻只有一个请求能够扣减库存。分布式锁可以有效避免并发写入冲突。
- 异步扣减: 将库存扣减操作异步化,通过消息队列等方式将扣减请求发送到异步处理队列中。这样可以降低同步扣减的压力,提高系统整体吞吐量。
- 缓存预减: 使用缓存对库存进行预减,减少对数据库的实际扣减操作。当缓存库存为负值时,再进行实际的数据库扣减,避免超卖。
- 定时任务回补: 针对因各种原因导致未能成功扣减库存的情况,可以设置定时任务进行库存的回补,确保库存的准确性。
以上策略的选择应根据具体业务需求和系统架构来决定。综合考虑事务隔离级别、数据一致性、系统复杂度等因素,可以设计出更高效和稳定的库存扣减方案。
- 黄牛抢购:
在秒杀业务中,由于商品存在差价,吸引了大量黄牛参与抢购,因此如何识别并阻挡这些黄牛的下单行为显得至关重要。
首先,防刷的核心在于检测可能是黄牛的用户,这涉及到风控领域,通常需要借助算法模型。通过分析用户的IP、设备信息、网络数据、行为模式等,可以推算出可能是黄牛的用户。
一旦识别出潜在的黄牛用户,需要对他们的流量进行防控。这可以通过以下手段实现:
- 用户ID黑名单: 将识别出的黄牛用户ID加入黑名单中。Nginx和业务系统都可以在流量过滤时检查黑名单,如果用户在黑名单中,直接拒绝请求。
- IP地址、设备限流: 对于黄牛用户的IP地址和设备进行限流。例如,限制某个IP地址在一段时间内只能下单几次。这可以利用令牌桶、漏桶等限流算法来实现。
- 借助限流工具: 利用诸如Nginx、Sentinel、Guava等限流工具,直接在系统层面进行流量控制。这些工具提供了强大的限流功能,能够有效防止过量的请求。
通过综合利用黑名单、限流算法和专门的防刷工具,可以提高系统对黄牛的防控能力,确保秒杀活动的公平性和稳定性。
- 重复下单:
在秒杀业务中,用户频繁尝试下单支付可能导致重复下单问题,占用额外库存,最终导致少卖的情况。因此,需要采取一些手段来避免重复下单。
首先,基于前述提到的token,可以用于重复下单的检测。通过在同一页面上未刷新的情况下,保持token的一致性,可以利用token进行重复下单的检测,有效避免了此类问题。
此外,在秒杀业务中通常存在限购策略,因此可以结合业务场景,判断用户是否已有在途订单,通过限购方式来防止用户重复下单。
然而,上述过程中可能存在并发情况,导致token检测和订单重复性判断时的并发问题。为了保证下单操作的幂等性,引入锁机制是必要的。
通过引入锁机制,可以确保在并发场景下,同一用户不会重复下单。可以采用分布式锁,数据库乐观锁,或者业务层面的锁机制,以确保下单操作的原子性和幂等性,防止重复扣减库存和生成重复订单。这有助于维护系统的稳定性和数据的准确性。
- 对普通交易的影响:
秒杀通常作为电商网站的一个功能,往往与其他业务一同部署,包括服务器、缓存和数据库等资源。为避免秒杀业务对普通交易造成过大的影响,需要考虑隔离策略。
隔离可以采用逻辑隔离和物理隔离两种方式,具体实施视业务需求而定。
- 物理隔离: 物理隔离是指将秒杀业务的前后端服务以及数据存储完全分离。这样可以确保秒杀活动对其他业务的影响最小化。前后端服务可以单独部署,甚至使用不同的服务器集群,以确保资源独立。
- 逻辑隔离: 在应用层面进行逻辑隔离,即通过对数据进行标记或打标签的方式来区分秒杀订单和普通订单。例如,在订单和商品数据上添加标记,明确标识出哪些是秒杀订单。这样在后续的查询和数据分析中,可以更方便地区分和处理不同类型的订单。
综合考虑业务场景和系统复杂度,可以选择适当的隔离策略。物理隔离更彻底,但成本和复杂度也更高。逻辑隔离则相对灵活,能够在业务层面实现隔离效果,同时降低了部署和维护的难度。
5、应用程序中存在包冲突的情况下,怎么发现和解决
包冲突是指在应用程序中引入的不同版本的库或依赖之间存在冲突,可能导致编译错误、运行时异常或其他不稳定行为。解决包冲突通常需要以下步骤:
- 诊断冲突: 确定哪些包发生了冲突,以及冲突的具体原因。可以通过查看编译日志、运行时异常信息或使用工具来分析依赖关系。
- 查看依赖关系: 使用构建工具或依赖管理工具查看项目的依赖关系。对于Java项目,可以查看Maven的依赖配置文件。了解每个依赖项的版本以及它们之间的关系。
- 升级或降级依赖版本: 如果发现包冲突是由于不同依赖项使用了相同库的不同版本而引起的,可以考虑升级或降级相关依赖项的版本,以便它们使用相同的库版本。
- 排除依赖项: 对于某些依赖项,可以使用排除(exclusion)机制来排除特定的传递性依赖项。这样可以防止冲突的库被引入。
- 在Maven中,可以使用
<exclusions>元素:
<dependency>
<groupId>example</groupId>
<artifactId>example-artifact</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>conflicting-group</groupId>
<artifactId>conflicting-artifact</artifactId>
</exclusion>
</exclusions>
</dependency>- 使用类加载器隔离: 对于较为复杂的场景,可以考虑使用类加载器隔离机制,将不同版本的库加载到不同的类加载器中。这样可以在同一应用中使用不同版本的库而不会发生冲突。但需要注意,类加载器隔离可能引入新的问题,需要谨慎使用。
- 升级项目依赖: 如果有可能,可以考虑升级项目的所有依赖项到最新版本,以确保使用的库都是相对较新的版本,减少冲突可能性。
- 使用依赖锁定工具: 一些工具如
Maven Enforcer Plugin可以帮助检测项目中的依赖冲突,提供更全面的依赖关系报告。
6、跨节点join查询如何实现
在分布式系统中,跨节点的Join查询是一个具有挑战性的问题,因为传统的关系数据库系统通常设计为在单一节点上执行Join操作。在分布式环境下,有一些常见的解决方案:
- 数据复制: 将涉及到Join操作的表数据在分布式环境中的每个节点上都复制一份。这样,每个节点可以在本地执行Join操作而无需跨节点通信。这种方法虽然简单,但会导致数据冗余和一致性的问题。
- 分布式Join算法: 开发专门的分布式Join算法,使得在分布式环境下也能高效执行Join操作。例如,MapReduce框架就提供了一些分布式Join的实现。这需要对数据进行划分和分布式计算。
- 使用分布式数据库: 选择支持分布式Join的分布式数据库系统。一些分布式数据库提供了内建的分布式Join支持,可以在分布式环境中高效地执行Join查询。
- 分片和合并: 将Join操作分解为可在每个节点上执行的部分,然后在一个中心节点上合并结果。这种方式需要设计合适的数据分片策略和结果合并机制。
- 数据缓存: 在每个节点上缓存可能用于Join的数据片段,以减少跨节点通信的需求。这需要根据查询模式预先缓存可能用到的数据。
- 消息传递: 使用消息传递机制,将涉及Join的查询分解为子查询,分发到各个节点执行,然后将结果进行合并。这需要有效的通信机制和任务协调。
- 全局索引: 在所有节点上建立全局索引,以加速Join查询。这需要维护一致的全局索引,可能带来额外的维护成本。
7、Excel中百万级数据怎么导入到数据库
在处理百万级数据从Excel读取并插入到数据库的场景中,涉及到多方面的问题,需要综合考虑性能、内存占用、错误处理等因素。以下是一个可能的解决方案:
- 内存溢出问题
使用流式读取方式,分批处理Excel数据。选择EasyExcel作为处理工具,它能够逐行读取数据而不将整个Excel加载到内存中,避免内存溢出的问题。
- 性能问题
考虑多线程处理,使用线程池同时读取不同的sheet,以提升并发性能。对于数据插入操作,借助多线程和数据库的批量插入功能,进一步提高性能。
- 错误处理
- 数据检查: 在读取和插入之前进行数据检查,处理数据格式错误、不一致等问题。
- 异常处理: 在插入过程中,对异常进行处理。采用自动重试机制,一定次数内尝试重新插入。记录日志以便后续分析。
- 数据重复处理: 设置Excel中某几个字段为数据库唯一性约束,处理数据重复问题。可以选择覆盖、跳过或报错,根据业务需求选择合适的方式。
整体方案
- 使用EasyExcel逐行读取Excel数据,通过多线程和线程池同时处理不同sheet的数据。
- 数据分批插入,设置一个批次大小,当达到批次大小时执行一次批量插入操作。
- 利用线程安全的队列(如ConcurrentLinkedQueue)暂存内存中的数据,确保并发安全性。
通过这种方案,能够在相对较短的时间内处理百万级数据的读取和插入,同时保证了内存的有效利用和错误处理的可靠性。具体的实现可以基于以上思路,结合实际项目需求和技术栈进行调整。
8、缓存预热怎么实现
在系统中进行缓存预热是为了提高系统性能和响应速度的一种策略。常见的缓存预热方案包括:
1. 启动过程中预热
一种常见的实现方式是在系统启动时进行缓存预热,特别适用于本地缓存。在Spring应用程序启动时,可以通过监听应用启动事件或在初始化阶段,将需要缓存的数据加载到缓存中。Spring提供了多个事件和扩展点,如ApplicationReadyEvent、CommandLineRunner、InitializingBean、@PostConstruct等,可用于执行预热逻辑。
2. 定时任务
定时任务是在系统运行过程中通过设定的时间间隔更新缓存,以确保缓存中的数据保持最新。在Spring中,可以使用@Scheduled注解轻松实现定时任务,适用于需要动态更新的情况。
3. 用时加载
基于用户的查询模式和业务需求,动态地在用户请求到达时将数据加载到缓存中。这种方式更加灵活,可以根据实际需求选择性地预热缓存。示例代码如下:
public Data fetchData(String key) {
// 先检查缓存中是否存在数据
Data cachedData = cache.get(key);
if (cachedData == null) {
// 如果缓存中不存在,根据业务需求加载数据到缓存中
// ...
}
return cachedData;
}4. 缓存加载器
某些缓存框架提供了缓存加载器的机制,当缓存中不存在数据时,自动调用加载器加载数据到缓存中。例如,在Caffeine中可以使用LoadingCache实现,简化了缓存预热的逻辑。示例代码如下:
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class MyCacheService {
private final LoadingCache<String, String> cache;
public MyCacheService() {
this.cache = Caffeine.newBuilder()
.refreshAfterWrite(1, TimeUnit.MINUTES) // 配置自动刷新,1分钟刷新一次
.build(key -> loadDataFromSource(key)); // 使用加载器加载数据
}
public String getValue(String key) {
return cache.get(key);
}
private String loadDataFromSource(String key) {
// 从数据源加载数据的逻辑
// ...
}
}以上方案可根据具体业务需求和技术栈进行选择和调整,以达到最佳性能和可维护性。
9、表数据量大的时候只能分库分表?
当MySQL中的单表数据量庞大时,考虑分库分表通常是为了提升数据库吞吐量和查询效率。然而,分库分表方案的实现较为复杂,且可能引发一系列问题,如跨库事务、分页查询等。在采取分库分表之前,应优先考虑以下方案:
- 数据库优化: 通过优化数据库结构、使用合适的索引、调整SQL语句等手段,可以有效提升查询性能。在2000万数据量级别,合理使用索引和进行表结构设计优化能够显著改善数据库性能。
- 缓存: 减轻数据库压力可以通过缓存实现。在近端引入本地缓存或分布式缓存,能够迅速返回结果并减轻数据库负担,提高系统性能。
- 分区: 数据分区可以减小单个表的物理存储空间,将数据分散存储在不同的表或文件中,以降低单表的数据量,从而提高查询性能。
- 数据归档: 定期清理不再需要或不再活跃的数据,将其归档到辅助存储中(如历史表、离线数仓),可以减少数据量,进而提升数据库效率。
- 分布式数据库: 考虑采用分布式数据库系统,充分利用多个节点分散数据,提高系统性能和容量。尽管分布式数据库成本较高,但相较于分库分表的改造而言,收益更为显著。
这些方案在实施时需要根据具体业务场景和需求进行综合考虑,选择最适合的优化方案,以达到性能提升的效果。
10、线上发现一个接口响应很慢,怎么排查问题
- 监控工具:可以使用性能监控工具来实时监控接口的响应时间、吞吐量和错误率等指标。常见的监控工具:Arthas、Prometheus、Grafana、AppDynamics等。
- 日志分析:查看接口的日志文件,关注接口的执行时间、请求参数和响应结果等消息。同构分析可以找到执行时间较长的接口,以及可能导致慢接口的原因。
- 接口性能测试:通过模拟多用户并发请求,观察接口的响应时间和吞吐量等指标,找出慢响应的接口。常见的性能测试工具:JMeter、LoadRunner等。
- 数据库查询分析:如果接口涉及数据库查询,通过数据库查询分析工具(如EXPLAIN语句)来检查查询执行计划,确保数据库查询效率。
- 代码剖析:对接口代码进行剖析,了解代码中的性能瓶颈和耗时操作,通过剖析结果找到影响性能的代码段,进行优化,常见的剖析工具:Java Profiler、VisualVM等
11、MQ里面数据到达1000万的时候,怎么尽快消费掉
- 增加消费者数量: 消息堆积了,消费不过来了,那就把消费者的数量增加一下,让更多的实例来消费这些消息。
- 提升消费者消费速度:消费者消费的慢可能是消息堆积的主要原因,想办法提升消费速度,比如引入线程池本地消息存储后即返回成功后续再慢慢消费等。
- 降低生产者的生产速度:如果生产者可控的话,可以让生产者生成消息的速度慢一点。
- 清理过期消息:有一些过期消息、或者一直无法成功的消息,在业务做评估之后,如果无影响或者影响不大其实是可以清理的。
- 调整 RocketMQ 的配置参数: RocketMQ 提供了很多可配置的参数,例如消息消费模式、消息拉取间隔时间等,可以根据实际情况来调整这些参数,从而优化消息消费的效率。
- 增加Topic队列数: 如果一个 Topic 的队列数比较少,那么就容易出现消息堆积的情况。可以通过增加队列数来提高消息的处理并发度,从而减少消息堆积。
12、涉及到订单超卖问题怎么解决
订单超卖:就是当有两个并发线程同时查询库存商品,这时数据库中商品库存就剩1个,所以两个线程得到的库存数量都是1,然后经过库存校验之后分别开始进行库存扣减,最终导致库存被扣减成负数。
- 悲观锁:在更新库存的时候使用数据库的锁机制,如
SELECT ... FOR UPDATE,确保同一时间只有一个线程能够操作库存。悲观锁会在操作数据之前先获取锁,其他事务需要等待锁释放后才能执行操作,但会增加数据库的负担和死锁的风险。 - 乐观锁:通过版本号或时间戳等机制,在更新库存时进行比较,如果发现库存不足,则拒绝更新。这样可以保证只有一个线程能够成功更新库存,其他线程需要进行重试或处理失败情况。但需要处理并发冲突的情况。
- 限流:对订单的创建进行限流,防止短时间内大量订单的涌入,从而减小并发冲突的可能性。可以使用令牌桶算法或漏桶算法来实现限流;
限制同一IP在一定时间内只能发送一个请求。可以通过在网关中自定义全局过滤器来实现IP限流;
限制接口每秒只接受一定数量的请求。可以使用Sentinel的限流功能;
- 库存预留:在用户下单时,先检查库存是否足够,并将一部分库存标记为已预留状态。在订单支付完成后再确认扣减库存。
- 缓存:利用缓存的原子操作特性来扣减库存,可以使用Redis的
incrby命令来实现库存的扣减。 - 消息队列:使用消息队列,将订单的创建和库存的更新分开处理。订单先进入消息队列,然后由后台异步任务进行处理,保证订单和库存的更新是顺序执行的。这样可以降低竞争条件的发生。
