后端思维之高并发方案

我有话想说

这篇文章的构思始于2023年,受限于个人经验与知识积累,初稿拖延至2025年1月才最终完成。在此过程中,许多同行大佬慷慨提供了审稿意见与建议,对此我深表感谢。

这是接近一篇万字长文,为方便大家阅读,我整理了文章的大纲并以思维导图的形式展示。你可以根据自己的兴趣点选择性阅读,希望这篇文章能为你应对高并发场景提供启发与帮助。

特别鸣谢:韩楠、王君、杜小非、冼润伟、李鸿庭(排名不分先后)

前言

在互联网时代,高并发已经成为后端开发者绕不开的话题。无论是电商平台的秒杀活动、抢购系统,还是社交应用的高频互动,高并发场景的出现往往伴随着巨大的技术挑战。

如何在流量激增的同时,确保系统稳定运行、快速响应?这不仅是对技术能力的考验,更是对架构设计和资源优化的综合考量。

在多年的工作实践中,我有幸接触并解决了许多高并发场景的实际问题。因此,在这篇文章中,我将结合理论与实践,深入剖析高并发的本质、应对策略,以及实际案例,希望能够为你揭开高并发背后的技术奥秘。

文中提到的高并发"标准"三字真言------"砍、缓、多" ,以及七大处理手段,均是我在工作中总结出的经验。这些方法并非涵盖所有可能的解决方案,但我希望它们能为你提供思路,同时也欢迎大家补充和交流。

什么是高并发?

百度百科:

通俗来讲,高并发是指在**同一个时间点**,有很多用户同时访问**同一API接口或者Url地址**。它经常会发生在有大活跃用户量,用户高聚集的业务场景中

简单的说,高并发是指系统在同一时间内接受到大量的客户端请求访问,需要系统(服务端)能够快速响应并处理请求的能力。在咱们互联网应用中,例如电商、游戏等在做活动或者促销的时候,这些热点业务就非常大可能同时被大量用户访问,并造成系统较大的负载。高并发一般伴随着数据增长、流量增加,这种现象可能是短时间的内的峰值,也可能是持续不断负载压力,因此需要开发在架构设计、技术选型、性能监控等多个方面进行优化、调整以提高系统的并发处理能力。

并发与并行的区别是什么?

并发和并行都涉及到同一时刻处理多个任务,但它们的概念和实现方式略有不同。

并发,指的是多个事情,在同一时间段内同时发生了。 并行,指的是多个事情,在同一时间点上同时发生了。

并发,是指一个系统能够同时处理多个任务或者请求,并且看起来好像这些任务是同时执行的。实际上,这些任务只是在最短时间内交替执行,因为计算机的处理速度非常快,例如在一个 CPU 上同时运行多个应用程序或是处理多个网络请求。

并行,是指一个系统可以真正意义上同时处理多个任务或请求,因为它有多个执行单元,可以同时执行多个任务或请求。例如在拥有多个 CPU 或多个核心的服务器上,可以同时处理多个请求或任务,这就是并行处理。

虽然两者一字之差 ,但是我认为他们属于不同层面 上的概念:并发属于是表达了指令发起端到指令执行端的请求情况(系统外部) (系统外部) ;并行属于是描述了系统(应用)执行任务时的模式 (系统内部)

高并发的怎样才算高?

不同的读者看到这里的时候,心里都会有一个答案:

  • 拥有大项目经历的同行肯定想,没个几十万、上百万的QPS都不叫高并发吧?
  • 萌新可能认为,我平常系统峰值最高就两三千的 QPS,那么起码也得高一个量级才算吧?

我是这么认为的:

高并发的高并没有一个具体的量化标准的 ,并不是必须得多少个万级别的 QPS 才算是高,因为【高】 在物理学里是相对的概念。

对于小型的系统或ToB系统来说,如果初期架构设计没考虑好或者资源有限,几百上千的 QPS 的并发访问可能已经会对系统造成一定的压力;

对于大型互联网公司或应用,每秒钟数万甚至数十万的并发访问甚至峰值达到百万级这都并不罕见。

