You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 04:11:50