循序渐进ActiveMQ(2)----activemq的安全认证,连接,会话,事务,签收模式及优先级

安全认证

上一个小节中,我们连接activemq是使用以下默认用户和密码,这样的坏处是,只要有人知道了我们activemq服务器的ip和端口,就可以脸上去消费掉我们的消息,这样非常的不安全,所以我们需要给activemq设置一个username和pasword。

/**
		 * 第一步:
		 * 建立ConnectionFactory工厂对象,需要填入用户名、密码、及要连接的地址,均
		 * 使用默认即可,默认端口“tcp://localhost:61616”
		 */
			ConnectionFactory connectionFactory = 
					new ActiveMQConnectionFactory(
							ActiveMQConnectionFactory.DEFAULT_USER, 
							ActiveMQConnectionFactory.DEFAULT_PASSWORD, 
							"tcp://localhost:61616"); 

做法很简单,就是在conf下的activemq.xml的broker节点插入一个子节点如下:


配置完成后,重新启动activemq才能生效,接着我们启动sender.java

Exception in thread "main" javax.jms.JMSSecurityException: User name [null] or password is invalid.
	at org.apache.activemq.util.JMSExceptionSupport.create(JMSExceptionSupport.java:52)
	at org.apache.activemq.ActiveMQConnection.syncSendPacket(ActiveMQConnection.java:1420)
	at org.apache.activemq.ActiveMQConnection.ensureConnectionInfoSent(ActiveMQConnection.java:1521)
	at org.apache.activemq.ActiveMQConnection.start(ActiveMQConnection.java:528)
	at jeff.mq.helloworld.Sender.main(Sender.java:38)
Caused by: java.lang.SecurityException: User name [null] or password is invalid.
	at org.apache.activemq.security.SimpleAuthenticationBroker.addConnection(SimpleAuthenticationBroker.java:85)
	at org.apache.activemq.broker.MutableBrokerFilter.addConnection(MutableBrokerFilter.java:102)
	at org.apache.activemq.broker.TransportConnection.processAddConnection(TransportConnection.java:809)
	at org.apache.activemq.broker.jmx.ManagedTransportConnection.processAddConnection(ManagedTransportConnection.java:79)
	at org.apache.activemq.command.ConnectionInfo.visit(ConnectionInfo.java:139)
	at org.apache.activemq.broker.TransportConnection.service(TransportConnection.java:334)
	at org.apache.activemq.broker.TransportConnection$1.onCommand(TransportConnection.java:188)
	at org.apache.activemq.transport.MutexTransport.onCommand(MutexTransport.java:50)
	at org.apache.activemq.transport.WireFormatNegotiator.onCommand(WireFormatNegotiator.java:113)
	at org.apache.activemq.transport.AbstractInactivityMonitor.onCommand(AbstractInactivityMonitor.java:270)
	at org.apache.activemq.transport.TransportSupport.doConsume(TransportSupport.java:83)
	at org.apache.activemq.transport.tcp.TcpTransport.doRun(TcpTransport.java:214)
	at org.apache.activemq.transport.tcp.TcpTransport.run(TcpTransport.java:196)
	at java.lang.Thread.run(Unknown Source)

可以看到报了用户和密码非法异常!

我们改下连接方式:

ConnectionFactory connectionFactory = 
					new ActiveMQConnectionFactory(
							"jeff", 
							"123456", 
							"tcp://localhost:61616");
再次启动!OK了



Connection方法使用

在成功创建ConnectionFactory后,下一步就是创建一个Connection,它是JMS规范定义的一个接口,负责返回一个可以与底层消息系统进行通信的Connection接口的实现。

通常消费端只使用单一连接Connection。

根据JMS文档,Connection的目的是“利用JMS Provider封装开放的连接”,以及表示“消费者和生产者之间开放TCP/IP套接字”。该文档还指出Connection应该是消费端进行身份验证的地方等等。

扫描二维码关注公众号,回复: 1922092 查看本文章

注意:当一个Connection被创建后,它的传输默认是关闭的,必须使用start方法开启。一个Connection可以建立一个或者多个Session。

一个程序执行完后,必须关闭之前创建的Connection,否则ActiveMq不能释放资源,关闭一个Connection也同时关闭了Session,MessageProcuder和MessageConsumer。

创建连接的构造方法:

Connection createConnection();

