使用隔離層級和重試機制,Spring Boot輕松實現(xiàn)高并發(fā)
高并發(fā)難題
高并發(fā)是開發(fā)者面臨的一項挑戰(zhàn)。使用Read Uncommitted隔離層級和重試機制,可以大幅提高系統(tǒng)的性能。
在高并發(fā)系統(tǒng)中,鎖異常始終是一個問題。多名用戶或進程同時訪問常常導致資源的爭用,導致鎖沖突,并導致異常和性能瓶頸。該問題不僅會打斷開發(fā)流程,還會妨礙用戶體驗。解決這個問題對于確保流暢的、無中斷的服務至關重要,同時優(yōu)化資源利用率,面對不斷增加的高并發(fā)應用的需求。
解決方案
鎖異常是處理大量寫操作和事務時最常見的異常。接下來,我們使用樂觀鎖來解決這個問題。
Spring Boot中的樂觀鎖是一種確保多用戶環(huán)境中數(shù)據(jù)完整性的并發(fā)控制機制。它支持多個客戶端同時讀取和更新數(shù)據(jù),同時最小化沖突。這是通過將版本號或時間戳與數(shù)據(jù)庫中的每條記錄相關聯(lián)來實現(xiàn)的。當客戶端更新記錄時,將檢查版本號以檢測自數(shù)據(jù)最初讀取以來,其他客戶端是否對其進行了更改。如果檢測到沖突,系統(tǒng)可以通過回滾事務并拋出異常來處理它,這個異常可以用于重試嘗試。樂觀鎖是Spring Boot的JPA(Java 持久化 API)的關鍵功能,用于以安全和高效的方式管理數(shù)據(jù)庫記錄。
首先,我們需要在實體中添加由spring boot管理的列,如下面的代碼所示。
@Entity
@Data
public class YourEntity {
@Id
@GeneratedValue
private Long id;
// 其他字段
@Version
private Long version; // 樂觀鎖版本列
}
在上面的代碼注釋中,當對特定行進行更新時,@Version會自動更改版本號。所以,處理此行的其他事務會發(fā)現(xiàn)版本號已經更改,并將引發(fā)異常。我們可以捕獲此異常來重試事務,如下代碼所示。
@Service
public class MyService {
@Autowired
private YourEntityRepository yourEntityRepository; // 假設有實體倉庫
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Retryable(
value = {OptimisticLockingException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 200))
public YourEntity yourBusinesslogicTransactionMethod(Long entityId) {
YourEntity entity = yourEntityRepository.findById(entityId).orElse(null);
if (entity == null) {
throw new EntityNotFoundException("Entity with ID " + entityId + " not found.");
}
// 更新數(shù)據(jù)
entity.setName("UpdatedName");
// 存儲到數(shù)據(jù)庫
try {
yourEntityRepository.save(entity); // not using native query, only using default JPA methods
} catch (OptimisticLockingException ex) {
// 如果出現(xiàn)樂觀鎖異常,則表示發(fā)生了并發(fā)更新。
// @Retryable注釋將觸發(fā)指定次數(shù)的重試。
throw ex;
} catch (Exception ex) {
// 處理其他異常
}
return entity;
}
}
在上面代碼中,我們捕獲異常,如果發(fā)現(xiàn)鎖異常,則重試事務。隔離層級通常與事務性注釋一起使用。
Spring Boot中的隔離層級通過定義并發(fā)事務所做更改的可見性來控制數(shù)據(jù)庫系統(tǒng)中的事務如何相互交互。隔離層級包括如下類型:
READ_UNCOMITTED:支持讀取其他事務未提交的更改,提供最小的隔離。
READ_COMMITTED:支持只讀取已提交的更改,防止臟讀取。
REPEATABLE_READ:確保在當前事務完成之前,其他事務的更改不可見,從而消除不可重復的讀取。
SERIALIZABLE:提供與其他事務的完全隔離,防止對數(shù)據(jù)的任何并發(fā)訪問。
READ_UNCOMITTED隔離層級提供了最高的并發(fā)性。因此,在上面的代碼中,我們首先讀取一行,然后嘗試更新該行,在更新過程中,如果值發(fā)生了更改,JPA將檢查版本列,這意味著在我們讀取后,其他寫入操作也更改了值。JPA在版本號更改時拋出鎖異常。我們捕獲此異常并重試事務。下次事務將讀取更新后的值。
在Retry注釋的幫助下,我們可以輕松配置重試策略,如重試嘗試、重試嘗試和嘗試重試的異常之間的持續(xù)時間。
代碼分析
版本注釋與JPA默認查詢完美配合。因此,在讀取和更新期間,請嘗試使用默認的JPA查詢,不要使用本機查詢。如果使用本機查詢,則需要自己更新版本列數(shù)據(jù)。
當請求數(shù)量非常高,而每行的并發(fā)請求較少時,這種方法非常好。在例子中,我們沒有每行的高并發(fā)性。對于一行,并發(fā)請求的可能性幾乎為零,但我們仍然得到了鎖異常,因為我們沒有指定任何隔離級別,默認隔離級別是READ_COMMITTED。
樂觀鎖在不確定數(shù)據(jù)庫鎖行為的情況下很有價值。不用依賴數(shù)據(jù)庫的鎖定機制,而是在應用程序代碼中處理數(shù)據(jù)并發(fā)沖突。
在樂觀鎖中,只有正在更新的行被鎖定,而不是整個表。當事務更新一行時,通常會增加與該行相關的版本號或時間戳,并且在更新過程中,會檢查版本或時間戳。如果另一個事務同時修改了同一行,則會檢測到并發(fā)沖突,您可以根據(jù)需要進行處理。
樂觀鎖不會自動鎖定相鄰行或多行。它的設計目的是通過只鎖定特定更新操作中涉及的行來最大限度地減少對并發(fā)訪問的影響。其他事務可以繼續(xù)讀取或修改同一表中不相關的行,而不會被阻止。
根據(jù)事務和數(shù)據(jù)庫系統(tǒng)的隔離級別,帶有WHERE子句的讀取查詢可能會鎖定行。但是,確切的鎖定行為會根據(jù)所使用的隔離級別而有所不同。在樂觀的鎖定場景中,重要的是要仔細考慮隔離級別,以最大限度地降低阻塞其他事務的風險。
總結
優(yōu)化高并發(fā)任務時,策略方法是必不可少的。在Spring Boot應用程序中,可利用隔離層級并采用高效的重試機制以及樂觀鎖,在性能和數(shù)據(jù)完整性之間取得平衡。