对于需要处理很多工作的业务接口中,对各种接口的调用往往造成这个接口耗时过长,而各种接口的频繁调用,也对服务器造成了很大压力。用线程来解决前面的问题,在线程的新建和销毁都需要耗费时间,即使用线程池来实现,服务器照样也有压力。而提升服务器性能来解决后一个问题,性价比不够高。如果这时能用上MQ,不为是一个比较好的解决方法。
如上图所示,在一个博彩类APP中,用户在界面下注比赛,会调用服务器的下注接口。下注接口是一个业务处理量非常大的接口。对用户的体验也非常主要,谁也不想下注一场比赛要等好几秒才下单成功。但这个接口不仅要把下注信息写入到数据库中,还要统计很多信息,比如用户的喜好,用户花费金额的排行榜,下注额在一场比赛的某一方的总额,总额过高,就要调低这一方的赔率,防止黑天鹅事件导致公司亏损过大等。
在普通应用处理场景,一条流水线下来,每个业务处理都要花费时间,同步进行的处理,累积出来的时间就延长了。如果访问量过大,还会造成服务器崩溃。所以我们想到,最重要的主业务还是主服务器处理,而那些统计及其其他不重要、不需要及时反馈的业务处理通过MQ来发送。MQ再推送到其他副业务处理服务器上处理。减轻了服务器压力,提高了响应能力。
详细代码参考我的git项目:https://github.com/888xin/rabbitmq
里面的主服务器(也就是MQ的发送端)是项目:rabbit-pruducer
, 副服务器(也就是MQ的消费端)是项目:rabbit-consumer
主服务器端(MQ发送端)先实现下注逻辑PlayBall.java
package com.lhx.contest;
import com.lhx.bean.Bet;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.IOException;
/**
* Created by lhx on 2016/9/18 16:36
*
* @Description
*/
@Service
public class PlayBall {
private Logger logger = LoggerFactory.getLogger(PlayBall.class);
@Resource
private AmqpTemplate fanoutTemplate;
public void bet(long userId, long contestId, double money, int support) throws IOException {
System.out.println(String.format("下注比赛了,用户为%d,赛事为%d,下注金额为%f,支持方为%d",userId, contestId, money, support));
System.out.println("下注写入数据库");
System.out.println("=============================");
System.out.println("现在开始统计信息,发rabbitMq");
Bet bet = new Bet();
bet.setUserId(userId);
bet.setContestId(contestId);
bet.setMoney(money);
bet.setSupport(support);
ObjectMapper mapper = new ObjectMapper();
String jsonStr = mapper.writeValueAsString(bet);
fanoutTemplate.convertAndSend(jsonStr);
System.out.println("+++++++++发送出去++++++");
}
}
rabbitMq.xml配置:使用fanout交换器
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd">
<!--配置connection-factory,指定连接rabbit server参数 -->
<rabbit:connection-factory id="connectionFactory"
username="guest" password="guest" host="localhost" port="5672" />
<!--定义rabbit template用于数据的接收和发送 -->
<rabbit:template id="amqpTemplate" connection-factory="connectionFactory"
exchange="exchangeTest" />
<!--通过指定下面的admin信息,当前producer中的exchange和queue会在rabbitmq服务器上自动生成 -->
<rabbit:admin connection-factory="connectionFactory" />
<!--fanout 把一条消息通过多条队列传输出去-->
<rabbit:template id="fanoutTemplate" connection-factory="connectionFactory"
exchange="fanoutExchange"/>
<!--定义queue -->
<rabbit:queue name="fanoutQueue" durable="true" auto-delete="false" exclusive="false" />
<rabbit:queue name="fanoutQueue2" durable="true" auto-delete="false" exclusive="false" />
<!--fanout交换器-->
<rabbit:fanout-exchange name="fanoutExchange">
<rabbit:bindings>
<rabbit:binding queue="fanoutQueue"></rabbit:binding>
<rabbit:binding queue="fanoutQueue2"></rabbit:binding>
</rabbit:bindings>
</rabbit:fanout-exchange>
</beans>
最后建一个main函数来执行:package com.lhx.run;
import com.lhx.contest.PlayBall;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by lhx on 2016/9/19 10:45
*
* @Description
*/
public class RunMain {
public static void main(final String... args) throws Exception {
AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");
PlayBall playBall = (PlayBall) ctx.getBean("playBall");
playBall.bet(8L, 101L, 100.00D, 1);
Thread.sleep(1000);
ctx.destroy();
}
}
副服务器端(MQ接收端)编写两个类(可以扩展任意多个类)来处理接收,需要先对服务器代码进行maven构建,从而可以直接引用服务器里面的bean对象。
package com.lhx.fanout;
import com.lhx.bean.Bet;
import org.codehaus.jackson.map.ObjectMapper;
import java.io.IOException;
/**
* Created by lhx on 2016/9/5 17:52
*
* @Description
*/
public class FanoutConsumer {
public void getInfo(String foo) throws IOException {
System.out.println("处理端1已经收到信息:" + foo);
System.out.println("============处理端1开始处理===========");
ObjectMapper mapper = new ObjectMapper();
Bet bet = mapper.readValue(foo, Bet.class);
System.out.println("============处理端1统计开始===========");
System.out.println("下注人数+1,userId为:" + bet.getUserId());
}
}
package com.lhx.fanout;
import com.lhx.bean.Bet;
import org.codehaus.jackson.map.ObjectMapper;
import java.io.IOException;
/**
* Created by lhx on 2016/9/5 17:52
*
* @Description
*/
public class FanoutConsumer2{
public void getInfo(String str) throws IOException {
System.out.println("处理端2已经收到信息:" + str);
System.out.println("============处理端2开始处理===========");
ObjectMapper mapper = new ObjectMapper();
Bet bet = mapper.readValue(str, Bet.class);
System.out.println("============处理端2统计开始===========");
System.out.println("下注金额累加,金额为:" + bet.getMoney());
}
}
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd">
<!--配置connection-factory,指定连接rabbit server参数 -->
<rabbit:connection-factory id="connectionFactory"
username="guest" password="guest" host="localhost" port="5672" />
<!--定义rabbit template用于数据的接收和发送 -->
<rabbit:template id="amqpTemplate" connection-factory="connectionFactory"
exchange="exchangeTest" />
<!--通过指定下面的admin信息,当前producer中的exchange和queue会在rabbitmq服务器上自动生成 -->
<rabbit:admin connection-factory="connectionFactory" />
<!--fanout 接收-->
<rabbit:queue name="fanoutQueue" durable="true" auto-delete="false" exclusive="false" />
<rabbit:queue name="fanoutQueue2" durable="true" auto-delete="false" exclusive="false" />
<rabbit:listener-container connection-factory="connectionFactory">
<rabbit:listener ref="fanoutConsumer" method="getInfo" queues="fanoutQueue"/>
<rabbit:listener ref="fanoutConsumer2" method="getInfo" queues="fanoutQueue2"/>
</rabbit:listener-container>
<bean id="fanoutConsumer" class="com.lhx.fanout.FanoutConsumer"/>
<bean id="fanoutConsumer2" class="com.lhx.fanout.FanoutConsumer2"/>
</beans>
再新建一个main1函数来运行
package com.lhx.run;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by lhx on 2016/9/19 10:49
*
* @Description
*/
public class RunMain {
public static void main(String[] args) {
AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");
}
}
先启动接收端的main函数,再启动发送端的main函数。正在等待队列信息的推送,接收端的窗口这时显示的效果如下:
运行发送端的main函数,会把消息发送出去,效果图:
这个时候,马上切换到消费端窗口,会看到处理后的效果: