基於SpringBoot、Redis、RocketMQ的秒殺系統設計

Sonihr秒殺系統設計

寫在前面

  1. 項目來源:

    https://github.com/Grootzz/seckill

  2. 對應課程

    https://coding.imooc.com/class/168.html

新技術棧:消息中間件

消息隊列可以解決什麼問題?

  1. 解決項目與項目之間消息傳遞和項目耦合的問題。具體而言,A項目向B項目傳遞數據,原來是通過A項目向B項目發送請求,等待返回數據,如果B項目掛了,那A項目就永久阻塞了。如果在A和B項目之間有一個MQ,那麼A向MQ發送請求,B從MQ中獲取請求,這樣就解耦了,而且將AB之間的通訊變成了異步的。
  2. kafka基於scala開發,吞吐量特別高,隊列堆積量很大,支持消費者長時間離線,一般用於日誌。rocketMQ基於kafka思想,通過java開發,提供豐富的拉取模式(推送是MQ向消費者傳遞,拉取是消費者向MQ主動獲取),支持事務消息,億級消息堆積能力,吞吐量很高,比kafka低一點。RabbitMQ,基於erlang開發

RabbitMQ

如何安裝

  1. https://blog.csdn.net/axela30w/article/details/81051287
    erlang可以在erlang中文網下載,官方網站非常緩慢。上面這篇博客的erlang安裝過程沒毛病,注意每次配置完成後要關閉當前cmd窗口再打開一個命令框,因爲你後配置的環境變量對當前的黑框框命令行窗口來說沒用,
    https://www.cnblogs.com/vaiyanzi/p/9531607.html
    rabbitMQ直接在官網下載即可,配置環境變量的方式如上問所示即可,均測試通過。然後先通過rabbitmq-plugins enable rabbitmq_management命令安裝插件,然後rabbitmq-service start開啓rabbitMQ服務,此時在瀏覽器地址欄輸入http://localhost:15672/#/即可進入rabbitMQ的服務頁面。

RocketMQ

基本知識點概述

  1. windows下命令行
    mqnamesrv.cmd -n localhost:9876
    mqbroker.cmd -n localhost:9876 autoCreateTopicEnable=true &
    mqshutdown.cmd broker
    mqshutdown.cmd namesrv
  2. 架構介紹
    • nameServer 起到控制中心/路由的作用,主要負責註冊其他組件,類似於Tomcat的Server。
    • broker 將broker的信息註冊到name server中,負責消息處理,是MQ的核心
    • producer 從name server中找broker
    • consumer 類似上面
  3. 消息生產和消費介紹
    • 消息:普通消息、順序消息、事務消息。順序消息實現有序消費,事務消息可以解決分佈式事務實現數據最終一致性。
    • 消費模式:1.Push,推送,consumer向broker發出請求,保持長連接,broker每5s檢測一次是否有消息,如果有就推送給consumer。Broker會主動記錄消息消費的偏移量。 2.Pull,拉取,定時獲取,獲取消息方便,負載均衡性能可控,但是消息的及時性差並且需要手動記錄信息消費的偏移量。 但是原理都是consumer向broker請求,只是實現機制不同
    • RocketMQ發送消息會默認存儲到4個隊列中(數量可配置)以應對併發。
    • ACK機制:就是有一些應答狀態的消息。
  4. 生產者邏輯:
    • 創建DefaultMQProducer
    • 設置NameSrvAddr
    • 創建一個Message,要指定Topic,tags用於消息過濾,keys是消息的唯一值,用於定位消息信息,body,需要用字節碼
    • 用SendResult sendResult = producer.send(message) 發送消息
    • producer.shutdown() 關閉producer
  5. 消費者邏輯:
    • 創建DefaultMQPushConsumer
    • 設置NameSrvAddr
    • 設置subscribe,訂閱要讀取的主題信息consumer.subscribe(“topic”,subExpression:"*") 後面參數爲過濾規則
    • 創建信息監聽器MessageListener,consumer.registerMessageListener(new MessageListenerConcurrently{@override …})
    • 開啓consumer,consumer.start()
  6. 順序消息
    • 順序消息是指按照消息的發送順序來消費,rocketMQ保證分區(queue)有序,但是不保證全局有序。一個broker中有多個queue,因此無法保證全局有序。
    • 解決方案:
      //消息會發送到隊列1中,send的第三個變量arg表示發到下標爲幾的隊列
      Message message = new Message("TopicTest" /* Topic */,
              "順序" /* Tag */,"順序信息".getBytes(RemotingHelper.DEFAULT_CHARSET));
      producer.send(message, new MessageQueueSelector() {
          @Override
          public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
              Integer index = (Integer)o;
              return list.get(index);
          }
      },1);
      
  7. 事務消息