因此,在讨论高并发时,我们不必将其想象为极端数量级的并发情况。关键在于理解特定业务场景下,在何种条件(包括人力、技术力、计算力)下,为了达到既定目标(如稳定性、安全性、用户体验)而需要处理的并发量

基于这些因素,当并发量达到一定水平,足以影响这些目标时,我们通常将这种情况视为高并发。这样的判断并不仅仅基于并发量的增加是否达到了某个具体的"高并发"标准。

高性能等于高并发吗?

首先,高并发与高性能之间确实存在直接的联系 。高性能指的是系统或应用程序能够迅速处理单一请求的能力,这意味着在相同的时间内 ,一个性能更优的系统能够处理更多的请求,从而提升其并发处理能力。

以一个系统接口为例,假设它在单线程环境下能够达到每秒100次查询(100QPS),这意味着处理单个请求的时间大约是0.01秒。如果我们将处理速度提升十倍,即每个请求的处理时间缩短到0.001秒,那么理论上的QPS可以提升到1000QPS。这个例子清晰地展示了高性能与高并发之间的正相关性。

然而,高性能与高并发并非完全等同

  • 高性能关注单个请求的处理效率,
  • 高并发关注系统同时处理大量请求的能力。

一个系统即使设计之初就考虑了高并发,能够同时接收大量请求,但如果单个请求的处理时间较长,其响应速度和整体性能可能仍不理想。

例如,某接口通过队列异步处理请求,虽然能应对高并发,但如果队列设计不合理或任务本身耗时较长(如5-8秒),会影响用户的实时体验。

综上所述,尽管高性能与高并发紧密相关,它们并不是同一概念。实际上,高性能的解决方案可以视为高并发解决方案的一个重要组成部分,但高并发系统的设计还需综合考虑分布式架构、缓存、限流等技术,以优化整体性能和用户体验。

不满足高并发会有什么后果?

在高并发环境下,如果系统不能有效处理大量并发请求,可能会导致多种严重后果,影响系统的性能和用户体验。下图是系统在高并发场景下不同的层面的后果表现:

然而我们后端开发关注的层面更多是偏向于接口、数据库还有服务器层面,因此我根据上图我重新筛选与整理了一份详细的表格如下:

层级 类型 问题 描述
应用层 性能下降 响应时间增加 系统无法处理高并发请求时,响应时间增加,用户反馈延迟
吞吐量降低 系统每秒处理的请求数减少,硬件资源无法充分利用
数据库 锁争用和死锁 锁争用 高并发环境下,多个事务争用资源,影响事务执行
死锁 多个事务相互等待释放锁,导致事务无法完成
数据不一致 数据竞争 高并发请求导致多个请求修改相同数据,数据不一致
脏读、不可重复读等 隔离级别不足导致脏读、不可重复读、幻读,影响数据正确性
服务层 资源耗尽 CPU过载 高并发请求导致CPU使用率过高,影响系统性能
内存耗尽 并发请求过多导致内存耗尽,缺乏内存管理
磁盘I/O瓶颈 频繁读写操作导致磁盘I/O瓶颈,影响性能
服务器崩溃 服务器过载 高并发请求导致服务器崩溃或宕机,无法提供服务
资源泄漏 文件句柄、数据库连接等未正确释放,导致性能下降或系统崩溃

有哪些通用的高并发方案?

通过上述我们清楚的了解到高并发处理不当的严重性,那么究竟有没有拿来即用的方案直接套上去就可以解决了呢?有,我把过往的经验总接了一下,从大方向来看一共三大类:限流、异步、冗余。 这三个词,我相信大家都不陌生,我也给他们都各用一句话来描述。

  1. 限流,溢出的流量就不要了
  2. 异步,不着急的任务就放缓处理
  3. 冗余,弄多几个副本分担压力

因此,高并发的通用解决方案我认为无疑就是三字真言:砍、缓、多。每个类型其实又细分共七大处理手段,我整理了一张表格给到各位,毫不夸张说,从我过往经验来看,以下方案可以解决我们日常遇到90%的并发问题。

