Oozie 生成JMS消息并向 JMS Provider发送消息过程分析

简介:

一,涉及到的工程

从官网下载源码,mvn 编译成 Eclipse工程文件:

 

对于JMS消息这一块,主要涉及到两个工程:

oozie-core工程有问题的原因是还需要一些其他的依赖工程未导入:

 

二,Oozie 生成 JMS消息 主要涉及到的一些类

oozie-core 工程中的:

 

 

oozie-client工程中的:

 

三,相关代码:

对于Oozie Server而言,它是消息的生产者。在oozie-default.xml/oozie-site.xml里面配置好连接参数,消息服务器....Oozie就使用这些配置进行连接,产生消息,发送消息。

JMSAccessorService.java

复制代码
/**
 * This class will <ul>
 * <li> Create/Manage JMS connections using user configured JNDI properties. </li>
 * <li> Create/Manage session for specific connection/topic and reconnects on failures. </li>
 * <li> Provide a way to create a subscriber and publisher </li>
 * <li> Pure JMS compliant (implementation independent but primarily tested against Apache ActiveMQ). </li>
 * </ul>
 */
public class JMSAccessorService implements Service {
复制代码

直接看注释就知道这个类的功能了。

 

复制代码
    /**
     * Map of JMS connection info to established JMS Connection
     */
    private ConcurrentMap<JMSConnectionInfo, ConnectionContext> connectionMap =
            new ConcurrentHashMap<JMSConnectionInfo, ConnectionContext>();
    /**
     * Map of JMS connection info to topic names to MessageReceiver
     */
    private ConcurrentMap<JMSConnectionInfo, Map<String, MessageReceiver>> receiversMap =
            new ConcurrentHashMap<JMSConnectionInfo, Map<String, MessageReceiver>>();
复制代码

ConcurrentHashMap线程安全的,用来保存与JMS Provider的连接信息

 

synchronized (this) {
                if (jmsProducerConnContext == null || !jmsProducerConnContext.isConnectionInitialized()) {
                    try {
                        jmsProducerConnContext = getConnectionContextImpl();
                        jmsProducerConnContext.createConnection(connInfo.getJNDIProperties());
                        jmsProducerConnContext.setExceptionListener(new JMSExceptionListener(connInfo,
复制代码
  private ConnectionContext getConnectionContextImpl() {
        Class<?> defaultClazz = conf.getClass(JMS_CONNECTION_CONTEXT_IMPL, DefaultConnectionContext.class);
        ConnectionContext connCtx = null;
        if (defaultClazz == DefaultConnectionContext.class) {
            connCtx = new DefaultConnectionContext();
        }
        else {
            connCtx = (ConnectionContext) ReflectionUtils.newInstance(defaultClazz, null);
        }
        return connCtx;
    }
复制代码

创建 Producer 连接的上下文环境

 

DefaultConnectionContext.java  默认的连接上下文环境

复制代码
public class DefaultConnectionContext implements ConnectionContext {

    protected Connection connection;
    protected String connectionFactoryName;
    private static XLog LOG = XLog.getLog(ConnectionContext.class);

    @Override
    public void createConnection(Properties props) throws NamingException, JMSException {
        Context jndiContext = new InitialContext(props);
        connectionFactoryName = (String) jndiContext.getEnvironment().get("connectionFactoryNames");
        if (connectionFactoryName == null || connectionFactoryName.trim().length() == 0) {
            connectionFactoryName = "ConnectionFactory";
        }
        ConnectionFactory connectionFactory = (ConnectionFactory) jndiContext.lookup(connectionFactoryName);
        LOG.info("Connecting with the following properties \n" + jndiContext.getEnvironment().toString());
        try {
            connection = connectionFactory.createConnection();
            connection.start();
复制代码

 

创建生产者的方法:

    @Override
    public MessageProducer createProducer(Session session, String topicName) throws JMSException {
        Topic topic = session.createTopic(topicName);
        MessageProducer producer = session.createProducer(topic);
        return producer;
    }

它由org.apache.oozie.jms.JMSJobEventListener类中的 sendMessage()调用。

 

Oozie 配置中关于JMSAccessorService的配置如下:

 

再来看看:JMSTopicService.java

    static {
        ALLOWED_TOPIC_NAMES.add(TopicType.USER.value);
        ALLOWED_TOPIC_NAMES.add(TopicType.JOBID.value);
    }
复制代码
    public static enum TopicType {
        USER("${username}"), JOBID("${jobId}");

        private String value;

        TopicType(String value) {
            this.value = value;
        }

        String getValue() {
            return value;
        }

    }
复制代码

可用的Topic名称有 ${username},也可以用jobId作为Topic名称,再看Oozie官方文档解释:

The topic is obtained by concatenating topic prefix and the substituted value for topic pattern. The topic pattern can be a constant value like workflow or coordinator which the administrator has configured or ${username}.

The getJMSTopicName API can be used if the job id is already known and will give the exact topic name to which the notifications for that job are published.

 

 private void parseTopicConfiguration() throws ServiceException {
        String topicName = conf.get(TOPIC_NAME, "default=" + TopicType.USER.value);
        if (topicName == null) {
            throw new ServiceException(ErrorCode.E0100, getClass().getName(), "JMS topic cannot be null ");
        }

Topic默认是${username}

 

发送消息的实现类JMSJobEventListener.java  根据相应的作业事件发送作业的执行结果

复制代码
/**
 * Class to send JMS notifications related to job events.
 *
 */
public class JMSJobEventListener extends JobEventListener {
    private JMSAccessorService jmsService = Services.get().get(JMSAccessorService.class);
    private JMSTopicService jmsTopicService = Services.get().get(JMSTopicService.class);
    private JMSConnectionInfo connInfo;
    public static final String JMS_CONNECTION_PROPERTIES = "oozie.jms.producer.connection.properties";
    public static final String JMS_SESSION_OPTS = "oozie.jms.producer.session.opts";
    public static final String JMS_DELIVERY_MODE = "oozie.jms.delivery.mode";
    public static final String JMS_EXPIRATION_DATE = "oozie.jms.expiration.date";
复制代码

 

连接方式、发送消息后是否自动回复、消息的生命周期,持久消息还是非持久消息...

复制代码
    public void init(Configuration conf) {
        LOG = XLog.getLog(getClass());
        String jmsProps = conf.get(JMS_CONNECTION_PROPERTIES);
        LOG.info("JMS producer connection properties [{0}]", jmsProps);
        connInfo = new JMSConnectionInfo(jmsProps);
        jmsSessionOpts = conf.getInt(JMS_SESSION_OPTS, Session.AUTO_ACKNOWLEDGE);
        jmsDeliveryMode = conf.getInt(JMS_DELIVERY_MODE, DeliveryMode.PERSISTENT);
        jmsExpirationDate = conf.getInt(JMS_EXPIRATION_DATE, 0);

    }
复制代码

 

发送消息的过程:

1)EventHandlerService ,里面有个内部类EventWoker线程,当有相应的作业事件发生时,Listener被触发

复制代码
**
 * Service class that handles the events system - creating events queue,
 * managing configured properties and managing and invoking various event
 * listeners via worker threads
 */
public class EventHandlerService implements Service {

//.....

public class EventWorker implements Runnable {

        @Override
        public void run() {
//.....other code
 while (iter.hasNext()) {
              try {
                     if (msgType == MessageType.JOB) {
                            invokeJobEventListener((JobEventListener) iter.next(), (JobEvent) event);
                        }

 private void invokeJobEventListener(JobEventListener jobListener, JobEvent event) {
            switch (event.getAppType()) {
                case WORKFLOW_JOB:
                    jobListener.onWorkflowJobEvent((WorkflowJobEvent)event);
复制代码

 

相应的作业监听器被触发后,创建相应的作业,获得待发送的地址Topic,并序列化消息

    @Override
    public void onWorkflowJobEvent(WorkflowJobEvent event) {
        WorkflowJobMessage wfJobMessage = MessageFactory.createWorkflowJobMessage(event);
        serializeJMSMessage(wfJobMessage, getTopic(event));
    }

 

序列化后,调用send进行发送

    private void serializeJMSMessage(JobMessage jobMessage, String topicName) {
        MessageSerializer serializer = MessageFactory.getMessageSerializer();
        String messageBody = serializer.getSerializedObject(jobMessage);
        sendMessage(jobMessage.getMessageProperties(), messageBody, topicName, serializer.getMessageFormat());
    }

 

创建连接上下文、创建会话、创建消息、设置消息的属性、创建生产者、设置传送模式和消息的生命周期、然后send消息。

复制代码
    protected void sendMessage(Map<String, String> messageProperties, String messageBody, String topicName,
            String messageFormat) {
        jmsContext = jmsService.createProducerConnectionContext(connInfo);
        if (jmsContext != null) {
            try {
                Session session = jmsContext.createThreadLocalSession(jmsSessionOpts);
                TextMessage textMessage = session.createTextMessage(messageBody);
                for (Map.Entry<String, String> property : messageProperties.entrySet()) {
                    textMessage.setStringProperty(property.getKey(), property.getValue());
                }
                textMessage.setStringProperty(JMSHeaderConstants.MESSAGE_FORMAT, messageFormat);
                LOG.trace("Event related JMS text body [{0}]", textMessage.getText());
                LOG.trace("Event related JMS entire message [{0}]", textMessage.toString());
                MessageProducer producer = jmsContext.createProducer(session, topicName);
                producer.setDeliveryMode(jmsDeliveryMode);
                producer.setTimeToLive(jmsExpirationDate);
                producer.send(textMessage);
                producer.close();
            }
复制代码

 

WorkflowJobMessage.java展示了一条Workflow消息长什么样:

复制代码
    /**
     * Constructor for a workflow job message
     * @param eventStatus event status
     * @param workflowJobId the workflow job id
     * @param coordinatorActionId the parent coordinator action id
     * @param startTime start time of workflow
     * @param endTime end time of workflow
     * @param status status of workflow
     * @param user the user
     * @param appName appName of workflow
     * @param errorCode errorCode of the failed wf actions
     * @param errorMessage errorMessage of the failed wf action
     */
    public WorkflowJobMessage(EventStatus eventStatus, String workflowJobId,
            String coordinatorActionId, Date startTime, Date endTime, WorkflowJob.Status status, String user,
            String appName, String errorCode, String errorMessage) {
        super(eventStatus, AppType.WORKFLOW_JOB, workflowJobId, coordinatorActionId, startTime,
                endTime, user, appName);
        this.status = status;
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
    }
复制代码

当提交的是Workflow Job,就会生成Workflow消息。

它有一个属性: @param coordinatorActionId the parent coordinator action id  (Coordinator Job里面的Action是Workflow Job)

看完了JMS消息体,再来看看消息头:

 

复制代码
/**
 *
 * Class holding constants used in JMS selectors
 */
public final class JMSHeaderConstants {
    // JMS Application specific properties for selectors
    public static final String EVENT_STATUS = "eventStatus";
    public static final String SLA_STATUS = "slaStatus";
    public static final String APP_NAME = "appName";
    public static final String USER = "user";
    public static final String MESSAGE_TYPE = "msgType";
    public static final String APP_TYPE = "appType";
    
    public static final String JOBID = "jobId";// add for my specific selectors
    // JMS Header property
    public static final String MESSAGE_FORMAT = "msgFormat";
}
复制代码

 

消息头里面的属性主要用来过滤。根据消息头里面的字段,使用JMS消息选择器对消息进行过滤。关于根据JobId进行过滤,可参考:Oozie JMS通知消息实现--根据作业ID来过滤消息

不知道有没有bug????

 

复制代码
/**
 * Message deserializer to convert from JSON to java object
 */
public class JSONMessageDeserializer extends MessageDeserializer {

    static ObjectMapper mapper = new ObjectMapper(); // Thread-safe.

    static {
        mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }
复制代码

消息的序列化机制,用的是jackson-mapper-asl jar包。因为消息要从生产者发给消息服务器,就需要序列化了。

 

有序列化就有反序列化:

复制代码
/**
 * Class to deserialize the jms message to java object
 */
public abstract class MessageDeserializer {

    /**
     * Constructs the event message from JMS message
     *
     * @param message the JMS message
     * @return EventMessage
     * @throws JMSException
     */
    @SuppressWarnings("unchecked")
    public <T extends EventMessage> T getEventMessage(Message message) throws JMSException {
        TextMessage textMessage = (TextMessage) message;
        String appTypeString = textMessage.getStringProperty(JMSHeaderConstants.APP_TYPE);
        String msgType = textMessage.getStringProperty(JMSHeaderConstants.MESSAGE_TYPE);
复制代码

先根据消息的属性解析出消息的类型。

 

        if (MessageType.valueOf(msgType) == MessageType.JOB) {
            switch (AppType.valueOf(appTypeString)) {
                case WORKFLOW_JOB:
                    WorkflowJobMessage wfJobMsg = getDeserializedObject(messageBody, WorkflowJobMessage.class);
                    wfJobMsg.setProperties(textMessage);
                    eventMsg = (T) wfJobMsg;

再根据类型来构造对象。


本文转自hapjin博客园博客,原文链接:http://www.cnblogs.com/hapjin/p/5511598.html,如需转载请自行联系原作者

相关文章
|
3月前
|
文件存储 虚拟化 数据安全/隐私保护
ESXI服务器备份工具docker版
YueBackup 是一款免费开源的 ESXi 虚拟机备份工具,提供 Web 管理界面,支持手动/定时备份、一键还原、多主机管理,无需虚拟机内安装 Agent,Docker 一键部署,适用于家庭及中小企业低成本灾备需求。(239字)
332 1
ESXI服务器备份工具docker版
|
11月前
|
人工智能 资源调度 安全
AI计算机视觉在公共安全领域的实践:从“滑倒重灾区”看毫秒级跌倒预警技术拆解
基于边缘AI视觉技术,构建从跌倒姿态识别、环境风险检测到智能联动响应的安全闭环。0.8秒内完成风险判定,联动警示、清洁与急救系统,实现19秒快速干预,骨折率下降76%,成本降81%,到店客流反增11%。
446 8
|
8月前
|
存储 机器学习/深度学习 编解码
阿里云服务器计算型c9i实例cpu型号、性能参数、收费标准与活动价格
阿里云服务器计算型c9i实例搭载全新CIPU架构与英特尔®至强®6处理器,算力稳定且安全加固,单核算力提升20%,支持AMX矩阵加速与TDX安全技术,适用于企业级应用、视频编解码等多场景。实例采用1:2处理器与内存配比,支持高性能NVMe协议与ESSD云盘,网络带宽强大,支持IPv4/IPv6及ERI技术。提供按量付费、包年包月等多种计费模式,当前活动价格低至6.4折,结合优惠券可进一步降低成本,适合承载高性能计算与关键业务。
|
7月前
|
存储 人工智能 自然语言处理
AI 应用软件的开发
AI应用已从“聊天框”升级为具备感知、决策与执行能力的智能体(Agent)。本文系统梳理四大类型(AIGC、RAG、AI Agents、嵌入式AI)、核心架构及国内五阶段开发流程,强调模型、业务与合规的深度集成。(239字)
|
10月前
|
应用服务中间件 Shell nginx
我的docker学习笔记
本指南系统讲解Docker核心实践:涵盖安装配置、镜像获取与管理、容器生命周期操作(启停/日志/进入/导入导出)、数据卷与Bind Mount数据持久化、网络配置(Bridge/自定义网络/DNS)、Dockerfile定制镜像(COPY/ADD/CMD/ENTRYPOINT/ENV/ARG/VOLUME等指令详解)及Docker Compose编排应用。内容实用,步骤清晰,适合快速上手与深入实践。(239字)
653 6
|
11月前
|
人工智能 安全 JavaScript
SonarQube Server 2025 Release 6 发布 - 代码质量、安全与静态分析工具
SonarQube Server 2025 Release 6 (macOS, Linux, Windows) - 代码质量、安全与静态分析工具
451 8
SonarQube Server 2025 Release 6 发布 - 代码质量、安全与静态分析工具
|
传感器 数据安全/隐私保护 开发者
《解锁分布式软总线:跨设备业务开发全攻略》
在数字化时代,分布式软总线技术作为实现设备间高效互联的核心,为跨设备业务开发提供了新可能。它通过发现、连接、组网和传输接口,打破设备通信壁垒,支持异构设备协同工作。开发者可利用这些接口设计智能应用,如多设备协同办公,同时需关注网络适应性与兼容性测试,以确保用户体验的流畅性和一致性。掌握该技术不仅能紧跟科技潮流,还能为用户带来便捷高效的数字化生活体验。
518 8
|
监控 安全 Shell
无字母数字webshell的命令执行
无字母数字WebShell是一种利用PHP等语言灵活特性的攻击手段,攻击者通过字符转换和编码技术绕过安全机制,执行恶意命令。然而,通过合理的防御措施,如禁用危险函数、使用WAF等,可以有效减少这种攻击带来的风险。在实践中,系统管理员应结合多种手段,提高服务器的安全性。
455 18
|
12月前
|
传感器 人工智能 运维
水利数字孪生技术深度分享
水利数字孪生融合物联网、大数据、AI等技术,构建物理水利系统的全要素虚拟映射,实现精准感知、智能仿真与优化调控。涵盖BIM-GIS建模、实时数据链、仿真引擎与可视化交互,应用于防洪调度、工程运维、水资源管理等领域。济南奥维数字科技通过自主引擎与场景实践,推动技术落地,助力“数字济南”建设,引领行业智能化升级。
1087 0
|
JavaScript 前端开发 Android开发
Flutter笔记:关于WebView插件的用法(下)
Flutter笔记:关于WebView插件的用法(下)
1490 5