Hazelcast IQueue高请求下对象堆积致应用性能下降问题
Hazelcast IQueue堆积导致应用性能下降问题排查与解决
问题背景
客户端REST应用通过Hazelcast IQueue与计算器应用通信:REST端将对象加入队列,计算器应用通过监听器接收对象并执行删除操作。正常场景下运行稳定,但当请求间隔小于0.3秒时,队列开始出现对象堆积,堆积量过大时会导致整个应用性能下降。
当前代码与配置
删除队列对象的代码
public void deleteFromQueue(String queueId, Object object) throws NullPointerException, ClassCastException { GenericMessage<?> incomeMessage = (GenericMessage<?>) object; boolean removed = false; removed = hazelcastInstance.getQueue(queueId).remove(object); if (!removed) { Iterator<Object> iterator = hazelcastInstance.getQueue(queueId).iterator(); while (iterator.hasNext()) { Object next = iterator.next(); GenericMessage<?> message = (GenericMessage<?>) next; if (incomeMessage.getHeaders().getKey().equalsIgnoreCase(message.getHeaders().getKey())) { removed = hazelcastInstance.getQueue(queueId).remove(next); break; } } if (!removed) { if (hazelcastInstance.getQueue(queueId).size() == 1) { hazelcastInstance.getQueue(queueId).poll(); } } } }
监听器线程池配置
@Bean(name = "threadPoolTaskExecutor") public Executor threadPoolTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setWaitForTasksToCompleteOnShutdown(true); executor.setQueueCapacity(15); executor.initialize(); return executor; }
监控信息
2024-04-30 00:00:01,578 INFO c.h.l.StandardLoggerFactory$StandardLogger [hz.hazelcast_calculator.HealthMonitor] [127.0.0.1]:0000 [NAME] [5.3.6] processors=8, physical.memory.total=31.3G, physical.memory.free=589.2M, swap.space.total=0, swap.space.free=0, heap.memory.used=8.6G, heap.memory.free=13.9G, heap.memory.total=22.5G, heap.memory.max=25.0G, heap.memory.used/total=38.20%, heap.memory.used/max=34.41%, minor.gc.count=142365, minor.gc.time=2012868ms, major.gc.count=2186, major.gc.time=3189851ms, unknown.gc.count=8, unknown.gc.time=38ms, load.process=0.00%, load.system=99.55%, load.systemAverage=14.04, thread.count=439, thread.peakCount=469, cluster.timeDiff=0, event.q.size=0, executor.q.async.size=0, executor.q.client.size=0, executor.q.client.query.size=0, executor.q.client.blocking.size=0, executor.q.query.size=0, executor.q.scheduled.size=0, executor.q.io.size=0, executor.q.system.size=0, executor.q.operations.size=1, executor.q.priorityOperation.size=0, operations.completed.count=1655285419, executor.q.mapLoad.size=0, executor.q.mapLoadAllKeys.size=0, executor.q.cluster.size=0, executor.q.response.size=0, operations.running.count=0, operations.pending.invocations.percentage=0.00%, operations.pending.invocations.count=1, proxy.count=953, clientEndpoint.count=3, connection.active.count=3, client.connection.count=2, connection.count=2
问题分析
队列删除逻辑性能极差
- Hazelcast IQueue的
remove(Object)是分布式操作,需要遍历整个队列匹配对象,队列堆积时该操作会占用大量集群资源与CPU。 - 第一次删除失败后再次遍历队列并调用
remove(next),双重遍历操作进一步加剧性能损耗。 size()方法是分布式状态查询操作,性能低下,且业务逻辑中该判断冗余。
- Hazelcast IQueue的
线程池配置无法应对高并发
- 核心线程数(5)与最大线程数(10)过低,8核机器无法充分利用CPU资源处理任务。
- 队列容量(15)过小,高并发场景下任务快速填满队列,导致新任务等待或被拒绝,处理速度跟不上入队速度。
系统资源已饱和
- 系统负载
load.systemAverage=14.04(8核机器负载超过8即饱和),CPU资源耗尽。 - GC频繁:Minor GC累计耗时超33分钟,Full GC累计耗时超53分钟,频繁GC导致应用频繁停顿,进一步降低处理效率。
- 线程数过多(439),上下文切换开销增大,占用额外CPU资源。
- 系统负载
解决方案
1. 重构队列消费与删除逻辑
- 优先使用头部消费:放弃按对象内容删除的逻辑,直接用
poll()/take()从队列头部获取任务,处理完成后自动移除元素,避免遍历操作。 - 改用IMap存储需按Key操作的对象:如果业务必须按消息Key删除,用Hazelcast IMap替代IQueue,将消息Key作为Map的Key,删除操作复杂度为O(1),性能远超队列遍历删除。
- 移除冗余的size判断:删除
hazelcastInstance.getQueue(queueId).size() == 1的逻辑,避免分布式状态查询开销。
2. 调整线程池配置
@Bean(name = "threadPoolTaskExecutor") public Executor threadPoolTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 根据8核机器调整核心线程数与最大线程数 executor.setCorePoolSize(8); executor.setMaxPoolSize(16); // 调整队列容量,或设置为无界队列(需监控内存) executor.setQueueCapacity(100); executor.setWaitForTasksToCompleteOnShutdown(true); // 使用CallerRunsPolicy避免任务丢失,同时起到限流作用 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; }
3. 优化JVM与GC配置
- 调整新生代大小,减少Minor GC次数:例如添加JVM参数
-Xmn8G(新生代分配8G内存)。 - 减少临时对象创建:优化消息处理逻辑,避免不必要的对象实例化。
4. 优化Hazelcast配置
- 若无需高可用性,减少队列备份数量,降低集群同步开销。
- 若REST与计算器应用部署在同一节点,改用Java本地队列(如
LinkedBlockingQueue)替代分布式队列,大幅提升性能。
5. 添加监控与告警
- 监控队列长度,设置阈值告警,堆积超过阈值时触发限流或扩容操作。
- 监控GC频率、系统负载与线程数,及时发现资源瓶颈。
内容的提问来源于stack exchange,提问作者Владимир Гичев
相关产品推荐
相关产品推荐

