面试题答案
一键面试1. 线程池拒绝策略选择
- AbortPolicy:默认策略,直接抛出
RejectedExecutionException
。在分布式微服务系统中不太适用,因为简单抛出异常可能导致任务丢失且不利于故障协调。 - CallerRunsPolicy:当线程池拒绝任务时,由调用线程(提交任务的线程)来执行该任务。在微服务中,调用线程可能来自外部请求线程,这可能会影响请求响应时间,但可以保证任务不丢失,且能在一定程度上缓解线程池压力。
- DiscardPolicy:直接丢弃被拒绝的任务,不做任何处理。这可能导致数据丢失,在对数据完整性要求高的系统中不适用。
- DiscardOldestPolicy:丢弃队列中最老的任务,然后尝试提交新任务。这可能导致重要任务被丢弃,需要谨慎使用。
在分布式微服务场景下,可根据具体业务需求自定义拒绝策略。例如,可以先将被拒绝的任务存储到本地缓存(如ConcurrentHashMap
),并开启一个定时任务定期重试提交到线程池,或者将任务发送到一个特殊的“重试队列”供后续处理。
2. 通信机制
- 消息队列:已经在使用的消息队列可以作为与其他微服务协调的重要通信渠道。当某个微服务线程池拒绝任务时,可以将相关任务信息(如任务ID、任务类型、重试次数等)封装成消息发送到特定的消息队列主题,其他相关微服务订阅该主题以获取信息并进行相应处理。
- RESTful API:除了消息队列,也可以使用RESTful API进行同步通信。例如,当微服务A的线程池拒绝任务时,可以通过调用微服务B的REST接口,告知B当前的任务处理情况,B根据这些信息调整自身的任务处理节奏。
3. 协调策略
- 限流策略:当某个微服务线程池拒绝任务时,该微服务可以向相关微服务发送限流信号。比如,微服务A通知下游微服务B减少发送到A的任务量,B接收到信号后,可采用令牌桶算法等限流手段,降低任务发送频率,避免A的线程池进一步过载。
- 任务转移:如果微服务A的线程池拒绝任务,且微服务B具有空闲资源,可以将部分任务转移到B处理。例如,通过消息队列将任务发送到B的任务队列,B从队列中获取任务并处理。
- 负载均衡调整:对于使用负载均衡器的系统,当某个微服务线程池出现拒绝任务情况时,负载均衡器可以动态调整请求分配策略,将请求更多地分配到其他负载较轻的微服务实例上。
4. 性能、可靠性和资源消耗的权衡
- 性能:
- 频繁的任务重试或任务转移可能会增加系统的额外开销,降低整体性能。因此,需要合理设置重试次数和时间间隔,避免过度重试。
- 使用
CallerRunsPolicy
虽然能保证任务不丢失,但可能会阻塞调用线程,影响请求响应时间。可根据业务场景合理选择,如对于非关键任务可采用此策略,关键任务则需要更复杂的处理方式。
- 可靠性:
- 为保证任务不丢失,需要采用可靠的存储方式(如本地缓存、持久化消息队列等)来暂存被拒绝的任务。但持久化操作会增加I/O开销,影响性能。
- 消息队列作为通信机制,要确保其高可用性,可采用主从复制、集群等方式,避免因消息队列故障导致协调信息丢失。
- 资源消耗:
- 线程池拒绝任务时,无论是重试任务、转移任务还是与其他微服务协调,都会消耗额外的资源(如网络带宽、CPU、内存等)。需要监控系统资源使用情况,合理设置线程池参数(如核心线程数、最大线程数、队列容量等),避免资源耗尽。
- 定时任务用于重试任务,要合理设置定时任务的频率和并发数,避免过多的定时任务占用过多系统资源。