什么是服务降级?

典型回答 限流和降级都是对系统的保护功能,一般用户在流量高峰时期,比如双十一大促。 降级是通过开关配置将某些不重要的业务功能屏蔽掉,以提高服务处理能力。在大促场景中经常会对某些服务进行降级处理,大促结束之后再进行复原。 区别于熔断机制,降级一般并不是彻底功能不可用,而是用一种默认返回、异步执行、延迟处理等方式进行降低处理。 扩展知识 服务降级 服务降级的概念听起来可能不是很容易理解,举一个生活中的例子就很好理解了。 有的时候我们去饭店吃饭,吃完饭以后服务员会拿一张问卷,让就餐者填写一下用户反馈。但是,这种让用户填写反馈的请求,只有在店里不忙的时候才会出现。如果店里非常忙,顾客很多的话,店员就不会再找就餐者填写问卷了。 其实,这种就是服务降级。在人流量大的时候,用户反馈这个功能就被降级了。因为他相对来说并没有那么重要。 接着再来看分布式系统的降级。 上图是一张淘宝商品的详情页,对于很多剁手党来说这个页面真的是再熟悉不过了。 但是,这个页面我粗略的大致数了一下,至少有15个以上的功能模块,如:图片、标题、定价、库存、推荐、评价、物流、收藏、下单等。 虽然这些功能都展示在同一个页面上,但是其实这些功能并不都是在同一个应用里面的。这十几个模块可能分别在十几个应用中实现的。 详情页在渲染的时候,要和十几个应用进行网络交互。 这些功能中,有一些是非常重要的,比如:定价、库存、下单等。还有一些是相对来说没那么重要的,比如:推荐、收藏等。 这个识别哪些功能是核心功能、哪些功能是非核心功能,然后对非核心功能采取不同的降级方案制定的过程叫做降级预案。 双十一当天,整个网站的流量十分巨大的,详情页的访问量更是整个网站的重灾之地。所以,一旦有大促的时候,需要有限保证主要功能的可用,至于那些次要的功能就可以被降级掉,即不显示某些模块,或者返回一些默认内容。 降级的方式 还拿之前的饭店中给用户做问卷调查的例子来说。当人流量大的时候,直接取消问卷调查只是一种方式。还有很多其他方式可以选择的。比如: 1、先让用户填写一下手机号,然后离店后,给用户发短信,让其填写电子问卷。 2、在店门口放一个问卷,用户离店时自己去填写问卷。 等等,只要愿意想,其实是有很多种方案的。 同样,对于大型网站来说,服务的降级其实也是有很多方式可以选的,常见的几种如下: 延迟服务 比如发表了评论,重要服务,比如在文章中显示正常,但是延迟给用户增加积分,只是放到一个缓存中,等服务平稳之后再执行。 在粒度范围内关闭服务(片段降级或服务功能降级) 比如关闭相关文章的推荐,直接关闭推荐区 页面异步请求降级 比如商品详情页上有推荐信息/配送至等异步加载的请求,如果这些信息响应慢或者后端服务有问题,可以进行降级; 页面跳转(页面降级) 比如可以有相关文章推荐,但是更多的页面则直接跳转到某一个地址。 写降级 比如秒杀抢购,我们可以只进行Cache的更新,然后异步同步扣减库存到DB,保证最终一致性即可,此时可以将DB降级为Cache。 读降级 比如多级缓存模式,如果后端服务有问题,可以降级为只读缓存,这种方式适用于对读一致性要求不高的场景; 11月11日的零点到11月12日的零点之间无法退款,其实是采用了关闭服务这种降级方式 降级的介入方式 按照是不是可以自动化降级,降级共有两种介入方式,分别是:自动开关降级和人工开关降级。 自动开关降级 自动开关降级的方式一般是当系统达到某些设定的条件(系统负载、资源使用情况、SLA等指标)之后,自动执行一些策略。 常见的可以作为自动降级条件的指标有以下几个: 服务超时 当访问的数据库/http服务/远程调用响应慢或者长时间响应慢,且该服务不是核心服务的话可以在超时后自动降级; 比如前面提到的详情页上有推荐和收藏功能,即使出现问题也不会影响用户的正常下单。如果是调用别人的远程服务,和对方定义一个服务响应最大时间,如果超时了则可以自动降级。 失败次数 调用外部服务的时候,除了超时以外,最常见的异常情况就是调用失败。比如详情页中的库存信息,如果是某一次查询请求失败了,那么就可以通过读取缓存数据等方式直接降级掉。 但是,这种降级可能存在一个问题,就是虽然一次请求展示了缓存,但是其他用户访问的时候还是会查询库存信息,这对于库存系统来说就是雪上加霜。因为他可能已经有问题了,但是上游系统还是在不断的对他发送请求。 所以,可以针对这个查询库存的接口做统一的降级。设定一个失败次数的阈值,一旦整体失败次数达到这个阈值了,就对后续一段时间内的该查询接口做降级。直到其功能恢复。 发生故障 上面提到的失败可能是服务不稳定造成的,过一段时间可以自动恢复的。还有一种情况可能是依赖的服务彻底跪了、或者网络不通了等等。这种情况就可以直接降级了。 当HTTP请求返回固定的错误码、或者一个RPC请求的时候底层服务抛了异常以后,就认为有故障发生,对其进行降级即可。 限流降级 还有种电商网站常见的策略,那就是限流降级。对于某些功能,设定一个流量阈值,一旦流量达到阈值的话,就进行降级。 比如秒杀功能,如果一瞬间流量太大,就可以进行限流降级。对于后续访问的用户直接提示已售空、跳转错误页、或者让他输入验证码重试等。 人工开关降级 还有一种降级方式,那就是人工开关降级。 人工开关降级的方式是指当系统维护人员在发现系统异常之后,通过人工修改参数、关闭服务等方式进行降级的方法。 这种方式的好处是比较灵活,能够根据异常情况灵活应对;但弊端是对人的要求比较高,一来需要维护人员对系统有足够的了解,另外要求维护人员在系统异常时能够在第一时间进行处置。 还有一种情况,可能也会人工介入,那就是在大促之前,预估到流量会十分巨大,提早的识别出风险,为了节省资源保证主流程的可用,开发人员可以手动将某个功能降级掉。 这里说的人工开关降级,并不一定是一定要人工操作,也可能是人工通过一个定时任务进行定时触发的。 降级工具 目前市面上,针对流量控制,限流降级主要有以下两种选择:Netflix Hystrix 和 Alibaba Sentinel。 Hystrix Hystrix是一个库,它提供了服务与服务之间的容错功能,主要体现在延迟容错和容错,从而做到控制分布式系统中的联动故障。Hystrix通过隔离服务的访问点,阻止联动故障,并提供故障的解决方案,从而提高了这个分布式系统的弹性。 ...

