和外部机构交互如何防止被外部服务不可用而拖垮

典型回答 在很多业务中,都需要和外部的一些机构进行交互,比如在电商场景,在发货环节,需要和各个快递公司做交互进行发货、物流更新。聚合支付场景需要和外部支付渠道做交互实现支付等。 很多时候,我们自己的可用性可以有一些保障的,但是,外部机构的可用性是没办法保证的,比如双十一大促期间,支付方式除了支付宝之外,还可能用银行卡支付,但是很难保证银联系统可以抗这么高的并发。、、 那么,遇到这样的情况,外部机构的SLA不可靠时,如何避免自己的应用不被拖垮呢? 主要有几个思路: 1、异步交互 2、文件交互 3、超时机制 4、限流降级熔断 5、业务取舍 异步交互 首先比较容易想到的就是和机构之间不要进行同步交互,同步交互的话会把所有的流量都发送到外部合作方,并且本系统还要同步等结果,不仅会浪费系统资源,还会让系统整体响应变慢。 在有些场景中,可以考虑和机构进行异步交互。 比如,我们在电商网站上做一些金融产品的时候,比如天猫运费险,当用户下单以后,要给买家投保一笔运费险。 这个场景,运费险投保是必须要成功的,因为买家下单时已经看到自己有运费险了,所以必须要让他能享受这个服务。(如果有些用户不应该享受,那就应该在展示的时候就拦截,而不是先展示,后拒绝) 像这个场景,我们如果每一笔都去调保险公司的话,那大概率他们是扛不住这个业务量的。而运费险投保如果稍微晚一会,在小时级别的话,理论上对业务是无损的,因为一般不太可能几个小时内就发货,收货,退货。 所以,我们就可以异步的和外部的保险公司进行交互,通过定时任务或者延迟消息的方式,和保司交互,这样可以起到削峰填谷的作用,并且这个链路可以不断重试,即使失败了,也不会影响到正常的下单主流程。 文件交互 文件交互是一种异步交互的极端例子,他在异步的基础上还做了批次。把之前的每单异步,变成了批量异步。 比如电商业务中,商家发货时,需要生成快递单号,但是这些快递单号如果实时去快递公司获取的话,那就有一定的失败的概率。 所以就可以事先先从快递公司申请一个批次的快递单号,然后自己本地发货时直接用,然后在批量的通知给快递公司就行了。 这种批量可以是一个接口调用,也可以用文件。用文件的好处就是甚至都不需要接口交互,只需要把文件生成好上传到FTP中,对方自己下载后解析就行了。 超时机制 在做外部接口交互时,并不是所有的情况都可以做异步,比如说高德的聚合打车。 当我们输入完出发点和目的地之后开始叫车时,他就需要实时的和各个租车平台对接交互,然后分发这笔订单。 这个过程就要同步交互,但是我们需要给每个第三方平台都设置一个请求的超时时间,比如2秒钟,超过2秒钟就自动中断了。而不是让用户一直在前端的等。 并且根据这种外部接口的响应时间的数据,我们可以做一些动态的负载均衡,把更多的流量分发给性能更好的服务商。 限流降级熔断 接口如果超时很严重,我们也需要对他做限流、降级和熔断。 限流:就是假如他的接口就能扛100QPS,那么我们就需要不能给他超过100的并发请求,那样他会处理不过来,导致整体性能下降。 降级:假如查询一个用户中心的服务查用户名,但是接口响应慢,那么就可以暂时降级,直接返回"陌生人"或者“用户名加载中”等信息。 熔断:假如外部某个接口特别慢,那么就可以干脆就不要再调他了。 这样做对业务是有损的。比如大促当天,银联的接口性能太差了,我们把银联降级掉,那么用户就会无法用银联支付了。对用户来说就少了个选择。 但是对于我们的系统来说,起到了很好的保护作用。可以在外部接口性能恢复以后再恢复调用就行了。 ✅什么是熔断? 业务取舍 大家知道最开始余额宝被做出来主要是干嘛的吗?其实最初是为了解决支付成功率低的问题的。 大促期间就是因为银联等外部渠道支付成功率太低了,但是很多人又因为银行有收益所以把钱存在银行。 那为了解决这个问题,一个好的办法,就是在业务上给用户多一个选择,让用户减少使用银行卡支付的概率。 就假如说,高德聚合打车,发现外部服务商技术都太差了,他做了自营,是不是也能大幅度降低对外部机构的依赖问题呢?

