activemq

activemq 简介

JMS中的角色

  • Broker

消息服务器,作为server提供消息核心服务

  • provider

生产者
消息生产者是由会话创建的一个对象,用于把消息发送到一个目的地。

  • Consumer

消费者
消息消费者是由会话创建的一个对象,它用于接收发送到目的地的消息。消息的消费可以采用以下两种方法之一:

  • 同步消费。通过调用消费者的receive方法从目的地中显式提取消息。receive方法可以一直阻塞到消息到达。
  • 异步消费。客户可以为消费者注册一个消息监听器,以定义在消息到达时所采取的动作。
  • p2p

基于点对点的消息模型
消息生产者生产消息发送到 queue 中,然后消息消费者从 queue 中取出并且消费消息。 消息被消费以后,queue 中不再有存储,所以消息消费者不可能消费到已经被消费的消
息。
Queue 支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费、其它 的则不能消费此消息了。 当消费者不存在时,消息会一直保存,直到有消费消费
在这里插入图片描述

  • pub/sub

基于订阅/发布的消息模型
消息生产者(发布)将消息发布到 topic 中,同时有多个消息消费者(订阅)消费该消
息。
和点对点方式不同,发布到 topic 的消息会被所有订阅者消费。 当生产者发布消息,不管是否有消费者。都不会保存消息 一定要先有消息的消费者,后有消息的生产者。
在这里插入图片描述

两者对比:

TopicQueue
模式Publish Subscribe messaging 发布 订阅消息Point-to-Point 点对点
有无状态topic 数据默认不落地,是无状态的。Queue 数据默认会在 mq 服 务器上以文件形式保存,比如 Active MQ 一 般 保 存 在 $AMQ_HOME\data\kahadb 下 面。也可以配置成 DB 存储。
完整性保障并不保证 publisher 发布的每条数 据,Subscriber 都能接受到。Queue 保证每条数据都能 被 receiver 接收。消息不超时。
消息是否会丢失一般来说 publisher 发布消息到某 一个 topic 时,只有正在监听该 topic 地址的 sub 能够接收到消息;如果没 有 sub 在监听,该 topic 就丢失了。Sender 发 送 消 息 到 目 标 Queue, receiver 可以异步接收这 个 Queue 上的消息。Queue 上的 消息如果暂时没有 receiver 来 取,也不会丢失。前提是消息不 超时。
消息发布接 收策略一对多的消息发布接收策略,监 听同一个topic地址的多个sub都能收 到 publisher 发送的消息。Sub 接收完 通知 mq 服务器一对一的消息发布接收策 略,一个 sender 发送的消息,只 能有一个 receiver 接收。 receiver 接收完后,通知 mq 服务器已接 收,mq 服务器对 queue 里的消 息采取删除或其他操作。

存储

KahaDB存储(默认)

KahaDB是默认的持久化策略,所有消息顺序添加到一个日志文件中,同时另外有一个索引文件记录指向这些日志的存储地址,还有一个事务日志用于消息回复操作。是一个专门针对消息持久化的解决方案,它对典型的消息使用模式进行了优化。
在data/kahadb这个目录下,会生成四个文件,来完成消息持久化
1.db.data 它是消息的索引文件,本质上是B-Tree(B树),使用B-Tree作为索引指向db-*.log里面存储的消息
2.db.redo 用来进行消息恢复 *
3.db-.log 存储消息内容。新的数据以APPEND的方式追加到日志文件末尾。属于顺序写入,因此消息存储是比较 快的。默认是32M,达到阀值会自动递增
4.lock文件 锁,写入当前获得kahadb读写权限的broker ,用于在集群环境下的竞争处理

<persistenceAdapter> <!--directory:保存数据的目录;journalMaxFileLength:保存消息的文件大小 --> <kahaDBdirectory="${activemq.data}/kahadb"journalMaxFileLength="16mb"/> </persistenceAdapter>

特性:
1、日志形式存储消息;
2、消息索引以 B-Tree 结构存储,可以快速更新;
3、 完全支持 JMS 事务;
4、支持多种恢复机制kahadb 可以限制每个数据文件的大小。不代表总计数据容量。

AMQ 方式

只适用于 5.3 版本之前。 AMQ 也是一个文件型数据库,消息信息最终是存储在文件中。内存中也会有缓存数据。
性能高于 JDBC,写入消息时,会将消息写入日志文件,由于是顺序追加写,性能很高。
为了提升性能,创建消息主键索引,并且提供缓存机制,进一步提升性能。
每个日志文件的 大小都是有限制的(默认 32m,可自行配置) 。
当超过这个大小,系统会重新建立一个文件。
当所有的消息都消费完成,系统会删除这 个文件或者归档。
主要的缺点是 AMQ Message 会为每一个 Destination 创建一个索引,如果使用了大量的 Queue,索引文件的大小会占用很多磁盘空间。
而且由于索引巨大,一旦 Broker(ActiveMQ 应用实例)崩溃,重建索引的速度会非常 慢。
虽然 AMQ 性能略高于 Kaha DB 方式,但是由于其重建索引时间过长,而且索引文件 占用磁盘空间过大,所以已经不推荐使用。

<persistenceAdapter> <!--directory:保存数据的目录 ;maxFileLength:保存消息的文件大小 --> <amqPersistenceAdapterdirectory="${activemq.data}/amq"maxFileLength="32mb"/> </persistenceAdapter>

JDBC存储

使用JDBC持久化方式,数据库默认会创建3个表,每个表的作用如下:
activemq_msgs:queue和topic的消息都存在这个表中
表字段信息:
container:消息的destination
sub_dest:如果是使用static集群,这个字段会有集群其他系统的信息
client_id:每个订阅者都必须有一个唯一的客户端id用以区分
sub_name:订阅者名称
selector:选择器,可以选择只消费满足条件的消息。条件可以用自定义属性实现,可支持多属性and和or操作
last_acked_id:记录消费过的消息的id。

activemq_acks:存储持久订阅的信息和最后一个持久订阅接收的消息ID
id:自增的数据库主键
container:消息的destination
msgid_prod:消息发送者客户端的主键
msg_seq:是发送消息的顺序,msgid_prod+msg_seq可以组成jms的messageid
expiration:消息的过期时间,存储的是从1970-01-01到现在的毫秒数
msg:消息本体的java序列化对象的二进制数据
priority:优先级,从0-9,数值越大优先级越高
xid:用于存储订阅关系。如果是持久化topic,订阅者和服务器的订阅关系在这个表保存。

activemq_lock:跟kahadb的lock文件类似,在集群环境中才有用,只有一个Broker可以获得消息,称为Master Broker,其他的只能作为备份等待Master Broker不可用,才可能成为下一个Master Broker。这个表用于记录哪个Broker是当前的Master Broker。

  • 修改配置文件activemq.xml
  1. Beans中添加
<bean id="mysql-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
	<property name="driverClassName" value="com.mysql.jdbc.Driver"/> 
	<property name="url" value="jdbc:mysql://localhost/activemq?relaxAutoCommit=true"/> 
	<property name="username" value="activemq"/>
	<property name="password" value="activemq"/>
	<property name="maxActive" value="200"/>
	<property name="poolPreparedStatements" value="true"/> 
</bean>
  1. 修改persistenceAdapter
<persistenceAdapter>
        <!-- <kahaDB directory="${activemq.data}/kahadb"/> -->
		<jdbcPersistenceAdapter dataSource="#mysql-ds" createTablesOnStartup="true" /> 
        </persistenceAdapter>

LevelDB存储

LevelDB持久化性能高于KahaDB,虽然目前默认的持久化方式仍然是KahaDB。并且,在ActiveMQ 5.9版本提供 了基于LevelDB和Zookeeper的数据复制方式,用于Master-slave方式的首选数据复制方案。 但是在ActiveMQ官网对LevelDB的表述:LevelDB官方建议使用以及不再支持,推荐使用的是KahaDB

Memory 消息存储

顾名思义,基于内存的消息存储,就是消息存储在内存中。persistent=”false”,表示不设置持 久化存储,直接存储到内存中
在broker标签处设置。

JDBC Message store with ActiveMQ Journal

这种方式克服了JDBC Store的不足,JDBC存储每次消息过来,都需要去写库和读库。 ActiveMQ Journal,使用延迟存储数据到数据库,当消息来到时先缓存到文件中,延迟后才写到数据库中。
当消费者的消费速度能够及时跟上生产者消息的生产速度时,journal文件能够大大减少需要写入到DB中的消息。 举个例子,生产者生产了1000条消息,这1000条消息会保存到journal文件,如果消费者的消费速度很快的情况 下,在journal文件还没有同步到DB之前,消费者已经消费了90%的以上的消息,那么这个时候只需要同步剩余的 10%的消息到DB。 如果消费者的消费速度很慢,这个时候journal文件可以使消息以批量方式写到DB。