官網樣例說明

  1. producer <----> MQ <----> consumer 同步和異步指的是發送者和MQ之間
  2. Send Messages Synchronously:可靠的同步傳輸,常常用於重要的 通知消息、短信通知等場景。(這邊可以用於註冊)
    • 原理:同步發送是指消息發送方發出數據後,會在收到接收方發回相應之後才發下一個數據包的通信方式。
  3. Send Messages Asynchronously:異步傳輸,常用於時間敏感的業務場景
    • 原理:發送方發出數據後,不等待接收方發回相應,而是接着發下一個數據包。RocketMQ的異步發送需要用戶實現異步發送回調接口(SendCallBack),這個接口規定了兩個方法onSuccess和onException,規定了成功或異常時的處理方式,即當MQ收到數據後會異步回調這個接口的實現類,從而通知調用方請求的響應結果。
  4. Send Messages in One-way Mode:單向傳輸用於可靠性中等的場景,比如日誌收集
    • 原理:發送方只負責發送消息,不等待MQ服務器迴應且沒有回調函數觸發,只請求不等待迴應。發送消息的速度非常快,一般在微秒級別。但是相對而言可靠性就比較低。

新技術棧:Druid連接池

SpringBoot中配置Druid連接池

  1. SpringBoot爲2.1.5,只需要在maven中導入如下依賴即可:

     <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>druid-spring-boot-starter</artifactId>
         <version>1.1.17</version>
     </dependency>
    
  2. 在application.properties中寫入如下配置
    ```
    spring.datasource.username=xxxx
    spring.datasource.password=xxxxxxx
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/xxxxxxx?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8

     spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
    
     #druid pool standard config
     spring.datasource.druid.max-active=30
     spring.datasource.druid.initial-size=3
     spring.datasource.druid.min-idle=3
     spring.datasource.druid.max-wait=12000
     spring.datasource.druid.time-between-eviction-runs-millis=60000
     spring.datasource.druid.min-evictable-idle-time-millis=30000
     spring.datasource.druid.pool-prepared-statements=true
     spring.datasource.druid.max-open-prepared-statements=30
    
     spring.datasource.druid.validation-query=select 1
     spring.datasource.druid.test-while-idle=true
     spring.datasource.druid.test-on-borrow=false
     spring.datasource.druid.test-on-return=false
    
     spring.datasource.druid.stat-view-servlet.enabled=true
     spring.datasource.druid.stat-view-servlet.login-username=xxxxx
     spring.datasource.druid.stat-view-servlet.login-password=xxxx
     spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
    
     spring.datasource.druid.web-stat-filter.enabled=true
     spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*
     ```
    
  3. 上述配置分爲三部分,1.JDBC連接用的 2.druid連接池的配置 3.druid監控需要配置一個servlet和一個filter。

  4. 但是很奇怪,我這樣配置在IE裏是正確的,但是在chrome中會反覆跳轉會login.html。

新技術棧:Redis

安裝redis(windows和linux都可以安裝)

  1. windows安裝只有低版本的,高版本的在linux中,但是本地測試的時候可以先用windows環境測試。
  2. redis的配置有很多人都說了,我配置好了redis.conf配置文件,阿里雲服務器也開啓了了6379端口,但是無法通過校園網連接,懷疑是校園網防火牆禁用了這個端口。
  3. 未來肯定要用linux部署的,因爲這個項目是分佈式的,而不是單機了。

