欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ActiveMQ学习记录(一)

程序员文章站 2022-03-23 16:46:14
...

    对应ActiveMQ这系列的文章,LZ大多是学习一些大神的博客,然后将这些东西汇聚起来。那么我们话不多说,开始学习ActiveMQ吧。

    首先介绍下一些概念性的东西:

    JMS

    首先来说较早以前,也就是没有JMS的那个时候,很多应用系统存在一些缺陷:

    1.通信的同步性

        client端发起调用后,必须等待server处理完成并返回结果后才能继续执行

    2.client 和 server 的生命周期耦合太高

        client进程和server服务进程都必须可用,如果server出现问题或者网络故障,那么client端会收到异常 
    3.点对点通信

        client端的一次调用只能发送给某一个单独的服务对象,无法一对多

    JMS,即Java Message Service,通过面向消息中间件(MOM:Message Oriented Middleware)的方式很好的解决了上面的问题。大致的过程是这样的:发送者把消息发送给消息服务器,消息服务器将消息存放在若干队列/主题中,在合适的时候,消息服务器会将消息转发给接受者。在这个过程中,发送和接受是异步的,也就是发送无需等待,而且发送者和接受者的生命周期也没有必然关系;在pub/sub模式下,也可以完成一对多的通信,即让一个消息有多个接受者。

    ActiveMQ学习记录(一)

    需要注意的是,JMS只是定义了Java访问消息中间件的接口,其实就是在包javax.jms中,你会发现这个包下除了异常定义,其他都是interface。我们可以扫一眼,比如Message:

    ActiveMQ学习记录(一)

    我想你应该发现了,JMS只给出接口,然后由具体的中间件去实现,比如ActiveMQ就是实现了JMS的一种Provider。这些消息中间件都符合JMS规范。说起规范,自然要定义一些术语:

    Provider/MessageProvider:生产者
    Consumer/MessageConsumer:消费者
    PTP:Point To Point,点对点通信消息模型
    Pub/Sub:Publish/Subscribe,发布订阅消息模型
    Queue:队列,目标类型之一,和PTP结合
    Topic:主题,目标类型之一,和Pub/Sub结合
    ConnectionFactory:连接工厂,JMS用它创建连接
    Connnection:JMS Client到JMS Provider的连接
    Destination:消息目的地,由Session创建

    Session:会话,由Connection创建,实质上就是发送、接受消息的一个线程,因此生产者、消费者都是Session创建的

    初步来看,Session非常核心,因为很多东西都是它创建的,在后文中可以通过代码来进一步认识这些术语。

    这里先了解下在MQ中只有两种消息模式,就是上面说的点对点和订阅消息模式,何为点对点,何为订阅消息呢。解释如下:

    点对点的消息模式:主要建立在一个队列上面,当连接一个列队的时候,发送端不需要知道接收端是否正在接收,可以直接向ActiveMQ发送消息,发送的消息,将会先进入队列中,如果有接收端在监听,则会发向接收端,如果没有接收端接收,则会保存在activemq服务器,直到接收端接收消息,点对点的消息模式可以有多个发送端,多个接收端,但是一条消息,只会被一个接收端给接收到,哪个接收端先连上ActiveMQ,则会先接收到,而后来的接收端则接收不到那条消息。

    订阅消息模式:同样可以有着多个发送端与多个接收端,但是接收端与发送端存在时间上的依赖,就是如果发送端发送消息的时候,接收端并没有监听消息,那么ActiveMQ将不会保存消息,将会认为消息已经发送,换一种说法,就是发送端发送消息的时候,接收端不在线,是接收不到消息的,哪怕以后监听消息,同样也是接收不到的。这个模式还有一个特点,那就是,发送端发送的消息,将会被所有的接收端给接收到,不类似点对点,一条消息只会被一个接收端给接收到。


    Start

    ActiveMQ是Apache出品的,非常流行的消息中间件,可以说要掌握消息中间件,需要从ActiveMQ开始,要掌握更加强大的RocketMQ,也需要ActiveMQ的基础,因此我们来搞定它吧。官网地址:http://activemq.apache.org/,目前最新的版本是5.15.3(注意jdk要使用1.8),我这边将以最新版来讲解。这篇文章主要是ActiveMQ的初步,因此我这边暂时用windows版本,后期采用Linux。具体的安装和里面的一些目录下面做简单介绍,其余可以查找相应资料了解。
    ActiveMQ学习记录(一)
    bin:存放的是ActiveMQ的启动脚本activemq.bat,注意分32、64位。
    conf配置文件,重点关注的是activemq.xml、jetty.xml、jetty-realm.properties。在登录ActiveMQ Web控制台需要用户名、密码信息;在JMS CLIENT和ActiveMQ进行何种协议的连接、端口是什么等这些信息都在上面的配置文件中可以体现。
    dataActiveMQ进行消息持久化存放的地方,默认采用的是kahadb,当然我们可以采用leveldb,或者采用JDBC存储到MySQL,或者干脆不使用持久化机制。
    webapps:ActiveMQ自带Jetty提供Web管控台。
    lib:ActiveMQ为我们提供了分功能的JAR包。
    docs:存放的是说明文档。
    examples:存放的是简单的实例。
    下面直接点击activemq.bat进行启动
    如果出现BeanFactory not initialized or already closed - call 'refresh' before....的错误,可以参考这个文章来修改https://blog.csdn.net/huang_sheng0527/article/details/75276113。如果还 不行,就看下conf/activemq.xml这个文件中的transportConnector是不是0.0.0.0,是的话改成127.0.0.1,如下所示:
    ActiveMQ学习记录(一)
    启动后出现如下图所示,并可以在浏览器中输入http://localhost:8161/admin/index.jsp进行登录。账号密码都是admin
    ActiveMQ学习记录(一)