类型 方案 描述
限流 业务限流 根据具体业务场景进行限流,例如抢购前答题、活动过量时限购。
技术限流 使用算法在应用层面(API网关)进行限流,例如基于漏桶、令牌桶算法等限流。
异步 调度任务 先把数据暂存下来,定期或特定条件下(闲时)进行批量处理。
消息队列 削峰:将瞬时高峰流量平滑处理,使后台服务逐步处理消息,避免系统崩溃。
异步化:将长时间处理的任务(如视频处理、数据分析等)异步化,避免主线程阻塞,提高响应速度。
冗余 集群 负载均衡:通过多个服务器节点分担压力,避免单个服务器过载;可用于API和数据库。
故障转移:某节点故障时,负载均衡器将请求分配给其他健康节点,保证服务可用性。
缓存 将热数据存放到高性能存储系统,降低数据库访问量,提高系统响应速度。
静态化 将动态产生的数据保存为静态文件或预先计算好固定规则数据,降低计算和数据库压力。例如介绍页、报表静态化。

三字真言,七大处理手段固然好使,但是并不代表可以滥用,像限流、集群、缓存等更多属于短期收益高的应急手段。

举个例子,可能我们的问题其实就是一个慢查询导致的数据库负载过高,从而影响了应用的工作线程数阻塞,最后影响到了应用服务器的CPU过载从而导致接口无法响应,这种情况下我们贸然的去堆硬件、加缓存而不去优化语句,这无疑是饮鸩止渴,还会额外增加成本(硬件、维护)。

如果本质问题未得到根本解决,问题终将再次出现,时间只是决定它重现的变量。

高并发有哪些场景?

从大层面来看,高并发场景可以分为"读"和"写"两类。以典型的互联网系统为例,读写比例通常为 8:2,即读多写少。因此,读写场景各自具有不同的特点,采用的优化方案也有所区别。

读场景

在互联网应用中,系统通常可以看作是一个资源整合的平台,因此读操作 占据了较大的比例。无论是数据库还是接口,读操作一般具有以下两个特点:幂等性和负载均衡性(除非接口设计得不合理,如读写混合的情况)。

幂等性指的是:相同的参数调用同一个方法,无论执行一次还是多次,响应的结果都是一致的。将这一概念应用到读场景中,意味着对于相同的输入参数、相同的处理逻辑,最终返回的结果始终一致。

负载均衡性

读操作由于具备天然的幂等性,API 服务通常倾向于设计为"无状态"。这种设计使得在面临负载瓶颈时,可以通过增加服务副本实现横向扩展(Scale-Out),无需引入复杂的逻辑处理。此时,系统的关注点更多集中在数据库(存储系统)和服务器的性能及负载上。

众所周知,关系型数据库在处理分布式写(例如分库分表)时面临较大的挑战,但在分布式读 方面具有天然优势。成熟的数据库通常能够通过简单的组件实现一主多从架构,支持读写分离。

无论是接口层面的服务集群,还是数据库层面的一主多从架构,其核心策略都在于通过【】副本来分担压力,提升性能与可用性。

然而,这种场景通常假设数据是静态的,不涉及复杂的计算。当面对复杂计算的高并发场景时,数据库(存储系统)的负载压力会更加明显。

优化手段

为应对上述问题,可以引入以下优化手段:

  1. 缓存:将热数据存储在内存(Redis)中,减少对数据库的直接访问。

  2. 静态化:将动态生成的数据转换为静态(如 HTML 文件、中间表数据),在一定时间内复用热数据。

无论是增加服务副本,还是使用缓存和静态化手段,其核心思想都是一致的:冗余。通过冗余数据或资源,减少系统在高并发场景下的负载压力。

写场景

相比读操作,写操作在高并发场景下更复杂,因其缺乏天生的数据幂等性和负载均衡。写操作的优化主要围绕数据一致性、高性能异步处理展开。

异步处理

异步处理在高并发的写场景中是最直接有效的,其核心思想采用【 】的策略。队列和调度任务在这里扮演了两个关键角色:缓冲和延缓

  • 队列 :可作为数据的临时存储区域,队列将高并发的写操作转化为串行处理(或少量并行),从而起到缓冲作用,有效减轻后端系统的瞬时压力。例如:数据采集
  • 调度任务 :通过控制处理频率(速度和时间),调度任务将非实时性强的操作延迟处理,并在流量低峰期集中批量执行,从而实现流量的平滑化。例如:排行榜、季度报表等

