【夜话系列】DelayQueue延迟队列(下):实战应用与面试精讲

🔥 本文是DelayQueue系列的下篇,聚焦实战应用场景和性能优化。通过多个真实案例,带你掌握DelayQueue在项目中的最佳实践和性能调优技巧。

📚 系列专栏推荐

  • JAVA集合专栏 【夜话集】
  • JVM知识专栏
  • 数据库sql理论与实战
  • 小游戏开发

在这里插入图片描述

文章目录

    • 一、DelayQueue实战应用
      • 1.1 订单超时自动取消
        • 1.1.1 业务场景分析
        • 1.1.2 实现方案设计
        • 1.1.3 完整代码示例
        • 1.1.4 注意事项与优化点
      • 1.2 限时优惠券管理
        • 1.2.1 优惠券过期处理
        • 1.2.2 动态失效时间控制
        • 1.2.3 并发安全处理
        • 1.2.4 实现代码与优化
      • 1.3 缓存过期清理
        • 1.3.1 缓存淘汰策略
        • 1.3.2 延迟清理机制
        • 1.3.3 内存占用优化
        • 1.3.4 示例实现
        • 1.3.5 性能优化建议
      • 1.4 实战应用总结
    • 二、面试重点解析
      • 2.1 原理相关题目
        • Q1: DelayQueue的核心原理是什么?它是如何保证元素按照延迟时间顺序被处理的?
        • Q2: DelayQueue与Timer/TimerTask相比有什么优势?
        • Q3: DelayQueue是如何实现线程安全的?
      • 2.2 实现细节考点
        • Q1: 如何实现一个自定义的延时任务放入DelayQueue?
        • Q2: DelayQueue的take()方法是如何实现的?为什么它能够精确地在延迟到期时返回元素?
        • Q3: DelayQueue中的元素可以被更新延迟时间吗?如何实现?
      • 2.3 应用场景案例
        • Q1: 请设计一个基于DelayQueue的限流器,实现令牌桶算法。
        • Q2: 如何使用DelayQueue实现一个支持定时取消的异步任务系统?
        • Q3: 在分布式系统中,如何结合Redis实现类似DelayQueue的功能?
      • 2.4 性能调优问题
        • Q1: 在高并发场景下,DelayQueue可能面临哪些性能问题?如何优化?
        • Q2: 如何设计一个支持持久化的DelayQueue,确保系统重启后任务不丢失?
      • 2.5 面试真题解析
        • 真题1: 如何使用DelayQueue实现一个限流器,要求每个接口每秒最多处理N个请求?
        • 真题2: 在一个电商系统中,如何使用DelayQueue实现秒杀活动的定时开始?
    • 三、思考题
    • 写在最后

一、DelayQueue实战应用

1.1 订单超时自动取消

1.1.1 业务场景分析

在电商系统中,订单创建后通常需要在一定时间内完成支付,否则系统会自动取消订单并释放库存。这是一个典型的延时任务场景:

  • 订单创建后,需要设置一个倒计时(通常为15分钟或30分钟)
  • 如果在倒计时结束前完成支付,需要取消该延时任务
  • 如果倒计时结束时订单仍未支付,则自动取消订单
  • 系统需要支持大量并发订单的超时管理

传统实现方式通常采用定时任务扫描数据库,但这种方式存在以下问题:

  • 数据库压力大,特别是订单量大的场景
  • 实时性不够,可能出现几秒甚至几分钟的延迟
  • 资源消耗高,需要频繁扫描数据库

使用DelayQueue可以很好地解决这些问题,实现内存级的订单超时管理。

1.1.2 实现方案设计

基于DelayQueue的订单超时取消方案设计如下:

  1. 创建一个实现Delayed接口的订单超时任务类
  2. 维护一个全局的DelayQueue,用于管理所有未支付订单的超时任务
  3. 订单创建时,向DelayQueue中添加对应的超时任务
  4. 订单支付成功时,从DelayQueue中移除对应的超时任务
  5. 启动专门的线程从DelayQueue中获取到期的任务,执行订单取消逻辑

