当前位置: 首页 > news >正文

Day16

1. Java为什么要用线程池?

在 Java 中频繁地创建和销毁线程会消耗大量系统资源,影响系统性能。线程池通过复用已创建的线程,避免了反复创建销毁的开销,并且可以控制线程的最大并发数,防止系统被大量线程压垮。同时,线程池还提供了任务队列、线程管理、超时处理等机制,使得线程的使用更加灵活和安全,是构建高性能并发程序的关键组件。

2. 介绍下有哪些线程池?

在 Java 中,线程池是由 Executor 框架提供支持的,核心接口是 ExecutorService,具体实现类由 ThreadPoolExecutor 提供。常用的线程池主要有以下几种,官方提供了几种快捷工厂方法(在 Executors 类中):

  1. newFixedThreadPool(int nThreads)固定线程池:创建一个固定大小的线程池,核心线程数和最大线程数都为 nThreads,不会回收线程。适用于任务数量恒定、长期执行,避免线程频繁创建销毁。
  2. newCachedThreadPool()缓存线程池:线程数不固定,有空闲线程就复用,否则就创建新线程;空闲线程超过 60 秒就回收。适用于执行很多短期异步任务,任务量不确定,适合高并发、瞬时请求。
  3. newSingleThreadExecutor()单线程线程池:始终只有一个线程执行任务,保证任务串行执行、按顺序处理。适用于需要按顺序执行任务、避免并发访问的场景(如日志记录、按顺序发送短信等)。
  4. newScheduledThreadPool(int corePoolSize)定时/周期任务线程池:支持任务的延时执行和周期执行。适用于周期性任务调度,如心跳检测、定时任务等。
  5. ThreadPoolExecutor可自定义:所有上述线程池本质上都是对 ThreadPoolExecutor 的封装。

3. Redis内存淘汰策略有哪些?

Redis 内存淘汰策略是指当内存使用达到设置的最大上限时,Redis 需要决定哪些数据要被删除,以释放空间存储新数据。
当 Redis 设置了 maxmemory(最大可用内存)之后,可以通过 maxmemory-policy 来配置淘汰策略。主要有以下几类:

  • noeviction:默认策略,不淘汰,只报错:写操作会返回错误。
  • allkeys-lru:从所有键中,优先淘汰最近最少使用的键。
  • volatile-lru:从设置了过期时间(expire)的键中,淘汰最近最少使用的键。
  • allkeys-random:从所有键中随机淘汰一个键。
  • volatile-random:从有过期时间的键中随机淘汰一个键。
  • volatile-ttl:从设置了过期时间的键中,淘汰即将过期的键(TTL最小)。
  • allkeys-lfu:从所有键中,淘汰最不常用的键。
  • volatile-lfu:从设置了过期时间的键中,淘汰最不常用的键。

redis.conf 文件配置方式:

# 设置最大内存限制
config set maxmemory 100mb# 设置淘汰策略,比如使用 allkeys-lru
config set maxmemory-policy allkeys-lru

4. Redis过期删除策略是什么?

Redis 采用惰性删除和定期删除相结合的方式来处理过期键,以在性能和内存效率之间取得平衡。惰性删除指的是只有在访问某个 key 时,才会检查其是否过期,若过期则立即删除,这种方式 CPU 开销极低,但可能导致大量过期但未被访问的数据长期占用内存。为了解决这一问题,Redis 还引入了定期删除机制,每隔一段时间会随机抽取一部分设置了过期时间的 key 进行检查和删除,这样可以在不影响主线程性能的前提下清除部分无效数据。通过将这两种策略结合使用,Redis 能够有效控制 CPU 占用的同时避免内存被过期数据占满,实现系统性能和资源利用率的双重优化。

5. Redis是怎么实现惰性删除的?

Redis 的 惰性删除策略(Lazy Deletion) 是通过 expireIfNeeded(redisDb *db, robj *key)函数实现的,这个函数定义在 db.c 文件中。

  1. 判断是否过期:首先通过keyIsExpired(db, key)判断该 key 是否已经过期。
  2. 如果未过期:直接返回,Redis 会继续正常执行后续操作。
  3. 如果已过期:
    • Redis 会执行删除操作
    • 是否采用异步删除(不会阻塞主线程),取决于配置项lazyfree-lazy-expire(默认是关闭的)
      • return server.lazyfree_lazy_expire ? dbAsyncDelete(db, key) : dbSyncDelete(db, key);
    • 如果是异步删除,Redis 会将该删除操作交给后台线程去处理
    • 最终 Redis 返回 NULL 给客户端,表示该 key 已过期并被移除
  4. 触发时机:只要客户端访问 key(如 GET、SET、DEL 等),都会先隐式调用expireIfNeeded()检查是否过期。

6. Redis是怎么实现定期删除的?

Redis 的定期删除通过后台任务每隔一段时间随机抽取部分数据库中的 key,检查它们是否过期,如果过期就删除,从而避免大量过期但未被访问的 key 长期占用内存。
Redis 是通过activeExpireCycle()函数(位于 expire.c 文件)来实现定期删除。该函数会被 Redis 的主循环周期性调用:

  1. 触发机制:
    • 每 100 ms 左右执行一次(在 Redis 的事件循环中定时触发)。
    • 每次从数据库中抽样一部分设置了过期时间的 key(默认每次最多 20 个),进行过期检查。
  2. 检查和删除:
    • 对这些 key 调用keyIsExpired()判断是否过期。
    • 如果过期,则直接删除(会调用和惰性删除一样的expireIfNeeded())。
    • 如果这次扫描中超过 25% 的 key 是过期的,那么认为当前数据库中过期 key 比例高,于是继续扫描(进行多轮采样,直到命中率小于 25% 或事件耗尽)。
  3. 事件限制:
    • Redis 会控制这次循环的总执行时间,防止长时间占用 CPU(默认上限 1ms 左右)。
    • 因此这个删除是渐进式、有限度的,不会一口气清掉所有过期数据。

7. RocketMQ怎么处理分布式事务?

RocketMQ 通过三阶段的事务消息机制(发送预备消息 → 执行业务逻辑并提交本地事务 → 消息确认提交或回滚),实现分布式事务的一致性控制,避免数据不一致问题。

  1. 发送半消息(prepare message):
    • 生产者先向 MQ 发送一条“半消息”(消息状态为暂时不可投递),此时消息会存入消息服务器,但消费者不可见。
    • 消息处于“待确认”状态。
  2. 执行本地事务:
    • 消息发送成功后,生产者执行本地事务(如修改数据库、写账本等)。
    • 本地事务执行完后,生产者向 RocketMQ 提交事务状态:
      • 提交(COMMIT):MQ 向半消息转为正常消息,投递给消费者
      • 回滚(ROLLBACK):MQ 删除半消息,不投递
      • 未知(UNKNOWN):MQ 无法判断事务结果
  3. 事务回查(Check):
    • 如果 MQ 长时间未收到事务状态反馈(如生产者宕机),Broker 会主动发起事务状态回查。
    • 生产者需实现TransactionListener接口中的checkLocalTransaction()方法,返回事务状态,供 MQ 决定是否提交消息。

在 RocketMQ 中,分布式事务通过「半消息机制」实现。首先,A 服务(消息生产者)向 Broker 发送一条 Half Message(即暂不可投递的“半消息”),这条消息中通常包含 B 服务(消费者)将要执行的操作信息,比如“账户增加 100 元”。Broker 成功接收该消息后,会将其暂时保存,但不会投递给消费者。随后,A 服务执行本地事务逻辑,比如向数据库写入订单记录。如果事务执行成功,A 服务会向 Broker 发送 Commit 消息,Broker 此时才会将该消息投递给 B 服务;若本地事务失败,则发送 Rollback 消息,Broker 会删除该半消息,不再投递。若由于网络等原因,Broker 一直未收到事务状态确认(即 Commit 或 Rollback),RocketMQ 会定时通过 事务回查机制 向 A 服务发起回调请求,确认事务的真实执行结果,最终决定是否提交或回滚消息。