协议

协议地址:https://activemq.apache.org/configuring-version-5-transports.html
ActiveMQ支持的client-broker通讯协议有:TCP、NIO、UDP、SSL、Http(s)、VM。

Transmission Control Protocol (TCP)

1:这是默认的Broker配置,TCP的Client监听端口是61616。
2:在网络传输数据前,必须要序列化数据,消息是通过一个叫wire protocol的来序列化成字节流。默认情况下,ActiveMQ把wire protocol叫做OpenWire,它的目的是促使网络上的效率和数据快速交互。
3:TCP连接的URI形式:tcp://hostname:port?key=value&key=value,加粗部分是必须的
4:TCP传输的优点:
(1)TCP协议传输可靠性高,稳定性强
(2)高效性:字节流方式传递,效率很高
(3)有效性、可用性:应用广泛,支持任何平台

<transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>

New I/O API Protocol(NIO)

1:NIO协议和TCP协议类似,但NIO更侧重于底层的访问操作。它允许开发人员对同一资源可有更多的client调用和服务端有更多的负载。
2:适合使用NIO协议的场景:
(1)可能有大量的Client去链接到Broker上一般情况下,大量的Client去链接Broker是被操作系统的线程数所限制的。因此,NIO的实现比TCP需要更少的线程去运行,所以建议使用NIO协议
(2)可能对于Broker有一个很迟钝的网络传输NIO比TCP提供更好的性能
3:NIO连接的URI形式:nio://hostname:port?key=value
4:Transport Connector配置示例:

<transportConnectors>
  <transportConnector
    name="tcp"
    uri="tcp://localhost:61616?trace=true" />
  <transportConnector
    name="nio"
    uri="nio://localhost:61618?trace=true" />
</transportConnectors>

上面的配置,示范了一个TCP协议监听61616端口,一个NIO协议监听61618端口

User Datagram Protocol(UDP)

1:UDP和TCP的区别
(1)TCP是一个原始流的传递协议,意味着数据包是有保证的,换句话说,数据包是不会被复制和丢失的。UDP,另一方面,它是不会保证数据包的传递的
(2)TCP也是一个稳定可靠的数据包传递协议,意味着数据在传递的过程中不会被丢失。这样确保了在发送和接收之间能够可靠的传递。相反,UDP仅仅是一个链接协议,所以它没有可靠性之说
2:从上面可以得出:TCP是被用在稳定可靠的场景中使用的;UDP通常用在快速数据传递和不怕数据丢失的场景中,还有ActiveMQ通过防火墙时,只能用UDP
3:UDP连接的URI形式:udp://hostname:port?key=value
4:Transport Connector配置示例:

<transportConnectors>
    <transportConnector
        name="udp"
        uri="udp://localhost:61618?trace=true" />
</transportConnectors>

Secure Sockets Layer Protocol (SSL)

1:连接的URI形式:ssl://hostname:port?key=value
2:Transport Connector配置示例:

<transportConnectors>
    <transportConnector name="ssl" uri="ssl://localhost:61617?trace=true"/>
</transportConnectors>

Hypertext Transfer Protocol (HTTP/HTTPS)

1:像web和email等服务需要通过防火墙来访问的,Http可以使用这种场合
2:连接的URI形式:http://hostname:port?key=value或者https://hostname:port?key=value
3:Transport Connector配置示例:

<transportConnectors>
    <transportConnector name="http" uri="http://localhost:8080?trace=true" />
</transportConnectors>

VM Protocol(VM)