ActiveMQ学习记录(一)

    对于访问ActiveMQ web控制台的用户名、密码在哪里配置的?URL当中的端口是在哪里配置的?

    用户名密码配置在conf/jetty-realm.properties文件,格式如***解说的很明白,格式是:用户名 : 密码 ,角色名

    ActiveMQ学习记录(一)

    URL端口等配置在conf/jetty.xml中

    ActiveMQ学习记录(一)

    好了,现在启动好Active后,来写一个HelloWorld的Demo来感受下ActiveMQ,具体流程就是这边写一个生产者用于发送消息,一个消费者来接收消息。具体步骤如下

     第一步:创建ConncectionFactory连接工厂

      ActiveMQ学习记录(一)

    事实上,这里有安全问题, 也就是任何人一旦知道MQ的地址,就可以连接访问了,所以我们可以在activemq.xml中配置指定的用户、密码才能访问ActiveMQ。

    关于broker_bind_url,默认是tcp://localhost:61616,说明采用的是TCP协议,端口是61616端口,ActiveMQ不仅仅支持TCP,还有其他协议,可以开启这些端口,如下:

ActiveMQ学习记录(一)

    第二步:创建Conncection

    ActiveMQ学习记录(一)
    Connection就代表了应用程序和消息服务器之间的通讯链路,获得了连接工厂后,就可以创建Connection。而ConnectionFactory存在重载方法:Connection createConnection(String username,String password) ;也就是说我们也可以在这里指定用户名、密码进行验证
     第三步:创建Session  

ActiveMQ学习记录(一)

    Session,用于发送和接收消息,而且是单线程的,支持事务的,如果Session开启事务支持,那么Session将保存一组信息,要么commit到MQ,要么回滚这些消息。Session可以创建MessageProducer/MessageConsumer。

    第四步:创建Destination

    ActiveMQ学习记录(一)

    所谓消息目标,就是消息发送和接受的地点,要么queue,要么topic。注意后面生产者消费者要发在同一个Queue里面,这里就是MY_QUEUE这个队列。

    第五步:创建MessageProducer(生产者)/MessageConsumer(消费者)

