1. 程式人生 > >java秒殺高併發------秒殺介面高併發秒殺優化 RabbitMQ模式

java秒殺高併發------秒殺介面高併發秒殺優化 RabbitMQ模式

RabbitMQ

我在windows平臺下安裝

整合RabbitMQ

要先安裝 erlang,要依賴他

啟動:
這裡寫圖片描述

安裝了管理介面後
rabbitmq-plugins enable rabbitmq_management
這裡寫圖片描述

SpringBoot整合 RabbitMQ

1.新增依賴 spring-boot-starter-amqp

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-amqp</artifactId
>
</dependency>

新增配置

#rabbitmq
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
#消費者數量
spring.rabbitmq.listener.simple.concurrency= 10
spring.rabbitmq.listener.simple.max-concurrency= 10
#消費者每次從佇列獲取的訊息數量
spring.rabbitmq.listener.simple.prefetch= 1 #消費者自動啟動 spring.rabbitmq.listener.simple.auto-startup=true #消費失敗,自動重新入隊 spring.rabbitmq.listener.simple.default-requeue-rejected= true #啟用傳送重試 spring.rabbitmq.template.retry.enabled=true spring.rabbitmq.template.retry.initial-interval=1000 spring.rabbitmq.template
.retry.max-attempts=3 spring.rabbitmq.template.retry.max-interval=10000 spring.rabbitmq.template.retry.multiplier=1.0

配置類

@Configuration
public class MQConfig {
    public static final String QUEUE ="queue";

    //佇列
    @Bean
    public Queue queue(){
        return new Queue("queue",true);
    }

2.建立訊息接受者

@Service
public class MQReceiver {

    private static Logger log = LoggerFactory.getLogger(MQReceiver.class);
        @RabbitListener(queues = MQConfig.QUEUE)
    public void receiver(String message){
        log.info("recevier"+message);
    }
}

3.建立訊息傳送者

public class MQSender {
    @Autowired
    AmqpTemplate amqpTemplate;

    @Autowired
    RedisService redisService;
      private static Logger log = LoggerFactory.getLogger(MQReceiver.class);

    public void send(Object message){
        //將bean轉為字串
        String msg = redisService.beanToString(message);
        //傳送
        amqpTemplate.convertAndSend(MQConfig.QUEUE,msg);
        log.info("send:"+msg);

    }
}

使用 guest預設是不允許遠端連線的。

RabbitMQ4種交換機模式

訊息是先發到交換機上,再由交換機發送到佇列

SirectExchange 安裝routingkey分發到指定佇列

Direct模式

配置

//佇列
@Bean
public Queue queue(){
    return new Queue(QUEUE,true);
}

傳送

public void send(Object message){
    //將bean轉為字串
    String msg = redisService.beanToString(message);
    //傳送
    amqpTemplate.convertAndSend(MQConfig.QUEUE,msg);
    log.info("send:"+msg);

}

接收


@RabbitListener(queues = MQConfig.QUEUE)
public void receiver(String message){
    log.info("recevier"+message);
}

Topic模式

多關鍵字匹配
配置:

public static final String TOPIC_QUEUE1 ="topic.queue1";public static final String TOPIC_QUEUE2 ="topic.queue2";public static final String TOPIC_EXCHANGE="topicExchange";public static final String ROUTING_KEY1="topic.key1";public static final String ROUTING_KEY2="topic.#";//# 萬用字元//Topic模式 交換機 [email protected] Queue topicQueue1(){    return new Queue(TOPIC_QUEUE1,true);}@Beanpublic Queue topicQueue2(){    return new Queue(TOPIC_QUEUE2,true);}//topic [email protected] TopicExchange topicExchange(){    return new TopicExchange(TOPIC_EXCHANGE);}//繫結@Beanpublic Binding topicBinding1(){    return BindingBuilder.bind(topicQueue1()).to(topicExchange()).with(ROUTING_KEY1);}@Beanpublic Binding topicBinding2(){    return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with(ROUTING_KEY2);}


傳送



public void sendTpoic(Object message){
    //將bean轉為字串
    String msg = redisService.beanToString(message);
    //傳送
    amqpTemplate.convertAndSend(MQConfig.TOPIC_EXCHANGE,MQConfig.ROUTING_KEY1,msg+":1");
    amqpTemplate.convertAndSend(MQConfig.TOPIC_EXCHANGE,MQConfig.ROUTING_KEY2,msg+":2");
    log.info("send topic:"+msg);

}

接收

@RabbitListener(queues = MQConfig.TOPIC_QUEUE1)
public void receiverTopic1(String message){
    log.info("recevier topic 1:"+message);
}

@RabbitListener(queues = MQConfig.TOPIC_QUEUE2)
public void receiverTopic2(String message){
    log.info("recevier topic 2:"+message);
}

這裡寫圖片描述

Fanout 廣播模式
將訊息分發到所有的繫結佇列,無routingkey的概念
還是繫結的之前兩個主題模式建立的佇列

配置

//Fanout模式 交換機Exchange(廣播)
@Bean
public FanoutExchange fanoutExchange(){
    return new FanoutExchange(FANOUT_EXCHANGE);
}

//繫結
@Bean
public Binding fanoutBinding1(){
    return BindingBuilder.bind(topicQueue1()).to(fanoutExchange());
}
@Bean
public Binding fanoutBinding2(){
    return BindingBuilder.bind(topicQueue2()).to(fanoutExchange());
}

傳送

public void sendFanout(Object message){
    //將bean轉為字串
    String msg = redisService.beanToString(message);
    //傳送
    amqpTemplate.convertAndSend(MQConfig.FANOUT_EXCHANGE,"",msg);
    log.info("send fanout: :"+msg);

}

接收
還是對那兩個主題的佇列進行監聽

@RabbitListener(queues = MQConfig.TOPIC_QUEUE1)
public void receiverTopic1(String message){
    log.info("recevier topic 1:"+message);
}

@RabbitListener(queues = MQConfig.TOPIC_QUEUE2)
public void receiverTopic2(String message){
    log.info("recevier topic 2:"+message);
}

這裡寫圖片描述

Headers

通過新增屬性 key-value匹配

配置

//Hander模式


@Bean
public HeadersExchange headerstExchange(){
    return new HeadersExchange(HEADERS_EXCHANGE);
}

@Bean
public Queue headerqueue(){
    return new Queue(HEADERS_QUEUE,true);
}


@Bean
public Binding headersBinding(){
    Map<String,Object> map  = new HashMap<String, Object>();
    map.put("header1","value1");
    map.put("header2","value2");
    //滿足key-value才會往佇列中放東西
    return BindingBuilder.bind(headerqueue()).to(headerstExchange()).whereAll(map).match();
}

傳送

public void sendHeader(Object message){
    //將bean轉為字串
    String msg = redisService.beanToString(message);
    //傳送
    MessageProperties properties = new MessageProperties();
    properties.setHeader("header1","value1");
    properties.setHeader("header2","value2");
    Message obj = new Message(msg.getBytes(),properties);
    amqpTemplate.convertAndSend(MQConfig.HEADERS_EXCHANGE,"",obj);
    log.info("send header: :"+msg);

}

接收

@RabbitListener(queues = MQConfig.HEADERS_QUEUE)
public void receiverHeader(byte[] message){
    log.info("recevier header :"+new String (message));
}

這裡寫圖片描述

使用RabbitMQ改寫秒殺功能

思路:減少資料庫訪問

1.系統初始化,把商品庫存數量載入到Redis

2.收到請求,Redis預減庫存,庫存不足,直接返回,否則3

3.請求入隊,立即返回排隊中

4.請求出隊,生成訂單,減少庫存

5.客戶端輪詢,是否秒殺成功

系統初始化,把商品庫存數量載入到Redis

如何在初始化的時候就將庫存資料存入快取中

實現這個藉口 InitializingBean

public class MiaoshaController implements InitializingBean{

然後
實現方法

//在系統初始化的時候呼叫
@Override
public void afterPropertiesSet() throws Exception {
    List<GoodsVo> goodsVoList =  goodsService.listGoodsVo();
    if (goodsVoList==null){
        return ;
         }
    for (GoodsVo goodsVo : goodsVoList){
        //將商品庫存載入到redis中  
          redisService.set(GoodsKey.getMiaoshaGoodsStock,""+goodsVo.getId(),goodsVo.getStockCount());
    }
}      

收到請求,Redis預減庫存,庫存不足,直接返回,否則3 請求入隊,立即返回排隊中

//減少庫存
long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock,""+goodsId);
if (stock<0){//如果沒有庫存了就返回秒殺失敗了。
    return Result.error(CodeMsg.MIAO_SHA_OVER);
}
//判斷是否秒殺到了。
MiaoshaOrder order  = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(),goodsId);
    if(order!=null) {//已經秒殺到了
        return Result.error(CodeMsg.REPEATE_MIAOSHA);
    }
//入隊
MiaoshaMessage mm = new MiaoshaMessage();
mm.setGoodsId(goodsId);
mm.setUser(user);
mqSender.sendMiaoshaMessage(mm);
return  Result.success(0);//排隊中
}

請求出隊,生成訂單,減少庫存

接收

   @RabbitListener(queues = MQConfig.MIAOSHA_QUEUE)
    public void receiver(String message) {
        log.info("recevier" + message);
        //將訊息還原為bean物件
        MiaoshaMessage mm = redisService.stringToBean(message, MiaoshaMessage.class);
        MiaoshaUser user = mm.getUser();
        long goodsId = mm.getGoodsId();
        //減庫存,訂單
        GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
        int stock = goods.getGoodsStock();
        if (stock <= 0) {//沒有庫存了.
            return;
        }
              //判斷是否重複秒殺
        MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
        if (order != null) {
            return;
        } else {
            //秒殺
            //減庫存 下訂單 寫入秒殺訂單
             miaoshaService.miaosha(user, goods);
        }

    }  

客戶端輪詢,是否秒殺成功

定義個介面

//秒殺排隊後輪詢
    //秒殺成功返回訂單id
    //秒殺失敗返回 -1
    //排隊中 返回 0
    @RequestMapping("/result")
    public Result miaoshResult(Model model, MiaoshaUser user, @RequestParam("goodsId") long goodsId) {
        //沒登入就跳轉到登入頁面
        model.addAttribute("user", user);
        if (user == null) {
            return Result.error(CodeMsg.SESSION_ERROR);
        }
//查詢下是否生成了訂單
        long rsult = miaoshaService.getMiaoshResult(user.getId(), goodsId);
        return Result.success(rsult);


    }

miaoshaService.getMiaoshResult

public long getMiaoshResult(Long userId, long goodsId) {
    MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(userId,goodsId);
    if (order!=null){
        return order.getId();
    }else {
        boolean isOver = getGoodsOver(goodsId);
        if (isOver){//賣完了還在沒有訂單
            return -1;
        }else {
            return 0;
        }

    }

沒有訂單有兩種情況,賣完了失敗,和排隊中

在上面的秒殺那做個標記。這個商品是否秒殺完了。存入redis中。
之後去判斷是否存在這個key就知道是哪種情況

@Transactional
public OrderInfo miaosha(MiaoshaUser user, GoodsVo goods) {
    //減少庫存 下訂單 寫入秒殺訂單
    boolean success = goodsServic.reduceStock(goods);
    //減庫存成功了才進行下訂單
    if (success) {
        //訂單表,秒殺表
        OrderInfo orderInfo = orderService.createOrder(user, goods);
        return orderInfo;
    } else {//說明商品秒殺完了。做一個標記
        setGoodsOver(goods.getId());
        return null;
    }

}

public void setGoodsOver(Long goodsId) {
    redisService.set(MiaoshaKey.isGoodsOver,""+goodsId,true);


}

private boolean getGoodsOver(long goodsId) {
    return redisService.exists(MiaoshaKey.isGoodsOver,""+goodsId);
}

前端請求做判斷。

相關推薦

java併發------介面併發優化 RabbitMQ模式

RabbitMQ 我在windows平臺下安裝 整合RabbitMQ 要先安裝 erlang,要依賴他 啟動: 安裝了管理介面後 rabbitmq-plugins enable rabbitmq_management Sprin

php結合redis實現並發下的搶購、功能

緩存 使用 fclose rtl global 簡單模擬 解決 fun 非阻塞 搶購、秒殺是如今很常見的一個應用場景,主要需要解決的問題有兩個: 1 高並發對數據庫產生的壓力 2 競爭狀態下如何解決庫存的正確減少("超賣"問題) 對於第一個問題,已經很容易想到用緩存來處理搶

天貓研發團隊(4面全題目):併發壓測+Mina+事務+叢集+架構

  一面 常見集合類的區別和適用場景 併發容器瞭解哪些? 如何判斷連結串列是否有環 concurrentHashMap如何實現 叢集伺服器 如何application 共享 JAVA網路程式設計中:BIO、NIO、AIO的區別和聯絡

並發系統的設計及實踐

擴展性 varnish 既然 減少 單個 發出 所有 emc 競爭 一個大型網站應用一般都是從最初小規模網站甚至是單機應用發展而來的,為了讓系統能夠支持足夠大的業務量,從前端到後端也采用了各種各樣技術,前端靜態資源壓縮整合、使用CDN、分布式SOA架構、緩存、數據庫加索引、

PHP和Redis實現在並發下的搶購及功能示例詳解

出現 comm 不同 高並發 日誌 sql 推薦 結果 update 搶購、秒殺是平常很常見的場景,面試的時候面試官也經常會問到,比如問你淘寶中的搶購秒殺是怎麽實現的等等。搶購、秒殺實現很簡單,但是有些問題需要解決,主要針對兩個問題: 一、高並發對數據庫產生的壓力二、競爭

java 獲取當天(今日)零點零分零

pri main void timezone println second one amp offset 兩種方法 一種得到的是時間戳,一種得到是日期格式: 1.日期格式的 Calendar calendar = Calendar.getI

JAVA微服務架構,併發,高效能,可用,分散式,叢集,快取,電商實戰教程下載

JAVA微服務架構,高併發,高效能,高可用,分散式,叢集,快取,電商實戰教程下載39套Java架構師,高併發,高效能,高可用,分散式,叢集,電商,快取,微服務,微信支付寶支付,公眾號開發,java8新特性,P2P金融專案,程式設計,功能設計,資料庫設計,第三方支付,web安全,效能調優,設計模式,資

java中 快速獲取當天0點0分0(00:00:00),23點59分59(23:59:59)

// 利用Apache lang包快速獲取凌晨0點0分0秒,23點59分59秒字串         System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd 00:0

Java併發程式設計入門與併發面試

第6章 J.U.C之AQS講解 AQS是J.U.C的重要元件,也是面試的重要考點。這一章裡將重點講解AQS模型設計及相關同步元件的原理和使用,都非常實用,具體包括:CountDownLatch、Semaphore、CyclicBarrier、ReentrantLock與鎖、Condition等。這些元

Java 併發第二階段實戰---併發設計模式,記憶體模型,CPU一致性協議,volatile關鍵字剖析

汪文君高併發程式設計第二階段01講-課程大綱及主要內容介紹. 汪文君高併發程式設計第二階段02講-介紹四種Singleton方式的優缺點在多執行緒情況下. 汪文君高併發程式設計第二階段03講-介紹三種高效優雅的Singleton實現方式. 汪文君高併發程式設計第二階段04講-多執行緒的休息室WaitSet詳細

java併發實戰(十)——併發除錯和JDK8新特性

由於之前看的容易忘記,因此特記錄下來,以便學習總結與更好理解,該系列博文也是第一次記錄,所有有好多不完善之處請見諒與留言指出,如果有幸大家看到該博文,希望報以參考目的看瀏覽,如有錯誤之處,謝謝大家指出與留言。一、內容提要 多執行緒除錯的方法 執行緒dump及分析 JDK

淺談Android記憶體洩漏&&實現不死的無介面後臺Service

以前看大神的部落格,都說沒有碰到過記憶體洩漏或者OOM就不算真正的搞過Android…以前我覺得這事離我還好遠…. 沒想到,這次真的是遇上了!! 專案需求是這樣的:app開機自啟,啟動之後app就一直在run,是一直run!(開機自啟,現在大概也就只有在AR

併發介面併發問題

事故 前些天上線的掃碼送會員活動。 場景:使用者登入賬號之後,掃二維碼,送七天黃金會員,限制每個帳號只能領取一個 有惡意使用者刷介面,在高併發下越過限制。 原因 領取會員流程: 1.後端先生成卡卷,將卡號放到訊息佇列中

Qt 實現啟動介面,3後自動消失

         很多大型軟體都有啟動介面,把程式的部分初始化後,再顯示主介面。         Qt如何實現該功能呢,例如我們在main.cpp例項化啟動類,然後顯示,sleep(30

併發之API介面限流

   在開發高併發系統時有三把利器用來保護系統:快取、降級和限流 快取 快取的目的是提升系統訪問速度和增大系統處理容量 降級 降級是當服務出現問題或者影響到核心流程時,需要暫時遮蔽掉,待高峰或者問題解決後再開啟 限流 限流的目的是通過對併發訪問/請求進行限速,或者對一個

JavaNIO Java進階知識點5:服務端併發的基石 - NIO與Reactor模式以及AIO與Proactor模式

javaNIO對於多路複用io(同步非阻塞io)的實現 package test; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.SelectionKey; import

java 記錄專案中把換算為天、時、分、的方式

public static void main(String[] args) throws Exception {int seconds=1000000;//60*60*24*2+3600*4+180+58;int day=seconds/(60*60*24);//換成天i

java---仿QQ登陸介面》---完全手寫

package com.fenghuo.view; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; import

Android 歡迎介面停留3的實現

0.寫在前面 在這篇教程中來實現一個類似於微信的的延遲3秒再進入主介面的效果。 1.專案準備 先新建一個空的android專案。裡面只自帶一個MainActivity,首先我們再新建一個Activity叫做WelcomeActivity繼承自A

併發處理之介面限流

最近開發的搶購活動上線後發現了兩個比較明顯的問題,其一:活動一開始,介面訪問量劇增;其二:黑名單中增加了一大批黑名單使用者(或者說IP),這其中就包含了一些惡意使用者或機器人刷介面。 針對一些高併發的介面,限流是處理高併發的幾大利劍之一。一方面,限流可以防止介面被刷,造成不