1、VM transport允许在VM内部通信,从而避免了网络传输的开销。这时候采用的连 接不是socket连接,而是直接的方法调用。
2、第一个创建VM连接的客户会启动一个embed VM broker,接下来所有使用相同的 broker name的VM连接都会使用这个broker。当这个broker上所有的连接都关闭 的时候,这个broker也会自动关闭。
3、连接的URI形式:vm://brokerName?key=value
4、Java中嵌入的方式: vm:broker:(tcp://localhost:6000)?brokerName=embeddedbroker&persistent=fal se , 定义了一个嵌入的broker名称为embededbroker以及配置了一个 tcptransprotconnector在监听端口6000上
5、使用一个加载一个配置文件来启动broker vm://localhost?brokerConfig=xbean:activemq.xml

activemq api

  • maven依赖
<!-- https://mvnrepository.com/artifact/org.apache.activemq/activemq-all -->
<dependency>
    <groupId>org.apache.activemq</groupId>
    <artifactId>activemq-all</artifactId>
    <version>5.15.11</version>
</dependency>

签收模式

Session.AUTO_ACKNOWLEDGE : 当客户端从receiver或onMessage成功返回时,Session自动签收客户端的这条消息的收条。
Session.CLIENT_ACKNOWLEDGE:客户端通过调用消息(Message)的acknowledge方法签收消息。在这种情况下,签收发生在Session层面:签收一个已经消费的消息会自动地签收这个Session所有已消费的收条。
Session.DUPS_OK_ACKNOWLEDGE:Session不必确保对传送消息的签收,这个模式可能会引起消息的重复,但是降低了Session的开销,所以只有客户端能容忍重复的消息,才可使用。

持久化

JMS 支持以下两种消息提交模式:

  • PERSISTENT。指示JMS Provider持久保存消息,以保证消息不会因为JMS Provider的失败而丢失。
  • NON_PERSISTENT。不要求JMS Provider持久保存消息。

优先级

可以打乱消费顺序
producer.setPriority
配置文件需要指定使用优先级的目的地:< policyEntry queue=“queue1” prioritizedMessages=“true” />

消息超时/过期

设置了消息超时的消息,消费端在超时后无法在消费到此消息。
producer.setTimeToLive

JMS的消息格式

JMS消息由以下三部分组成的

  • 消息头。
    每个消息头字段都有相应的getter和setter方法。
  • 消息属性。
    如果需要除消息头字段以外的值,那么可以使用消息属性。
  • 消息体。
    JMS定义的消息类型有TextMessage、MapMessage、BytesMessage、StreamMessage和ObjectMessage。

事务

session.commit();
session.rollback();

死信

此类消息会进入到ActiveMQ.DLQ队列且不会自动清除,称为死信
此处有消息堆积的风险

  • 修改死信队列名称
			  <policyEntry queue="f" prioritizedMessages="true" >
				<deadLetterStrategy> 

					<individualDeadLetterStrategy   queuePrefix="DLxxQ." useQueueForQueueMessages="true" /> 

				</deadLetterStrategy> 
			  </policyEntry>

useQueueForQueueMessages: 设置使用队列保存死信,还可以设置useQueueForTopicMessages,使用Topic来保存死信

  • 让非持久化的消息也进入死信队列
			<individualDeadLetterStrategy   queuePrefix="DLxxQ." useQueueForQueueMessages="true"  processNonPersistent="true" /> 

processNonPersistent=“true”

  • 过期消息不进死信队列
<individualDeadLetterStrategy   processExpired="false"  /> 
  • 独占消费者

Queue queue = session.createQueue(“xxoo?consumer.exclusive=true”);

  • 生产者
public class Producer {

    @Test
    public void produce() throws Exception{
        //建立工厂
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
                ActiveMQConnectionFactory.DEFAULT_USER,
                ActiveMQConnectionFactory.DEFAULT_PASSWORD,
                "tcp://localhost:61616"
                );

        //创建连接
        Connection connection = connectionFactory.createConnection();
        //transacted:是否开启事务
        //Session.AUTO_ACKNOWLEDGE
        //创建session
        Session session = connection.createSession(true, Session.SESSION_TRANSACTED);
        try{
            //找目的地,获取destination
            Queue test = session.createQueue("test");
            //创建生产者
            MessageProducer producer = session.createProducer(test);

            //发送TextMessage(延迟10秒发送)
            TextMessage message = session.createTextMessage("test textmessage");
            message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY,10000);
            producer.send(message);

            //发送bytemessage
            BytesMessage bytesMessage = session.createBytesMessage();
            bytesMessage.writeBytes("bytemessage".getBytes());
            producer.send(bytesMessage);

            //发送MapMessage
            MapMessage mapMessage = session.createMapMessage();
            mapMessage.setString("name","张三");
            mapMessage.setInt("age",23);
            //设置属性,可用于过滤
            mapMessage.setIntProperty("age",23);
            producer.send(mapMessage);

            //发送ObjectMessage
            Student student = new Student().setName("tony").setAge(12).setGender("男");
            ObjectMessage objectMessage = session.createObjectMessage();
            objectMessage.setObject(student);
            producer.send(objectMessage);

            //提交事务
            session.commit();
        }catch (Exception e){
            e.printStackTrace();
            //消息回滚
            session.rollback();
        }
        connection.close();
    }
}
  • 消费者
public class consumer {

    @Test
    public void consume() throws Exception{
        //建立工厂
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
                ActiveMQConnectionFactory.DEFAULT_USER,
                ActiveMQConnectionFactory.DEFAULT_PASSWORD,
                "tcp://localhost:61616"
        );
        //添加所有包信任
        connectionFactory.setTrustAllPackages(true);
        //创建连接
        Connection connection = connectionFactory.createConnection();
        connection.start();
        //transacted:是否开启事务
        //Session.AUTO_ACKNOWLEDGE
        //创建session
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //找目的地,获取destination
        Queue test = session.createQueue("test");
        //创建消费者
        MessageConsumer consumer = session.createConsumer(test);
        while (true){
            consumer.setMessageListener(new MessageListener() {
                @Override
                public void onMessage(Message message) {
                    if (message instanceof TextMessage){
                        TextMessage textMessage = (TextMessage) message;
                        try {
                            System.out.println("textMessage:"+textMessage.getText());
                        } catch (JMSException e) {
                            e.printStackTrace();
                        }
                    }else if (message instanceof BytesMessage){
                        BytesMessage bm = (BytesMessage)message;
                        byte[] b = new byte[1024];
                        int len = -1;
                        while (true) {
                            try {
                                if (!((len = bm.readBytes(b)) != -1)) break;
                            } catch (JMSException e) {
                                e.printStackTrace();
                            }
                            System.out.println("BytesMessage:"+new String(b, 0, len));
                        }
                    }else if (message instanceof MapMessage){
                        MapMessage mes = (MapMessage) message;
                        try {
                            System.out.println("MapMessage name:"+mes.getString("name")+"age:"+mes.getInt("age"));
                        } catch (JMSException e) {
                            e.printStackTrace();
                        }
                    }else if (message instanceof ObjectMessage){
                        Student student = null;
                        try {
                            student = (Student) ((ActiveMQObjectMessage) message).getObject();
                        } catch (JMSException e) {
                            e.printStackTrace();
                        }
                        System.out.println("ObjectMessage:"+student);
                    }
                }
            });
        }
    }
}

springboot 集成

spring:
  activemq:
    broker-url: tcp://localhost:61616
    user: admin
    password: admin
    packages:
      trust-all: true

  jms:
    pub-sub-domain: true
  • 配置
@Configuration
@EnableJms
public class ActiveMqConfig {
    // topic模式的ListenerContainer
    @Bean
    public JmsListenerContainerFactory<?> jmsListenerContainerTopic(ConnectionFactory activeMQConnectionFactory) {
        DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
        bean.setPubSubDomain(true);
        bean.setConnectionFactory(activeMQConnectionFactory);
        return bean;
    }

    // queue模式的ListenerContainer
    @Bean
    public JmsListenerContainerFactory<?> jmsListenerContainerQueue(ConnectionFactory activeMQConnectionFactory) {
        DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
        bean.setConnectionFactory(activeMQConnectionFactory);
        return bean;
    }
}
  • 生产者
@SpringBootTest
@RunWith(SpringRunner.class)
public class Producer {

    @Autowired
    private JmsTemplate jmsTemplate;

    @Test
    public void produce(){
        jmsTemplate.convertAndSend(new ActiveMQQueue("springboot"),"测试springboot activemq");
        jmsTemplate.convertAndSend(new ActiveMQQueue("createqueue"),"queue-哈哈哈");
        jmsTemplate.convertAndSend(new ActiveMQTopic("createtopic"),"topic-啊啊啊啊");
    }

    @Test
    public void produce2(){
        jmsTemplate.send(new ActiveMQQueue("springboot"), new MessageCreator() {
            @Override
            public Message createMessage(Session session) throws JMSException {
                TextMessage textMessage = session.createTextMessage();
                textMessage.setText("textMessage:测试");
                return textMessage;
            }
        });
    }
}
  • 消费者
@Component
public class Consumer {

    @JmsListeners(value = {@JmsListener(destination = "springboot",containerFactory = "jmsListenerContainerQueue"),
            @JmsListener(destination = "createqueue",containerFactory = "jmsListenerContainerQueue")})
    //@JmsListener(destination = "createtopic")
    public void consumer(String message){
        System.out.println("message:"+message);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值