ActiveMQ学习记录(一)

ActiveMQ学习记录(一)

     第六步:在生产端设置持久化方式

    ActiveMQ学习记录(一)


       第七步:定义消息对象,并发送

    ActiveMQ学习记录(一)

    ActiveMQ学习记录(一)

    生产者和消费者之间传递的对象主要是由3部分构成:消息头(路由)+消息属性(消息选择器,以后介绍)+消息体(JMS规范的五种类型消息)

ActiveMQ学习记录(一)

    第八步:生产者释放连接

    ActiveMQ学习记录(一)

    必须close connection,只有这样ActiveMQ才会释放资源!消费者的代码和上面非常类似,只不过就是创建MessageConsumer进行receive而已,注意receive()/receive(long)/receiveNoWait(),这些说明消费者可以采用阻塞模式、非阻塞模式接受消息。不过这边我使用另一种更好的方式,让消费者实现一个消息的监听,然后监听到消息的话就接收并处理。

      第九步:消费者实现监听

    ActiveMQ学习记录(一)

    第十步:测试

    启动Consumer后启动Producer,出现如下结果

    ActiveMQ学习记录(一)

    同时在WEB端管控台也可以看出

ActiveMQ学习记录(一)    Messages Enqueued:表示生产了多少条消息,记做P

    Messages Dequeued:表示消费了多少条消息,记做C

    Number Of Consumers:表示在该队列上还有多少消费者在等待接受消息

    Number Of Pending Messages:表示还有多少条消息没有被消费,实际上是表示消息的积压程度,就是P-C

    其他介绍:    

    在说说Session:在通过Connection创建Session的时候,需要设置2个参数,一个是否支持事务,另一个是签收的模式。我们重点说一下签收模式:

    什么是签收?通俗点说,就是消费者接受到消息后,需要告诉消息服务器,我收到消息了。当消息服务器收到回执后,本条消息将失效。因此签收将对PTP模式产生很大影响。如果消费者收到消息后,并不签收,那么本条消息继续有效,很可能会被其他消费者消费掉!

    AUTO_ACKNOWLEDGE:表示在消费者receive消息的时候自动的签收    CLIENT_ACKNOWLEDGE:表示消费者receive消息后必须手动的调用acknowledge()方法进行签收

    DUPS_OK_ACKNOWLEDGE:签不签收无所谓了,只要消费者能够容忍重复的消息接受,当然这样会降低Session的开销

    在实际中,我们应该采用哪种签收模式呢?CLIENT_ACKNOWLEDGE,采用手动的方式较自动的方式可能更好些,因为接收到了消息,并不意味着成功的处理了消息,假设我们采用手动签收的方式,只有在消息成功处理的前提下才进行签收,那么只要消息处理失败,那么消息还有效,仍然会继续消费,直至成功处理!

    关于消息的priority/ttl/deliveryMode:

    消息有优先级及存活时间,在MessageProducer进行send的时候,存在多个重载方法,我们来看一下:

ActiveMQ学习记录(一)

    在上面的code当中,我们创建生产者的时候,指定了Destination,设置了持久化方式,实际上这些都可以不必指定的,而是到send的时候指定。而且在实际业务开发中,往往根据各种判断,来决定将这条消息发往哪个Queue,因此往往不会在MessageProducer创建的时候指定Destination。

    TTL,消息的存活时间,一句话:生产者生产了消息,如果消费者不来消费,那么这条消息保持多久的有效期    priority,消息优先级,0-9。0-4是普通消息,5-9是加急消息,消息默认级别是4。注意,消息优先级只是一个理论上的概念,并不能绝对保证优先级高的消息一定被消费者优先消费!也就是说ActiveMQ并不能保证消费的顺序性!    deliveryMode,如果不指定,默认是持久化的消息。如果可以容忍消息的丢失,那么采用非持久化的方式,将会改善性能、减少存储的开销。

    

    最后附上所有代码:

