1. 程式人生 > >專案中遇到的bug及面試問題總結

專案中遇到的bug及面試問題總結

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.其他想到的但還沒實現

  1. 伺服器系統的負載均衡+叢集
  2. 資料庫資料達到1000W以上就很慢,分庫分表

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

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

程式設計師說 受不了 怎麼辦?
短時間的大訪問量 網站伺服器 同網站,不同專案部署,/獨立域名 避免對網站造成影響
高併發問題,不停重新整理 資料庫 頁面靜態化
頻寬 200k的頁面 併發1w次 ,頻寬為2G 頻寬 秒殺頁快取cdn 租借臨時頻寬,反向代理伺服器,nginx ,甚至使用者瀏覽器。(cookie)
不能提前下單 伺服器 url動態化,+隨機數
下單之後的搶的問題 sql 樂觀鎖

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

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

2.map標記減少Redis訪問遮蔽一定的請求減輕快取壓力

3.訊息佇列非同步處理

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

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

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

頁面載入速度

  1. 頁面靜態化,快取在客戶端
  2. 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);

相關推薦

Java EE專案異常設計處理總結

異常,為我們處理非正常的業務流程提供了很好的解決方案,如果你有過dbase、c、pascal等過程式語言開發的經歷,你一定會深刻體會到,異常機制給你的程式碼可讀行、可維護性帶來的好處,同時,程式的健壯性也得到了增強。 在 java專案中,異常設計要注意下面的幾點。 一、自定義異常父類的選擇 A、自定義異常的

專案遇到的bug面試問題總結

1.Redis伺服器 can not get resource from pool.  1000個執行緒併發還能跑,5000個執行緒的時候出現這種問題,查後臺debug日誌,發現redis 執行緒池不夠。剛開始設定的是: # redis 配置檔案 #redis redis

關於以太坊智慧合約在專案實戰過程的設計經驗總結(1)

此文已由作者蘇州授權網易雲社群釋出。 歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗 1.智慧合約的概述 近幾年,區塊鏈概念的大風吹遍了全球各地,有的人覺得這是一個大風口,有的人覺得他是個泡沫。眾所周知,比特幣是區塊鏈1.0,而以太坊被稱為了區塊鏈2.0,而區塊鏈1.0和2.0最主要的差別就在於以太坊擁有

關於以太坊智慧合約在專案實戰過程的設計經驗總結(2)

此文已由作者蘇州授權網易雲社群釋出。 歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗 7.智慧合約經驗分享 1)智慧合約開發的工具的問題 古人云“工欲善其事必先利其器”,同意良好的智慧合約的開發工具對智慧合約的開發效率有極大的提升。以下是一些比較好的智慧合約的開發組合: &nb

JAVA專案常用的異常知識點總結

JAVA專案中常用的異常知識點總結 1. java.lang.nullpointerexception 這個異常大家肯定都經常遇到,異常的解釋是"程式遇上了空指標",簡單地說就是呼叫了未經初始化的物件或者是不存在的物件,這個錯誤經常出現在建立圖片,呼叫陣列這些操作中,比如圖片未經初始化,或者圖片

salesforce零基礎學習(九十)專案的零碎知識點小總結(三)

本次的內容其實大部分人都遇到過,也知道解決方案。但是因為沒有牢記於心,導致問題再次出現還是花費了一點時間去排查了原因。在此記錄下來,好記性不如爛筆頭,爭取下次發現類似的現象可以直接就知道原因。廢話少說,進入正題。 我們在Goods__c表中有一個欄位型別為Picklist,欄位值有以下內容: 我們想要

unity AssetBundle打包使用總結(WebGL)