March 22, 2026 · 1 min · santu

什么是滑动窗口限流?

典型回答 滑动窗口限流是一种流量控制策略,用于控制在一定时间内允许执行的操作数量或请求频率。它的工作方式类似于一个滑动时间窗口,在窗口内允许的操作数量是固定的,窗口会随着时间的推移不断滑动。 首先需要把时间划分成多个连续的时间片段,每一个片段都有一个固定的时间间隔,如1s、1h等。 然后再定义一个时间窗口,比如10s,随着时间的推移,这个窗口不断的向右移动。为了实现限流的功能,我们通常需要定义一个计数器,统计时间窗口内的请求数。 当时间窗口移动时,需要把上一个时间片段中的请求数减掉,当有新的请求或操作到达系统时,系统会检查窗口内的计数是否已满。如果计数未满,请求被允许执行;如果计数已满,请求被拒绝或进入等待队列,或执行其他限流操作。 滑动窗口限流的主要优点是可以在时间内平滑地控制流量,而不是简单地设置固定的请求数或速率。这使得系统可以更灵活地应对突发流量或峰值流量,而不会因为固定速率的限制而浪费资源或降低系统性能。 滑动窗口限流可以在分布式系统、API服务、网络通信等各种应用场景中使用,以确保系统的稳定性和可用性,防止过多的请求或操作对系统造成负担或崩溃。 扩展知识 固定窗口限流 固定窗口限流中,也是需要定义时间片段和时间窗口,只不过在计数上有一个区别,那就是当随着时间的推移,到了下一个时间窗口时,固定窗口限流的计数器的数量会被清零。重新开始计数。 固定窗口限流的主要特点是窗口大小是固定的,不管请求是否均匀分布,每个窗口内的请求数量都是相同的。这可能导致某些时间段内请求过多,而在其他时间段内则很少,不同窗口之间可能出现流量的不平衡。 实现滑动窗口 ✅如何基于Redis实现滑动窗口限流?

