項目中遇到的Redis緩存問題

来源:https://www.cnblogs.com/Infernal/archive/2019/07/07/11147331.html
-Advertisement-
Play Games

1.Redis伺服器 can not get resource from pool. 1000個線程併發還能跑,5000個線程的時候出現這種問題,查後臺debug日誌,發現redis 線程池不夠。剛開始設置的是: 順便也改了一下jdbc 的連接池參數,最大空閑和最大連接數都改成1000.在測一下。可 ...


1.Redis伺服器 can not get resource from pool.

1000個線程併發還能跑,5000個線程的時候出現這種問題,查後臺debug日誌,發現redis 線程池不夠。剛開始設置的是:

# redis 配置文件
#redis
redis.host=127.0.0.1
redis.port=6379
redis.timeout=300        等待時間  10s改為300s
redis.password=123456
redis.poolMaxTotal=1000   連接數,剛開始最大連接數 設置為100.
redis.poolMaxIdle=500      最大空閑連接數  100改成500
redis.poolMaxWait=300

 

順便也改了一下jdbc 的連接池參數,最大空閑和最大連接數都改成1000.在測一下。可以

spring.datasource.filters=stat
spring.datasource.maxActive=1000
spring.datasource.initialSize=100
spring.datasource.maxWait=60000
spring.datasource.minIdle=500
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=select 'x'
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=true
spring.datasource.maxOpenPreparedStatements=20

 

2.5000併發下的問題,20個商品,庫存減到-4980。

  後來看代碼發現,判斷庫存用的是if(stock==0 ) 拋出異常。應該用stock<0,因為 若此時同時2個線程進來,就永遠小於0,後面的業務邏輯都可以執行。

3.然後就是超賣的問題

  第一次壓力測試的時候,5000個線程,分別取不同的token(sessionId),同時訪問 秒殺這個介面,商品個數只放了20個。結果出現最後商品數量變負的問題。

4.編碼的問題

  介面限流防刷的時候,通過計數器限流,如果超過某個閾值,向前端返回一個codeMsg對象用於顯示的時候,顯示的是String是亂碼的問題,之前由於一直返回都是json 格式,都是封裝好在data里。

  這次返回是直接通過輸出流直接寫到response直接返回位元組數組的,而不是spring controller 返回數據(springboot 預設utf-8),出現亂碼問題,用utf-8編碼,解決。

5.壓測是如何壓測的,以及壓測的瓶頸?

  壓測是利用Jmeter壓測。(Apache開發的基於java的壓測工具)。

壓測具體實現:

  1.在資料庫中提前插入5000個用戶密碼(腳本 for迴圈 id是13000000+i),密碼統一為“123456”,隨機鹽值也是固定的,方便操作。用JDBC存入資料庫。作為5000個備用用戶。

  2.然後寫了一個小腳本讓5000個用戶post請求我的登陸介面(login),生成sessionId並存入緩存,並改寫了一下login介面讓其換回sessionId。把這5000個用戶的id和對應sessionid寫到了一個TXT文件裡面。

  3.最後利用jmeter 創建5000個線程,賬號每個線程攜帶提前寫好的用戶token(sessionId),參數就是商品id和sessionid,商品id確定我要買的票是哪個,sessionid用來獲取用戶信息。(從緩存中拿)

壓測的瓶頸:

qps-126/s----靜態化-250/s---介面優化-860/s.

瓶頸主要是對資料庫的訪問。

  1.資料庫讀取,寫入,處理請求的速度。

  資料庫讀取寫入加上網路IO速度很慢,減少對資料庫的訪問,在緩存這一端就屏蔽掉大部分訪問資料庫的請求(Redis預減庫存操作)

  2.利用消息隊列,非同步業務邏輯的處理速度慢,可以先返回結果,讓其輪詢。

  3.利用記憶體map,減少對Redis伺服器的訪問,flag機制。

  4.其他想到的但還沒實現

伺服器系統的負載均衡+集群

資料庫數據達到1000W以上就很慢,分庫分表