March 22, 2026 · 1 min · santu

高并发的库存系统,在数据库扣减库存,怎么实现?

典型回答 这是一个典型的数据库热点数据更新的问题。 我们都知道,当多个并发事务同时尝试更新同一行热点数据时,可能会导致锁竞争和冲突。这会增加事务的等待时间和冲突概率,导致性能下降,并可能引发死锁问题。 详见以下两个问题,都是热点数据并发更新导致的: ✅数据库连接池满排查过程 ✅数据库CPU被打满排查过程 这类问题的解决思路可以分为3类,分别是排队、拆分以及批次执行。 排队 首先,我们常见的排队方案就是要么加锁、要么单线程执行。 所以,如果能使用Redis的话,那么就可以利用他的高并发、单线程特点来解决这个问题,因为题目要求是只能使用数据库,所以这个就不展开讲了,这种方案,在秒杀场景中用的还挺多的,可以参考: ✅库存扣减如何避免超卖和少卖? 除了上面这种基于Redis排队的方案,其实基于数据库也是可以的。 正常的update竞争抢锁的时候,也是排队的,但是这个会让事务持续自旋抢锁,严重耗费数据库CPU 给大家提供一个思路,那就是在数据库层面做改造。比如腾讯云Mysql和阿里云Mysql都做过类似的优化。 腾讯云数据库MySQL热点更新: https://cloud.tencent.com/document/product/236/63239 阿里云数据库Inventory Hint: https://www.alibabacloud.com/help/zh/apsaradb-for-rds/latest/inventory-hint 主要思路就是,针对于频繁更新或秒杀类业务场景,大幅度优化对于热点行数据的update操作的性能。当开启热点更新自动探测时,系统会自动探测是否有单行的热点更新,如果有,则会让大量的并发 update 排队执行,以减少大量行锁造成的并发性能下降。 也就是说,他们改造了MySQL数据库,让同一个热点行的更新语句,在执行层进行排队。这样的排队相比update的排队,要轻量级很多,因为他不需要自旋,不需要抢锁。 阿里的数据库还提供了事务性Hint来控制并发和快速提交/回滚事务,提高业务吞吐能力,上面的链接中也有介绍,可以自行了解下。 具体原理参见: ✅阿里的数据库能抗秒杀的原理 拆分 除了排队,还可以做一些拆分操作,比如说把一个大的库存拆分成多个小库存,有点类似于concurrentHashMap中的分段锁的机制,通过拆分的手段降低锁的粒度来提升并发度。 拆分后,一次扣减动作就可以分散到不同的库、表中进行。 但是这个方案存在一个比较大的问题,那就是可能会存在碎片。 假如一共有1000个库存,在一起的话,是可以扣减999个的。但是如果分散到多个子库存中,那么就会没办法一次性扣减999。 当然,也是可以通过一些手段来解决的,比如对库存的腾挪或者超占等,但是整体看上去方案并不完美。所以这个方案用得不多。 合并 除了拆分以外,还有一种合并批量执行的方式。 多条update在一起会有锁争抢,那如果把多个UPDATE合成一个UPDATE不就可以降低锁冲突了么。 比如一个用户,有10个占用库存请求,每次占用1个,那么就可以提供一个批量占用的接口,让上游一次性把10个占用合并一起,这样数据库只需要做一次更新就行了。 这个方案有一些局限性,就是不是所有请求都可以合并的,有些场景,如电商的秒杀,用户需要很快的知道反馈,而批量执行就需要有个窗口来聚合,用户是不能接受这种等待窗口的。 所以,在一些异步链路上,是可以用这种方案的。 其他方案 当然,还有一些其他的方案,在一些简单场景也可以考虑。 1、异步化,通过异步更新的方式,将高并发的更新削峰填谷掉。 2、把update转换成insert,直接插入一次占用记录,然后异步统计剩余库存,或者通过SQL统计流水方式计算剩余库存。 3、SQL限流,这是一种保护数据库的手段,就是不要让这些请求都打到数据库上。提前拒绝掉。