这种设计的优势在于:

  • 内存级处理,性能高
  • 精确的超时控制,无需频繁扫描数据库
  • 支持动态取消超时任务
  • 系统重启后可以通过数据库中的订单状态和创建时间重建延时队列
1.1.3 完整代码示例
import java.util.Map;
import java.util.concurrent.*;/*** 基于DelayQueue实现的订单超时自动取消功能*/
public class OrderTimeoutCancelService {// 订单超时时间,单位毫秒private final long ORDER_TIMEOUT = 30 * 60 * 1000; // 30分钟// 延迟队列,用于处理订单超时private final DelayQueue<OrderDelayTask> delayQueue = new DelayQueue<>();// 用于存储订单与任务的映射关系,便于取消任务private final Map<String, OrderDelayTask> taskMap = new ConcurrentHashMap<>();// 订单服务,实际业务中通过依赖注入获取private final OrderService orderService;public OrderTimeoutCancelService(OrderService orderService) {this.orderService = orderService;// 启动处理线程new Thread(this::processTimeoutOrders).start();}/*** 添加订单超时任务* @param orderId 订单ID*/public void addOrderTimeoutTask(String orderId) {// 创建超时任务OrderDelayTask task = new OrderDelayTask(orderId, ORDER_TIMEOUT);// 添加到延迟队列delayQueue.offer(task);// 保存映射关系taskMap.put(orderId, task);System.out.println("订单[" + orderId + "]加入超时队列,将在" + ORDER_TIMEOUT/1000 + "秒后自动取消");}/*** 订单支付成功,取消超时任务* @param orderId 订单ID*/public void orderPaid(String orderId) {OrderDelayTask task = taskMap.remove(orderId);if (task != null) {// 从队列中移除任务(这里利用了equals方法判断)delayQueue.remove(task);System.out.println("订单[" + orderId + "]已支付,取消超时任务");}}/*** 处理超时订单的线程任务*/private void processTimeoutOrders() {System.out.println("订单超时处理线程已启动");while (true) {try {// 获取超时的订单任务OrderDelayTask task = delayQueue.take();// 从映射中移除taskMap.remove(task.getOrderId());// 执行订单取消逻辑orderService.cancelOrder(task.getOrderId(), "订单超时未支付");System.out.println("订单[" + task.getOrderId() + "]超时未支付,已自动取消");} catch (InterruptedException e) {Thread.currentThread().interrupt();break;} catch (Exception e) {// 处理异常,实际项目中应该有更完善的异常处理System.err.println("处理超时订单异常:" + e.getMessage());}}}/*** 订单延迟任务*/static class OrderDelayTask implements Delayed {private final String orderId;private final long expireTime; // 过期时间,单位:毫秒public OrderDelayTask(String orderId, long delayTime) {this.orderId = orderId;this.expireTime = System.currentTimeMillis() + delayTime;}public String getOrderId() {return orderId;}@Overridepublic long getDelay(TimeUnit unit) {return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);}@Overridepublic int compareTo(Delayed o) {return Long.compare(this.expireTime, ((OrderDelayTask) o).expireTime);}@Overridepublic boolean equals(Object obj) {if (this == obj) return true;if (obj == null || getClass() != obj.getClass()) return false;OrderDelayTask that = (OrderDelayTask) obj;return orderId.equals(that.orderId);}@Overridepublic int hashCode() {return orderId.hashCode();}}/*** 模拟订单服务接口*/interface OrderService {void cancelOrder(String orderId, String reason);}/*** 测试代码*/public static void main(String[] args) throws InterruptedException {// 模拟订单服务实现OrderService orderService = (orderId, reason) -> System.out.println("执行订单[" + orderId + "]取消操作,原因:" + reason);// 创建订单超时服务OrderTimeoutCancelService service = new OrderTimeoutCancelService(orderService);// 模拟创建3个订单service.addOrderTimeoutTask("ORDER_001");service.addOrderTimeoutTask("ORDER_002");service.addOrderTimeoutTask("ORDER_003");// 模拟1秒后支付了订单2Thread.sleep(1000);service.orderPaid("ORDER_002");// 等待所有订单处理完成Thread.sleep(35000);}
}
1.1.4 注意事项与优化点
  1. 任务去重

    • 重写了equals和hashCode方法,确保可以根据订单ID正确移除任务
    • 使用ConcurrentHashMap存储订单ID与任务的映射,便于快速查找和取消任务
  2. 异常处理

    • 处理线程中捕获所有异常,避免因单个任务异常导致整个处理线程终止
    • 实际项目中应该添加更完善的日志记录和异常处理机制
  3. 系统重启恢复

    • 系统重启后,内存中的DelayQueue会丢失所有任务
    • 解决方案:系统启动时,从数据库加载所有未支付且未超时的订单,重新加入DelayQueue
    // 系统启动时恢复未处理的订单超时任务
    public void recoverOrderTasks() {List<Order> pendingOrders = orderService.findPendingPaymentOrders();for (Order order : pendingOrders) {// 计算剩余超时时间long createTime = order.getCreateTime().getTime();long now = System.currentTimeMillis();long remainTimeout = createTime + ORDER_TIMEOUT - now;// 如果订单还未超时,则加入延迟队列if (remainTimeout > 0) {OrderDelayTask task = new OrderDelayTask(order.getOrderId(), remainTimeout);delayQueue.offer(task);taskMap.put(order.getOrderId(), task);} else {// 已超时但未处理的订单,直接执行取消逻辑orderService.cancelOrder(order.getOrderId(), "系统重启,订单超时未支付");}}System.out.println("成功恢复" + pendingOrders.size() + "个未支付订单的超时任务");
    }
    
  4. 性能优化

    • 使用线程池替代单个线程处理超时订单,提高并发处理能力
    • 批量处理超时订单,减少数据库操作次数
    • 考虑使用分布式延迟队列,解决单机容量和可靠性问题

1.2 限时优惠券管理

1.2.1 优惠券过期处理

电商和营销系统中,限时优惠券是常见的营销手段。优惠券通常有固定的有效期,过期后需要自动失效。传统的优惠券过期处理方式有:

  1. 定时任务扫描:定期扫描数据库,将过期优惠券标记为失效
  2. 使用时判断:用户使用优惠券时判断是否过期
  3. 缓存过期:将优惠券信息存入Redis等缓存,设置过期时间

这些方式各有优缺点,但都不够实时或资源消耗较大。使用DelayQueue可以实现内存级的优惠券过期管理,既保证实时性,又减少系统资源消耗。

1.2.2 动态失效时间控制

优惠券的一个特点是失效时间可能是动态的:

  • 固定日期失效:如"2023-12-31 23:59:59"
  • 相对时间失效:如"领取后7天内有效"
  • 活动结束失效:如"双11活动结束后失效"

DelayQueue可以很好地支持这些动态失效时间控制,只需在创建延时任务时计算正确的延迟时间即可。

1.2.3 并发安全处理

优惠券系统面临的并发场景主要有:

  • 大量用户同时领取优惠券
  • 用户使用优惠券的同时,优惠券可能正好过期
  • 系统需要动态调整优惠券的有效期

这些场景需要保证数据一致性和操作的原子性。DelayQueue本身是线程安全的,但与数据库操作的配合需要特别注意事务和锁的使用。

1.2.4 实现代码与优化
import java.util.*;
import java.util.concurrent.*;/*** 基于DelayQueue实现的限时优惠券管理系统*/
public class CouponExpirationManager {// 延迟队列,用于处理优惠券过期private final DelayQueue<CouponExpireTask> delayQueue = new DelayQueue<>();// 用于存储优惠券ID与任务的映射关系private final Map<String, CouponExpireTask> taskMap = new ConcurrentHashMap<>();// 优惠券服务,实际业务中通过依赖注入获取private final CouponService couponService;// 线程池,用于处理过期优惠券private final ExecutorService executorService;public CouponExpirationManager(CouponService couponService) {this.couponService = couponService;// 创建线程池this.executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(),r -> {Thread thread = new Thread(r, "coupon-expiration-thread");thread.setDaemon(true); // 设置为守护线程return thread;});// 启动处理线程this.executorService.execute(this::processExpiredCoupons);}/*** 添加优惠券过期任务* @param couponId 优惠券ID* @param expireTime 过期时间点(时间戳)*/public void addCouponExpireTask(String couponId, long expireTime) {// 计算延迟时间long delay = expireTime - System.currentTimeMillis();if (delay <= 0) {// 已过期,直接处理couponService.expireCoupon(couponId);return;}// 创建过期任务CouponExpireTask task = new CouponExpireTask(couponId, delay);// 添加到延迟队列delayQueue.offer(task);// 保存映射关系taskMap.put(couponId, task);System.out.println("优惠券[" + couponId + "]加入过期队列,将在" + new Date(expireTime) + "过期");}/*** 更新优惠券过期时间* @param couponId 优惠券ID* @param newExpireTime 新的过期时间点(时间戳)*/public void updateCouponExpireTime(String couponId, long newExpireTime) {// 移除旧任务CouponExpireTask oldTask = taskMap.remove(couponId);if (oldTask != null) {delayQueue.remove(oldTask);}// 添加新任务addCouponExpireTask(couponId, newExpireTime);System.out.println("优惠券[" + couponId + "]过期时间已更新为" + new Date(newExpireTime));}/*** 取消优惠券过期任务(优惠券被使用或手动作废)* @param couponId 优惠券ID*/public void cancelExpireTask(String couponId) {CouponExpireTask task = taskMap.remove(couponId);if (task != null) {delayQueue.remove(task);System.out.println("优惠券[" + couponId + "]过期任务已取消");}}/*** 处理过期优惠券的线程任务*/private void processExpiredCoupons() {System.out.println("优惠券过期处理线程已启动");while (!Thread.currentThread().isInterrupted()) {try {// 批量处理过期优惠券,提高效率List<CouponExpireTask> expiredTasks = new ArrayList<>();CouponExpireTask task = delayQueue.take(); // 获取一个过期任务expiredTasks.add(task);// 尝试一次性获取多个过期任务delayQueue.drainTo(expiredTasks, 100);// 批量处理过期优惠券List<String> couponIds = new ArrayList<>(expiredTasks.size());for (CouponExpireTask expiredTask : expiredTasks) {String couponId = expiredTask.getCouponId();taskMap.remove(couponId);couponIds.add(couponId);}// 批量更新数据库couponService.batchExpireCoupons(couponIds);System.out.println("已处理" + couponIds.size() + "张过期优惠券");} catch (InterruptedException e) {Thread.currentThread().interrupt();break;} catch (Exception e) {System.err.println("处理过期优惠券异常:" + e.getMessage());}}}/*** 关闭管理器*/public void shutdown() {executorService.shutdown();try {if (!executorService.awaitTermination(30, TimeUnit.SECONDS)) {executorService.shutdownNow();}} catch (InterruptedException e) {executorService.shutdownNow();Thread.currentThread().interrupt();}}/*** 优惠券过期任务*/static class CouponExpireTask implements Delayed {private final String couponId;private final long expireTime; // 过期时间点,单位:毫秒public CouponExpireTask(String couponId, long delayTime) {this.couponId = couponId;this.expireTime = System.currentTimeMillis() + delayTime;}public String getCouponId() {return couponId;}@Overridepublic long getDelay(TimeUnit unit) {return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);}@Overridepublic int compareTo(Delayed o) {return Long.compare(this.expireTime, ((CouponExpireTask) o).expireTime);}@Overridepublic boolean equals(Object obj) {if (this == obj) return true;if (obj == null || getClass() != obj.getClass()) return false;CouponExpireTask that = (CouponExpireTask) obj;return couponId.equals(that.couponId);}@Overridepublic int hashCode() {return couponId.hashCode();}}/*** 优惠券服务接口*/interface CouponService {void expireCoupon(String couponId);void batchExpireCoupons(List<String> couponIds);}/*** 测试代码*/public static void main(String[] args) throws InterruptedException {// 模拟优惠券服务实现CouponService couponService = new CouponService() {@Overridepublic void expireCoupon(String couponId) {System.out.println("优惠券[" + couponId + "]已过期");}@Overridepublic void batchExpireCoupons(List<String> couponIds) {System.out.println("批量处理过期优惠券:" + couponIds);}};// 创建优惠券过期管理器CouponExpirationManager manager = new CouponExpirationManager(couponService);// 模拟添加优惠券过期任务Calendar calendar = Calendar.getInstance();// 优惠券1:5秒后过期calendar.add(Calendar.SECOND, 5);manager.addCouponExpireTask("COUPON_001", calendar.getTimeInMillis());// 优惠券2:10秒后过期calendar.add(Calendar.SECOND, 5);manager.addCouponExpireTask("COUPON_002", calendar.getTimeInMillis());// 优惠券3:15秒后过期calendar.add(Calendar.SECOND, 5);manager.addCouponExpireTask("COUPON_003", calendar.getTimeInMillis())

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

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

相关文章

Redis(笔记)

简介&#xff1a; 常用数据类型: 常用操作命令&#xff1a; Redis的Java客户端&#xff1a; 操作字符串类型的数据&#xff1a; 操作Hash类型的数据&#xff1a; 操作列表类型的数据&#xff1a; 操作集合类型的数据&#xff1a; 操作有序集合类型数据&#xff1a; 通用命令…

PhotoShop学习05

1.选区基础知识 选区&#xff0c;就是选定一些区域&#xff0c;我们对图片的更改只在选区内生效&#xff0c;这样可以精细调整图片的部分而不会影响整体。它的快捷键是M。 我们用点击鼠标后滑动就会出现虚线框&#xff0c;虚线框内的就是我们选定的区域。这时我们再滑动就会创…

使用Redission实现分布式锁

分布式锁在分布式系统中非常重要&#xff0c;主要用于解决多个进程/服务并发访问共享资源时的数据一致性问题。在日常开发中常用于&#xff1a; 1. 防止重复操作&#xff08;幂等性控制&#xff09; 场景&#xff1a;用户重复提交订单、重复支付、重复点击等。 示例&#xff1…

VScode 画时序图(FPGA)

1、先安装插件&#xff1a; 2、然后就可以编写一个.js文件&#xff0c;如下&#xff1a; {signal: [{name: clk, wave: p.......|..},{name: rstn, wave: 01......|..},{name: din_vld, wave: 0.1.0...|..},{name: din, wave: "x.x...|..", data: ["D0", …

嵌入式学习笔记——I2C

IIC协议详解 一、IIC协议简介二、IIC总线结构图三、IIC通信流程详解1. 空闲状态 : 双高空闲2. 起始信号&#xff08;START&#xff09;: 时高数下开始3. 停止信号&#xff08;STOP&#xff09;: 时高数上结束4. 数据传输格式 : 时高数稳&#xff0c;时低数变5. 应答信号 四、写…

Apifox Helper 与 Swagger3 区别

核心定位差异 Apifox Helper 定位&#xff1a;基于 IDEA 的代码注释解析工具&#xff0c;与 Apifox 平台深度集成&#xff0c;实现文档自动生成接口管理测试协作的一体化流程。 特点&#xff1a; 通过解析 Javadoc、KDoc 等注释生成文档&#xff0c;代码零侵入&#xff08;无…

单片机实现多线程的方法汇总

在单片机上实现“多线程”的方法有几种&#xff0c;下面按照从简单到复杂、从轻量到系统性来列出常见的方案&#xff1a; &#x1f9f5; 一、伪多线程&#xff08;最轻量&#xff09; 方法&#xff1a;主循环 状态机 / 定时器轮询 主循环中轮流调用各个任务的处理函数&#x…

网络:华为数通HCIA学习:静态路由基础

文章目录 前言静态路由基础静态路由应用场景 静态路由配置静态路由在串行网络的配置静态路由在以太网中的配置 负载分担配置验证 路由备份&#xff08;浮动静态路由&#xff09;配置验证 缺省路由配置验证 总结 华为HCIA 基础实验&#xff0d;静态路由 & eNSP静态路由 基础…

[项目总结] 在线OJ刷题系统项目技术应用(下)

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

Qt音频输出:QAudioOutput详解与示例

1. 简介 QAudioOutput是Qt多媒体框架中的一个关键类&#xff0c;它提供了将PCM&#xff08;脉冲编码调制&#xff09;原始音频数据发送到音频输出设备的接口。作为Qt多媒体组件的一部分&#xff0c;QAudioOutput允许开发者在应用程序中实现音频播放功能&#xff0c;支持多种音…

【计算机网络】Linux配置SNAT/DNAT策略

什么是NAT&#xff1f; NAT 全称是 Network Address Translation&#xff08;网络地址转换&#xff09;&#xff0c;是一个用来在多个设备共享一个公网 IP上网的技术。 NAT 的核心作用&#xff1a;将一个网络中的私有 IP 地址&#xff0c;转换为公网 IP 地址&#xff0c;从而…

Redis淘汰策略详解!

目录 一、为什么需要淘汰策略&#xff1f; &#x1f914;二、Redis 的淘汰策略详解 &#x1f447;三、如何选择合适的淘汰策略&#xff1f; &#x1f914;➡️✅四、如何切换 Redis 的淘汰策略&#xff1f; ⚙️&#x1f527;五、总结 &#x1f389; &#x1f31f;我的其他文章…

存储基石:深度解读Linux磁盘管理机制与文件系统实战

Linux系列 文章目录 Linux系列前言一、磁盘1.1 初识磁盘1.2 磁盘的物理结构1.3 磁盘的存储结构1.4 磁盘的逻辑结构 二、文件系统2.1 系统对磁盘的管理2.2 文件在磁盘中的操作 前言 Linux 文件系统是操作系统中用于管理和组织存储设备&#xff08;如硬盘、SSD、USB 等&#xff…

本节课课堂总结

匿名子类&#xff1a; 说明 和 Java 一样&#xff0c;可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类。 单例对象&#xff08;伴生对象&#xff09; Scala语言是完全面向对象的语言&#xff0c;所以并没有静态的操作&#xff08;即在Scala中没有静态的概念&a…

I²C、SPI、UART、CAN 通信协议详解

一、协议基本特性对比 特性ICSPIUARTCAN通信类型同步、半双工同步、全双工异步、全双工异步、多主多从信号线SDA&#xff08;数据&#xff09;、SCL&#xff08;时钟&#xff09;MOSI、MISO、SCK、SS&#xff08;片选&#xff09;TX&#xff08;发送&#xff09;、RX&#xff…

【diffusers 进阶(十五)】dataset 工具,Parquet和Arrow 数据文件格式,load dataset 方法

系列文章目录 【diffusers 极速入门&#xff08;一&#xff09;】pipeline 实际调用的是什么&#xff1f; call 方法!【diffusers 极速入门&#xff08;二&#xff09;】如何得到扩散去噪的中间结果&#xff1f;Pipeline callbacks 管道回调函数【diffusers极速入门&#xff0…

第十三章:持久化存储_《凤凰架构:构建可靠的大型分布式系统》

第十三章 持久化存储 一、Kubernetes存储设计核心概念 &#xff08;1&#xff09;存储抽象模型 PersistentVolume (PV)&#xff1a;集群级别的存储资源抽象&#xff08;如NFS卷/云存储盘&#xff09;PersistentVolumeClaim (PVC)&#xff1a;用户对存储资源的声明请求&#…

以太网安全

前言&#xff1a; 端口隔离可实现同一VLAN内端口之间的隔离。用户只需要将端口加入到隔离组中&#xff0c;就可以实现隔离组内端口之间的二层数据的隔离端口安全是一种在交换机接入层实施的安全机制&#xff0c;旨在通过控制端口的MAC地址学习行为&#xff0c;确保仅授权设备能…

跨域问题前端解决

由于浏览器的同源策略&#xff0c;前后端分离的项目&#xff0c;调试的时候总是会遇到跨域的问题&#xff0c;这里通过修改前端代码解决跨域问题。 首先先查看前端代码的根目录下&#xff0c;有没有vue.config.js文件, 若有&#xff0c;使用方法1&#xff0c;若没有此文件&…

Elasticsearch 报错index_closed_exception

index_closed_exception 是 Elasticsearch 中的一个异常类型&#xff0c;它通常发生在尝试对一个已经被关闭&#xff08;closed&#xff09;的索引执行搜索、写入或其他操作时。在 Elasticsearch 中&#xff0c;索引是用来存储和检索数据的逻辑命名空间&#xff0c;可以将其类比…