6.用戶登陸的整個流程是如何實現的?

  1.首先輸入登陸頁面的url.[http://localhost:8080/login/to_login,controller根據map映射返回給html頁,到達登陸頁面]

  2.整個頁面是一個login表單,包含用戶名和密碼兩個輸入框部分,還有一個登陸按鈕和重置按鈕。

  3.在前端,給登陸按鈕綁定一個login()方法,login()方法中會獲取表單中的用戶名和密碼,然後將密碼利用封裝好的md5()函數以及設置的固定鹽值進行拼接,鹽值設置為“1a2b3c”,然後進行MD5演算法生成4個32位拼接的散列值作為輸入密碼(用於 網路傳輸),作為參數傳給後端。(這裡的目的主要是第一道加密,防止http明文傳輸,泄漏密碼)。

  4.然後ajax非同步訪問do_login 介面,參數為用戶名和md5之後的密碼,後端接收到前端傳輸來的參數後,會對用戶名和密碼進行參數校驗,驗證是否為空,是否有格式問題(密碼長度6位以上,用戶名格式11位等等),如果驗證不通過,返回CodeMsg(),封裝好的對應的錯誤信息給前端。

  5.如果驗證成功,進入下一步,用戶的登陸,首先通過用戶名取用戶對象信息(先從緩存中取,取不到取資料庫取,取到了將用戶信息存入緩存中,下一次登錄我們可以先從緩存中取用戶,降低資料庫壓力),然後返回一個user對象,再判斷這個user對象是否為空,若是空就拋出異常,不是空的情況說明資料庫中有該用戶,然後根據傳入的密碼和數據中保存的隨機鹽值,進行md5再次拼接,獲得的值若是和資料庫中的密碼一致,那麼說明登陸成功。

  關鍵點: 6.登陸成功的時候,隨機生成uuid作為sessionId,將其寫入cookie中返回給客戶端,並且將模塊首碼+該用戶id作為key和sessionId 作為值,存入緩存(這裡為分散式緩存提供的基礎)。這時候跳轉到 搶票列表頁面,如果密碼不匹配,拋出異常,返回。

7.秒殺的兩個關鍵點如何應對--高併發應對策略+頁面載入速度?

  短時間的大訪問量 網站伺服器 同網站,不同項目部署,/獨立功能變數名稱 避免對網站造成影響 高併發問題,不停刷新 資料庫 頁面靜態化
同網站,不同項目部署,/獨立功能變數名稱 避免對網站造成影響 寬頻 同網站,不同項目部署,/獨立功能變數名稱 避免對網站造成影響 不能提前下單 伺服器 url動態化,+隨機數
下單之後的搶的問題 sql 樂觀鎖

大量訪問高併發的應對(主要訪問大量訪問資料庫崩潰)

  1.Redis預減庫存減少資料庫訪問

  2.map標記減少Redis訪問屏蔽一定的請求減輕緩存壓力

  3.消息隊列非同步處理

    • 流量削峰 開始搶購的瞬間 大量併發進入,先將請求入隊,若隊列滿了,那麼捨棄再入隊的請求返回一個異常
    • 先給前端一個數據返回表示排隊中,再進行後續的業務處理,前端輪詢最後成功或者失敗在顯示業務結果

  4.資料庫運行的問題,傳統的sql寫成存儲過程(直接調用),加速sql

  5.資料庫里鎖及唯一索引來處理搶的問題。

頁面載入速度

  頁面靜態化,緩存在客戶端

CDN伺服器

  在上表中列出來的解決方案中看出,利用 頁面靜態化、數據靜態化,反向代理 等方法可以避免 帶寬和sql壓力 ,但是隨之而來一個問題,頁面搶單按鈕也不會刷新了,可以把 js 文件單獨放在js伺服器上,由另外一臺伺服器寫 定時任務 來控制js 推送。

  另外還有一個問題,js文件會被大部分瀏覽器緩存,我們可以使用xxx.js?v=隨機數 的方式來避免js被緩存

8.頁面靜態化的過程

  更為激進的緩存方式(之前可以用將html源碼緩存起來再讀,避免伺服器渲染html過程)。

什麼是瀏覽器緩存:

  簡單來說,瀏覽器緩存就是把一個已經請求過的Web資源(如html頁面,圖片,js,數據等)拷貝一份副本儲存在瀏覽器中。緩存會根據進來的請求保存輸出內容的副本。當下一個請求來到的時候,如果是相同的URL,緩存會根據緩存機制決定是直接使用副本響應訪問請求,還是向源伺服器再次發送請求。比較常見的就是瀏覽器會緩存訪問過網站的網頁,當再次訪問這個URL地址的時候,如果網頁沒有更新,就不會再次下載網頁,而是直接使用本地緩存的網頁。只有當網站明確標識資源已經更新,瀏覽器才會再次下載網頁。

頁面靜態化的好處:

  我們知道瀏覽器會將html,圖片等靜態數據,緩存到本地,在高併發搶票場景,用戶會通過不斷的刷新頁面來進行搶票操作,這樣帶來Web帶寬的浪費以及伺服器的訪問壓力。於是,我們可以通過將搶票頁面做成靜態頁面html頁,其中的票務數據通過ajax非同步調用介面來獲取,僅僅交互的是部分數據,減少了帶寬,也加快用戶訪問的速度。

  function getDetail() {
        var goodsId = g_getQueryString("goodsId");
        $.ajax({
            url : "/goods/to_detail/"+goodsId,
            type : "GET",
            success: function (data) {
                if (data.code  == 0) {// 訪問後端detail 介面拿到數據
                    render(data.data);//渲染界面的方法
                }else {
                    layer.msg(data.msg)
                }
            },
            error:function () {
             layer.msg("客戶端請求有誤!")
            }
        })
    }

    function render(detail) {
        var  goodsVo =detail.goodsVo;
        var miaoshaStatus =detail.miaoshaStatus;
        var remainSeconds =detail.remainSeconds;
        var user =detail.user;
        if (user) {
            $("#userTip").hide();//沒有就不展示
        }
        //用獲取的參數 放入 對應的模板中
            $("#goodsName").text(goodsVo.goodsName);
            $("#goodsImg").attr("src", goodsVo.goodsImg);
            $("#startTime").text(new Date(goodsVo.startDate).format("yyyy-MM-dd hh:mm:ss"));
            $("#remainSeconds").val(remainSeconds);
            $("#goodsId").val(goodsVo.id);
            $("#goodsPrice").text(goodsVo.goodsPrice);
            $("#miaoshaPrice").text(goodsVo.miaoshaPrice);
            $("#stockCount").text(goodsVo.stockCount);
            countDown();//調用倒計時
    }
    function countDown() {
        var remainSeconds = $("#remainSeconds").val();
        // var remainSeconds = $("#remainSeconds").val();
        var timeout;//定義一個timeout 保存Timeout 值
        if (remainSeconds>0){//秒殺未開始
            $("#buyButton").attr("disabled",true);/*還沒開始的時候按鈕不讓點*/
            $("#miaoshaTip").html("秒殺倒計時:"+remainSeconds+"秒");
            /*且做一個倒計時*/
            timeout=setTimeout(function () {//setTimeout 為時間到了之後執行 該函數
                $("#countDown").text(remainSeconds-1);//將顯示中的值 -1
                $("#remainSeconds").val(remainSeconds-1);// remianSeconds 值減一
                countDown();//在調用該方法 實現迴圈
            },1000)
        }else if (remainSeconds == 0){//秒殺進行中
            $("#buyButton").attr("disabled",false);
            //當remainSeconds =0
            clearTimeout(timeout);//取消timeout 代碼執行
            $("#miaoshaTip").html("秒殺進行中!")//修改其中的內容
            /**加入秒殺數學驗證碼 功能
             * 1.一開始圖形驗證碼和輸入框都是隱藏的
             * 2.當秒殺進行的時候,顯示驗證碼和輸入框
             * */
            $("#verifyCodeImg").attr("src", "/miaosha/verifyCode?goodsId="+$("#goodsId").val());//訪問驗證碼介面
            $("#verifyCodeImg").show();
            $("#verifyCode").show();

        } else {//秒殺結束
            $("#buyButton").attr("disabled",true);
            $("#miaoshaTip").html("結束!!!")//修改其中的內容
        }
    }

 

  做法:首先將票務詳情這個template 模板 html頁放在static 文件下,然後改掉thymeleaf 模板語言標簽讓其成為純html語言,然後將票務列表中的鏈接指向(本來是requestMapping,向後端contrller 請求這個詳情業務及數據,然後利用spring渲染模板,在返回的),現在直接指向static文件下的票務詳情頁(鏈接中帶商品id作為參數),最後在這個html頁面寫ajax非同步訪問後端介面/getdetail,後端介面也改造一下返回的是這個商品的全部詳細信息,封裝在data里,以json的形式,然後寫了一個render(),把從後端傳來的數據寫進對應數據中。

 /** 頁面靜態化:商品詳情頁面
     * 方法:返回的是一個靜態html 頁面 + 利用ajax(通過介面)從服務端獲取對應數據 + js技術將數據放入html
     * */
    @RequestMapping(value = "/to_detail/{goodsId}") // 前端傳入的參數 goodsId
    @ResponseBody
    public Result<GoodsDetailVo> detail(HttpServletRequest request, HttpServletResponse response, Model model, MiaoshaUser user,
                         @PathVariable("goodsId") Long goodsId){//通過註解@PathVariable獲取路徑參數
        /*先將user 傳進去 用來判斷是否登錄*/
        model.addAttribute("user",user);
        /*根據傳入的Id 通過service 拿到對應的Good信息*/
        GoodsVo goods = goodsService.getGoodsById(goodsId);
        model.addAttribute("goods",goods);

        long startTime = goods.getStartDate().getTime();
        long endTime = goods.getEndDate().getTime();
        long nowTime = System.currentTimeMillis();/* 拿到現在時間的毫秒值*/
        /**這裡要做一個秒殺時間的判斷 秒殺開始 秒殺結束 秒殺進行
         * */
        int miaoshaStatus = 0;/*用該變數來表示 秒殺的狀態 0 表示秒殺未開始 1 開始 2 結束*/
        int remainSeconds = 0; /*表示剩餘時間 距離秒殺開始的時間*/
        if (nowTime<startTime){//秒殺未開始
            miaoshaStatus = 0;
            remainSeconds = (int)((startTime-nowTime)/1000);//註意此時是 毫秒值 要除以1000
        }else if (endTime<nowTime){//秒殺結束
            miaoshaStatus = 2;
            remainSeconds = -1;
        }else {//秒殺進行中
            miaoshaStatus = 1;
            remainSeconds = 0;
        }
        model.addAttribute("remainSeconds",remainSeconds);
        model.addAttribute("miaoshaStatus",miaoshaStatus);
        /*
        將我們需要的數據 封裝到GoodsDetailVo中
         */
        GoodsDetailVo goodsDetailVo = new GoodsDetailVo();
        goodsDetailVo.setGoodsVo(goods);
        goodsDetailVo.setMiaoshaStatus(miaoshaStatus);
        goodsDetailVo.setRemainSeconds(remainSeconds);
        goodsDetailVo.setUser(user);
        return Result.success(goodsDetailVo);

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Linux環境工作常用命令 cd / 進入根目錄 mkdir dirName 創建文件夾 touch fileName 創建一個空文件 vi/vim fileName 編輯一個文件,如果文件不存在,則會新建該文件 mv fileName/dirName 剪切/修改 文件或者文件夾的名稱 cp r s ...
  • 我的錯誤案例: ,這個後臺插不進去,就姓名那欄的中文編碼問題。 遇到這個錯誤,應該是創建表的時候沒有設置好編碼,這個錯誤不用多想,我也試過在更改表那裡設置編碼,但還是不行,還是有殘留 直接drop table ’table_name‘,把整個表刪除了再重新建,例子如下: CREATE TABLE t ...
  • 1.主題管理 kafka-topics.sh工具腳本用於對主題操作,如創建、刪除、修改、分區數、副本數及主題級別的配置。 1.1創建名為kafka-test主題,有2個副本,3個分區 [hadoop@h201 kafka_2.12-0.10.2.1]$ bin/kafka-topics.sh --c ...
  • Oracle基礎學習筆記 最近找到一份實習工作,有點頭疼的是,有階段性考核,這...,實際想想看,大學期間只學過資料庫原理,並沒有針對某一資料庫管理系統而系統的學習,這正好是一個機會,於是乎用了三天時間學習了一下Oracle數據的相關內容,以下是我總結的一些知識點,有錯誤的地方請及時通知我改正。 一 ...
  • 特別提示 本說明中的mysql 是基於windwos平臺下的5.5 版本 安裝完成後 請到mysql中設置配置文件 鏈接分享:鏈接:https://pan.baidu.com/s/1tv4ulZW1iUVl0ukn5WtV6w 提取碼:rso9 (永久有效) 本篇教程的主要目的為 好記性不如爛筆頭 ...
  • mysql 表的完整性約束 [TOC] 約束概念 unsigned 設置某一個數字無符號 (整數類型 ,浮點類型不能是unsigned的) not null 某一個欄位不能為空(嚴格模式會影響非空設置的效果) default 給某個欄位設置預設值(設置預設值) unique 設置某一個欄位不能重覆 ...
  • mysql支持的數據類型 [TOC] 數據類型官方文檔 數值類型 數值類型表 | 類型 | 大小 | 範圍(有符號) | 範圍(無符號)unsigned約束 | 用途 | | | | | | | | TINYINT | 1位元組 | ( 128,127) | (0,255) | 小整數值 | | SM ...
  • 1.前言 記得從上大學的時候就已經聽說過這個詞語 以前的理解可能就是數據量超大很多嘛 到這幾年大數據這個詞語被人們談論得也越來越頻繁 也越來越想瞭解它的所以自己才會去學習 我覺得做任何事之前肯定有某種驅使你去瞭解它的過程 以下僅是自己個人的理解 2.什麼是大數據? 大數據不僅是數據量大 (G,TB, ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...