public class MQUtils {

    private static String  MQ_USER = ActiveMQConnectionFactory.DEFAULT_USER;
    private static String  MQ_PASSWORD = ActiveMQConnectionFactory.DEFAULT_PASSWORD;
    private static String  MQ_BROKE_URL = ActiveMQConnectionFactory.DEFAULT_BROKER_BIND_URL;
    private static Session session = null;
    private static final String MY_QUEUE = "myqueue";

    private  static ConnectionFactory createConnectionFactory(){
        //创建连接工厂ConnectionFactory,需要账号密码,这里使用默认的账号密码
        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
                MQ_USER,MQ_PASSWORD,MQ_BROKE_URL
        );
        return connectionFactory;
    }

    private static Connection createConnection() throws JMSException {
        //构造从工厂得到连接对象
        Connection connection = createConnectionFactory().createConnection();
        //注意,需要start下,不过测试过这个步骤不写也是可以的,但是最好写上
        connection.start();
        return connection;
    }

    public  static Session createSession() throws JMSException {
        if(session == null ){
            //前面2步骤操作就是为了创建Session(上下文环境对象)做准备的
            //创建Session有一些配置参数,比如是否启用事务,签收模式是什么(这里先设置为自动签收模式auto),具体以后详述
            session = createConnection().createSession(Boolean.FALSE,Session.AUTO_ACKNOWLEDGE);
        }
        return session;
    }

    public static Destination createDestination() throws JMSException {
        //通过Session创建Destination对象, 在PTPT模式下是queue,在pub/sub模式下是topic
        //private static final String MY_QUEUE = "myqueue";
        Destination destination = createSession().createQueue(MY_QUEUE);
        return destination;
    }

    public static TextMessage createTextMessage(String message) throws JMSException {
        //需要使用session创建,证明session的重要性
        TextMessage textMessage = createSession().createTextMessage();
        textMessage.setText(message);
        return textMessage;
    }

    public static void close() throws JMSException {
        if(session != null){
            session.close();
        }
    }
}
public class Producer {
    
    public static void main(String[] args) throws JMSException {
        //通过session(MQUtils.createSession())创建 发送消息的生产者
        MessageProducer producer = MQUtils.createSession().createProducer(MQUtils.createDestination());

        //设置持久化/非持久化特性,如果非持久化,那么意味着MQ重启后消息将会丢失
        //如果持久化到kahdb/leveldb/jdbc方式的浒,意味着消息持久化
        //DeliveryMode.PERSISTENT 当activemq关闭的时候,队列数据将会被保存
        //DeliveryMode.NON_PERSISTENT 当activemq关闭的时候,队列里面的数据将会被清空
        producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);

        //定义JMS规范的消息类型,这里先使用简单的TextMessage

        TextMessage textMessage = MQUtils.createTextMessage("Hello ActiveMQ!!");
        producer.send(textMessage);

        //关闭连接
        MQUtils.close();
    }
}
public class Consumer {
    public static void main(String[] args) throws JMSException {
        //通过session(MQUtils.createSession())创建 发送消息的消费者
        MessageConsumer consumer = MQUtils.createSession().createConsumer(MQUtils.createDestination());

        //实现一个消息的监听器
        //实现这个监听器后,以后只要有消息,就会通过这个监听器接收到
        consumer.setMessageListener(new MessageListener() {
            @Override
            public void onMessage(Message message) {
                try {
                    //获取到接收的数据
                    String text = ((TextMessage)message).getText();
                    System.out.println("Consumer get : "  + text);
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

    第一篇就到这里END了,Thanks!

    

    参考博文:https://www.jianshu.com/p/ecdc6eab554c

   

相关标签: MQ