ActiveMQ学习记录(一)

    对应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模式下,也可以完成一对多的通信,即让一个消息有多个接受者。

    

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

    

    我想你应该发现了,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。具体的安装和里面的一些目录下面做简单介绍,其余可以查找相应资料了解。
    
    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,如下所示:
    
    启动后出现如下图所示,并可以在浏览器中输入http://localhost:8161/admin/index.jsp进行登录。账号密码都是admin
    

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

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

    

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

    

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

     第一步:创建ConncectionFactory连接工厂

      

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

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


    第二步:创建Conncection

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

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

    第四步:创建Destination

    

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

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



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

    

 

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

    

    

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

    第八步:生产者释放连接

    

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

      第九步:消费者实现监听

    

    第十步:测试

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

    

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

    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的时候,存在多个重载方法,我们来看一下:

    在上面的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

   

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值