接口中的大事務,該如何進行優化?
1 前言
作爲後端開發的程序員,我們常常會的一些相對比較複雜的邏輯,比如我們需要給前端寫一個調用的接口,這個接口需要進行相對比較複雜的業務邏輯操作,比如會進行,查詢、遠程接口或本地接口調用、更新、插入、計算等一些邏輯,將最終接口的返回結果給到前端,而經過這麼一系列的業務邏輯操作,接口對 DB 的操作、對代碼業務邏輯判斷、進行接口調用這些都是需要時間的,而只要這是一個事務操作,每次對數據庫進行的交互都會產生一條事務記錄。
那麼這樣就會對我們接口返回的效率產生影響,而且這個影響是隨着數據量的增長而增長的,這時候我們就需要對一整個大事務進行拆分,從而提升整體接口的效率。
2 何爲大事務
就拿我最近開發寫的一個接口來說吧,大致是這麼一個邏輯,我需要根據頁面的提交的數據生成一個收款單,整體接口處理的業務如下,我把它們寫在了一個接口裏,可以理解爲這是一個大事物,這個接口執行的時間是相對比較長的,而且將這些邏輯全部寫在一個接口裏面,本身來說也是不太合理的。
3 大事務存在的一些問題
併發數據不一致
不加鎖的情況下,由於種種原因第一次接口的調用還沒執行完,還在等待第三方的調用回寫數據,第二次調用又進來對數據進行了更改,第二次調用先執行完,這時候第一次接口調用拿到了第三方接口的返回,去回寫狀態發現已經被更新,導致無效操作。
加鎖容易阻塞
加鎖的情況下, 不會出現數據不一致情況,但是由於大事物執行時間較長,容易造成鎖超時失效,鎖定太多的數據造成阻塞,嚴重影響效率。
Undo logo 事務日誌性能問題
容易造成 Undo logo 日誌數據量很大,降低了日誌的查詢性能,包括對事務的回滾效率也會降低。
併發數據庫壓力太大
併發量達到一定程度,會對數據庫讀寫造成不小的壓力,會堆積大量等待線程。
4 如何優化大事務
事務裏面不要進行遠程 RPC 調用
首先事務裏面進行遠程的接口調用,如果不採用分佈式事務框架,本身就會存在事務不一致的情況,無法進行數據的回滾操作,併發情況下遠程服務響應不及時,會出現接口返回不一致問題,當然必須採用異步調用,後面會提到。
編程型事務更加靈活
聲明式事務只需要加在方法頭加 @Transactional 註解即可開啓事務,但是還是不太靈活,意味着整個方法所進行對數據庫操作都要加進事務,當然一次查詢也要進入事務,這並不是我們想要的,我們在 update、insert 操作上進行事務操作,方便進行回滾。
public Boolean transactionCommit(String userName) {
//查詢用戶
SysUser sysUser = userMapper.selectUserByUserName(userName,null);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
if (null != sysUser) {
//用戶信息狀態更新 status更新爲1
userMapper.updateStatus(userName);
}
} catch (Exception e){
//回滾
transactionStatus.setRollbackOnly();
}
}
});
//再次查詢
SysUser sysUser1 = userMapper.selectUserByUserName(userName,"1");
/log/.info("狀態爲1的用戶信息"+JSON./toJSONString/(sysUser1));
return true;
}
編程式事務的靈活點在於可以控制事務執行方法,運用 transactionTemplate 類進行事務操作,查詢操作可以寫在外面,這樣查詢獲取數據的操作就不會進入 mysql 事務表。
數據分批處理
對於事務的更新或者插入,前端可能會有批量操作,大規模數據的批量更新、插入也會對事務接口產生影響,一旦其中有更新或插入失敗,爲了保證事務的一致性,整個操作都要進行回滾;
-
前端:可以限制數據,對後端接口的訪問,可以將數據進行分頁,多次請求,可以避免事務提交大量數據。
-
後端:也可以去數據進行分頁處理,例如每次可以限制 50 條進行操作,如果是新增邏輯,使用 Mybatis 的批量更新大大提升效率
List<List<ReceivableFeeSaveDTO>> partition = Lists.partition(receivableFeeSaveDTOList, 50);
大事務拆分小事務
可以將一個事務接口,拆分成多個事務接口,並且每個事務接口只做一件事,比如上面的收款單生成接口,金額回寫、第三方接口調用、調用後的結果回寫都可以抽成一個哥小事務接口。
就好比做一件很複雜的事情,咋一眼看上去很複雜,但是我們把這複雜的步驟,進行多個步驟的拆分,每個階段完成每個階段的事情,就可以將整個過程簡化,看起來就沒那麼複雜了。
異步並行處理
重中之重,事務裏如果無法避免遠程調用,那麼肯定是需要進行異步調用,因爲無法保證遠程接口的及時響應性,CompletableFuture 異步編排特性可以用到,task1 和 task2 任務結束後,執行 task3。
CompletableFuture<Object> task1 =CompletableFuture.supplyAsync(() -> {
System.out.println("單號check線程" + Thread.currentThread().getId());
//單號check接口 校驗失敗拋出異常
return "賬單實體信息";
}, executor);
CompletableFuture<Object> task2 = CompletableFuture.supplyAsync(() -> {
System.out.println("收款單生成線程" + Thread.currentThread().getId());
try {
//收款單生成
return “賬單編號”;
Thread.sleep(3000);
System.out.println("任務2結束:");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, executor);
//task1、task2 執行完執行task3 ,需要感知task1和task2的執行結果
CompletableFuture<Boolean> future = task1.thenCombineAsync(task2, (t1, t2) -> {
System.out.println("賬單金額回寫線程" + Thread.currentThread().getId());
// t1 、t2返回判斷
//回寫返回結果
return ture;
}, executor);
5 總結
可見大事務是我們接口效率低下的罪魁禍首,有時候我們爲了快速實現功能,可能會忽略一些關乎於性能的東西,而這些東西是我們能力提升的一個契機。
隨着你的進步,你也許會有疑問之前爲什麼這麼寫代碼,當你有這種感覺的時候,那麼恭喜你,你已經站在另一個山崗,俯瞰山下一切都是那麼的渺小,不多說我先去優化接口了~
來源:juejin.cn/post/7213636024110956599
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/hHsdRYWHRaS6rt3kFBUfew