前不久,刚学习了jms的简单入门,后面紧接着就做了一个关于jms的负载均衡的项目,做完之后颇有打通任督二脉的感觉,感觉很多之前不是很理解的东西,都有些理解了,比如服务器端的监听、具体的jms的使用等,收获有点大。
流程如下图所示:
客户端:
xml配置,这里用到了两台服务器,connectionFactory便可以看出,因为传的是对象,用到了转换器
< beans xmlns ="http://www.springframework.org/schema/beans"
xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context ="http://www.springframework.org/schema/context"
xsi:schemaLocation ="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd" >
<!-- 配置JMS连接工厂 -->
<!-- <bean id="connectionFactory" class="org.apache.activemq.spring.ActiveMQConnectionFactory"> -->
<!-- <property name="brokerURL" value="tcp://localhost:61616" /> -->
<!-- </bean> -->
< bean id ="connectionFactory" class ="org.apache.activemq.pool.PooledConnectionFactory" destroy-method ="stop" >
< property name ="connectionFactory" >
< bean class ="org.apache.activemq.ActiveMQConnectionFactory" >
< property name ="brokerURL" >
< value >tcp://localhost:61616 </ value >
</ property >
< property name ="useAsyncSend" >
< value >true </ value >
</ property >
</ bean >
</ property >
</ bean >
< bean id ="connectionFactory_1" class ="org.apache.activemq.pool.PooledConnectionFactory" destroy-method ="stop" >
< property name ="connectionFactory" >
< bean class ="org.apache.activemq.ActiveMQConnectionFactory" >
< property name ="brokerURL" >
< value >tcp://192.168.130.13:61616 </ value >
</ property >
< property name ="useAsyncSend" >
< value >true </ value >
</ property >
</ bean >
</ property >
</ bean >
<!-- 发送消息的目的地(一个队列) -->
< bean id ="destination" class ="org.apache.activemq.command.ActiveMQQueue" >
<!-- 设置消息队列的名字 -->
< constructor-arg index ="0" value ="activeMQQueue" />
</ bean >
<!-- 消息转换 -->
< bean id ="messageConverter" class ="com.pis.activeMQ.ObjectMessageConverter" />
<!-- 配置JMS模版 -->
< bean id ="jmsTemplate_1" class ="org.springframework.jms.core.JmsTemplate" >
< property name ="connectionFactory" ref ="connectionFactory" />
< property name ="messageConverter" ref ="messageConverter" />
</ bean >
< bean id ="jmsTemplate_2" class ="org.springframework.jms.core.JmsTemplate" >
< property name ="connectionFactory" ref ="connectionFactory_1" />
< property name ="messageConverter" ref ="messageConverter" />
</ bean >
<!-- 生产消息配置 -->
< bean id ="queueProducer" class ="com.pis.activeMQ.client.MessageProducer" >
< property name ="destination" ref ="destination" />
< property name ="jmsTemplate" >
< list >
< ref bean ="jmsTemplate_1" />
< ref bean ="jmsTemplate_2" />
</ list >
</ property >
</ bean >
<!-- 生产消息action bean -->
< bean id ="jmsAction" class ="com.pis.action.JmsAction" >
< property name ="queueProducer" ref ="queueProducer" />
</ bean >
</ beans >
转换器如下所示: 转来转去有点恶心的代码 O(∩_∩)O~
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.ObjectMessage;
import javax.jms.Session;
import org.springframework.jms.support.converter.MessageConversionException;
import org.springframework.jms.support.converter.MessageConverter;
public class ObjectMessageConverter implements MessageConverter{
// 从消息中取出对象
@Override
public Object fromMessage(Message message) throws JMSException,MessageConversionException {
Object object = null;
if(message instanceof ObjectMessage) {
// 两次强转,获得消息中的主体对象字节数组流
byte[] obj = ( byte[])((ObjectMessage)message).getObject();
// 读取字节数组中为字节数组流
ByteArrayInputStream bis = new ByteArrayInputStream(obj);
try {
// 读字节数组流为对象输出流
ObjectInputStream ois = new ObjectInputStream(bis);
// 从对象输出流中取出对象 并强转
object = ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
return object;
}
// 将对象转换成消息
@Override
public Message toMessage(Object object, Session session) throws JMSException,MessageConversionException {
ObjectMessage objectMessage = session.createObjectMessage();
ByteArrayOutputStream bos = new ByteArrayOutputStream(); // 字节数组输出流
try {
ObjectOutputStream oos = new ObjectOutputStream(bos); // 对象输出流
oos.writeObject(object); // 写入对象
byte[] objMessage = bos.toByteArray(); // 字节数组输出流转成字节数组
objectMessage.setObject(objMessage); // 将字节数组填充到消息中作为消息主体
} catch (IOException e) {
e.printStackTrace();
}
return objectMessage;
}
}
生产者 这里用到了原子类来计数,避免使用线程同步,也是第一次接触,convertAndSend方法会调用转换器,把对象转换成消息类型
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.jms.core.JmsTemplate;
import com.pis.model.Product;
public class MessageProducer {
private ActiveMQQueue destination;
private List<JmsTemplate> jmsTemplate;
private Product product;
// 原子整型计数(CAS),可以不使用同步
private AtomicInteger current = new AtomicInteger(0);
// 轮询算法解决负载均衡
private JmsTemplate findJmsTemplate(){
int cur = current.getAndIncrement();
int index = cur%jmsTemplate.size();
return jmsTemplate.get(index);
}
// 发送消息
public void sendMessage(Product product){
this.findJmsTemplate().convertAndSend(destination, product);
}
public ActiveMQQueue getDestination() {
return destination;
}
public void setDestination(ActiveMQQueue destination) {
this.destination = destination;
}
public List<JmsTemplate> getJmsTemplate() {
return jmsTemplate;
}
public void setJmsTemplate(List<JmsTemplate> jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
}
服务器端:
xml配置如下:这里只是我本机的服务器配置,另外一台如法炮制,这里用到了监听器,见名知意,大概干嘛用的一看就知道,有消息是会触发监听器,监听器指定使用queueConsumer中的receive方法,这就很清楚了,来一条收一条,来两条收一双。
< beans xmlns ="http://www.springframework.org/schema/beans"
xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context ="http://www.springframework.org/schema/context"
xsi:schemaLocation ="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd" >
< bean id ="connectionFactory" class ="org.apache.activemq.pool.PooledConnectionFactory" destroy-method ="stop" >
< property name ="connectionFactory" >
< bean class ="org.apache.activemq.ActiveMQConnectionFactory" >
< property name ="brokerURL" >
< value >tcp://localhost:61616 </ value >
</ property >
< property name ="useAsyncSend" >
< value >true </ value >
</ property >
</ bean >
</ property >
</ bean >
<!-- 发送消息的目的地(一个队列) -->
< bean id ="destination" class ="org.apache.activemq.command.ActiveMQQueue" >
<!-- 设置消息队列的名字 -->
< constructor-arg index ="0" value ="activeMQQueue" />
</ bean >
<!-- 消息转换 -->
< bean id ="messageConverter" class ="com.pis.activeMQ.ObjectMessageConverter" />
<!-- 生产消息配置 -->
< bean id ="queueConsumer" class ="com.pis.activeMQ.server.MessageConsumer" />
< bean id ="queueListener" class ="org.springframework.jms.listener.adapter.MessageListenerAdapter" >
< constructor-arg ref ="queueConsumer" />
< property name ="defaultListenerMethod" value ="receive" />
< property name ="messageConverter" ref ="messageConverter" />
</ bean >
< bean id ="queueListenerContainer" class ="org.springframework.jms.listener.DefaultMessageListenerContainer" >
< property name ="connectionFactory" ref ="connectionFactory" />
< property name ="destination" ref ="destination" />
< property name ="messageListener" ref ="queueListener" />
</ bean >
</beans>
消费者 很简洁的代码
import com.pis.model.Product;
public class MessageConsumer {
public void receive(Product product) {
// 如果消费到了就会打印出来
System.out.println("server端收到消息:"+product.getName());
}
}
好了,我这是个struts2+spring的代码,有了第一个xml中的jmsAction的配置,下面就是action的代码
import com.opensymphony.xwork2.ActionSupport;
import com.pis.activeMQ.client.MessageProducer;
import com.pis.model.Product;
public class JmsAction extends ActionSupport {
private static final long serialVersionUID = 132132131312L;
private MessageProducer queueProducer;
private Product product;
@Override
public String execute() throws Exception {
System.out.println(product.getName());
queueProducer.sendMessage(product);
return null;
}
public MessageProducer getQueueProducer() {
return queueProducer;
}
public void setQueueProducer(MessageProducer queueProducer) {
this.queueProducer = queueProducer;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
}
product类,记得要实现serializable方法!
import java.io.Serializable;
public class Product implements Serializable{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
好吧,struts中的配置我就不给了,太简单了,直接访问 localhost:8060/pis/produceMessage.action?product.name=85252 顺便说下,由于懒,这里我就直接把product实体类当成参数传进去了,吼吼,没有界面……懒吧
期间测试可以看http://localhost:8161/admin/queues.jsp 和http://192.168.130.13:8161/admin/queues.jsp可以很明显的看出每次浏览器回车一下,只有其中的一个消息多列一条,完美实现了负载均衡,并且对object类型的message队列学习了一下,收获很大