1. 程式人生 > >樂優商城(四十七)秒殺總結

樂優商城(四十七)秒殺總結

目錄

一、快取優化

1.1 頁面快取

將不經常改動的頁面直接快取到redis中,然後用Thymeleaf檢視解析器將快取的頁面直接渲染出來。

1.2 物件快取

將經常使用的物件資訊放入redis中,比如說使用者資訊,抽插redis肯定比抽插資料庫快。但是,這裡面就涉及到一個數據同步問題,即如何保持redis中放入的是最新的資料。策略就是遇到資料更新的時候,先更新資料庫中的資訊,然後使快取失效,當再次拉取資料的時候就會從資料庫中獲取,第一次獲取成功後就放入快取當中。

1.3 頁面靜態化

將頁面直接快取到使用者的瀏覽器上,或者將頁面直接轉化為靜態網頁。靜態化是指把動態生成的HTML頁面變為靜態內容儲存,以後使用者的請求到來,直接訪問靜態頁面,不再經過服務的渲染。而靜態的HTML頁面可以部署在nginx中,從而大大提高併發能力,減小tomcat壓力。通過Thymeleaf模板引擎來生成靜態網頁。

1.4 靜態資源優化

js/css壓縮,減少流量;多個js/css組合,減少連線數;CDN優化。

二、資料庫優化

減少對資料庫的訪問,可以提高效能。秒殺時因為有大量使用者進行下訂單操作,所有可以使用訊息佇列來緩解資料庫壓力。同時也可以想辦法優化對redis的訪問,設定記憶體標記等。

三、安全方面相關的優化

3.1 秒殺地址隱藏

功能:防止秒殺地址被刷。

思路:秒殺開始之前,先去請求介面獲取秒殺地址

  • 改造介面,帶上PathVariable引數
  • 新增生成地址的介面
  • 秒殺收到請求,先驗證PathVariable

3.1.1 建立秒殺路徑