Connection createConnection(String username,String password,String url);


Session的使用

一旦从ConnectionFactory中获取到一个Connection,必须从Conenction中创建一个或者多个Session。

Session是一个发送或者接收消息的线程,可以使用Session创建MessageProcuder,MessageConsumer和Message。

Session createSession(boolean transacted,int acknowledgeMode);

transacted:布尔值,是否使用事务,结束事务的两种方法:提交或者回滚。(TIPS:事务是对生产者而言的      )

  当一个事务提交时,消息被处理。如果事务中有一个步骤失败,事务就回滚,这个事务中已经被执行的动作就会撤销。在发送消息的最后,必须使用session.commmit();显示提交事务。

acknowledgeMode:整型,签收模式。(TIPS:签收是对消费者而言的)

签收模式有三种:

(1)Session.AUTO_ACKNOWLEDGE

当客户端从receive或者onMessage成功返回时,Session自动签收消费者的这条消息的收条。

(2)Session.CLIENT_ACKNOWLEDGE

客户端通过调用消息Message的acknowledge方法签收消息,在这种情况下,签收发生在session层面,签收一个消费的消息会自动签收这个session所有已消费的消息的收条。

(3)Session.DUPS_OK_ACKNOWLEDGE

此选项指示Session必须确保对传送消息的签收。它可能引起消息的重复消费。但是降低了session的开销。所以只有消费者能容忍重复消费,才能使用。


概念介绍完了,我们看下代码实现:

首先,看下事务的实现:

在创建session的时候我们开启事务支持:

                //开启事务
		Session session =connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);

需要注意的是,当消息发送完毕后,需要显示提交事务,这样消息服务器才能收到消息:

	       //提交事务
		session.commit();

控制台看到存在12条消息,是因为我们发送了两次,每次6条。

然后启动消费者,消费者的代码不需要变(无需事务支持),消费了12条消息!

这样,生产者的事务提交就验证成功了!!!



然后,我们看下签收模式的验证:


先解释下这个图,第一步生产者发送数据给mq,消费者有两种方式获得消息,receive这种同步pull主动拉取方式,或者是onMessage这种异步push推送方式。得到消息后就是第三步的ACK确认签收了,签收涉及以下三种方式:

AUTO_ACKNOWLEDGE

刚才验证事务的时候,我们使用的就是自动签收,意思就是只要消费者消息收到了这条消息,就自动签收,发送ack确认消息给activemq告知已经收到了,这样activemq中的消息才能真正被消费,不然会一直存在!由于自动确认签收的机制,消息一定会被消费,不需要额外确认。


CLIENT_ACKNOWLEDGE

生产者首先生产6条信息

代码:

Session session =connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
还是开启事务的自动签收;


消费者代码:

	Session session =connection.createSession(Boolean.FALSE, Session.CLIENT_ACKNOWLEDGE);

是关闭事务的客户端签收确认方式,我们先不使用消息确认:

               MessageConsumer messageConsumer = session.createConsumer(destination);
		while(true){
			TextMessage msg = (TextMessage)messageConsumer.receive();
			if(msg==null)break;
			
			System.out.println("收到内容: "+msg.getText());
		}

打印:


消息已经收到了,再观察activemq:


消息一直没有被消费掉!

这是因为我们没有对消息确认签收,所以activemq得不到确认就无法删除消息!

改下代码:

	MessageConsumer messageConsumer = session.createConsumer(destination);
		while(true){
			TextMessage msg = (TextMessage)messageConsumer.receive();
			if(msg==null)break;
			msg.acknowledge();
			System.out.println("收到内容: "+msg.getText());
		}

多了个msg.acknowledeg();这样消息就被消费掉了,再次启动过消费者!



消息被成功消费掉了!同时验证了【签收模式只跟消费者有关系】这句话!

msg.acknowledeg();

查看源码可以了解到它是另外开启一个线程去通知activemq确认签收!是异步的!



MessageProducer

消息生产者,它是由session创建的一个对象,用来向Destination目标(队列或者主题)去发送消息。

关于其发送消息的构造方法有:

public void send(Message message) throws JMSException;

 public void send(Message message, int deliveryMode, int priority, long timeToLive) throws JMSException ;

 public void send(Destination destination, Message message) throws JMSException;

public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive) throws JMSException;

参数解释:

deliveryMode为传送模式

    ActiveMq支持两种消息传送模式:PERSISTENT和NON_PERSISTENT,如果不指定传送模式,默认为持久消息;如果可以容忍消息丢失,使用非持久化模式可以改善性能和减小存储开销。

priority为消息优先级

    消息优先级有从0~9十个级别,0-4是普通消息,5-9是加急消息,如果不指定优先级,则默认为4,JMS不要求严格按照这10个优先级发送消息,但必须保证加急消息要优先于普通消息到达。

timeToLive为消息过期时间

    默认消息永不过期,但是可以设置过期时间,单位是毫秒。


我们看下优先级问题,先看下sender生产端代码:

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnectionFactory;

/**
 * @author jeffSheng
 * 2018年7月3日
 */
public class Sender {

	
	public static void main(String[] args) throws Exception {
		/**
		 * 第一步:
		 * 建立ConnectionFactory工厂对象,需要填入用户名、密码、及要连接的地址,均
		 * 使用默认即可,默认端口“tcp://localhost:61616”
		 */
			ConnectionFactory connectionFactory = 
					new ActiveMQConnectionFactory(
							"jeff", 
							"123456", 
							"tcp://localhost:61616");
		
		/**
		 * 第二步:
		 * 通过ConnectionFactory工厂对象我们创建一个Connection连接,并且调用Connection的start方法
		 * 开启连接,Connection连接默认是关闭的。
		 */
		Connection connection = connectionFactory.createConnection();
		connection.start();
		
		/**
		 * 第三步:
		 * 通过Connection对象创建Session会话(上下文环境对象),用于接收消息,参数配置1为是否启动事务,
		 * 参数配置2为签收模式,一般我们设置自动签收。
		 */
		//我们这里不开启事务
		
//		Session session =connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
		//开启事务
		Session session =connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
		/**
		 * 第四步:
		 * 通过Session创建Destination对象,指的是一个客户端用来指定生产消息目标和消息消息来源的对象,
		 * 在ptp模式中,Destination被称作Queue即队列;在Pub/Sub模式中Destination被称作Topic即主题
		 * 在程序众包给可以使用多个Queue和Topic
		 */
		Destination destination = session.createQueue("queue1");
		
		
		/**
		 * 第五步:
		 * 我们需要通过Session对象常见消息的发送和接收对象(生产者和消费者)
		 * MessageProcuder/MessageConsumer
		 */
		MessageProducer messageProducer = session.createProducer(null);
		
		/**
		 * 第六步:
		 * 我们可以使用MessageProducer的setDeliverMode方法为其设置持久化特性和非持久化特性(DeliverMode)
		 */
		//如果设置为持久话方式,我们需要指定具体持久话策略,比如jdbc持久化到数据库
//		messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
		
		/**
		 * 第七步:
		 * 最后我们使用JMS规范的TextMessage形式创建数据(通过Session对象),并用MessageProducer的send方法发送数据,
		 * 同理客户端使用receive方法进行接收数据。最后不要忘记关闭Connection
		 */
		for (int i = 0; i <= 5; i++) {
			TextMessage textMessage = session.createTextMessage();
			textMessage.setText("我是消息,Id:"+i);
			/**
			 * 参数解释:
			 * 第一个参数:目的地
			 * 第二个参数:消息
			 * 第三个参数:是否持久化
			 * 第四个参数:优先级0~9,0-4是普通消息,5-9是加急消息
			 * 第五个参数:存活时间,这里我们设置的是2分钟
			 */
			messageProducer.send(destination,textMessage,DeliveryMode.NON_PERSISTENT,i,1000*60*2);
			System.out.println("生产者:"+textMessage.getText());
		}
		//提交事务
		session.commit();
		
		//关闭方法会递归向下关闭会话等连接
		if(connection!=null){
			connection.close();
		}
		
	}
	
}

在发送消息的时候再指定目的地队列,是否持久化和优先级,存活时间。执行后:



消费端代码不变,执行消费端:



消息被消费了,但是我们是指定了优先级的,如果按照优先级,我们期望的结果是:

收到内容: 我是消息,Id:5
收到内容: 我是消息,Id:4
收到内容: 我是消息,Id:3
收到内容: 我是消息,Id:2
收到内容: 我是消息,Id:1
收到内容: 我是消息,Id:0

然而并没有!这是因为我们并没有为这个队列queue1开启优先级,需要在activemq.xml配置下:


这样我们再次启动客户端:


会发现,接收到的mq消息的顺序就是我们期望的样子!(试验过关闭生产者的事务,结果还是一样的,说明activemq组织了queue1队列中消息的优先级顺序。

思考:如果是多线程环境下,多个生产者向activemq中的queue1队列发送优先级消息,消费者收到的结果是怎么样的?可以肯定还是按照优先级顺序的。

我们改造下生产者代码,创建50个线程,一起发送消息给activemq,这些消息的优先级是0到9之间的随机数。

package jeff.mq.helloworld;

import java.util.concurrent.CountDownLatch;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnectionFactory;

/**
 * @author jeffSheng
 * 2018年7月3日
 */
public class Sender {
	
	private static final CountDownLatch latch = new CountDownLatch(1);  
	
	public static void main(String[] args) throws Exception {
		final Sender s = new Sender();
		for (int i = 0; i < 50; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						s.sender();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}).start();;
		}
		Thread.sleep(3000);
		latch.countDown();
	}
	
	public void sender() throws Exception{
	
			ConnectionFactory connectionFactory = 
					new ActiveMQConnectionFactory(
							"jeff", 
							"123456", 
							"tcp://localhost:61616");
		
		Connection connection = connectionFactory.createConnection();
		connection.start();
		
		Session session =connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
		
		Destination destination = session.createQueue("queue1");
		
		MessageProducer messageProducer = session.createProducer(null);
		
		
		latch.await();
			TextMessage textMessage = session.createTextMessage();
			int n = (int)(Math.random()*10);
			textMessage.setText("我是消息,Id:"+n);
			messageProducer.send(destination,textMessage,DeliveryMode.NON_PERSISTENT,n,1000*60*2);
			System.out.println("生产者:"+textMessage.getText());

		//关闭方法会递归向下关闭会话等连接
		if(connection!=null){
			connection.close();
		}
	}
}

启动生产者后:

log4j:WARN No appenders could be found for logger (org.apache.activemq.transport.WireFormatNegotiator).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
生产者:我是消息,Id:0
生产者:我是消息,Id:0
生产者:我是消息,Id:8
生产者:我是消息,Id:7
生产者:我是消息,Id:7
生产者:我是消息,Id:1
生产者:我是消息,Id:4
生产者:我是消息,Id:9
生产者:我是消息,Id:7
生产者:我是消息,Id:4
生产者:我是消息,Id:1
生产者:我是消息,Id:0
生产者:我是消息,Id:6
生产者:我是消息,Id:4
生产者:我是消息,Id:2
生产者:我是消息,Id:0
生产者:我是消息,Id:2
生产者:我是消息,Id:1
生产者:我是消息,Id:3
生产者:我是消息,Id:7
生产者:我是消息,Id:8
生产者:我是消息,Id:1
生产者:我是消息,Id:1
生产者:我是消息,Id:8
生产者:我是消息,Id:2
生产者:我是消息,Id:9
生产者:我是消息,Id:3
生产者:我是消息,Id:7
生产者:我是消息,Id:1
生产者:我是消息,Id:5
生产者:我是消息,Id:6
生产者:我是消息,Id:3
生产者:我是消息,Id:4
生产者:我是消息,Id:4
生产者:我是消息,Id:4
生产者:我是消息,Id:0
生产者:我是消息,Id:0
生产者:我是消息,Id:8
生产者:我是消息,Id:1
生产者:我是消息,Id:0
生产者:我是消息,Id:9
生产者:我是消息,Id:1
生产者:我是消息,Id:4
生产者:我是消息,Id:2
生产者:我是消息,Id:5
生产者:我是消息,Id:8
生产者:我是消息,Id:6
生产者:我是消息,Id:8
生产者:我是消息,Id:2
生产者:我是消息,Id:4

启动消费者(代码不变):

log4j:WARN No appenders could be found for logger (org.apache.activemq.transport.WireFormatNegotiator).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
收到内容: 我是消息,Id:9
收到内容: 我是消息,Id:9
收到内容: 我是消息,Id:9
收到内容: 我是消息,Id:8
收到内容: 我是消息,Id:8
收到内容: 我是消息,Id:8
收到内容: 我是消息,Id:8
收到内容: 我是消息,Id:8
收到内容: 我是消息,Id:8
收到内容: 我是消息,Id:7
收到内容: 我是消息,Id:7
收到内容: 我是消息,Id:7
收到内容: 我是消息,Id:7
收到内容: 我是消息,Id:7
收到内容: 我是消息,Id:6
收到内容: 我是消息,Id:6
收到内容: 我是消息,Id:6
收到内容: 我是消息,Id:5
收到内容: 我是消息,Id:5
收到内容: 我是消息,Id:4
收到内容: 我是消息,Id:4
收到内容: 我是消息,Id:4
收到内容: 我是消息,Id:4
收到内容: 我是消息,Id:4
收到内容: 我是消息,Id:4
收到内容: 我是消息,Id:4
收到内容: 我是消息,Id:4
收到内容: 我是消息,Id:3
收到内容: 我是消息,Id:3
收到内容: 我是消息,Id:3
收到内容: 我是消息,Id:2
收到内容: 我是消息,Id:2
收到内容: 我是消息,Id:2
收到内容: 我是消息,Id:2
收到内容: 我是消息,Id:2
收到内容: 我是消息,Id:1
收到内容: 我是消息,Id:1
收到内容: 我是消息,Id:1
收到内容: 我是消息,Id:1
收到内容: 我是消息,Id:1
收到内容: 我是消息,Id:1
收到内容: 我是消息,Id:1
收到内容: 我是消息,Id:1
收到内容: 我是消息,Id:0
收到内容: 我是消息,Id:0
收到内容: 我是消息,Id:0
收到内容: 我是消息,Id:0
收到内容: 我是消息,Id:0
收到内容: 我是消息,Id:0
收到内容: 我是消息,Id:0

可以看到即便是多个生产者一起发送优先级不同的消息,最后消费者还是按照优先级给排列了!

深入思考下,由于我们是先启动生产者,再启动消费者,相当于消费者是一次性读取消息的,结果的确是按照优先级排序了,但是在消费者启动的情况下,我们使用这50个线程一起发消息,则结果是:

收到内容: 我是消息,Id:9
收到内容: 我是消息,Id:0
收到内容: 我是消息,Id:8
收到内容: 我是消息,Id:7
收到内容: 我是消息,Id:0
收到内容: 我是消息,Id:8
收到内容: 我是消息,Id:5
收到内容: 我是消息,Id:4
收到内容: 我是消息,Id:4
收到内容: 我是消息,Id:1
收到内容: 我是消息,Id:8
收到内容: 我是消息,Id:4
收到内容: 我是消息,Id:9
收到内容: 我是消息,Id:9
收到内容: 我是消息,Id:9
收到内容: 我是消息,Id:9
收到内容: 我是消息,Id:9
收到内容: 我是消息,Id:8
收到内容: 我是消息,Id:8
收到内容: 我是消息,Id:8
收到内容: 我是消息,Id:8
收到内容: 我是消息,Id:7
收到内容: 我是消息,Id:7
收到内容: 我是消息,Id:6
收到内容: 我是消息,Id:6
收到内容: 我是消息,Id:5
收到内容: 我是消息,Id:5
收到内容: 我是消息,Id:4
收到内容: 我是消息,Id:3
收到内容: 我是消息,Id:2
收到内容: 我是消息,Id:2
收到内容: 我是消息,Id:2
收到内容: 我是消息,Id:0
收到内容: 我是消息,Id:9
收到内容: 我是消息,Id:9
收到内容: 我是消息,Id:8
收到内容: 我是消息,Id:8
收到内容: 我是消息,Id:8
收到内容: 我是消息,Id:7
收到内容: 我是消息,Id:6
收到内容: 我是消息,Id:5
收到内容: 我是消息,Id:5
收到内容: 我是消息,Id:5
收到内容: 我是消息,Id:4
收到内容: 我是消息,Id:3
收到内容: 我是消息,Id:2
收到内容: 我是消息,Id:2
收到内容: 我是消息,Id:1
收到内容: 我是消息,Id:1
收到内容: 我是消息,Id:0

结果却并没有按照优先级排序

结论是:说明activemq在这种消费者启动的时候接收高并发的优先级消息是没有做优先级排序的!












猜你喜欢

转载自blog.csdn.net/shengqianfeng/article/details/80933699