SringBoot整合redis

  1. https://blog.csdn.net/dingse/article/details/80572783

  2. 首先在pom中配置,starter配置redis,後一個配置jedis。

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
    </dependency>
    

    然後要在application.properties中進行setter注入,這裏的配置文件都會被注入到RedisProperties類中,然後這個類會被注入到容器中。

    #redis
    #redis數據庫索引(默認爲0)
    spring.redis.database=0
    #redis服務器IP地址
    spring.redis.host=localhost
    #redis端口號
    spring.redis.port=6379
    #redis密碼,默認爲空
    spring.redis.password=
    #連接redis超時時間(毫秒)
    spring.redis.time-out=0ms
    #jedis連接池
    ###############################
    #最大等待時間
    spring.redis.jedis.pool.max-wait=1000ms
    #最小空閒數量
    spring.redis.jedis.pool.min-idle=1
    #最大空閒數量
    spring.redis.jedis.pool.max-idle=10
    #最大連接數量
    spring.redis.jedis.pool.max-active=1000
    

    最後寫一個RedisPoolFactory,利用RedisProperties來生成一個JedisPool實例,並且通過@Bean注入到容器中,注入到容器中的bean名稱爲方法名,類型爲返回值類型。因爲@Autowired是按照類型注入的,因此名字可以隨意。

    @Service
    public class RedisPoolFactory {
    
        //自動注入redis配置屬性文件
        @Autowired
        private RedisProperties properties;
    
        @Bean
        public JedisPool jedisPool(){
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxIdle(properties.getJedis().getPool().getMaxIdle());
            config.setMaxTotal(properties.getJedis().getPool().getMaxActive());
            config.setMaxWaitMillis(properties.getJedis().getPool().getMaxWait().toMillis());
            JedisPool pool = new JedisPool(config,properties.getHost(),properties.getPort(),100);
            return pool;
        }
    
    }
    

RedisService與XxxKeyPrefix

  1. RedisService類似慕課網那個免費秒殺項目中的redisDao,本質上就是寫一個通用的redis交互類,類似於UserDao,ProductDao一樣。腦補一下,作爲緩存肯定要寫set和get方法,但是因爲redis中不能存對象,所以我們有兩種處理方式1.將對象轉成json以字符串形式保存。2.將對象序列化後以字符串形式保存。前者對人類友好但是相對而言速度慢,可以採用alibaba的fastJson實現,後者可以使用google的ProtoStuff實現快速序列化。本項目中目前還是轉化成json。
  2. 第一步轉化成json存在的問題是,首先我們要寫兩個工具方法,string轉對象,對象轉String。這兩個方法都必須指定對象的Class類型,所以可以用泛型Class。
  3. 爲了保證key的標識有意義且唯一,因此通過模板設計模式,KeyPrefix接口,BasKeyPrefix抽象類,OrderPrefix,UserPrefix…作爲前綴,加上key成爲真正的key,可以保證不同的業務用不同的key,不會相互干擾。最好在getPrexfix的時候加上className,這樣可以儘量保證不同業務間的key不同。
    @Override
    public String getPrefix() {
        String simpleName = getClass().getSimpleName();
        return simpleName + ":" + prefix;
    }
    
  4. prefix中繼承了過期時間的參數,在baseKeyPrefix中默認爲0,不需要設置過期時間的採用父類默認構造方法即可,否則在構造方法中傳入參數。在GoodsKeyPrefix中可以要先寫幾個static變量,作爲預設的前綴。
  5. 在redisService中增加了一些常用的方法,exists,incr,decr,delete,判斷是否存在,自增自減,刪除。

登錄系統設計亮點

全局處理Exception

  1. SpringBoot中會將所有的異常以統一的頁面展示,用@ControllerAdvice註解一個全局異常Handler類,在該類中不同的方法用不同的@ExceptionHandler(xxxxException.class)註解,表示對某某異常進行攔截,然後在方法中傳入參數爲request和exception e,可以對e進行操作。本項目中是判斷e的不同類型然後返回不同的值。
  2. 和mmall的項目一樣,返回值要用一個複用的對象,本項目中用的是Result,包含msg,data和statusCode。如果正確則調用success返回data,否則返回CodeMsg,無論是T data還是CodeMsg,前端收到的都是json數據。

分佈式session

  1. 因爲分佈式情況下,有多臺服務器,會通過負載均衡服務器進行分配,因此要將session分佈式保存。
  2. 因爲服務器是分佈式的,因此想到用redis保存<token,session_data>,其中session_data表示本來user對象應該放在session中,但是因爲是分佈式session,因此要把user對象放到redis中。token是服務器生成的UUID,並且通過cookie交給瀏覽器緩存,緩存時間和redis的緩存時間一致,爲2天。流程爲:瀏覽器發起登錄請求->服務器接受請求併到數據庫中做密碼比對->mybatis返回user對象->uuid生成token-><token,user>放入redis中->將token放入cookie並返回給瀏覽器->瀏覽器再次發出請求時,當需要進行用戶鑑權時,先從redis中獲取user對象,如果獲取不到,再穿透到數據庫中查找。這一步是爲了鑑權時,從session中獲取user來判斷是否有權限,還有必須你需要在頁面上顯示用戶信息,每次都要從緩存裏獲取時,也要用到分佈式session
  3. 僅僅談登錄而已的話,流程爲:瀏覽器登錄請求->服務器查redis<userId(唯一性),user對象)>->如果查到則返回user對象並比較user對象的密碼和請求中的密碼是否一致->如果沒查到則穿透到數據庫中,並且set進redis中->把token和user放入redis中
  4. 修改密碼的時候,先插入數據庫中,刪除redis中原來的值,然後設置爲新值。每次只要執行與token有關的方法,都重新生成cookie,重新生成token,重新插入redis中,這樣可以更新token。
  5. 當我們在查看購物車的時候,需要取出session中的userid作爲查詢條件, 在本項目中就是要從redis中根據token取出user。所以每次我都要判斷請求攜帶參數或cookie中是否有token,這樣冗餘代碼太多了,我們可以用WebMvcConfiguration+HandlerMethodArgumentResolver組合來解決。其實本質上相當於是SpringMVC中在mvc.xml中配置一個handlermethodargumentResolver。通過代碼可以看出,resolver實現了職責鏈的設計模式,類似springMVC中handlerAdapter的樣子,先用support判斷當前resolver是否可以解析,如果可以就進行解析,邏輯是攔截參數中有Uesr類的controller方法,如果是,則從request中獲取cookie和parameter,然後從redis中找user,然後返回user實例。下面給出在SpringMVC中的配置方法
    <!-- public class CurrentUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver{...} //具體代碼就不寫了 -->
    <!-- 配置文件 -->
    <mvc:argument-resolvers>  
        <bean class="org.study.lyncc.web.resolver.CurrentUserHandlerMethodArgumentResolver"/>  
    </mvc:argument-resolvers>  
    

秒殺優化(緩存篇)

緩存的模板流程

  1. 查詢:先查緩存,緩存沒有就查數據庫,數據庫查完後寫入緩存。
  2. 插入:先插入數據庫,再插入緩存。如果反過來,就失去了數據庫持久化的保障。
  3. 更新:先更新數據庫,然後讓原緩存更新。否則,失去數據庫持久化保障。
  4. 刪除:先從數據庫刪除,再讓緩存失效。否則,先讓緩存失效,但是數據庫還沒刪,如果有請求訪問redis,會重新加載將要被刪除的緩存,然後此時數據庫刪除了,但是緩存還未刪除。
  5. 總結:要保證一切讀都是先從緩存讀,一起寫都是先向數據庫寫。

頁面緩存

  1. 對goodlist商品頁面進行緩存,即不走渲染流程,而是直接手動渲染。(這一步因爲我是前後端分離,因此我不用處理)
  2. 手動渲染頁面,而不是交給springboot。適用於變化不大的數據,緩存時間比較短。

對象緩存

  1. 分佈式Session,緩存user信息。
  2. 查找秒殺訂單時候,查redis。當秒殺邏輯下單成功後,保存到redis中。

頁面靜態化(html+ajax)

  1. 靜態頁面是網頁(html、htm)的代碼都在頁面中,不是用類似jsp的方式動態生成頁面的。
  2. 什麼意思?就是頁面存的是HTML,動態數據通過接口從服務器獲取,好處是不用動態生成頁面而是直接返回htm、html頁面,再用過ajax填充數據,類似於刷淘寶的時候,有的數據資源其實是沒出來的,要等等才能出來,這是一個道理。否則如果要等界面渲染,用戶就不能向後拖拽,要等到全部從服務器獲取到之後再返回,
  3. 動態數據通過ajax獲取後端發來的json數據然後解析,但是前端的其他內容都是靜態的。
  4. 對商品列表頁,商品詳情頁和訂單頁都做靜態化處理,ajax異步獲取服務端接口信息,這也是前後端分離的作用。

靜態資源優化手段

  1. JS/CSS壓縮,檢索流量。(會減少控制之類的無用字符,webpack工具)
  2. 多個JS/CSS組合,減少連接數。(服務器支持,比如Tengine,將多個JS/CSS請求合併成一次請求。)代碼層面上可以用一個js包括多個js,訪問總js即可。
  3. CDN,將服務器數據緩存到全網的很多個CDN節點上,這樣用戶總是訪問自己開銷最小的節點。CDN:內容分發網絡。

解決超賣

  1. 查詢語句中加入update t_order set count = count -1 where … and count > 0,通過數據庫保證非負。
  2. 解決一個用戶秒殺兩個商品,可以將(用戶,商品)建立唯一索引,插入之前先查詢數據庫是否存在,如果存在則直接返回重複秒殺錯誤。也可以進行優化,第一次生成秒殺訂單成功後,將訂單存儲在redis中,再次讀取訂單信息的時候直接從redis中讀取即可。

回顧

  1. 瓶頸在哪裏?數據庫,如何解決?用庫存
  2. 用戶發起請求時,通過做頁面靜態化可以將應用緩存在客戶的瀏覽器中。
  3. 請求到達網站之前,可以讓請求首先訪問CDN,獲得最近的節點。
  4. Nginx等服務器也可以加緩存。
  5. 頁面級緩存
  6. 對象級緩存
  7. 採用緩存必然會產生數據不一致,需要自己進行取捨。

秒殺優化(接口優化篇)

總思路

  1. 系統初始化時,把商品存庫數量加載到redis中
  2. 當收到秒殺請求後,redis預減庫存,庫存不足則直接返回
  3. 秒殺成功的請求入rabbitMQ,立即返回“正在搶購頁面…”,當異步下單成功後才返回訂單。
  4. 客戶端輪詢是否秒殺成功,服務器請求出隊,生成訂單,減少庫存。

redis預減庫存

  1. 優化原因:進一步減少對數據庫的訪問
  2. 初始化SeckillController時,實現InitializingBean接口,並實現setProperties方法,在這個方法中查詢一次mysql數據庫,並且把所有的商品信息都寫入緩存中。緩存中存儲了初始數據,那麼之後的減少都可以在redis中完成。

內存標記

  1. 優化原因:減少對redis的訪問,因爲redis有網絡開銷
  2. 用hashmap來存儲goodId的狀態,是否售罄。因爲hashmap是非線程安全的,所以我們根本來說是用數據庫來保證一致性的,所以對hashmap的線程安全要求沒那麼高。比如redis已經減少到0了,此時還有100個線程,那比如有50個線程中因爲線程標記而減少了對redis的開銷,就已經達到了目的。

異步下單

  1. 優化原因:請求先入隊列緩衝,再異步下單,增強用戶體驗
  2. 利用RocketMQ的同步模式,因爲對交易而言對實時性要求要讓步於可靠性。當用戶下單時,直接給用戶返回下單成功,但是其實真正的下單工作是交給MQ的接受者來完成的,比如生成訂單,支付等複雜費時的操作都可以異步的完成。
  3. 客戶端可以查詢當前訂單狀態,或者由服務器向客戶端發送郵件/短信通知異步處理的結果。

數據庫分庫分表

  1. 推薦中間件:阿里巴巴開發的mycat

小結(一圖流)

秒殺優化(安全優化篇)

總思路

  1. 防止接口暴露。
  2. 驗證碼。1. 防止刷接口 2. 可以分散用戶的請求,即錯峯。
  3. 接口限流。

驗證碼與防止接口暴露

  1. 驗證碼和秒殺接口是對應的。什麼意思呢?試想一下,假如開放秒殺後就暴露出秒殺接口,那就有可能有黑客刷秒殺接口,因爲在開放秒殺後接口就是唯一的了。但是加上驗證碼之後,每次驗證碼提交後都會隨機生成一個與用戶名、商品、隨機UUID相關的秒殺接口,每個人每一次的秒殺接口都不同,因此避免了刷接口。
  2. 首先訪問miaosha/verifyCode?goodsId=1,服務器根據goodsId、user生成一個驗證碼,並將驗證碼的計算結果放到redis中。然後訪問miaosha/path?goodsId=1&verifyCode=xxx,如果verifyCode與緩存中一致,則返回一個秒殺地址,秒殺地址是UUID隨機生成的,存放在redis中,key爲前綴+userId+goodsId。
  3. 生成驗證碼的過程是生成一張圖品,BufferdImage類,Graphic類用於繪圖,包括背景、邊框、噪聲、字符串。字符串是計算表達式,將計算表達式exp用ScriptEngine的eval方法可以直接計算出結果,或者利用算法將中綴表達式轉化爲後綴表達式(數字直接輸出,符號(加減乘除左括號)入棧,輸出所有優先級小於等於當前入棧符號的符號,遇到右括號則輸出至左括號,左括號彈出但不輸出。),然後計算結果。補充知識:後綴轉中綴->先把後綴轉換爲樹,在中序遍歷一次

接口防刷限流

  1. 基礎版本:redis中保存key爲uri+IP,每次訪問的時候都記錄訪問的真實IP。這裏IP的獲取如果沒有配置反向代理可以直接從request.getRemoteAddr來獲取。如果用Nginx配置了反向代理需要在配置中配置保留代理前的IP地址

    https://blog.csdn.net/b1303110335/article/details/77173548
    https://blog.csdn.net/youanyyou/article/details/79406454

    然後通過通過request的getHeader方法獲取正確的IP。

    給定redis的這個鍵值對一個過期時間,比如5s,那麼在5s內訪問超過指定次數的就會被拒絕訪問,5s之後緩存失效又會重新放入緩存中,這樣每5秒每個IP就有指定次數的機會。(redis採用惰性過期+定期過期的過期策略,在秒殺環境下,一個用戶經常多次刷新,因此採用惰性過期比較適合。)

  2. 進階版本:

    • 通過攔截器,可以減少對業務代碼的侵入。繼承HandlerInterceptor接口,實現preHandle方法,內部完成redis緩存的相關邏輯。
    • 通過註解實現配置,不依靠配置文件。攔截器獲取方法上的註解,通過註解找到redis緩存多少秒,多少次,是否需要登錄。登錄需要從request的cookie或者url中獲取uesr。
    • 通過ThreadLocal保存user對象,因爲servlet是單實例多線程的,一次請求就是一個線程,在這次請求中所有從UserContext中get的user都是同一個且線程安全的、

壓測

部署到Linux服務器中

  1. 先在Linux服務器中的mysql中建立seckill表,注意這邊要把sql的時間更改掉,改成2019年,不然無法秒殺。
  2. 如何把Springboot中的項目打包成war包?分爲兩步,第一步在pom.xml中加入依賴和插件。
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>
    
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <configuration>
            <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
    </plugin>
    
    然後在MainApp中添加代碼:
    @SpringBootApplication
    public class MainApp extends SpringBootServletInitializer {
    
        public static void main(String[] args) {
            SpringApplication.run(MainApp.class, args);
        }
    
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
            return builder.sources(MainApp.class);
        }
    }
    
  3. 源程序沒有context,當我們部署到服務器tomcat時會產生問題,比如我們打包的war包是叫seckill,我們把seckill.war放到tomcat的webapps中,瀏覽的url應該是域名:端口/seckill/xxx。我們在application.properties中加上一句server.servlet.context-path=/seckill,相當於是指定了上下文爲seckill,其他都不必改。

Jmeter壓測

  1. 阿里雲學生版服務器走mysql查詢大概是80QPS,走redis查詢大概是200QPS,寫了一個腳本來記錄top命令,發現CPU和MEM佔用率都在50%左右,那瓶頸是什麼呢?後來發現,服務器的帶寬是1M的,無法支持大規模併發,因此轉而在本機實現。
  2. 本機瓶頸在CPU,因爲本機既要做壓測,又要充當服務器,實測下來mysql和redis大概都是800到1000的QPS。

模擬多用戶

  1. 不僅數據庫中要有多個用戶,在redis中還要有多個token。
  2. 寫循環insert5000個用戶進表彙總,包括手機、姓名。然後用HttpClient類給服務器發送login請求以獲取token,並將token寫入本地文件中,這個文件就是壓測時的csv文件,其中寫入了userId和token。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章