项目中有需要异步的业务,所以用到了Rabbitmq。
mq结构参考
目前项目有前端、后台以及对外的api,考虑到这三个子项目以后会用到mq,所以我将mq的结构抽离出来:发送端+发送端的dubbo+zookeeper接口、消费端,在需要异步请求的代码中,引入发送端的接口就可以了;然后消费端进行处理消息。在业务代码中无需引用mq的jar包或配置,减少耦合的问题。
1,Linux上Rabbitmq的安装
- 下载Rabbitmq,查看你的linux环境版本(命令:cat /etc/issue)。我的是CentOS6,所以下载的是CentOS6.x这个
- 下载erlang,erlang是Rabbitmq的编译语言,需要配对使用。rabbitmq与erlang以下
- 上传到linux然后安装:
yum install -y erlang-19.0.4-1.el6.x86_64.rpm
yum install -y rabbitmq-server-3.5.6-1.noarch.rpm
- 启动rabbitmq即可
service rabbitmq-server start
2,Rabbimq的管理
- 创建用户
#创建用户
rabbitmqctl add_user 用户名 用户密码
#分配用户角色:超级管理员(rabbitmq的角色包括none、
management、policymaker、monitoring、administrator)
rabbitmqctl set_user_tags root administrator
#赋予Virtual host权限,下面的/是默认的Virtual host
rabbitmqctl set_permissions -p / root "." "." ".*"
- 登录可视化界面,地址是http://linux地址:15672,输入刚刚创建的用户名及密码即可。如果访问不了查看防火墙是否关闭或对15672这个端口是否开放。在java客户端的默认端口是5672,也要开放。
- 添加一个Virtual host,每个Virtual host是相互隔离的,可以理解成一个独立的mq服务器。
rabbitmqctl add_vhost 虚拟名称
也可以在管理界面添加。包括交换机(exchange)、队列(queue)、绑定(binding)都可以在管理界面添加,不过这个一般是在代码中配置的
- 赋予用户于Virtual host的权限
rabbitmqctl set_permissions -p 虚拟名称 root "." "." ".*"
3,Spring+MVC与Rabbitmq的整合:发送端
- 消息发送端配置文件spring-rabbitmq.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit-1.6.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.0.xsd">
<!-- 创建连接工厂 -->
<rabbit:connection-factory
id="connectionFactory" virtual-host="${rabbit.virtual-host}"
username="${rabbit.username}" password="${rabbit.password}"
host="${rabbit.host}" port="${rabbit.port}" />
<rabbit:admin id="amqpAdmin" connection-factory="connectionFactory" ignore-declaration-exceptions="true"/>
<!-- 替换JACKSON为FASTJSON -->
<!-- <bean id="messageConverter" class="自定义的转换器"></bean> -->
<!-- spring template声明 -->
<!-- <rabbit:template id="amqpTemplate" connection-factory="connectionFactory" message-converter="messageConverter"/> -->
<rabbit:template id="amqpTemplate" connection-factory="connectionFactory"/>
</beans>
连接信息rabbimq.properties
rabbit.host=linux地址
rabbit.username=root
rabbit.password=root
rabbit.port=5672
rabbit.virtual-host=root_host
然后在你的spring配置文件引入这两个文件
配置文件中的消息转换工具,就是将消息序列化,可以用默认的Jackson,也可以用其他的转换工具,这里用的是Fastjson
import java.io.UnsupportedEncodingException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.support.converter.AbstractMessageConverter;
import org.springframework.amqp.support.converter.MessageConversionException;
import net.sf.ezmorph.object.DateMorpher;
import net.sf.json.JSONObject;
import net.sf.json.util.JSONUtils;
/**
* rabbitMQ 转换器 fastJson
*/
public class FastJsonMessageConverter extends AbstractMessageConverter {
public static final String DEFAULT_CHARSET = "UTF-8";
private volatile String defaultCharset = DEFAULT_CHARSET;
public FastJsonMessageConverter() {
super();
}
public void setDefaultCharset(String defaultCharset) {
this.defaultCharset = (defaultCharset != null) ? defaultCharset : DEFAULT_CHARSET;
}
public Object fromMessage(Message message) throws MessageConversionException {
return null;
}
@SuppressWarnings("unchecked")
public <T> T fromMessage(Message message, T t) {
String json = "";
try {
json = new String(message.getBody(), defaultCharset);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
JSONUtils.getMorpherRegistry().registerMorpher(new DateMorpher(
new String[] { "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss" }));
return (T) JSONObject.toBean((JSONObject.fromObject(json)), t.getClass());
}
protected Message createMessage(Object objectToConvert, MessageProperties messageProperties)
throws MessageConversionException {
byte[] bytes = null;
try {
String jsonString = com.alibaba.fastjson.JSONObject.toJSONString(objectToConvert);
bytes = jsonString.getBytes(this.defaultCharset);
} catch (UnsupportedEncodingException e) {
throw new MessageConversionException("Failed to convert Message content", e);
}
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON);
messageProperties.setContentEncoding(this.defaultCharset);
if (bytes != null) {
messageProperties.setContentLength(bytes.length);
}
return new Message(bytes, messageProperties);
}
}
- 配置类,交换机与队列的绑定。交换机与队列类似一个多对多的关系
首先写一个枚举
public class RabbitInfo {
/** 交换机名称 */
public final static String USER_EXCHANGE = "user_exchange";
/** 绑定名称 */
public final static String USER_DELETE_BINDING = "user_delete_binding";
/** 队列名称 */
public final static String USER_DELETE_QUEUE = "user_delete_queue";
public enum RabbitEnum {
ITEMEDIT(USER_EXCHANGE, USER_DELETE_BINDING, USER_DELETE_QUEUE);
private String exchange;
private String binding;
private String queue;
private RabbitEnum(String exchange, String binding, String queue) {
this.exchange = exchange;
this.binding = binding;
this.queue = queue;
}
public String getExchange() {
return exchange;
}
public String getBinding() {
return binding;
}
public final String getQueue() {
return queue;
}
}
}
然后初始化枚举里面定义的交换机、绑定、以及队列
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import common.rabbitmq.RabbitInfo;
import common.rabbitmq.RabbitInfo.RabbitEnum;
/**
* 交换机与队列绑定
*
* @author awarmbody
*
*/
@Configuration
public class DirectRabbitConfig {
@Autowired
private AmqpAdmin amqpAdmin;
public static final Logger LOGGER = LoggerFactory.getLogger(DirectRabbitConfig.class);
@PostConstruct
public void init() {
RabbitEnum[] rabbitEnums = RabbitInfo.RabbitEnum.values();
Map<String,DirectExchange> exchanges = new HashMap<String,DirectExchange>();
Map<String,Queue> queues = new HashMap<String,Queue>();
Map<String,Binding> bindings = new HashMap<String,Binding>();
for (RabbitEnum rabbitEnum : rabbitEnums) {
DirectExchange exchange;
Queue queue;
if(exchanges.containsKey(rabbitEnum.getExchange())) {
exchange = exchanges.get(rabbitEnum.getExchange());
}else {
exchange = (DirectExchange) ExchangeBuilder.directExchange(rabbitEnum.getExchange()).durable(true).build();
exchanges.put(rabbitEnum.getExchange(),exchange);
}
if(queues.containsKey(rabbitEnum.getQueue())) {
queue = queues.get(rabbitEnum.getQueue());
}else {
queue = QueueBuilder.durable(rabbitEnum.getQueue()).build();
queues.put(rabbitEnum.getQueue(), queue);
}
if(bindings.containsKey(rabbitEnum.getBinding())) {
LOGGER.warn("There are bindings with the same name:exchange-name=" + rabbitEnum.getExchange()
+ ",queue-name=" + rabbitEnum.getQueue() + ",binding-key=" + rabbitEnum.getBinding());
continue;
}
amqpAdmin.declareExchange(exchange);
amqpAdmin.declareQueue(queue);
Binding binding = BindingBuilder.bind(queue).to(exchange).with(rabbitEnum.getBinding());
amqpAdmin.declareBinding(binding);
bindings.put(rabbitEnum.getBinding(), binding);
}
exchanges = null;
bindings = null;
queues = null;
}
}
消息发送端的配置就到这里了。
- 发送消息
写一个用户的接口,定义一个删除用户的方法,然后将接口的实现类通过dubbo注解发布发zookeeper,在业务代码里注入UserService就可以,业务代码没有任何关于mq的信息。这是多态和反射的一些概念这里就不陈述了
public interface UserService {
void deleteUser(String exchangeName, String bindingKey, Long userId);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private AmqpTemplate amqpTemplate;
@Override
public void deleteUser(String exchangeName, String bindingKey, Long userId) {
amqpTemplate.convertAndSend(exchangeName,bindingKey, goodId);
}
}
4,Spring+MVC与Rabbitmq的整合:消费端
- 消息消费端配置spring-rabbitmq.xml,相对于发送端的配置多了一个开启注解扫描(我用的是注解监听,挺方便的)、以及初始化消息监听。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit-1.6.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.0.xsd">
<!-- 创建连接工厂 -->
<rabbit:connection-factory
id="connectionFactory" virtual-host="${rabbit.virtual-host}"
username="${rabbit.username}" password="${rabbit.password}"
host="${rabbit.host}" port="${rabbit.port}" />
<rabbit:admin connection-factory="connectionFactory" />
<!-- 替换JACKSON为FASTJSON -->
<!-- <bean id="messageConverter" class="自定义的转换器"></bean> -->
<!-- 开启rabbitMQ注解 -->
<rabbit:annotation-driven />
<!-- 消息监听 -->
<bean id="rabbitListenerContainerFactory"
class="org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory">
<!-- <property name="messageConverter" ref="messageConverter" /> -->
<property name="connectionFactory" ref="connectionFactory" />
<property name="concurrentConsumers" value="3" />
<property name="maxConcurrentConsumers" value="10" />
</bean>
</beans>
连接信息rabbimq.properties
rabbit.host=linux地址
rabbit.username=root
rabbit.password=root
rabbit.port=5672
rabbit.virtual-host=root_host
然后在你的spring配置文件引入这两个文件
<!--引入配置属性文件 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:/config/mq/rabbitmq.properties</value>
</list>
</property>
</bean>
<!--引入rabbitmq配置文件 -->
<import resource="spring-rabbitmq.xml" />
- 接收消息
@Component
public class UserCustomer {
@Autowired
private UserService userService;
/**
* 刪除用户
* @param userId
*/
@RabbitListener(queues = RabbitInfo.USER_DELETE_QUEUE)
public void deleteUser(Long userId) {
userService.deleteUser(userId);
}
}
最后
当业务数据量较大的时候,就要考虑线程的事务(推荐用redis缓存一个变量作为一个锁,快捷高效)、消息发送失败或消费失败的处理(可基于消息确认机制处理,Rabbitmq有提供)