Controller

    /**
     * 建立秒殺路徑
     * @param goodsId
     * @return
     */
    @GetMapping("get_path/{goodsId}")
    public ResponseEntity<String> getSeckillPath(@PathVariable("goodsId") Long goodsId){
        UserInfo userInfo = LoginInterceptor.getLoginUser();
        if (userInfo == null){
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
        String str = this.seckillService.createPath(goodsId,userInfo.getId());
        if (StringUtils.isEmpty(str)){
            return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
        }
        return ResponseEntity.ok(str);
    }

Service

將使用者id和秒殺商品的id先進行加密,然後放入redis中,並且設定過期時間為60秒。

/**
     * 建立秒殺地址
     * @param goodsId
     * @param id
     * @return
     */
    @Override
    public String createPath(Long goodsId, Long id) {
        String str = new BCryptPasswordEncoder().encode(goodsId.toString()+id);
        BoundHashOperations<String,Object,Object> hashOperations = this.stringRedisTemplate.boundHashOps(KEY_PREFIX_PATH);
        String key = id.toString() + "_" + goodsId;
        hashOperations.put(key,str);
        hashOperations.expire(60, TimeUnit.SECONDS);
        return str;
    }

3.1.2 路徑驗證

改造建立秒殺訂單介面,讓其先驗證路徑

Controller

Service

    /**
     * 驗證秒殺地址
     * @param goodsId
     * @param id
     * @param path
     * @return
     */
    @Override
    public boolean checkSeckillPath(Long goodsId, Long id, String path) {
        String key = id.toString() + "_" + goodsId;
        BoundHashOperations<String,Object,Object> hashOperations = this.stringRedisTemplate.boundHashOps(KEY_PREFIX_PATH);
        String encodePath = (String) hashOperations.get(key);
        return new BCryptPasswordEncoder().matches(path,encodePath);
    }

3.2 介面限流

功能:限定使用者在某一段時間內有限次的訪問地址。

思路:將使用者訪問地址的次數寫入redis當中,同時設定過期時間。當用戶每次訪問,該值就加一,當訪問次數超出限定數值時,那麼就直接返回。

實現:為了具有通用性,以註解的形式呼叫該方法。

3.2.1 定義註解

package com.leyou.seckill.access;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Author: 98050
 * @Time: 2018-11-23 23:38
 * @Feature: 介面限流注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AccessLimit {

    /**
     * 限流時間
     * @return
     */
    int seconds();

    /**
     * 最大請求次數
     * @return
     */
    int maxCount();

    /**
     * 是否需要登入
     * @return
     */
    boolean needLogin() default true;
}

3.2.2 新增攔截器

通過攔截器,攔截AccessLimit註解,然後進行介面限流。主要是使用redis的自增機制。

package com.leyou.seckill.interceptor;

import com.leyou.auth.entity.UserInfo;
import com.leyou.seckill.access.AccessLimit;
import com.leyou.utils.JsonUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;

/**
 * @Author: 98050
 * @Time: 2018-11-23 23:45
 * @Feature: 介面限流攔截器
 */
@Service
public class AccessInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private RedisTemplate<String,String> redisTemplate;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod){
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);
            if (accessLimit == null){
                return true;
            }

            //獲取使用者資訊
            UserInfo userInfo = LoginInterceptor.getLoginUser();
            int seconds = accessLimit.seconds();
            int maxCount = accessLimit.maxCount();
            boolean needLogin = accessLimit.needLogin();
            String key = request.getRequestURI();
            if (needLogin){
                if (userInfo == null){
                    render(response, "使用者沒有登入");
                    return false;
                }
                key += "_" + userInfo.getId();
            }else {
                //不需要登入,則什麼也不做
            }
            String count = redisTemplate.opsForValue().get(key);
            if (count == null){
                redisTemplate.opsForValue().set(key,"1",seconds, TimeUnit.SECONDS);
            }else if(Integer.valueOf(count) < maxCount){
                redisTemplate.opsForValue().increment(key,1);
            }else {
                render(response,"稍後再試");
            }

        }

        return super.preHandle(request, response, handler);
    }

    private void render(HttpServletResponse response, String str) throws IOException {
        OutputStream outputStream = response.getOutputStream();
        outputStream.write(str.getBytes("UTF-8"));
        outputStream.flush();
        outputStream.close();
    }
}

配置攔截器

3.2.3 使用

在需要限流的方法上,直接使用註解即可。

四、相關知識

4.1 微服務架構如何保障高可用

  • 首先你的hystrix資源隔離以及超時這塊,必須設定合理的引數,避免高峰期,頻繁的hystrix執行緒卡死

如何設定Hystrix執行緒池大小

假設你的服務A,每秒鐘會接收30個請求,同時會向服務B發起30個請求,然後每個請求的響應時長經驗值大概在200ms,那麼你的hystrix執行緒池需要多少個執行緒呢?

計算公式是:30(每秒請求數量) * 0.2(每個請求的處理秒數) + 4(給點緩衝buffer) = 10(執行緒數量)。

如果對上述公式存在疑問,不妨反過來推算一下,為什麼10個執行緒可以輕鬆抗住每秒30個請求?

一個執行緒200毫秒可以執行完一個請求,那麼一個執行緒1秒可以執行5個請求,理論上,只要6個執行緒,每秒就可以執行30個請求。

也就是說,執行緒裡的10個執行緒中,就6個執行緒足以抗住每秒30個請求了。剩下4個執行緒都在玩兒,空閒著。

那為啥要多搞4個執行緒呢?很簡單,因為你要留一點buffer空間。

萬一在系統高峰期,系統性能略有下降,此時不少請求都耗費了300多毫秒才執行完,那麼一個執行緒每秒只能處理3個請求了,10個執行緒剛剛好勉強可以hold住每秒30個請求。所以你必須多考慮留幾個執行緒。

