秒殺注意事項以及總體簡略設計前端
1.如何解決賣超問題
--在sql加上判斷防止數據邊爲負數
--數據庫加惟一索引防止用戶重複購買
--redis預減庫存減小數據庫訪問 內存標記減小redis訪問 請求先入隊列緩衝,異步下單,加強用戶體驗redis
註冊功能 -- 若是有前端的牛人加入修改幾個頁面那是再好不過了哈哈哈
全局異常處理攔截
1.定義全局的異常攔截器
2.定義了全局異常類型
3.只返回和業務有關的
4.詳情請看GlobleException算法
頁面級緩存thymeleafViewResolver
spring
對象級緩存redis
redis永久緩存對象減小壓力
redis預減庫存減小數據庫訪
內存標記方法減小redis訪問sql
訂單處理隊列rabbitmq
請求先入隊緩衝,異步下單,加強用戶體驗
請求出隊,生成訂單,減小庫存
客戶端定時輪詢檢查是否秒殺成功 數據庫
解決分佈式session
--生成隨機的uuid做爲cookie返回並redis內存寫入
--攔截器每次攔截方法,來從新獲根據cookie獲取對象
--下一個頁面拿到key從新獲取對象
--HandlerMethodArgumentResolver 方法 supportsParameter 若是爲true 執行 resolveArgument 方法獲取miaoshauser對象
--若是有緩存的話 這個功能實現起來就和簡單,在一個用戶訪問接口的時候咱們把訪問次數寫到緩存中,在加上一個有效期。
經過攔截器. 作一個註解 @AccessLimit 而後封裝這個註解,能夠有效的設置每次訪問多少次,有效時間是否須要登陸!編程
秒殺安全 -- 安全性設計
秒殺接口隱藏
數字公式驗證碼
接口防刷限流(通用 註解,攔截器方式)後端
通用緩存key的封裝採用什麼設計模式
模板模式的優勢
-具體細節步驟實現定義在子類中,子類定義詳細處理算法是不會改變算法總體結構
-代碼複用的基本技術,在數據庫設計中尤其重要
-存在一種反向的控制結構,經過一個父類調用其子類的操做,經過子類對父類進行擴展增長新的行爲,符合「開閉原則」
-缺點: 每一個不一樣的實現都須要定義一個子類,會致使類的個數增長,系統更加龐大設計模式
redis的庫存如何與數據庫的庫存保持一致
redis的數量不是庫存,他的做用僅僅只是爲了阻擋多餘的請求透穿到DB,起到一個保護的做用
由於秒殺的商品有限,好比10個,讓1萬個請求區訪問DB是沒有意義的,由於最多也就只能10個
請求下單成功,全部這個是一個僞命題,咱們是不須要保持一致的緩存
redis 預減成功,DB扣減庫存失敗怎麼辦
-其實咱們能夠不用太在乎,對用戶而言,秒殺不中是正常現象,秒殺中才是意外,單個用戶秒殺中
-1.原本就是小几率事件,出現這種狀況對於用戶而言沒有任何影響
-2.對於商戶而言,原本就是爲了活動拉流量人氣的,賣不完還能夠省一部分費用,可是活動還參與了,也就沒有了任何影響
-3.對網站而言,最重要的是體驗,只要網站不崩潰,對用戶而言沒有任何影響
爲何redis數量會減小爲負數
//預見庫存
long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock,""+goodsId) ;
if(stock <0){
localOverMap.put(goodsId, true);
return Result.error(CodeMsg.MIAO_SHA_OVER);
}
假如redis的數量爲1,這個時候同時過來100個請求,你們一塊兒執行decr數量就會減小成-99這個是正常的
進行優化後改變了sql寫法和內存寫法則不會出現上述問題
爲何要單獨維護一個秒殺結束標誌
-1.前提全部的秒殺相關的接口都要加上活動是否結束的標誌,若是結束就直接返回,包括輪尋的接口防止一直輪尋
-2.管理後臺也能夠手動的更改這個標誌,防止出現活動開始之後就沒辦法結束這種意外的事件
rabbitmq如何作到消息不重複不丟失即便服務器重啓
-1.exchange持久化
-2.queue持久化
-3.發送消息設置MessageDeliveryMode.persisent這個也是默認的行爲
-4.手動確認
爲何threadlocal存儲user對象,原理
1.併發編程中重要的問題就是數據共享,當你在一個線程中改變任意屬性時,全部的線程都會所以受到影響,同時會看到第一個線程修改後的值<br>
有時咱們但願如此,好比:多個線程增大或減少同一個計數器變量<br>
可是,有時咱們但願確保每一個線程,只能工做在它本身的線程實例的拷貝上,同時不會影響其餘線程的數據<br>
舉例: 舉個例子,想象你在開發一個電子商務應用,你須要爲每個控制器處理的顧客請求,生成一個惟一的事務ID,同時將其傳到管理器或DAO的業務方法中,
以便記錄日誌。一種方案是將事務ID做爲一個參數,傳到全部的業務方法中。但這並非一個好的方案,它會使代碼變得冗餘。
你可使用ThreadLocal類型的變量解決這個問題。首先在控制器或者任意一個預處理器攔截器中生成一個事務ID
而後在ThreadLocal中 設置事務ID,最後,不論這個控制器調用什麼方法,都能從threadlocal中獲取事務ID
並且這個應用的控制器能夠同時處理多個請求,
同時在框架 層面,由於每個請求都是在一個單獨的線程中處理的,因此事務ID對於每個線程都是惟一的,並且能夠從全部線程的執行路徑獲取
運行結果能夠看出每一個線程都在維護本身的變量:
Starting Thread: 0 : Fri Sep 21 23:05:34 CST 2018<br>
Starting Thread: 2 : Fri Sep 21 23:05:34 CST 2018<br>
Starting Thread: 1 : Fri Jan 02 05:36:17 CST 1970<br>
Thread Finished: 1 : Fri Jan 02 05:36:17 CST 1970<br>
Thread Finished: 0 : Fri Sep 21 23:05:34 CST 2018<br>
Thread Finished: 2 : Fri Sep 21 23:05:34 CST 2018<br>
局部線程一般使用在這樣的狀況下,當你有一些對象並不知足線程安全,可是你想避免在使用synchronized關鍵字<br>
塊時產生的同步訪問,那麼,讓每一個線程擁有它本身的對象實例<br>
注意:局部變量是同步或局部線程的一個好的替代,它老是可以保證線程安全。惟一可能限制你這樣作的是你的應用設計約束<br>
因此設計threadlocal存儲user不會對對象產生影響,每次進來一個請求都會產生自身的線程變量來存儲
maven 隔離
maven隔離就是在開發中,把各個環境的隔離開來,通常分爲
本地(local)
開發(dev)
測試(test)
線上(prod)
在環境部署中爲了防止人工修改的弊端! spring.profiles.active=@activatedProperties@
redis 分佈式鎖實現方法
我用了四種方法 , 分別指出了不一樣版本的缺陷以及演進的過程 orderclosetask
V1---->>版本沒有操做,在分佈式系統中會形成同一時間,資源浪費並且很容易出現併發問題
V2--->>版本加了分佈式redis鎖,在訪問核心方法前,加入redis鎖能夠阻塞其餘線程訪問,能夠
很好的處理併發問題,可是缺陷就是若是機器忽然宕機,或者線路波動等,就會形成死鎖,一直
不釋放等問題
V3版本-->>很好的解決了這個問題v2的問題,就是加入時間對好比果當前時間已經大與釋放鎖的時間
說明已經能夠釋放這個鎖從新在獲取鎖,setget方法能夠把以前的鎖去掉在從新獲取,舊值在於以前的
值比較,若是無變化說明這個期間沒有人獲取或者操做這個redis鎖,則能夠從新獲取
V4---->>採用成熟的框架redisson,封裝好的方法則能夠直接處理,可是waittime記住要這隻爲0
服務降級--服務熔斷(過載保護))
自動降級: 超時.失敗次數,故障,限流
人工降級:秒殺,雙11
9.全部秒殺相關的接口好比:秒殺,獲取秒殺地址,獲取秒殺結果,獲取秒殺驗證碼都須要加上
秒殺是否開始結束的判斷
秒殺相似場景sql的寫法注意事項
1.在秒殺一類的場景裏面,由於數據量億萬級全部即便有的有緩存有的時候也是扛不住的,不可避免的透穿到DB
全部在寫一些sql的時候就要注意:
1.必定要避免全表掃描,若是掃一張大表的數據就會形成慢查詢,致使數據的鏈接池直接塞滿,致使事故
首先考慮在where和order by 設計的列上創建索引
例如: 1. where 子句中對字段進行 null 值判斷 .
2. 應儘可能避免在 where 子句中使用!=或<>操做符
3. 應儘可能避免在 where 子句中使用 or 來鏈接條件
4. in 和 not in 也要慎用,不然會致使全表掃描
5. select id from t where name like '%abc%' 或者
6.select id from t where name like '%abc' 或者
7. 若要提升效率,能夠考慮全文檢索。
8.而select id from t where name like 'abc%' 纔用到索引 慢查詢通常在測試環境不容易復現
9.應儘可能避免在 where 子句中對字段進行表達式操做 where num/2 num=100*2
2.合理的使用索引 索引並非越多越好,使用不當會形成性能開銷
3.儘可能避免大事務操做,提升系統併發能力
4.儘可能避免象客戶端返回大量數據,若是返回則要考慮是否需求合理,實在不得已則須要在設計一波了!!!!!
前端限流:
首先前端可使用一個驗證碼輸入模式,讓用戶輸入驗證碼,經過獲取驗證碼和輸入驗證碼得時候來減輕數據庫壓力,而後再進行一個防重複點擊,在點擊一次以後須要五秒或者十秒以後再讓用戶進行交易
固然這種方式對於黑客是沒法行得通得.
後端限流:
對用戶進行訪問路徑次數使用redis進行記錄,或者使用內存threadlocal進行記錄用戶訪問次數,若是用戶超過三次就不讓用戶購買,這樣就能夠防止用戶進行攻擊.
至關於前端進行了兩次限流同樣,對用戶進行了限制交易
redis分佈式鎖問題:
可使用redisson進行使用redis分佈式鎖,這樣就能夠很好避免原生得分佈式鎖得一些弊端,同時也能夠避免多線程得安全問題就是庫存變成負數得問題.
當庫存爲0,那麼就直接返回數據給前端,當分佈式線程鎖住了,能夠提醒用戶,當前人數過多,
rabbitmq得可靠性投遞;
能夠先將消息,以及oreder實體落入數據庫,當消息消費以後能夠進行下一步進行手動簽收,這樣就能夠,再開啓一個task任務掃描數據庫表中數據,當是下單成功以後,就表明該消息成功消費,若是沒有消費成功,再進行重試.