March 22, 2026 · 1 min · santu

MySQL 里有 2000W 数据,Redis 中只存 20W 的数据,如何保证 Redis 中的数据都是热点数据_

典型回答 这个问题其实考查的是Redis的数据预热+更新+淘汰+过期相关的内容。主要回答的时候考虑以下几个方面: 1、数据预热 MySQL中有2000万,我们要往Redis中放20万,那么,不能随机放20万吧,总要挑选一些合适的数据放进去,那么就需要做缓存的预热了,所以为预热,那就是要把热点数据放进去,而不是放冷数据,那样的话就没有意义了。 所以,我们可以根据实际的业务情况,把那些当前比较热的或者接下来一段时间可能比较热的数据把他们提前放到缓存中,这样至少可以保证缓存刚生效的时候,数据是相对热的。 2、热点数据更新 热点数据不是一成不变的,随着线上业务的不断变化,热点数据也会不断地发生汰换,所以,我们在实际工作中,一般会做实时的热点数据的检测, 下面这个介绍过如何实时收集热key: ✅什么是热Key问题,如何解决热key问题 可以了解下JD的这个热key框架 :https://gitee.com/jd-platform-opensource/hotkey 3、缓存过期策略 选择正确的缓存策略非常重要。使用 Redis 的 LRU(最近最少使用)或 LFU(最不经常使用)等缓存策略可以确保 Redis 中保留的是经常访问的热点数据。这些策略会自动移除不常用的数据,保留经常访问的数据。 ✅Redis 的过期策略是怎么样的? LRU策略适用于短期内访问频率较高的热点数据。LFU策略适用于长期内访问频率较高的热点数据。如果业务场景中热点数据的访问模式相对稳定,而且在短期内访问频率较高,那么LRU更适合;如果热点数据的访问频率存在较大波动,而且我们更关心长期内经常访问的数据,那么LFU更适合。 4、缓存淘汰策略 Redis也有多种内存淘汰策略,并且可以结合给key设置超时时间,然后采用volatile-lru方式来把不太用的key移除掉,节省Redis的内存。 ✅Redis的内存淘汰策略是怎么样的?

March 22, 2026 · 1 min · santu

一个接口3000QPS,接口RT为200MS,预估需要几台机器?

典型回答 要估算需要多少台机器来支持一个接口的QPS和RT,需要考虑多个因素,包括机器的性能、并发连接数、负载均衡、缓存等。 如果接口RT为200ms,那么我们可以计算知道,单台机器单个线程一秒钟的吞吐量: 单线程吞吐量 = 1000ms / RT 单线程吞吐量 = 1000ms / 200ms = 5请求/秒 这表示每台机器的每个线程每秒最多可以同时处理5个请求。 那么,假设我们的web服务器是tomcat,默认有200个线程,那么他一秒钟的吞吐量就要再乘以200(单核情况下理论极限): 单机吞吐量:5 * 200 = 1000 那么,根据这个吞吐量来计算的话,如果要抗3000的QPS,那么就需要: 3000/1000 = 3台机器。 但是,以上这个计算完全理想化了,完全忽略了CPU、内存、Load等硬件的情况。因为随着并发量的升高,应用的CPU、内存、磁盘等等各种硬件配置的使用情况也是在不断攀升的。 所以实际在预估的时候,需要在单机上进行压测,根据压测结果来预估需要多少台机器。 ✅什么是压测,怎么做压测? 最理想的情况是需要3台机器,但是实际情况的话基本上会比这个要多一些,所以大致预估就是按照他的2-3倍来算的话,就是6-9台机器。

March 22, 2026 · 1 min · santu

用了一锁二查三更新,为啥还出现了重复数据?