如何設定請求超時時間

接著來,那麼請求的超時時間設定為多少?答案是300毫秒。

如果你的超時時間設定成了500毫秒,想想可能會有什麼後果?

考慮極端情況,如果服務B響應變慢,要500毫秒才響應,你一個執行緒每秒最多隻能處理2個請求了,10個執行緒只能處理20個請求。

  • 其次,針對個別的服務故障,要設定合理的降級策略,保證各個服務掛了,可以合理的降級,系統整體可用!

如果你的某個服務掛了,那麼你的hystrix會走熔斷器,然後就會降級,你需要考慮到各個服務的降級邏輯。

舉一些常見的例子:

  • 如果查詢資料的服務掛了,你可以查本地的快取
  • 如果寫入資料的服務掛了,你可以先把這個寫入操作記錄日誌到比如mysql裡,或者寫入MQ裡,後面再慢慢恢復
  • 如果redis掛了,你可以查mysql
  • 如果mysql掛了,你可以把操作日誌記錄到es裡去,後面再慢慢恢復資料。

具體用什麼降級策略,要根據業務來定,不是一成不變的。

4.2

相關推薦

商城總結

目錄 一、快取優化 1.1 頁面快取 將不經常改動的頁面直接快取到redis中,然後用Thymeleaf檢視解析器將快取的頁面直接渲染出來。 1.2 物件快取 將經常使用的物件資訊放入redis中,比如

商城功能優化

目錄 3.2 測試 一、優化思路 主要就是減少資料庫的訪問: 1. 將商品的庫存資訊放入Redis中 2. 收到秒殺請求,Redis預減庫存,庫存不足直接返回,否則進行下一步操作

商城——訂單微服務

目錄 四、細節優化 4.1 支付頁面顯示總金額 4.1.1 支付頁面 4.1.2 支付成功頁面 4.2 修改訂單號的傳遞方式 4.2.1 修改訂單提交函式 4.2.2 修改支付頁面 4.3 訂單提交時進行登入認證 4.4 本地資料刪除 4.5 購物車資料更新

商城知識點

一、明文密碼加密處理 1.1 介紹 使用BCryptPasswordEncoder進行密碼加密。 spring security中的BCryptPasswordEncoder方法採用SHA-256 +隨機鹽+金鑰對密碼進行加密。SHA系列是Hash演算法,不是加密演算法

商城—— 訂單中心

目錄 一、我的訂單頁 1.1. 頁面效果 1.2 後臺介面 1.3 頁面改造 1.3.1 資料載入 1.3.2 分頁條 1.4 測試 1.5 訂單狀態過濾 1.5.1 全部訂單(16) 1.5.2 待付款(3) 1.5.3 待發貨(4) 1.5.4 待

商城——訂單微服務

目錄 五、地址管理 5.1 頁面效果 5.2 資料庫表設計 5.3 頁面優化 5.3.1 在data中定義資料 5.3.2 模態框 5.3.3 方法繫結 5.3.4 效果展示 5.4 後臺介面 5.4.1 實體類 5

商城——訂單微服務

目錄 二、訂單結算頁 2.1 頁面跳轉 2.2 收貨人資訊 2.3 支付方式 2.4 商品列表 2.4.1 購物車資訊獲取 2.4.2 頁面渲染 2.5 總金額 2.6 提交訂單 2.6.1 頁面提交 2.6.2 精度損失問題 三、微信支付 3.1

商城——訂單微服務

目錄 一、訂單系統介面 1.1 Swagger-UI 1.1.1 什麼是OpenApi 1.1.2 什麼是Swagger 1.1.3 快速入門 1.2 測試介面 1.2.1 建立訂單介面 1.2.2 生成ID方式 1.2.3 查詢訂單介面

商城——購物車