March 22, 2026 · 1 min · santu

什么是熔断?

典型回答 现在很多网站的背后都是一个庞大的分布式系统,多个系统之间的交互大多数都是采用RPC的方式,但是因为是远程调用,所以被调用者的服务的可用情况其实是不可控的。 而越是庞大的系统,上下游的调用链就会越长,而如果在一个很长的调用链中,某一个服务由于某种原因导致响应时间很长,或者完全无响应,那么就可能把整个分布式系统都拖垮。 如果其中某一个服务由于自身原因导致响应很慢,那么就可能导致上游的服务相应也很慢,这样循环往复,就会导致整个系统全线崩溃,这就是服务雪崩。 在服务的依赖调用中,当被调用方出现故障时,出于自我保护的目的,调用方会主动停止调用,并根据业务需要进行相应处理。调用方这种主动停止调用的行为我们称之为熔断。 扩展知识 为什么需要熔断 其实,在分布式系统中,为了保证整体服务可用性和一致性,很多系统都会引入重试机制,在有些情况下,重试其实是可以解决问题的,比如网络问题等,都可以通过重试来解决。 但是,有些情况下,重试并不能解决问题,反而会加剧问题的严重性,比如下游系统因为请求量太大,导致CPU已经被打满,数据库连接池被占满,这时候上游系统调不通就会不断进行重试,这种重试请求,对于下游系统来说,无疑是雪上加霜,给下游系统造成二次伤害。 而分布式系统,大多数的服务雪崩也都是因为不断重试导致的,这种重试有可能是框架级别的自动重试、有可能是代码级别的重试逻辑、还有可能是用户的主动重试。 有些重试是无法避免的,而且如果因为防止雪崩,就不设计重试机制,也是一种因噎废食。 熔断器模式 熔断器模式(Circuit Breaker Pattern),是一个现代软件开发的设计模式。用以侦测错误,并避免不断地触发相同的错误(如维护时服务不可用、暂时性的系统问题或是未知的系统错误)。 假设有个应用程序每秒会与数据库沟通数百次,此时数据库突然发生了错误,程序员并不会希望在错误时还不断地访问数据库。因此会想办法直接处理这个错误,并进入正常的结束程序。简单来说, 熔断器会侦测错误并且“预防”应用程序不断地重试调用一个近乎毫无回应的服务(除非该服务已经安全到可重试连线了)。 熔断器模式是防止微服务系统雪崩的一种重要手段。 一个比较完善的熔断器,一般包含三种状态: 关闭 熔断器在默认情况下下是呈现关闭的状态,而熔断器本身带有计数功能,每当错误发生一次,计数器也就会进行“累加”的动作,到了一定的错误发生次数断路器就会被“开启”,这个时候亦会在内部启用一个计时器,一旦时间到了就会切换成半开启的状态。 开启 在开启的状态下任何请求都会“直接”被拒绝并且抛出异常讯息。 半开启 在此状态下断路器会允许部分的请求,如果这些请求都能成功通过,那么就意味着错误已经不存在,则会被切换回关闭状态并重置计数。倘若请求中有“任一”的错误发生,则会恢复到“开启”状态,并且重新计时,给予系统一段休息时间。 上图是熔断器的三种状态的转换情况。 如果在微服务系统的调用过程中,引入熔断器,那么整个系统将天然具备以下能力: 快速失败:当因为调用远程服务失败次数过多,熔断器开启时,上游服务对于下游服务的调用就会快速失败,这样可以避免上游服务被拖垮。 无缝恢复:因为熔断器可以定期检查下游系统是否恢复,一旦恢复就可以重新回到关闭状态,所有请求便可以正常请求到下游服务。使得系统不需要人为干预。 Q:熔断模式我听懂了,那么这个模式如何运用到系统中啊? A:其实熔断器的原理理解了,实现起来也不是很复杂 Q:那你们在代码中都是自己写熔断器的吗? A:倒也不是,实现上有一些框架可以帮我们实现,并且还有些附加功能 熔断工具 熔断器为了实现快速失败和无缝恢复,就需要进行服务调用次数统计、服务调用切断等操作,如果想要自己实现一个熔断器其实也是可以的。 但是,市面上有一些框架已经帮我们做了这些事情。如Hystrix和Sentinel、resilience4j等。 Hystrix Hystrix(https://github.com/Netflix/Hystrix )是Netflix开源的一款容错系统,能帮助使用者码出具备强大的容错能力和鲁棒性的程序。提供降级,熔断等功能。 但是,在2018年底,Hystrix在其Github主页宣布,不再开放新功能,推荐开发者使用其他仍然活跃的开源项目。 Hystrix虽然不再开发新功能 ,但对用户的影响应该不会太大,一是因为开发者可以继续使用Hystrix的最新版本1.5.18 resilience4j Hystrix停更之后,Netflix官方推荐使用resilience4j(https://github.com/resilience4j/resilience4j ),它是一个轻量、易用、可组装的高可用框架,支持熔断、高频控制、隔离、限流、限时、重试等多种高可用机制。 与Hystrix相比,它有以下一些主要的区别: Hystrix调用必须被封装到HystrixCommand里,而resilience4j以装饰器的方式提供对函数式接口、lambda表达式等的嵌套装饰,因此你可以用简洁的方式组合多种高可用机制 Hystrix的频次统计采用滑动窗口的方式,而resilience4j采用环状缓冲区的方式 关于熔断器在半开状态时的状态转换,Hystrix仅使用一次执行判定是否进行状态转换,而resilience4j则采用可配置的执行次数与阈值,来决定是否进行状态转换,这种方式提高了熔断机制的稳定性 关于隔离机制,Hystrix提供基于线程池和信号量的隔离,而resilience4j只提供基于信号量的隔离 Sentinel Sentinel(https://github.com/alibaba/Sentinel )是阿里中间件团队开源的,面向分布式服务架构的轻量级高可用流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助用户保护服务的稳定性。 Hystrix 的关注点在于以隔离和熔断为主的容错机制,超时或被熔断的调用将会快速失败,并可以提供 fallback 机制。 而 Sentinel 的侧重点在于: 多样化的流量控制 熔断降级 系统负载保护 实时监控和控制台 下图是Sentinel的GitHub主页中关于Sentinel和Hystrix的对比:

March 22, 2026 · 1 min · santu

漏桶和令牌桶有啥区别?

典型回答 漏桶和令牌桶都是用来做流量控制的算法。经常会有人把他们放在一起对比。 他们之间的关系是都有一个固定容量的桶,都是按照固定的速率向桶中添加水(或者令牌),但是他们有一个最大的区别,那就是漏桶的这个桶底部是漏的,它同样会按照固定的速率把水流出,所以漏桶输出流量是匀速的,不管输入流量如何变化。而令牌桶的底部不是漏的,他不会以固定的速率流出,只会以固定的速率向桶中添加令牌。 以上就是二者的差别。举个例子: 漏桶的这个桶,一秒钟流入一滴水,同样一秒钟漏出一滴水。那么,一秒钟就只能处理一个请求,超过的请求会被拒绝掉,达到限流的效果。 也就是说,漏桶这种算法,在5秒钟只能可以处理5个请求,并且每秒钟一个。但是如果出现这种情况,前4秒钟都没有请求,第5秒同时来了5个请求,漏桶是无法处理5个请求的,他只能处理1个,因为这一秒钟只会有一滴水漏出来。 这就是典型的突发流量的问题。而令牌桶可以很好的解决这个问题。 令牌桶的实现逻辑是同样1秒钟产生一个令牌放到桶中,但是如果这个令牌没有被消费的话,他就会一直在桶中,不会被漏出去。还是刚刚那个例子,前4秒没有请求要处理的话,那么5秒钟就可以积攒5个令牌,这时候第5秒来了5个请求的时候,他去桶中是可以一次取出5个令牌,然后把这5个请求都给处理掉的。这就很好地应对了突发流量的问题。 所以,漏桶算法适合于需要限制数据的平均传输速率并确保数据传输的平滑性的场景。令牌桶算法更加灵活,适合于那些既需要限制数据平均传输速率,又需要允许一定程度突发传输的场景。 扩展知识 漏桶算法 漏桶算法是一种流量控制算法,可以平滑控制流量的进出,原理比较简单:假设我们有一个水桶按固定的速率向下方滴落一滴水,无论有多少请求,请求的速率有多大,都按照固定的速率流出,对应到系统中就是按照固定的速率处理请求。 漏桶算法通过一个固定容量的漏桶来控制请求的处理速率,每个请求被看作是一定数量的水,需要先放到漏桶中。当漏桶满时,请求将被拒绝或延迟处理,从而保证了系统的稳定性。 漏桶通过定时器的方式将水以恒定的速率流出,与请求的数量无关,从而平滑控制了请求的处理速率。当请求到来时,先将请求看作是一定数量的水,需要将这些水放入漏桶中。 如果漏桶未满,请求将被立即处理并从漏桶中取出对应数量的水。如果漏桶已满,请求将被拒绝或被延迟处理,直到漏桶中有足够的空间存放请求对应数量的水。 当定时器触发时,漏桶中的水以恒定的速率流出,此时可以继续处理请求。 总之,漏桶算法通过一个固定容量的漏桶来控制请求的处理速率,可以平滑控制流量的进出,保证系统的稳定性和安全性。 但需要注意的是,漏桶算法无法处理突发流量,因为他只能按照固定的速度来处理请求,如果某个请求的流量突增,因为漏桶的机制就导致了他还是只能一个一个的按照固定速度进行消费。 为了解决这种突发流量的问题,就有了令牌桶算法。 令牌桶算法 令牌桶其实和漏桶的原理类似,令牌桶按固定的速率往桶里放入令牌,并且只要能从桶里取出令牌就能通过。 也就是说,我不管现在请求量是多还是少,都有一个线程以固定的速率再往桶里放入令牌,而有请求过来的时候,就会去桶里取出令牌,能取到就执行,取不到就拒绝或者阻塞。 令牌桶通过定时器的方式向桶中添加令牌,每秒钟添加一定数量的令牌,从而平滑控制了请求的处理速率。这样如果突发流量过来了,只要令牌桶中还有足够的令牌,就可以快速的执行,而不是像漏桶一样还要按照固定速率执行。 令牌桶的好处就是把流量给平滑掉了,在流量不高的时候也会不断的向桶中增加令牌,这样就有足够的令牌可供请求消费。 在Java中,我们可以借助Guava提供的RateLimiter来实现令牌桶。

March 22, 2026 · 1 min · santu

如何设计一个能够支持高并发的系统?

典型回答 设计一个能够支持高并发的系统需要考虑多方面的因素,包括架构、性能优化、容错和可伸缩性等。以下是一些一般性的建议和实践。(这个问题一般都是个引子,就是先抛一个很大的问题,看你回答的咋样,以及能说上多少,然后再展开去问的。每一个点这里没做展开,给大家贴了相关文章,逐一去看就好了。) 分布式架构:将系统分解成多个模块,采用分布式架构来降低单点故障的风险,并提高系统的可伸缩性和性能。 集群部署:将一个服务通过集群进行部署,来提升系统整体的吞吐量及响应速度,并使用负载均衡技术将请求均衡分配给多个服务器,以提高系统的性能和可用性。 ✅什么是分布式系统?和集群的区别? 利用缓存:使用缓存、NoSQL等技术,以提高数据读写的性能和可靠性。 异步处理:采用异步处理机制,如使用消息队列、事件驱动等技术,以降低请求响应时间和提高系统吞吐量。 ✅为什么要使用消息队列? 预加载:使用预加载技术来提前加载需要的资源,以减少用户等待时间。 代码优化和调优:对系统代码进行优化和调优,如采用异步I/O、避免锁(减小锁的粒度)、减少循环和递归、避免长事务等,以提高系统性能。 ✅服务端接口性能优化有哪些方案? 数据库优化:合理的数据库设计和优化,包括**合理的索引设计、分库分表、读写分离、**缓存优化等,可以有效提高系统的并发度和响应速度。 分库分表:将一个大型的数据库拆分成多个小型的数据库(分库),然后将每个小型数据库中的表再进行拆分(分表),从而减轻单个数据库或表的读写压力,通过分库分表,可以将大量的读写操作分散到多个数据库或表中,从而提高系统的并发度和响应速度。 ✅什么是分库?分表?分库分表? 读写分离:读写分离是一种常用的数据库优化技术,它将读操作和写操作分配到不同的数据库实例上处理。通过读写分离,主库主要负责写操作,从库则负责读操作,从而提高了系统的并发度和可扩展性。同时,读写分离还可以提高系统的可用性和容错能力,因为即使主库出现故障,从库仍然可以提供读服务。 ✅什么是读写分离?如何实现? 防止雪崩:通过使用限流、熔断、降级等技术,可以防止系统因为某个组件出现故障而导致整个系统崩溃的雪崩效应。 ✅限流、降级、熔断有什么区别? 容错和监控:实现容错机制,如备份、容灾、负载降级等,以保障系统的可用性。同时,使用监控工具来实时监测系统的运行状况和性能瓶颈,及时做出调整和优化。 ✅什么是异地多活? 测试和评估:进行全面的性能测试和评估,包括压力测试、负载测试、安全测试等,以发现并解决系统的性能瓶颈和安全隐患。 综上所述,设计高并发系统需要从多个方面考虑,需要综合运用各种技术和工具,进行全面的测试和评估,以实现系统的高可用、高性能和高安全性。

March 22, 2026 · 1 min · santu

什么是限流?常见的限流算法有哪些?

典型回答 限流是一种控制流量的技术,用于保护系统免受突发流量或恶意流量的影响。其基本原理是通过控制请求的速率或数量,确保系统在可承受的范围内运行。 常见的限流算法有: 漏桶算法(常用):系统请求先进入漏桶,再从漏桶中逐一取出请求执行,控制漏桶的流量。 令牌桶算法(常用):系统请求会得到一个令牌,从令牌桶中取出一个令牌执行,控制令牌桶中令牌的数量。 ✅漏桶和令牌桶有啥区别? 计数器算法(简单):系统请求被计数,通过比较当前请求数与限流阈值来判断是否限流。 可以阻塞算法:当系统达到限流阈值时,不再接受新请求,等到限流阈值降下来再接受请求。 令牌环算法:与令牌桶算法类似,但是在多个令牌桶之间形成环形结构,以便在不同的请求处理速率之间进行平衡。 最小延迟算法:基于预测每个请求的处理时间,并在处理完请求后进行延迟,以控制请求的速率。 滑动窗口(常用):基于一个固定大小的时间窗口,允许在该时间窗口内的请求数不超过设定的阈值。这个时间窗口随着时间的推移不断滑动,以适应不同时间段内的请求流量。 ✅什么是滑动窗口限流?

March 22, 2026 · 1 min · santu

什么是预热?它有何作用?

典型回答 预热一般指缓存预热,一般用在高并发系统中,为了提升系统在高并发情况下的稳定性的一种手段。 缓存预热是指在系统启动之前或系统达到高峰期之前,通过预先将常用数据加载到缓存中,以提高缓存命中率和系统性能的过程。缓存预热的目的是尽可能地避免缓存击穿和缓存雪崩,还可以减轻后端存储系统的负载,提高系统的响应速度和吞吐量。 减少冷启动影响:当系统重启或新启动时,缓存是空的,这被称为冷启动。冷启动可能导致首次请求处理缓慢,因为数据需要从慢速存储(如数据库)检索。 提高数据访问速度:通过预先加载常用数据到缓存中,可以确保数据快速可用,从而加快数据访问速度。 平滑流量峰值:在流量高峰期之前预热缓存可以帮助系统更好地处理高流量,避免在流量激增时出现性能下降。 保证数据的时效性:定期预热可以保证缓存中的数据是最新的,特别是对于高度依赖于实时数据的系统。 减少对后端系统的压力:通过缓存预热,可以减少对数据库或其他后端服务的直接查询,从而减轻它们的负载。 比如秒杀商品、大促活动等,这些是可以提前预知哪些key会变成热key的,所以就可以做提前的预热。 缓存预热的一般做法是在系统启动或系统空闲期间,将常用的数据加载到缓存中,主要做法有以下几种: 系统启动时加载:在系统启动时,将常用的数据加载到缓存中,以便后续的访问可以直接从缓存中获取。 定时任务加载:定时执行任务,将常用的数据加载到缓存中,以保持缓存中数据的实时性和准确性。 手动触发加载:在系统达到高峰期之前,手动触发加载常用数据到缓存中,以提高缓存命中率和系统性能。 缓存预热是一种提高高并发系统性能和可靠性的重要方法,通过预先将常用的数据加载到缓存中,避免缓存击穿和缓存雪崩等问题,从而保证系统的稳定性和可靠性。 ✅什么是缓存击穿、缓存穿透、缓存雪崩? 扩展知识 缓存预热的方法 在进行缓存预热时,有一些工具可供使用。常见的是以下这几个: ✅如何实现缓存的预热? 如果要自己实现本地缓存的预热,可以看上面的文章,如果要给redis预热,可以看下面的: RedisBloom:RedisBloom是Redis的一个模块,提供了多个数据结构,包括布隆过滤器、计数器、和TopK数据结构等。其中,布隆过滤器可以用于Redis缓存预热,通过将预热数据添加到布隆过滤器中,可以快速判断一个键是否存在于缓存中。(https://github.com/RedisBloom/RedisBloom) Redis Bulk loading:这是一个官方出的,基于Redis协议批量写入数据的工具(https://redis.io/docs/latest/develop/clients/patterns/bulk-loading/) Redis Desktop Manager:Redis Desktop Manager是一个图形化的Redis客户端,可以用于管理Redis数据库和进行缓存预热。通过Redis Desktop Manager,可以轻松地将预热数据批量导入到Redis缓存中。

March 22, 2026 · 1 min · santu

单机限流和集群限流的区别是什么?

典型回答 顾名思义,一个是针对单台服务器限流,一个是针对整个集群做限流。比如单机限100,那么就是单机最大就能抗100QPS,如果是集群限100,那么就意味着集群不管有多少台机器,总共 QPS 只能抗100。 单机限流非常好实现,比如用 guava 的 RateLimiter 就可以,通过控制每个服务实例的请求处理速率来保护服务的稳定性和可靠性。 集群限流通常需要使用分布式的限流算法和工具,比如 Redis、Sentinel、Hystrix等,以确保每个服务实例或节点都遵守全局的流量控制策略。 ✅Hystrix和Sentinel的区别是什么? 扩展知识 有了集群限流,还需要做单机限流吗? 需要。 因为单机限流主要关注保护单个服务节点。即使集群级别有流量控制,单个实例依然可能因为本地的请求过多而出现性能问题或崩溃。 举个例子,集群10台机器,每台机器可以抗100QPS,一共就是1000,如果你集群限制1000QPS,那么平均每台是100。 但是这个"平均"只是理想情况,受限于负载均衡策略,相一旦流量不均匀了,某台机器的 QPS 就会比其他机器要高一些。 这种情况其实是非常常见的,尤其是在很多分布式系统中会有一些同机房优先、同单元优先等策略的影响下,很容易出现流量倾斜。 一旦只做了集群限流,而没有做单机限流,就可能把某台单机给打挂了。 还有就是,只有集群限流这种,你相当于不能有机器宕机或者下线,如果有部分机器下线了,那么平均下来单机的 QPS 就会上升,就有可能把一些单机给打挂。 而这种服务器下线在正常不过了,比如你应用发布过程中,就是会有一部分机器因发布而下线,那么就会出现问题。 有了单机限流,还需要做集群限流吗? 一般来说也是需要的。 因为单机限流了,说明我们的单机是可以扛得住了,但是如果集群中机器数不断扩充,没有做集群限流的话,会带来一些问题。 比如说我们经过压测,单机可以看500QPS,那我们配置了单机500 QPS的限流,随着业务不断增长,我们扩容到了10台机器,那么如果没有集群限流,就会最多可能要扛5000QPS。 可是,问题是能不能扛得住5000呢?单机能抗500,10台机器的集群就能抗5000吗?其实不一定,因为这还要看你的数据库、缓存、下游接口等等的承载能力的。 所以,集群限流也还是有必要的。

March 22, 2026 · 1 min · santu

什么是自适应限流?

典型回答 所谓自适应限流,就是限流器结合服务器实例的Load、CPU、内存、接口的RT、QP、并发线程数等指标,进行的一种自适应的流控策略。即通过监控这些指标的变化,来动态的调整限流,来达到保证系统稳定性的目的。 这种思想也有很多其他的应用,比如自适应扩容,也是根据机器的Load,CPU等情况,进行动态的扩容等。 其实主要就是要给系统定义一个基线,或者水位,一旦这些指标达到了水位之上,比如CPU利用率超过60%,那么就开启自适应限流,通过限流的方式来保护系统不被打垮。 著名的Sentinel限流框架就支持自适应限流,目前他支持以下的阈值类型: Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5。 CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0)。 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。 以下是Sentinel给的一个自适应限流的示例,供参考: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.demo.system; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.util.TimeUtil; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; /** * @author jialiang.linjl */ public class SystemGuardDemo { private static AtomicInteger pass = new AtomicInteger(); private static AtomicInteger block = new AtomicInteger(); private static AtomicInteger total = new AtomicInteger(); private static volatile boolean stop = false; private static final int threadCount = 100; private static int seconds = 60 + 40; public static void main(String[] args) throws Exception { tick(); initSystemRule(); for (int i = 0; i < threadCount; i++) { Thread entryThread = new Thread(new Runnable() { @Override public void run() { while (true) { Entry entry = null; try { entry = SphU.entry("methodA", EntryType.IN); pass.incrementAndGet(); try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { // ignore } } catch (BlockException e1) { block.incrementAndGet(); try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { // ignore } } catch (Exception e2) { // biz exception } finally { total.incrementAndGet(); if (entry != null) { entry.exit(); } } } } }); entryThread.setName("working-thread"); entryThread.start(); } } private static void initSystemRule() { SystemRule rule = new SystemRule(); // max load is 3 rule.setHighestSystemLoad(3.0); // max cpu usage is 60% rule.setHighestCpuUsage(0.6); // max avg rt of all request is 10 ms rule.setAvgRt(10); // max total qps is 20 rule.setQps(20); // max parallel working thread is 10 rule.setMaxThread(10); SystemRuleManager.loadRules(Collections.singletonList(rule)); } private static void tick() { Thread timer = new Thread(new TimerTask()); timer.setName("sentinel-timer-task"); timer.start(); } static class TimerTask implements Runnable { @Override public void run() { System.out.println("begin to statistic!!!"); long oldTotal = 0; long oldPass = 0; long oldBlock = 0; while (!stop) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } long globalTotal = total.get(); long oneSecondTotal = globalTotal - oldTotal; oldTotal = globalTotal; long globalPass = pass.get(); long oneSecondPass = globalPass - oldPass; oldPass = globalPass; long globalBlock = block.get(); long oneSecondBlock = globalBlock - oldBlock; oldBlock = globalBlock; System.out.println(seconds + ", " + TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal + ", pass:" + oneSecondPass + ", block:" + oneSecondBlock); if (seconds-- <= 0) { stop = true; } } System.exit(0); } } }

March 22, 2026 · 3 min · santu

高并发场景中,乐观锁和悲观锁哪个更适合?

典型回答 ✅乐观锁与悲观锁如何实现? 乐观锁和悲观锁是在处理并发访问时使用的两种不同的策略。 乐观锁的基本思想是假设冲突很少发生,每个线程在修改数据之前,先获取一个版本号或时间戳,并在更新时检查这个版本号或时间戳,以确保其他线程没有同时修改数据。 **乐观锁适用于读操作频繁,写操作相对较少的场景。**当冲突较少,且并发写入的概率较低时,乐观锁的性能可能更好。 悲观锁则是假设冲突经常发生,因此在访问共享资源之前,线程会先获取锁,确保其他线程无法同时访问相同的数据。这可能导致并发性降低,因为只有一个线程能够访问数据。 **悲观锁适用于写操作较为频繁,且并发写入的概率较高的场景。**悲观锁可以有效地避免多个线程同时修改相同数据的情况。 乐观锁和悲观锁还有个区别:乐观锁因为比较乐观,所以一般是先做业务逻辑操作,比如参数处理,内存中进行模型组装调整,然后再去更新数据库。悲观锁因为比较悲观,所以会先尝试加锁,然后再去做业务逻辑操作。 也就是说,乐观锁是先干活,后加锁。悲观锁是先加锁,再干活。 而高并发的写操作时,你干了一大堆活,把模型都组装好了,内存计算也都做完了,结果最后去数据库那更新的时候发现版本号变了。这不是大冤种吗? 所以,应该是先尝试获取锁,如果获取锁成功,再进行业务操作,否则就直接返回失败。这样可以做fail-fast。 综上,在高并发场景中,一般来说**并发写入的冲突较为频繁,所以建议优先考虑悲观锁。**即在做并发操作前,先尝试获取锁,如果获取锁成功,在进行业务操作,否则就直接返回失败。 比如,我们通常在并发场景下都使用分布式锁,即先加分布式锁,然后再操作。这个就是一个悲观锁的思想,我认为冲突一定很大,所以我先尝试加锁。拿到锁再开始干活。 扩展知识 乐观锁并非无锁 不管是乐观锁还是悲观锁,都并不是无锁的。这一点一定要注意。 ✅数据库乐观锁的过程中,完全没有加任何锁吗?

March 22, 2026 · 1 min · santu

留言给博主