8. RocketMQ消息顺序怎么保证?

在 RocketMQ 中,保证消息顺序主要依赖于 顺序消息(Ordered Message)机制。
RocketMQ 保证消息顺序的关键在于消息发送和消费始终路由到同一个队列(MessageQueue)。在发送顺序消息时,Producer 会通过自定义的 MessageQueueSelector 选择逻辑,根据某个业务关键字段(如订单 ID、用户 ID等)将同一类消息始终发送到同一个队列,从而在该队列内保持顺序。Consumer 在消费时开启顺序消费模式(顺序消费是单线程的),确保消息被严格暗战存储顺序依次消费。因此,RocketMQ 是通过“同类消息进同一队列 + 单线程消费”来实现局部顺序性。需要注意的是,这种顺序性仅在单个队列内有效,也就是分区有序,无法做到全局顺序。
比如一个订单系统中,同一个订单会产生多个状态变更消息(下单、支付、发货),为了保证这些状态变化按顺序消费,可以用订单号作为 hash key,将这些消息都发送到同一个队列。消费端则按队列顺序处理,就可以确保消息顺序一致。

9. RocketMQ消息积压了怎么办?

当 RokcetMQ 出现消息积压时,首先应排查是否是 Consumer 消费能力不足或消费异常导致的,比如消费失败重试、线程数不足、业务逻辑慢等。其次,需要监控 Broker 中的消息堆积情况(如堆积条数、消息进度落后),定位是哪个 Consumer Group 出问题。之后,可以通过增加消费者实例数、提升消费线程数、优化消费逻辑或临时开启批量消费等方式进行缓解。如果仍无法解决,考虑削峰填谷:对生产端限流、或将消息持久化后异步处理。极端情况下,可以考虑使用临时的 消费补偿机制 来快速清理堆积。
怎么排查消息堆积严重: 先通过 RocketMQ 控制台或 mqadmin 查看具体哪个 Consumer Group 积压严重,再检查 Consumer 是否存活、消费线程是否足够、是否存在消费失败或阻塞,然后根据具体情况进行水平扩容、优化消费逻辑、开启批量消费,最后评估是否需要限流生产者或引入补偿机制来兜底。

相关文章:

  • C盘的“下载”修改位置时出错了,怎么还原
  • three.js 零基础到入门
  • 软件更新机制的测试要点与稳定性提升
  • python中使用LibreHardwareMonitorLib.dll获取电脑硬件信息~~【不用同步打开exe文件】
  • 【LangChain4J】LangChain4J 第三弹:多模态与文生图的实现
  • 删除有序数组中的重复项
  • CZGL.SystemInfo:跨平台的系统信息获取库
  • Deep Research实践
  • 程序代码篇---随机数与随机数种子
  • 【Java学习笔记】Arrays类
  • C++17 和 C++20 中的新容器与工具:std::optional、std::variant 和 std::span
  • 大语言模型(LLM)面试问题集
  • 实验一:数据选择器实验
  • C++核心编程_继承同名静态成员处理方式
  • 深入理解链接与加载:从静态库到动态库的全流程解析
  • 【第八篇】 SpringBoot高级配置(配置篇)
  • 【SpringBoot自动化部署方法】
  • 图像超分辨率
  • 深度学习模块缝合
  • 线程与线程池
  • 西宁网站建设多少钱/云和数据培训机构怎么样
  • 东莞网页设计费用/seo公司网站推广
  • 简单房地产网站/优化大师官方免费下载
  • 网站建设的频道是什么/百度竞价推广有哪些优势
  • 建设外贸网站哪家好/百度手机怎么刷排名多少钱
  • 崇信县人民政府网站/市场营销的八个理论