典型回答 ✅如何解决接口幂等的问题? 不懂什么是一锁二查三更新的,先看上面这个问题↑↑↑。 那么,用这个这个方式做幂等防控,就一定不会出现脏数据了么?先看下以下代码: 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 @Transactional(rollbackFor = Exception.class) public boolean order(Request request) { RLock lock = redisson.getLock(request.getIdentifier()); try { //一锁 lock.lock(); //二查 OrderDo order = orderMapper.find(request.getIdentifier()); if (order!=null) { resp.setResultFail(ReturnCodeEnum.USER_EXIST); return false; } //三更新,保存订单数据 orderMapper.insertOrder(request); } finally { lock.unlock(); } //三更新,保存订单流水 orderStreamMapper.insertOrder(request); //三更新,保存事件流水 eventMapper.insert(request); return true; } 以上代码,其实是有可能出现脏数据的。 ...

March 22, 2026 · 1 min · santu

如果单表数据量大,只能考虑分库分表吗?

典型回答 ✅从B+树的角度分析为什么单表2000万要考虑分表?? 我们知道,当MySQL中的单表数据量很大以后,一般就要考虑做分库分表了,主要原因就是单表量太大会导致查询效率下降。 但是,因为分库分表其实不仅实现方案复杂,并且也会带来很多问题,比如跨库事务、分页查询等,不到万不得已,不建议大家直接做分库分表。 ✅分库分表后会带来哪些问题? 在分库分表前应该先考虑其他的方案。以及分表的必要性。 一般来说,我们做分库的目的是提升数据库的吞吐量,做分表的目的是提升查询的效率。那么,当数据量大的时候,也可以用其他的方案来解决,应该优先考虑以下几个方案: 1、数据库优化 比如说,2000万的数据量,其实大多数情况下用好索引就可以提升很大的效率的,有的时候把索引优化好,表结构设计好(比如适当做数据冗余),SQL语句(比如减少多表JOIN)优化好,就可以大大提升性能。 2、缓存 减少对数据库的压力可以通过缓存来实现,不要把所有的流量都交给数据库来抗。适当的在近端做一些缓存,如本地缓存、分布式缓存等就可以快速的返回,还能减少数据库的压力。 3、分区 通过数据分区可以减小单个表的物理存储空间,数据分区后,存储在不同的表或文件中,以减小单表的数据量,可以提高查询性能。 4、数据归档 我们可以定期的清理一些不再需要,或者不再活跃的数据,把他们进行数据归档到辅助存储中(如历史表,离线数仓等),这样就可以减少数据的数量,就能大大提升效率。 5、分布式数据库 另外,也可以考虑采用分布式数据库系统,这样可以在多个节点上分散数据,提高性能和容量。虽然分布式数据库需要的成本比较高,但是相比分库分表的改造来说,受益也比较大。

March 22, 2026 · 1 min · santu

数据库逻辑删除后,怎么做唯一性约束?

典型回答 假设我们有一张开通表,里面记录了用户的服务开通记录 service_record,主要有以下几个字段 字段 类型 是否可空 id long 否 gmt_create date 否 gmt_modified date 否 telephone varchar 否 product_code varchar 否 state varchar 否 然后如果有用户退出的时候,我们需要记录用户的退出状态,这时候一般我们都不能做物理删除的,因为数据库的物理删除意味着数据都没有了,那么后续的各种数据分析,报表等都没办法做了,所以大多数选择逻辑删除,那么就是引入一个新的字段: 字段 类型 是否可空 id long 否 gmt_create date 否 gmt_modified date 否 telephone varchar 否 product_code varchar 否 state varchar 否 is_deleted int 否 这个字段一般定义为deleted或者is_deleted,常见的做法是默认是0,表示未删除,如果逻辑删除的话则把他设置为1。 那么问题来了, 我们怎么避免用户在开通的时候因为并发导致插入多条开通记录呢? 当然,我们可以做各种分布式锁,但是我们也提到过(如https://www.yuque.com/hollis666/ec96i7/gz2qwl),不管怎么做,最终都得在数据库层面做兜底,而一般这种情况,就需要做一个唯一性约束。 可是同一个用户是可以有多条开通记录的,唯一性约束如果用telephone+product_code 那么就肯定是不行的,会导致用户无法多次开通。 但是如果再加上is_deleted,是可以区分开通和退出了,但是一个用户如果多次退出又不行了,那么该如何支持这种业务需求呢? 后面一共有四种方案,前两个方案是如果直接做物理删除如何实现业务需求!后面两个是如果就是不能做物理删除该怎么办? 如果是题目中的问题的解答,可以直接看3、4两个方案,但是前两个也建议大家看一下,有的时候,面试的时候不只是可以回答面试官的问题,也可以想办法从根上让他的问题不会发生。 方案一,物理删除+数据归档 最简单的方案就是直接做物理删除了,但是物理删除怎么实现数据分析、报表等功能呢,就是说如何做数据留痕呢? 简单的方案就是做数据归档或者离线表。 就是说再创建一张service_record_his表,然后再用户退出时,在同一个事务中把service_record表中的数据插入到service_record_his中,然后再把service_record表中的记录删除: 1 2 3 4 INSERT INTO service_record_his SELECT * FROM service_record WHERE user_id = 'xxx' and product_code = ""; DELETED FROM service_record WHERE user_id = 'xxx' and product_code = ""; 这样我们就可以基于service_record_his进行数据分析和做报表了。 ...

March 22, 2026 · 1 min · santu

电商下单场景,如何设计一个数据一致性方案?

典型回答 假设有一个电商下单链路,上下游一共有5个系统,分别包括了订单系统、库存系统、积分系统、邮件通知系统、以及外部机构系统。 下单的时候,订单系统需要创建订单,库存系统需要扣减库存,积分系统需要给用户增加积分,邮件通知系统需要通知用户下单成功,外部机构系统给用户投保一笔运费险。 那么, 我们需要设计一个完整的一致性方案,该怎么做呢? 其实这个问题考察的就是分布式事务的运用。 ✅常见的分布式事务有哪些? 常见的分布式事务有XA(2PC、3PC)、TCC、Seata、可靠消息最终一致性(本地消息表、MQ事务消息)、最大努力通知等,那么该如何在实际场景中选择具体哪种方案呢? 首先,我们看一下订单系统、库存系统、积分系统、邮件通知系统、以及外部机构系统这五个系统对一致性的要求。 为啥不都考虑强一致性呢?这样就可以保证数据一定一致,没什么隐患。 但是这样做成本太高了,也会让下单的成功率大大降低。我们想一想,在下单环节中,是不是只要订单创建成功、库存扣减成功,是不是就可以认为下单成功了。而至于其他的邮件通知、积分增加、其实是不应该影响到用户的下单流程的。我们不能说因为给你加积分失败了,所以下单就失败了吧。 所以,我们需要区别对待。那么就需要分析一下这些系统之间的一致性要求。 首先,订单系统和库存系统,这两个基本上是要求强一致性的,因为下单成功、库存必须扣减成功,否则就会超卖。库存扣减成功,订单也一定要创建成功,否则就会少卖。 所以,订单系统和库存系统对一致性的要求很高,基本上是强一致性的。 订单系统和积分系统之间的一致性要求是其次的, 因为首先我们要保障下单成功后积分增加一定要成功,但是并不要求一定要做到非常的实时,稍微有一点系统延迟,其实也是可以的。 订单系统和邮件通知系统之间的一致性要求就更弱了,用户下单成功后,发送邮件通知这个动作,其实即使是丢了,对订单以及用户体验来说,其实影响并不大。 而订单系统和外部机构系统之间的这个一致性保障的话就比较特殊了,按理来说其实是很高的,订单创建成功之后,运费险一定要投保成功,但是实际在技术实现上可以做一些特殊的设计,这个我们后面讲。 在分析完了以上几个系统之间的一致性要求之后,我们就可以具体的做方案的设计了。(以上分析并不绝对,不同的系统之间并不一样,有的电商对于积分增加要求不高,但是对于邮件通知要求高,所以,实际需要根据业务来确定。) 首先,订单和库存系统,基本上就是要强一致性,所以,可选的方案就是XA以及TCC,也就是需要通过协调者来整体控制系统之间的数据的一致性,做到要么都成功,要么都失败。 ✅什么是TCC,和2PC有什么区别? 接下来订单系统和积分系统之间,我们其实可以考虑基于可靠消息实现最终一致性,因为他要求消息不能丢,并且可以接受延迟。所以可以给予MQ事务消息,或者本地消息表来实现。 ✅如何基于MQ实现分布式事务 订单系统和邮件通知系统之间,我们就可以更加简单一点,用一个最大努力通知就行了,就算失败了也没关系。 ✅什么是最大努力通知? 最后,至于订单系统和外部机构系统之间,理论上是要保证强一致性的,或者说怎么也得是秒级别的最终一致性。 但是实际情况是,外部机构的可用性和电商平台的可用性是完全天差地别的。很容易在投保的过程中失败,所以,这个地方一般的做法都是在协议上约定: 1、只要风控通过,投保一定成功。 2、可以先资金流理赔,然后再补齐信息流。 有了以上的约定之后,我们就可以把和外部系统之间的交互完全做成异步,基本上都是通过离线文件,每天同步一次,把当天的投保数据和理赔数据同步过去就可以了。 以上,就是一个基于实际业务场景,来选择分布式事务的方案的例子,这里面没提Seata的方案,其实Seata他并不是一个方案,其实是一堆方案的组合,因为他支持很多种模式,所以,以上的方案来说,也可以整体换成Seata,但是Seata的方案其实还是比较重的,所以一般小公司用的不多。但是如果要用的话,确实也是很好很强大方案。 ✅什么是Seata?他有哪几种模式?

March 22, 2026 · 1 min · santu

如何实现百万级数据从Excel导入到数据库?

典型回答 这个场景题考察的方面其实挺多的,还是那句话,我们要学会拆解,我们先来看一下,百万级数据从Excel读取,并插入到数据库,可能会遇到哪些问题: 1**、内存溢出问题** 百万级数据量的Excel文件会非常大,都加载到内存中可能会导致内存溢出。 2、性能问题 百万级数据从Excel读取并插入到数据,可能会很慢,需要考虑性能问题。 3、错误处理 在文件的读取及导入过程中,可能会遇到各种各样的问题,我们需要妥善的处理好这些问题 内存溢出问题 百万级数据量,一次性都读取到内存中,肯定是不现实的,那么好的办法就是基于流式读取的方式进行分批处理。 在技术选型上,我们选择使用EasyExcel,他特别针对大数据量和复杂Excel文件的处理进行了优化。在解析Excel时EasyExcel不会将Excel一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。 性能问题 百万级数据的处理,如果用单线程的话肯定是很慢的,想要提升性能,那么就需要使用多线程。 多线程的使用上涉及到两个场景,一个是用多线程进行文件的读取,另一个是用多线程实现数据的插入。这里就涉及到一个生产者-消费者的模式了,多个线程读取,然后多个线程插入,这样可以最大限度的提升整体的性能。 而数据的插入,我们除了借助多线程之外,还可以同时使用数据库的批量插入的功能,这样就能更加的提升插入速度。 错误处理 在文件的读取和数据库写入过程中,会需要解决各种各样的问题,比如数据格式错误、数据不一致、有重复数据等。 所以我们需要分两步来,第一步就是先进行数据的检查,在开始插入之间就把数据的格式等问题提前检查好,然后在插入过程中,对异常进行处理。 处理方式有很多种,可以进行事务回滚、可以进行日志记录。这个根据实际情况,一般来说不建议做回滚,直接做自动重试,重试几次之后还是不行的话,再记录日志然后后续在重新插入即可。 并且在这个过程中,需要考虑一下数据重复的问题,需要在excel中某几个字段设置成数据库唯一性约束,然后在遇到数据冲突的时候,进行处理,处理方式可以是覆盖、跳过以及报错。这个根据实际业务情况来,一般来说跳过+打印日志是相对合理的。 所以,整体方案就是: 借助EasyExcel来实现Excel的读取,因为他并不会一次性把整个Excel都加载到内存中,而是逐行读取的。为了提升并发性能,我们再进一步将百万级数据分散到不同的sheet中,然后借助线程池,多线程同时读取不同的sheet,在读取过程中,借助EasyExcel的ReadListener做数据处理。 在处理过程中,我们并不会每一条数据都操作数据库,这样对数据库来说压力太大了,我们会设定一个批次,比如1000条,我们会把从Excel中读取到的数据暂存在内存中,这里可以使用List实现,当读取了1000条之后,就执行一次数据的批量插入,批量插入可以借助mybatis就能简单的实现了。 经过验证,如此实现之后,读取一个100万数据的Excel并插入数据,耗时在100秒左右,不超过2分钟。 具体实现 为了提升并发处理的能力,我们把百万级数据放到同一个excel的不同的sheet中,然后通过使用EasyExcel并发的读取这些sheet。 EasyExcel提供了ReadListener接口,允许在读取每一批数据后进行自定义处理。我们可以基于他的这个功能来实现文件的分批读取。 先增加依赖: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <dependencies> <!-- EasyExcel --> <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>最新的版本号</version> </dependency> <!-- 数据库连接和线程池 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> </dependencies> 然后实现并发读取多个sheet的代码: ...

March 22, 2026 · 3 min · santu

如何实现缓存的预热?

典型回答 缓存预热是在系统启动或者在业务高峰期之前,将一些数据加载到缓存中,以提高系统的性能和响应速度。 一般来说,我们在进行缓存预热的时候,有以下几种方案: 启动过程中预热 比较常见的一种实现方式就是在系统启动的时候预热,这种方式一般对于本地缓存十分常见,因为本地缓存本身就是基于JVM内存的,当应用重启时,本地缓存就会被清空,而在应用启动之后,就需要把本地缓存的内容预热好。这样才能起到缓存的效果。 那么,一种有效的方式就是基于Spring来实现。在Spring应用程序启动时,可以通过监听应用启动事件,或者在应用的初始化阶段,将需要缓存的数据加载到缓存中。这可以通过在启动时调用相关服务或者数据访问层的方法来实现。 Spring其实提供了一系列事件及扩展点,可以让我们在他的生命周期的各个阶段做我们想做的事情。如基于ApplicationReadyEvent、CommandLineRunner、InitializingBean、@PostConstruct等。 ✅如何在Spring启动过程中做缓存预热 定时任务 在启动过程中预热有一个问题,那就是一旦启动之后,如果需要预热新的数据,或者需要修改数据,就不支持了,那么,在应用的运行过程中,我们也是可以通过定时任务来实现缓存的更新预热的。 我们通常依赖这种方式来确保缓存中的数据是最新的,避免因为业务数据的变化而导致缓存数据过时。 在Spring中,想要实现一个定时任务也挺简单的,基于@Scheduled就可以轻易实现 1 2 3 4 5 @Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点执行 public void scheduledCachePreload() { // 执行缓存预热逻辑 // ... } 用时加载 就像Redis可以基于用户的查询来进行延迟删除key一样 ,我们也可以基于用户的查询进行延迟的更新缓存。 也就是说,在用户请求到来时,根据用户的访问模式和业务需求,动态地将数据加载到缓存中。这种方式更加灵活,可以根据实际需求选择性地预热缓存。 1 2 3 4 5 6 7 8 9 10 11 public Data fetchData(String key) { // 先检查缓存中是否存在数据 Data cachedData = cache.get(key); if (cachedData == null) { // 如果缓存中不存在,根据业务需求加载数据到缓存中 // ... } return cachedData; } 缓存加载器 一些缓存框架提供了缓存加载器的机制,可以在缓存中不存在数据时,自动调用加载器加载数据到缓存中。这样可以简化缓存预热的逻辑。如Caffeine中就有这样的功能: ...

March 22, 2026 · 1 min · santu

留言给博主