陪玩原始碼介面效能優化,需要你掌握的關於呼叫的一些事

雲豹科技程式設計師發表於2021-12-23
陪玩原始碼的效能優化其實包含很多層次的內容,像資料庫效能、演算法效能、介面效能等。其中介面效能的優化可以採用的方法有很多,今天我們主要來了解一下從呼叫方面入手的優化方法。

一、遠端呼叫

很多時候,我們需要在陪玩原始碼的某個介面中,呼叫其他服務的介面。
比如有這樣的業務場景:
在使用者資訊查詢介面中需要返回:使用者名稱稱、性別、等級、頭像、積分、成長值等資訊。
而使用者名稱稱、性別、等級、頭像在使用者服務中,積分在積分服務中,成長值在成長值服務中。為了彙總陪玩原始碼中的這些資料統一返回,需要另外提供一個對外介面服務。
於是,使用者資訊查詢介面需要呼叫使用者查詢介面、積分查詢介面 和 成長值查詢介面,然後彙總資料統一返回。
呼叫過程如下圖所示:
陪玩原始碼介面效能優化,需要你掌握的關於呼叫的一些事
呼叫遠端介面總耗時 530ms = 200ms + 150ms + 180ms
顯然這種序列呼叫遠端介面效能是非常不好的,呼叫遠端介面總的耗時為所有的遠端介面耗時之和。
那麼如何優化陪玩原始碼的遠端介面效能呢?
1、 並行呼叫
上面說到,既然陪玩原始碼序列呼叫多個遠端介面效能很差,為什麼不改成並行呢?
如下圖所示:
陪玩原始碼介面效能優化,需要你掌握的關於呼叫的一些事
呼叫遠端介面總耗時 200ms = 200ms(即耗時最長的那次遠端介面呼叫)
在java8之前可以通過實現Callable介面,獲取執行緒返回結果。
java8以後通過CompleteFuture類實現該功能。我們這裡以CompleteFuture為例:
public UserInfo getUserInfo(Long id) throws InterruptedException, ExecutionException {
    final UserInfo userInfo = new UserInfo();
    CompletableFuture userFuture = CompletableFuture.supplyAsync(() -> {
        getRemoteUserAndFill(id, userInfo);
        return Boolean.TRUE;
    }, executor);
    CompletableFuture bonusFuture = CompletableFuture.supplyAsync(() -> {
        getRemoteBonusAndFill(id, userInfo);
        return Boolean.TRUE;
    }, executor);
    CompletableFuture growthFuture = CompletableFuture.supplyAsync(() -> {
        getRemoteGrowthAndFill(id, userInfo);
        return Boolean.TRUE;
    }, executor);
    CompletableFuture.allOf(userFuture, bonusFuture, growthFuture).join();
    userFuture.get();
    bonusFuture.get();
    growthFuture.get();
    return userInfo;
}
溫馨提醒一下,這兩種方式別忘了使用執行緒池。示例中我用到了executor,表示自定義的執行緒池,為了防止陪玩原始碼在高併發場景下,出現執行緒過多的問題。
2、資料異構
上面說到的使用者資訊查詢介面需要呼叫使用者查詢介面、積分查詢介面 和 成長值查詢介面,然後彙總資料統一返回。
那麼,陪玩原始碼能不能把資料冗餘一下,把使用者資訊、積分和成長值的資料統一儲存到一個地方,比如:redis,存的資料結構就是使用者資訊查詢介面所需要的內容。然後通過使用者id,直接從redis中查詢資料出來,不就OK了?
如果在高併發的場景下,為了提升介面效能,遠端介面呼叫大概率會被去掉,而改成儲存冗餘資料的資料異構方案。
陪玩原始碼介面效能優化,需要你掌握的關於呼叫的一些事
但需要注意的是,如果陪玩原始碼使用了資料異構方案,就可能會出現資料一致性問題。
使用者資訊、積分和成長值有更新的話,大部分情況下,會先更新到資料庫,然後同步到redis。但這種跨庫的操作,可能會導致兩邊資料不一致的情況產生。

二、重複呼叫

重複呼叫在陪玩原始碼中可以說隨處可見,但如果沒有控制好,會非常影響介面的效能。
不信,我們一起看看。
1、迴圈查資料庫
有時候,我們需要從陪玩原始碼指定的使用者集合中,查詢出有哪些是在資料庫中已經存在的。
實現程式碼可以這樣寫:
public List<User> queryUser(List<User> searchList) {
    if (CollectionUtils.isEmpty(searchList)) {
        return Collections.emptyList();
    }
    List<User> result = Lists.newArrayList();
    searchList.forEach(user -> result.add(userMapper.getUserById(user.getId())));
    return result;
}
這裡如果有50個使用者,則需要迴圈50次,去查詢資料庫。我們都知道,每查詢一次資料庫,就是一次遠端呼叫。
如果查詢50次資料庫,就有50次遠端呼叫,這是非常耗時的操作。
那麼,我們如何優化呢?
具體程式碼如下:
public List<User> queryUser(List<User> searchList) {
    if (CollectionUtils.isEmpty(searchList)) {
        return Collections.emptyList();
    }
    List<Long> ids = searchList.stream().map(User::getId).collect(Collectors.toList());
    return userMapper.getUserByIds(ids);
}
提供一個根據使用者id集合批量查詢使用者的介面,只遠端呼叫一次,就能查詢出所有的資料。
這裡有個需要注意的地方是:id集合的大小要做限制,最好一次不要請求太多的資料。要根據陪玩原始碼實際情況而定,建議控制每次請求的記錄條數在500以內。
2、死迴圈
有些小夥伴看到這個標題,可能會感到有點意外,死迴圈也算?
陪玩原始碼中不是應該避免死迴圈嗎?為啥還是會產生死迴圈?
有時候死迴圈是我們自己寫的,例如下面這段程式碼:
while(true) {
    if(condition) {
        break;
    }
    System.out.println("do samething");
}
這裡使用了while(true)的迴圈呼叫,這種寫法在CAS自旋鎖中使用比較多。
當滿足condition等於true的時候,則自動退出該迴圈。
如果condition條件非常複雜,一旦出現判斷不正確,或者少寫了一些邏輯判斷,就可能在某些場景下出現死迴圈的問題。
陪玩原始碼出現死迴圈,大概率是開發人員人為的bug導致的,不過這種情況很容易被測出來。
還有一種隱藏的比較深的死迴圈,是由於陪玩原始碼寫的不太嚴謹導致的。如果用正常資料,可能測不出問題,但一旦出現異常資料,就會立即出現死迴圈。
3、無限遞迴
如果想要列印某個分類的所有父分類,可以用類似這樣的遞迴方法實現:
public void printCategory(Category category) {
  if(category == null 
      || category.getParentId() == null) {
     return;
  } 
  System.out.println("父分類名稱:"+ category.getName());
  Category parent = categoryMapper.getCategoryById(category.getParentId());
  printCategory(parent);
}
正常情況下,這段程式碼是沒有問題的。
但如果某次有人誤操作,把某個分類的parentId指向了它自己,這樣就會出現無限遞迴的情況。導致介面一直不能返回資料,最終陪玩原始碼會發生堆疊溢位。
建議寫遞迴方法時,設定一個遞迴的深度,比如:分類最大等級有4級,則深度可以設定為4。然後在遞迴方法中做判斷,如果深度大於4時,則自動返回,這樣就能避免陪玩原始碼出現無限迴圈的情況。
本文轉載自網路,轉載僅為分享乾貨知識,如有侵權歡迎聯絡雲豹科技進行刪除處理 原文連結:https://mp.weixin.qq.com/s/0ez_mkyr0i4MZd7DEN7M8A


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69996194/viewspace-2849100/,如需轉載,請註明出處,否則將追究法律責任。

相關文章