資源打包,教程很多。但,適合自己專案的打包又有幾個?None 打包注意點: (1)資源儘量分資料夾放置,便於打包資源的管理 (2)打包相對獨立資源 (3)打包時,儘量只打包素材,且大小控制在10M左右,避免影響下載速度 (4)打入包的例項化物體,在其他指令碼被引用的時候,通過查詢方式(名

webpack實戰專案程式碼打包和優化總結

網上關於webpack的優化的已經很多了,只是都比較零散,結合實戰專案自己做個總結 webpack 優化,實際專案中主要做到了一下幾點: 1、 檔案壓縮(css, js, html, 字型檔案, 圖片檔案) 2、 babel-loader 避免不必要的轉義 3、 babel-轉義結果進行快取

Redis的安裝以及在專案使用Redis的一些總結和體會

第一部分:為什麼我的專案中要使用Redis 我知道有些地方沒說到位,希望大神們提出來,我會吸取教訓,大家共同進步! 註冊時郵件啟用的部分使用Redis 傳送郵件時使用Redis的訊息佇列,減輕網站壓力。 使用Lucene.Net在進行分詞時使用Redis訊息佇列和多執行緒來避免介面卡死等效能問

javaWEB專案的中文亂碼問題總結

寫這篇部落格主要就是在做專案的時候,碰到後臺到前臺資料亂碼的問題,並藉此總結平常碰到的各種編碼設定,並且也百度了很多,有時候也是經常想不起來,所以記錄一下,方便以後檢視!!!   場景:  在實際運用場景中,當前臺發起請求後,我們需要從後臺返回資料給前臺(前臺模糊查詢

ssm框架,web專案applicationContext.xml相關配置檔案解析

一、概述 applicationContext.xml,即Spring上下文配置檔案,用於完成Spring和MyBatis的整合。主要配置bean自動掃描、依賴注入、資料庫、事務等。如下 <?xml version="1.0" encoding="UTF-8"?> &l

SSM框架專案bug彙總

這個是實習過程中,主要用來了解和學習公司框架的一個測評系統,主要是採用了SSM框架的maven的JAVA Web專案,是比較常見的資訊管理系統。在做系統時總是會遇到糾結很久但瞭解後卻很容易解決的bug,在此寫下這篇bug彙總,希望自己和剛剛學習的童鞋們節約時間成

在使用laravel進行開發時能更有好的尋找程式碼bugsql的優化

安裝 Debugbar 使用 Composer 安裝: $ composer require "barryvdh/laravel-debugbar:~3.1" --dev 生成配置檔案,存放位置 config/debugbar.php: $ php artisan vend

Hibernate懶載入在SSH專案的配置原理

Hibernate懶載入在SSH專案中 當使用懶載入的時候, 查找出來的物件中的資料如物件中的集合,它並沒有加載出來,只有當物件呼叫的時候才會向資料庫發出查詢語句, 這就會造成在hibernate中得到一個查詢出來的物件,當在action或者jsp頁面取裡面的

vue專案使用echarts 遇到問題總結2

一:echarts圖形的父級容器寬度設定為百分比形式,導致echarts變形;12解決方案:固定寬度沒問題,只要設定成百分比,有的好用,有的不好用,只能將寬高設定為rem,好在相差不大;二:vue-cli專案下使用 vue的tab切換3個不同的echarts圖表,未打包正常顯

專案遇到的一些問題總結(08.23更新)

寫一些最近工作中Vue專案中遇到的問題。 巴啦啦小魔仙,汙卡拉,全身變,小本本,出來吧! 會不定期更新,所以建議收藏。 1.獲取一個物件的鍵(key) 在某種特定需求下(未知的Object型別資料),我們想拿到這個未知物件中第一個元素的鍵(也就是newData)。程式碼如

web專案獲取資源資源路徑

public class PathServlet extends HttpServlet {     public void doGet(HttpServletRequest request, HttpServletResponse response)           

自己在Web前端專案解決bug的思路

在專案中往往會遇到各種各樣的程式bug,而且有些bug很隱蔽。例如僅僅一個字母引發的“血案”…… 因為剛剛入行,所用的技術不夠強勁,所以很多技術都是老套的辦法。現在咱把前不久公司的一個專案中的bug修復辦法及思路給記錄下來,以便將來用於往後的工作當中。 首先是bug的執行

移動開發一些bug解決方案

網頁開發要面對各種各樣的瀏覽器,讓人很頭疼,而移動開發中,你不但要面對瀏覽器,還要面對各種版本的手機,ios好一點,而安卓就五花八門了,你可能在開發中也被它們折磨過,或者正在被它們折磨,我在這裡說幾個我在開發中遇到的比較難纏問題,和解決方案,給其他朋友提個醒,因為一旦碰到了這

iOS開發-SQLite資料庫在App專案的設定使用

在iOS開發中,除了UI頁面以外使用最多的估計是資料物件的儲存了~比如聊天記錄、通訊錄和通訊記錄、瀏覽記錄等等。我們要根據不同的使用方式去對這些資料儲存並使用。常用的有:①沙盒機制 ②屬性列表plist ③SQLite ④CoreData,本文介紹的就是基礎的SQLite