RabbitMQ應用一:分散業務處理

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



這個時候,馬上切換到消費端窗口,會看到處理後的效果:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章