然而,异步处理并非万能,存在一定的场景局限性。并不是所有的写操作都适合使用队列。例如,对于时效性要求较高的请求,异步处理可能无法满足需求,此时需要采用一些特殊手段来弥补,例如轮询查询、WebSocket 推送等实时机制。

高性能写

在高性能存储场景中,NoSQL ------ Redis 是常见的选择。一个典型的应用场景是抢购系统,其中针对高并发写操作的解决方案通常采用 "预扣减" 策略。其处理流程如下:

  1. 数据同步:通过后台服务或定时调度任务,将数据库中的库存数据提前同步到 Redis。
  2. 库存判断:通过 Redis 实现库存的快速判断。
  3. 扣减操作:每次对热门商品的库存进行扣减时,直接在 Redis 中使用 INCR -N 指令完成。

这种方法有效避免了数据库在高并发写入场景下因锁机制导致的性能瓶颈,同时充分利用 Redis 的高吞吐能力,显著提升了系统的响应效率。

限流

在互联网领域,流量被视为至关重要的资源,因此有一句话广为流传: "流量为王",因为流量直接关系到用户接触度和潜在的商业价值。

尽管流量的增加在理论上是有利的,但在资源有限的现实环境中,过量的流量可能会成为系统的负担,甚至导致系统崩溃。

因此,为了避免系统因流量激增而超出承载能力,我们通常采用限流策略 ,其核心思想是通过"砍" 的方式对流量进行控制。限流策略可以分为技术限流业务限流两种方式。

技术限流

技术限流通过技术手段对访问流量进行控制,确保系统在其负载能力范围内平稳运行。通常,这类限流措施会在流量入口(如 API 网关)处实现。常见的技术限流策略包括:

  • 速率限制(Rate Limiting) :限制用户在单位时间内的最大请求次数。
  • 令牌桶(Token Bucket) :通过令牌分发机制控制请求速率,允许一定程度的突发流量。
  • 漏桶(Leaky Bucket) :将请求以固定速率排出,平滑流量波动。
  • 熔断器(Circuit Breaker) :在系统检测到异常或过载时,主动中断请求链路,保护系统免于崩溃。

业务限流

业务限流从业务层面出发,通过调整业务策略来控制流量,不仅可以减轻系统负担,还能优化用户体验。常见的业务限流策略包括:

  • 用户分级服务:根据用户等级或类型实施差异化的访问策略,例如,VIP 用户享有更高的请求优先级或更大的访问额度。
  • 预约机制:针对流量激增的场景,通过预约制度控制用户访问。例如,在特定时间段内限制服务人数,分批次提供服务。

我遇到的高并发优化场景

在之前的讨论中,我们探讨了许多高并发场景的理论知识。接下来,我将分享一些实际工作中的优化案例。

无状态让API服务"力大飞砖"

多年来,我司主要通过 Redis 和 服务集群 来优化系统性能。随着用户数量和日活跃度的持续增长,API服务的CPU压力逐渐增大。为应对这一挑战,我们从设计之初便采用了 无状态服务,并引入 Nginx 实现负载均衡,使服务能够根据流量需求进行 横向扩展,从而实现集群化部署。

问题背景

近期,由于合作方投流,平台流量进一步增长,特别是在晚高峰时,部分API服务节点出现满负载情况,而数据库负载却保持正常。通过监控和代码分析发现,问题出在某些接口的实现上。这些接口每次读取大量数据,并通过 Foreach 进行逐条查询和计算。由于查询是基于主键的,数据库压力不大,但数据量过大直接导致单次请求执行时间过长。当晚高峰多名用户并发请求时,这些接口瞬间占满API服务的工作线程,导致 CPU负载飙升。

临时应对

为应对流量高峰,我们通过 API横向扩容 的方式,临时增加了多台节点机,缓解了服务压力,确保平台能够稳定运行,抓住这波流量。

缓存很有用,但姿势要对

为了优化性能,我们几乎对所有核心业务(如首页数据、推荐位、排行榜、作品内容等)都采用了 缓存策略

这种方法在过去几年中效果显著:只要出现性能瓶颈,引入缓存几乎总能解决问题。尤其是首页业务,这类数据通常是每隔数小时更新一次的伪静态数据,使用缓存完全合理。

然而,这也引出了一个值得思考的问题:缓存是否能解决所有性能问题?

缓存虽然能够显著提升数据读取性能,但对于复杂计算、接口设计缺陷以及高并发场景下的线程占用问题,缓存并非万能。我们需要结合具体场景,从代码优化、接口设计、数据库查询效率等多方面入手,才能真正解决性能瓶颈。 在一年的最后一天,我们发现了一个严重问题。12月31日午夜12点,咱们数据库的CPU使用率突然从20%激增至100%。通过检查接口日志和数据库阻塞日志,问题锁定在一条长期使用的排行榜SQL语句。按理说,这部分数据应有缓存,为何系统会崩溃?经过代码审查,问题如下:

问题分析

伪代码如下:

C# 复制代码
    // 缓存策略模式 - cache-aside
    var redisKey = "rankinglist:" + DateTime.Now.ToString("yyyyMMdd");
    var rankingListCache = redis.Get(redisKey); // 从缓存获取数据
    if (rankingListCache != null)
        return rankingListCache;


    var data = db.RankingList.GetList(); // 从数据库获取数据,复杂查询
    if (data.Any())
    {
        redis.Set(redisKey, data, 3600); // 写入缓存
        return data;
    }
    return new List();
  1. 缓存键设计问题

缓存键基于 DateTime.Now.ToString("yyyyMMdd") 生成,导致跨年、跨月、跨日时,年度榜、月榜、周榜在午夜12点立即失效,触发所有请求直接访问数据库。

  1. 缓存穿透问题

仅当 data.Any()`为真时才会更新缓存。如果数据库查询结果为空,则不会写入缓存,导致每次请求都直接访问数据库。

临时优化

针对上述问题,我们进行了以下优化:

C# 复制代码
    var redisKey = "rankinglist:" + type; // 改为基于榜单类型的缓存键
    var rankingListCache = redis.Get(redisKey); // 从缓存获取数据
    if (rankingListCache != null)
        return rankingListCache;


    var data = db.RankingList.GetList(); // 从数据库获取数据
    if (data.Any())
    {
        redis.Set(redisKey, data, 3600); // 缓存有效数据
        return data;
    }
    else
    {
        redis.Set(redisKey, new List(), 60); // 缓存空数据1分钟
        return new List();
    }
  1. 缓存键设计优化

去掉基于日期的缓存键,改为按榜单类型(如年度榜、月榜、周榜)生成缓存键,避免因日期变更导致缓存大规模失效。

  1. 缓存穿透防护

即使数据库查询结果为空,也缓存空数据(有效期1分钟),避免频繁查询数据库。

通过以上优化,临时解决了Redis引发的缓存穿透和缓存雪崩问题。

长期优化

从数据库架构设计角度,我们进一步采取了 主从分离策略,将首页只读业务和复杂查询迁移至从库。迁移后,通过 Zabbix 监控发现,主库CPU负载高峰现象彻底解决,主库负载降低了50%,主从库运行稳定,性能大幅提升。

异步与静态化

一个工作日的早晨,系统再次报警,部分用户反馈无法访问平台功能。问题持续约 20 分钟后逐渐恢复。通过日志和监控分析发现,问题源于从库 CPU 负载达到 100%,由类报表功能的复杂查询引发。

前一天启动的活动吸引了大量用户次日参与,但部分缓存失效导致相关功能查询直接落库。由于查询语句执行时间较长(5-10 秒),结果无法及时写入 Redis,后续用户的请求均直接查询数据库,导致 CPU 瞬间飙升至 100%。这种现象称为缓存击穿 。高并发下的查询超时 进一步加剧了无法缓存的情况,形成恶性循环

得益于主从分离策略,本次仅部分功能受影响,但复杂查询在高并发场景下对数据库负载的压力较大,亟需优化。

考虑到相关数据短时间内不会变化,我们对架构进行了调整:

  1. 静态化数据处理:将实时 SQL 查询改为通过调度任务定时执行,将查询结果压缩后存储至中间表。
  2. 接口调整:修改接口逻辑,直接从中间表读取数据。

这种策略有效降低了数据库对复杂查询的计算压力,同时显著提高了接口的并发处理能力。

复杂 SQL 在高并发场景下对数据库影响较大,尤其配置不足时更易成为瓶颈。通过静态化处理和中间表设计,既缓解了数据库压力,又优化了用户体验。

限制不住,就扛下来

俗话说:"人怕出名,猪怕壮"随着平台不断发展壮大,关注度提高的同时,挑战也随之而来,如盗版和竞争对手使用爬虫抓取数据。这些爬虫不仅可能将商业数据发布到免费网站,损害平台利益,还会通过频繁请求导致系统压力激增。

虽然 "流量为王" ,但恶意爬虫 的流量对平台毫无价值。为此,我们迅速在网关层面实施限流策略,从 IP、Cookies、UA 等多个维度限制密集请求,有效应对了基础、粗暴的爬虫攻击。

然而,某日搜索库服务器 CPU 使用率骤然飙升。分析后发现,黑客通过模拟客户端身份并以分布式方式 绕过限流策略 发起攻击。为长期防御此类问题,我们需要从前后端入手,增加校验规则、更换密钥、加强客户端防护等多方面提升系统安全性。

但我们也深知,没有绝对完美的安全策略,后端服务必须具备一定的抗压能力。

非常遗憾的是,我们用Like做搜索作为技术债务保留了下来,也是这次事故的凶手,为解决这一问题,我们引入了 ElasticSearch,通过定时任务定期同步搜索库数据至 ElasticSearch,并调整接口逻辑指向 ElasticSearch。这一优化显著提升了搜索性能,使系统在高并发场景下更稳定,也更从容地应对爬虫流量攻击。

怎么应对火热抢购?

虽然我们公司主营文娱类业务,但任何形式的优惠或免费活动都能吸引大量用户,类似电商平台的抢购活动。

以平台推出的福利商城为例,用户通过完成任务或参与活动获得虚拟币,用于兑换"代券"免费观看作品。代券每天限量,每晚12点系统自动刷新库存,开启新一轮兑换。

在一次五一活动中,我们吸引了大量用户参与。然而活动结束后的第二天凌晨,主库 CPU 负载突然飙升至 100%,持续约 15 分钟。经过分析发现,问题出在用户集中抢兑代券时,SQL 执行遇到高并发锁竞争。尽管库存扣减的 SQL 语句很简单:

C# 复制代码
    UPDATE TableA SET Stock = Stock - 1 WHERE Stock > 0;

问题的根源在于并发环境下共享数据 产生的锁竞争。

锁竞争的高负载原因:

1.自旋锁的忙等

自旋锁在锁被占用时,线程会不断循环尝试获取锁而不挂起,导致CPU持续执行无效操作,显著增加CPU负载。

2.频繁的上下文切换

高并发下,线程竞争锁未果时会被挂起并进入等待队列,锁释放后再被唤醒。频繁的挂起和唤醒引发大量上下文切换,消耗CPU资源,提升负载。

3.死锁检测的开销

在高并发场景中,数据库或操作系统需要扫描等待图或事务依赖关系以检测死锁,这种复杂计算会显著增加CPU负担。

为解决这一问题,我提出了两种优化方案:

  1. 调整库存刷新时间
    建议将库存刷新时间改为凌晨 4-5 点的低峰期,避开用户集中"蹲点"兑换的高峰时段。这是成本最低的解决方案,但团队认为用户已习惯现有模式,调整可能影响用户体验。
  2. 预扣库存方案
    针对高并发问题,设计了基于 Redis 的预扣库存机制:
    1. 库存同步至 Redis:后台对热门商品新增或编辑时,将库存同步到 Redis。
    2. Redis 扣减逻辑:客户端请求兑换时,系统先检查 Redis 中的库存,若库存量大于 0,使用 INCR 命令扣减库存。只有扣减成功,才生成兑换记录。
    3. 库存回写数据库:当 Redis 库存扣减至 0 时,将库存同步回数据库,并更新库存状态供客户端展示。

通过 Redis 的高性能读取和写入操作,避免了数据库的锁竞争问题,同时显著降低了 CPU 负载。

抢购可以使用队列处理么?

针对"抢购"类业务场景,可以考虑引入队列机制来缓解热点数据更新导致的高负载问题。队列的先进先出 (FIFO)特性和串行处理机制 能够有效降低数据库压力,避免高并发写入引发的性能瓶颈。然而,队列的引入往往意味着异步处理用户请求,这对需要即时反馈 的场景带来了新的挑战

例如,类似【智行火车票】APP的排队下单系统(尽管未明确其是否使用队列),其逻辑与队列机制非常相似:

  1. 用户下单后进入排队页面(中间页),页面通过定时刷新更新订单状态。
  2. 处理完成后,页面展示支付按钮供用户操作。
  3. 支付完成但尚未出票时,用户会跳转到订单详情页,出票后则以通知形式告知用户。

再举个例子,以上面说的福利商城的"抢购"业务为例:

  1. 用户点击【兑换】按钮后,系统将请求放入队列中,队列逐步消费这些请求。用户此时会进入一个中间等待页面,等待兑换结果。
  2. 由于并发情况下库存数据未能实时同步扣减,最终兑换结果可能失败。
  3. 对于结果通知的方式,可以选择通过 WebSocket 实时推送,也可以在中间页由客户端轮询获取结果。

然而,这种改造方式需要前后端协同配合,且需要调整用户交互逻辑,改造成本相对较高。

针对"抢购"类业务场景,优化方案有多种选择。具体方案应根据系统的实际体量和业务需求,选择最优的处理方式。

队列适用场景分析

在我们的平台上,队列适用于以下写入场景:

  1. 客户端行为日志采集
    用户操作频繁、请求量大,但对响应时间要求不高。日志采集主要用于生成报告供内部分析,因此无需实时写入数据库。通过队列异步处理,可有效缓解高并发写入压力。
  2. 个推数据处理
    个推数据虽需基于用户行为进行实时推荐,但本质上是伪实时场景。推荐算法的响应时间通常在 0.01 秒到 3 秒之间波动,当无法即时生成推荐结果时,可使用公共推荐库作为替代。队列能够将推荐计算转移至后台,分散系统压力,提升整体性能。
  3. 公共数据统计
    用户观看广告后,平台会赠送代金券,同时广告的总观看次数需要更新。在晚高峰时,大量用户集中操作会对广告数据表造成写入压力,甚至引发锁竞争或死锁问题。为优化性能,可将"广告观看次数 +1"的操作通过消息队列(MQ)异步处理。用户领取奖励后,系统将更新请求写入队列,由后台异步更新数据库,避免高并发直接写入。

高并发不是终点

高并发不是终点,而是一场持续的"攻防战"。优化高并发系统需要从技术与业务 双重角度出发,既要平衡用户体验、系统性能与资源成本,又要根据具体场景灵活应用各种策略。无论是三字真言"砍、缓、多" ,还是七大处理手段 ,都没有绝对的万能解法------正如软件工程的经典原则所言: 【没有银弹】 。真正的高并发优化核心,不仅在于提升系统性能与稳定性,更在于如何在有限的资源条件下,以最优成本满足业务需求。

高并发不是终点,而是开发者不断突破技术边界的新起点。希望本文的经验与总结,能够为你应对高并发场景提供启发与帮助。

相关推荐
wenb1n2 分钟前
【docker】揭秘容器启动命令:四种方法助你轻松还原
后端
孟君的编程札记4 分钟前
别只知道 Redis,真正用好缓存你得懂这些
java·后端
用户960102251627 分钟前
kubesphere的告别,从可用环境提取Kubesphere镜像
后端
种子q_q9 分钟前
组合索引、覆盖索引、聚集索引、非聚集索引的区别
后端·面试
码事漫谈10 分钟前
WaitForSingleObject 函数参数影响及信号处理分析
后端
ffutop11 分钟前
gRPC mTLS 问题调试指南
后端
讨厌吃蛋黄酥12 分钟前
利用Mock实现前后端联调的解决方案
前端·javascript·后端
JavaArchJourney12 分钟前
Spring Cloud 微服务架构
后端
苦学编程的谢1 小时前
SpringBoot统一功能处理
java·spring boot·后端
程序员爱钓鱼1 小时前
Go语言实战案例:使用channel实现生产者消费者模型
后端·go·trae