對於需要處理很多工作的業務接口中,對各種接口的調用往往造成這個接口耗時過長,而各種接口的頻繁調用,也對服務器造成了很大壓力。用線程來解決前面的問題,在線程的新建和銷燬都需要耗費時間,即使用線程池來實現,服務器照樣也有壓力。而提升服務器性能來解決後一個問題,性價比不夠高。如果這時能用上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函數,會把消息發送出去,效果圖:
這個時候,馬上切換到消費端窗口,會看到處理後的效果: