Glassfish 4上Java EE多租户系统打印请求过载调度优化问询
基于Glassfish 4实现FCFS打印请求处理的解决方案
首先,你的核心问题是突发请求量超过服务器同步处理能力,导致资源耗尽宕机。引入FCFS本质是把同步阻塞的请求处理改成异步排队处理,Java EE生态里的JMS(Java Message Service)正好适配这个场景,而且Glassfish 4原生支持JMS,不需要额外引入太多依赖,是最稳妥的方案。
下面是具体的实现思路和步骤:
1. 配置Glassfish的JMS队列
先在Glassfish控制台创建专门的打印请求队列,用来存储待处理的请求:
- 登录Glassfish管理控制台(默认地址
http://localhost:4848) - 进入
Resources > JMS Resources > JMS Queues - 点击
New,填写队列名称(比如PrintRequestQueue)、JNDI名称(比如jms/PrintRequestQueue),其他保持默认即可 - 保存后,Glassfish会自动维护队列的消息存储,确保请求不会丢失
2. 修改请求接收端:将请求异步发送到队列
原来的代码应该是同步处理打印请求,现在改成把请求参数封装成消息,快速发送到JMS队列,避免阻塞请求线程:
import javax.annotation.Resource; import javax.jms.ConnectionFactory; import javax.jms.JMSContext; import javax.jms.Queue; import javax.jms.TextMessage; import com.fasterxml.jackson.databind.ObjectMapper; // 在你的请求处理Servlet/REST资源类中注入JMS资源 @Resource(lookup = "jms/PrintRequestQueue") private Queue printQueue; @Resource(lookup = "jms/ConnectionFactory") // Glassfish默认连接工厂的JNDI private ConnectionFactory connectionFactory; private final ObjectMapper objectMapper = new ObjectMapper(); public String handlePrintRequest(PrintRequest request) { // 用JMSContext简化发送逻辑(Java EE 7+支持) try (JMSContext context = connectionFactory.createContext()) { // 把打印请求序列化为JSON,作为消息内容 String requestJson = objectMapper.writeValueAsString(request); TextMessage message = context.createTextMessage(requestJson); // 发送到队列,这一步是非阻塞的,毫秒级就能返回给客户端 context.createProducer().send(printQueue, message); return "请求已加入打印队列,将按顺序处理"; } catch (Exception e) { // 处理消息发送失败的情况,比如返回错误提示 return "请求提交失败,请稍后重试"; } }
这样所有突发请求都会被快速放入队列,不会占用服务器处理线程,从根源上避免资源耗尽宕机。
3. 创建消息驱动Bean(MDB)实现FCFS处理
JMS队列天然就是先到先服务的,我们只需要创建一个MDB监听队列,就能按请求到达顺序处理:
import javax.ejb.MessageDriven; import javax.jms.Message; import javax.jms.MessageListener; import javax.jms.TextMessage; import com.fasterxml.jackson.databind.ObjectMapper; // 绑定到我们创建的打印请求队列 @MessageDriven( mappedName = "jms/PrintRequestQueue", activationConfig = { // 限制同时处理的请求数,避免打印服务过载,比如设为5 @javax.ejb.ActivationConfigProperty(propertyName = "maxSession", propertyValue = "5") } ) public class PrintRequestMDB implements MessageListener { private final ObjectMapper objectMapper = new ObjectMapper(); @Override public void onMessage(Message message) { try { if (message instanceof TextMessage) { TextMessage textMessage = (TextMessage) message; String requestJson = textMessage.getText(); // 反序列化为打印请求对象 PrintRequest request = objectMapper.readValue(requestJson, PrintRequest.class); // 执行原有打印业务逻辑,这里是同步处理,一个MDB实例一次处理一个消息 processPrintRequest(request); } } catch (Exception e) { // 处理异常:记录日志,或者把失败消息转到死信队列(可选) e.printStackTrace(); } } private void processPrintRequest(PrintRequest request) { // 你的原有打印逻辑:比如根据租户ID获取对应打印机配置、调用打印服务等 // 注意多租户隔离:必须从request中拿到租户ID,确保资源不混用 } }
MDB由Glassfish容器管理,maxSession参数可以控制并发处理的请求数,避免超过服务器和打印服务的承载上限。
4. 多租户场景的额外注意事项
因为你的系统是多租户架构,需要确保打印请求的租户隔离:
- 打印请求消息中必须携带租户ID,MDB处理时根据ID获取对应租户的打印资源(比如打印机、模板)
- 不需要为每个租户单独创建队列,只要在消息中标记租户ID即可,这样更节省服务器资源
- 如果有特殊租户需要优先处理,可以配置JMS消息优先级,但你的需求是FCFS,保持默认优先级即可
5. 资源调优与监控
- 调整Glassfish线程池:进入
Configurations > server-config > Thread Pools > thread-pool-1(默认EJB处理线程池),根据服务器CPU核心数调整线程数,比如8核CPU可以设为16-24 - 监控队列状态:在Glassfish控制台的
Resources > JMS Resources > JMS Queues查看队列消息数,确保处理速度能跟上请求入队速度 - 配置死信队列:如果某些请求处理失败(比如打印机故障),可以把失败消息转发到死信队列,后续人工排查,避免阻塞整个队列
6. 验证方案
在测试环境模拟500个并发请求,重点验证:
- 所有请求都能被快速响应,客户端不会超时
- MDB严格按请求到达顺序处理消息
- 服务器CPU、内存使用率保持在合理范围,无宕机情况
- 多租户的打印资源能正确隔离,不会出现串用问题
内容的提问来源于stack exchange,提问作者jaletechs
相关产品推荐
相关产品推荐