目錄 一、搭建購物車微服務 1.1 建立module 1.2 pom依賴 1.3 配置檔案 1.4 啟動類 二、購物車功能分析 2.1 需求 2.2 流程圖 三、未登入購物車 3.1 準備 3.1.1 購物車的資料結構 3.1.2 web本地儲存

商城——授權中心

目錄 三、首頁判斷登入狀態 3.1 頁面程式碼 3.2 後臺實現校驗使用者介面 3.3 測試 3.4 重新整理token 四、閘道器的登入攔截 4.1 引入jwt相關配置 4.2 編寫過濾邏輯 4.3 白名單 三、首頁判斷登入狀態 雖然cookie已經

商城——使用者註冊

目錄 七、根據使用者名稱和密碼查詢使用者 7.1 介面說明 7.2 Controller 7.3 Service 7.4 測試 八、在註冊頁進行測試 七、根據使用者名稱和密碼查詢使用者 7.1 介面說明 功能描述:查詢功能,根據引數中的使用者名稱和密碼查詢指定

商城——使用者註冊

  五、傳送簡訊功能 5.1 介面說明 功能描述:根據使用者輸入的手機號,生成隨機驗證碼,長度為6位,純數字。並且呼叫簡訊服務,傳送驗證碼到使用者手機。 介面路徑:POST /code 引數說明: 引數 說明

商城服務鑑權

一、使用者鑑權 客戶端請求服務時,根據提交的token獲取使用者資訊,看是否有使用者資訊及使用者資訊是否正確,這個在樂優商城中已經實現。 二、服務鑑權 微服務中,一般有多個服務,服務與服務之間相互呼叫時,有的服務介面比較敏感,比如資金服務,不允許其他服務隨便呼叫,所以要進行服務呼叫的許可

商城——訂單微服務

目錄 一、建立訂單微服務 1.1 建立module 1.2 pom依賴 1.3 配置檔案 1.4 啟動類 1.5 配置匯入 1.6 屬性讀取 1.7 支付工具類 1.8 修改閘道器配置 二、實體類準備 2.1 Order.java 2.2 OrderD

商城——RabbitMQ及資料同步

一、RabbitMQ 1.1 搜尋與商品服務的問題 1.2 訊息佇列(MQ) 1.2.1 什麼是訊息佇列 1.2.2 AMQP和JMS 1.2.3 常見MQ產品 1.2.4 RabbitMQ 1.3 下載和安裝 1.3.1 下載 1.3.2 安裝 二、五種

商城——授權中心

目錄 一、無狀態登入原理 1.1 什麼是有狀態 1.2 什麼是無狀態 1.3 如何實現無狀態 1.4 JWT 1.4.1 簡介 1.4.2 資料格式 1.4.3 JWT互動流程 1.4.4 非對稱加密 1.5 結合Zuul的鑑權流程 1.5.1 沒有RSA

程序猿的奮鬥史工作三個月小結

最好的 一點 收獲 工具 添加 data 至少 英文 family 文/溫國兵 經常有網友問我在哪家公司工作。坦言。我如今所處的公司是一家初創公司。沒有光環,沒有炫耀,有的是踏實和認真。也正是由於這樣,我才有很多其它機會。

Python學習筆記SMTP發送郵件

email debug lib 第一個 發郵件 參數 html郵件 命令 兼容 摘抄自:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001432005

Linux學習總結NFS服務配置 上篇

nfs rpc 1 NFS基本架構 NFS network file system 也就是網絡文件系統。NFS最早由Sun公司開發,分2,3,4三個版本,2和3由Sun起草開發,4.0開始Netapp公司參與並主導開發,最新為4.1版本NFS數據傳輸基於RPC協議,RPC為Remote Proced

函數模板

函數模板 泛型編程 代碼復用 多參數定義 函數模板重載 我們到目前為止,學習了 C++ 這麽久。提個小問題:在 C++ 中有幾種交換變量的方法呢?通過定義宏代碼塊和定義函數。下來我們做個實驗#include <iostream> #include <str