SpringBoot實現Java高併發秒殺系統之併發優化
秒殺系統架構的設計和優化分析,以我一個小菜雞,目前是說不出來的o(╥﹏╥)o。
因此呢,我這裡僅從本專案已經實現的優化來介紹一下:
本專案中做到了以下優化:
秒殺介面採用md5加密方式防刷。
訂單表使用聯合主鍵方式,限制一個使用者只能購買該商品一次。
配合Spring事務控制實現簡單的優化。
使用redis快取優化。
Spring的事務控制
Spring的宣告式事務通過:傳播行為、隔離級別、只讀提示、事務超時、回滾規則來進行定義。
傳播行為
事務的第一個方面就是傳播行為。傳播行為定義了客戶端與被呼叫方法之間的事務邊界。Spring定義了7中不同的傳播行為,傳播規則規定了何時要建立一個事務或何時使用已有的事務:
傳播行為 含義
PROPAGATION_MANDATORY 表示該方法必須在事務中執行。如果當前事務不存在,則會丟擲一個異常
PROPAGATION_NESTED 表示如果當前已經存在一個事務,那麼該方法將會在巢狀事務中執行。巢狀的事務可以獨立與當前事務進行單獨的提交或回滾
PROPAGATION_NEVER 表示當前方法不應該執行在事務上下文中,如果當前正在有一個事務執行,則會丟擲異常
PROPAGATION_NOT_SUPPORTED 表示該方法不應該執行在事務中。
PROPAGATION_REQUIRED 表示當前方法必須執行在事務中。如果當前事務存在,方法將會在該事務中執行。否者,會啟動一個新的事務
PROPAGATION_REQUIRES_NEW 表示當前方法必須執行在它自己的事務中。一個新的事務將被啟動
PROPAGATION_SUPPORTS 表示當前方法不需要事務上下文,但是如果存在當前事務的話,那麼該方法會在這個事務中執行
隔離級別
宣告式事務的第二個維度就是隔離級別。隔離級別定義了一個事務可能受其他併發事務影響的程度。多個事務併發執行,經常會操作相同的資料來完成各自的任務,但是可以回導致以下問題:
更新丟失:當多個事務選擇同一行操作,並且都是基於最初的選定的值,由於每個事務都不知道其他事務的存在,就會發生更新覆蓋的問題。
髒讀:事務A讀取了事務B已經修改但為提交的資料。若事務B回滾資料,事務A的資料存在不一致的問題。
不可重複讀:書屋A第一次讀取最初資料,第二次讀取事務B已經提交的修改或刪除的資料。導致兩次資料讀取不一致。不符合事務的隔離性。
幻讀:事務A根據相同條件第二次查詢到的事務B提交的新增資料,兩次資料結果不一致,不符合事務的隔離性。
理想情況下,事務之間是完全隔離的,從而可以防止這些問題的發生。但是完全的隔離會導致效能問題,因為它通常會涉及鎖定資料庫中的記錄。侵佔性的鎖定會阻礙併發性,要求事務互相等待以完成各自的工作。
因此為了實現在事務隔離上有一定的靈活性。因此,就會有多重隔離級別:
隔離級別 含義
ISOLATION_DEFAULT 使用後端資料庫預設的隔離級別
SIOLATION_READ_UNCOMMITTED 允許讀取尚未提交的資料變更。可能會導致髒讀、幻讀或不可重複讀
ISOLATION_READ_COMMITTED 允許讀取併發事務提交的資料。可以阻止髒讀,但是幻讀或不可重複讀仍可能發生
ISOLATION_REPEATABLE_READ 對同一欄位的多次讀取結果是一致的,除非資料是被本事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍可能發生
ISOLATION_SERIALIZABLE 完全服從ACID的事務隔離級別,確保阻止髒讀、不可重複讀、幻讀。這是最慢的事務隔離級別,因為它通常是通過完全鎖定事務相關的資料庫來實現的
回滾規則
Spring的事務管理器預設是針對unchecked exception回滾,也就是預設對Error異常和RuntimeException異常以及其子類進行事務回滾。
也就是說事務只有在遇到執行期異常才會回滾,而在遇到檢查型異常時不會回滾。
這也就是我們之前設計Service業務層邏輯的時候一再強調捕獲try catch異常,且將編譯期異常轉換為執行期異常。
簡單優化
這裡我們還是要關注一些專案中的兩個核心的業務:1.減庫存;2.插入購買明細。我們以一張圖來看一下這兩個操作的事務執行流程:
可以看到我們的秒殺操作主要是基於Mysql的事務進行的,而基於MySQL事務的秒殺操作主要瓶頸是網路延遲和GC(Java垃圾回收機制)。執行一條update語句首先要拿到MySQL的行級鎖rowLock,而我們要解決的就是如何降低update對rowLock的持有時間。
我們先了解一下MySQL的InnoDB儲存引擎的行級鎖(rowLock):
行鎖的劣勢:開銷大;加鎖慢;會出現死鎖
行鎖的優勢:鎖的粒度小,發生鎖衝突的概率低;處理併發的能力強
加鎖的方式:自動加鎖。對於UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及資料集加排他鎖;對於普通SELECT語句,InnoDB不會加任何鎖;當然我們也可以顯示的加鎖:
共享鎖:select * from tableName where … + lock in share more
排他鎖:select * from tableName where … + for update
InnoDB和MyISAM的最大不同點有兩個:一,InnoDB支援事務(transaction);二,預設採用行級鎖。加鎖可以保證事務的一致性,可謂是有人(鎖)的地方,就有江湖(事務)。
詳細的介紹請看博文:MySQL 表鎖和行鎖機制
所以在此基礎上我們可以進行簡單的優化:
很簡單,就是調整update和insert操作的執行順序。目的就是為了縮短update對rowLock的持有時間提高效能,因為我們的查詢語句使用了insert ignore into xx的方式來避免重複秒殺,那麼閒執行insert語句可以在插入時就排除可能存在重複秒殺的操作,這樣就不用再向下執行更新操作了。在一定程度上降低了一倍的rowLock持有時間。
下面是原始碼:
@Override
@Transactional
public SeckillExecution executeSeckill(long seckillId, BigDecimal money, long userPhone, String md5)
throws SeckillException, RepeatKillException, SeckillCloseException {
if (md5 == null || !md5.equals(getMD5(seckillId))) {
throw new SeckillException(“seckill data rewrite”);
}
//執行秒殺邏輯:1.減庫存;2.儲存秒殺訂單
Date nowTime = new Date();
try {
//記錄秒殺訂單資訊
int insertCount = seckillOrderMapper.insertOrder(seckillId, money, userPhone);
//唯一性:seckillId,userPhone,保證一個使用者只能秒殺一件商品
if (insertCount <= 0) {
//重複秒殺
throw new RepeatKillException(“seckill repeated”);
} else {
//減庫存
int updateCount = seckillMapper.reduceStock(seckillId, nowTime);
if (updateCount <= 0) {
//沒有更新記錄,秒殺結束
throw new SeckillCloseException(“seckill is closed”);
} else {
//秒殺成功
SeckillOrder seckillOrder = seckillOrderMapper.findById(seckillId);
return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, seckillOrder);
}
}
} catch (SeckillCloseException e) {
throw e;
} catch (RepeatKillException e) {
throw e;
} catch (Exception e) {
logger.error(e.getMessage(), e);
//所有編譯期異常,轉換為執行期異常
throw new SeckillException(“seckill inner error:” + e.getMessage());
}
}
Redis快取優化
準備
如果想使用Redis快取進行優化,首先你需要連線什麼是Redis快取,以及Spring提供的一種操作Redis快取的框架:Spring-data-redis。最終要的是:你需要在本地電腦上安裝好Redis快取伺服器:
所以呢,我推薦你看一下我的幾篇文章:
Redis即Spring-data-redis入門學習
優雅的整合SSM+Shiro+Redis+Solr框架
在看了上面的文章後相信你已經初步瞭解了使用Spring-data-redis操作Redis快取伺服器,下面講解針對本專案的快取優化實現:
啟動安裝好的Redis快取伺服器,修改專案中的 resources/application.yml 關於Redis和Jedis的配置,
例中我使用的本地Redis伺服器:host:127.0.0.1;port:6379
瀋陽婦科醫院哪家好:http://iask.sina.com.cn/h-fk
瀋陽性病醫院哪家好:http://xb.029nk.com/