精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

接口性能優化的11個小技巧

開發 前端
在skywalking中可以通過?traceId(全局唯一的id),串聯一個接口請求的完整鏈路。可以看到整個接口的耗時,調用的遠程服務的耗時,訪問數據庫或者redis的耗時等等,功能非常強大。

前言

接口性能優化對于從事后端開發的同學來說,肯定再熟悉不過了,因為它是一個跟開發語言無關的公共問題。

該問題說簡單也簡單,說復雜也復雜。

有時候,只需加個索引就能解決問題。

有時候,需要做代碼重構。

有時候,需要增加緩存。

有時候,需要引入一些中間件,比如mq。

有時候,需要需要分庫分表。

有時候,需要拆分服務。

等等。。。

導致接口性能問題的原因千奇百怪,不同的項目不同的接口,原因可能也不一樣。

本文我總結了一些行之有效的,優化接口性能的辦法,給有需要的朋友一個參考。

1.索引

接口性能優化大家第一個想到的可能是:優化索引。

沒錯,優化索引的成本是最小的。

你通過查看線上日志或者監控報告,查到某個接口用到的某條sql語句耗時比較長。

這時你可能會有下面這些疑問:

  • 該sql語句加索引了沒?
  • 加的索引生效了沒?
  • mysql選錯索引了沒?

1.1 沒加索引

sql語句中where條件的關鍵字段,或者order by后面的排序字段,忘了加索引,這個問題在項目中很常見。

項目剛開始的時候,由于表中的數據量小,加不加索引sql查詢性能差別不大。

后來,隨著業務的發展,表中數據量越來越多,就不得不加索引了。

可以通過命令:

show index from `order`;

能單獨查看某張表的索引情況。

也可以通過命令:

show create table `order`;

查看整張表的建表語句,里面同樣會顯示索引情況。

通過ALTER TABLE命令可以添加索引:

ALTER TABLE `order` ADD INDEX idx_name (name);

也可以通過CREATE INDEX命令添加索引:

CREATE INDEX idx_name ON `order` (name);

不過這里有一個需要注意的地方是:想通過命令修改索引,是不行的。

目前在mysql中如果想要修改索引,只能先刪除索引,再重新添加新的。

刪除索引可以用DROP INDEX命令:

ALTER TABLE `order` DROP INDEX idx_name;

用DROP INDEX命令也行:

DROP INDEX idx_name ON `order`;

1.2 索引沒生效

通過上面的命令我們已經能夠確認索引是有的,但它生效了沒?此時你內心或許會冒出這樣一個疑問。

那么,如何查看索引有沒有生效呢?

答:可以使用explain命令,查看mysql的執行計劃,它會顯示索引的使用情況。

例如:

explain select * from `order` where code='002';

結果:

圖片圖片

通過這幾列可以判斷索引使用情況,執行計劃包含列的含義如下圖所示:

圖片圖片

如果你想進一步了解explain的詳細用法,可以看看我的另一篇文章《explain | 索引優化的這把絕世好劍,你真的會用嗎?》

說實話,sql語句沒有走索引,排除沒有建索引之外,最大的可能性是索引失效了。

下面說說索引失效的常見原因:

圖片圖片

如果不是上面的這些原因,則需要再進一步排查一下其他原因。

1.3 選錯索引

此外,你有沒有遇到過這樣一種情況:明明是同一條sql,只有入參不同而已。有的時候走的索引a,有的時候卻走的索引b?

沒錯,有時候mysql會選錯索引。

必要時可以使用force index來強制查詢sql走某個索引。

至于為什么mysql會選錯索引,后面有專門的文章介紹的,這里先留點懸念。

2. sql優化

如果優化了索引之后,也沒啥效果。

接下來試著優化一下sql語句,因為它的改造成本相對于java代碼來說也要小得多。

下面給大家列舉了sql優化的15個小技巧:

圖片圖片

由于這些技巧在我之前的文章中已經詳細介紹過了,在這里我就不深入了。

更詳細的內容,可以看我的另一篇文章《聊聊sql優化的15個小技巧》,相信看完你會有很多收獲。

3. 遠程調用

很多時候,我們需要在某個接口中,調用其他服務的接口。

比如有這樣的業務場景:

在用戶信息查詢接口中需要返回:用戶名稱、性別、等級、頭像、積分、成長值等信息。

而用戶名稱、性別、等級、頭像在用戶服務中,積分在積分服務中,成長值在成長值服務中。為了匯總這些數據統一返回,需要另外提供一個對外接口服務。

于是,用戶信息查詢接口需要調用用戶查詢接口、積分查詢接口 和 成長值查詢接口,然后匯總數據統一返回。

調用過程如下圖所示:

圖片圖片

調用遠程接口總耗時 530ms = 200ms + 150ms + 180ms

顯然這種串行調用遠程接口性能是非常不好的,調用遠程接口總的耗時為所有的遠程接口耗時之和。

那么如何優化遠程接口性能呢?

3.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,表示自定義的線程池,為了防止高并發場景下,出現線程過多的問題。

3.2 數據異構

上面說到的用戶信息查詢接口需要調用用戶查詢接口、積分查詢接口 和 成長值查詢接口,然后匯總數據統一返回。

那么,我們能不能把數據冗余一下,把用戶信息、積分和成長值的數據統一存儲到一個地方,比如:redis,存的數據結構就是用戶信息查詢接口所需要的內容。然后通過用戶id,直接從redis中查詢數據出來,不就OK了?

如果在高并發的場景下,為了提升接口性能,遠程接口調用大概率會被去掉,而改成保存冗余數據的數據異構方案。

圖片圖片

但需要注意的是,如果使用了數據異構方案,就可能會出現數據一致性問題。

用戶信息、積分和成長值有更新的話,大部分情況下,會先更新到數據庫,然后同步到redis。但這種跨庫的操作,可能會導致兩邊數據不一致的情況產生。

4. 重復調用

重復調用在我們的日常工作代碼中可以說隨處可見,但如果沒有控制好,會非常影響接口的性能。

不信,我們一起看看。

4.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以內。

4.2 死循環

有些小伙伴看到這個標題,可能會感到有點意外,死循環也算?

代碼中不是應該避免死循環嗎?為啥還是會產生死循環?

有時候死循環是我們自己寫的,例如下面這段代碼:

while(true) {
    if(condition) {
        break;
    }
    System.out.println("do samething");
}

這里使用了while(true)的循環調用,這種寫法在CAS自旋鎖中使用比較多。

當滿足condition等于true的時候,則自動退出該循環。

如果condition條件非常復雜,一旦出現判斷不正確,或者少寫了一些邏輯判斷,就可能在某些場景下出現死循環的問題。

出現死循環,大概率是開發人員人為的bug導致的,不過這種情況很容易被測出來。

還有一種隱藏的比較深的死循環,是由于代碼寫的不太嚴謹導致的。如果用正常數據,可能測不出問題,但一旦出現異常數據,就會立即出現死循環。

4.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時,則自動返回,這樣就能避免無限循環的情況。

5. 異步處理

有時候,我們接口性能優化,需要重新梳理一下業務邏輯,看看是否有設計上不太合理的地方。

比如有個用戶請求接口中,需要做業務操作,發站內通知,和記錄操作日志。為了實現起來比較方便,通常我們會將這些邏輯放在接口中同步執行,勢必會對接口性能造成一定的影響。

接口內部流程圖如下:

圖片圖片

這個接口表面上看起來沒有問題,但如果你仔細梳理一下業務邏輯,會發現只有業務操作才是核心邏輯,其他的功能都是非核心邏輯。

在這里有個原則就是:核心邏輯可以同步執行,同步寫庫。非核心邏輯,可以異步執行,異步寫庫。

上面這個例子中,發站內通知和用戶操作日志功能,對實時性要求不高,即使晚點寫庫,用戶無非是晚點收到站內通知,或者運營晚點看到用戶操作日志,對業務影響不大,所以完全可以異步處理。

通常異步主要有兩種:多線程 和 mq。

5.1 線程池

使用線程池改造之后,接口邏輯如下:

圖片圖片

發站內通知和用戶操作日志功能,被提交到了兩個單獨的線程池中。

這樣接口中重點關注的是業務操作,把其他的邏輯交給線程異步執行,這樣改造之后,讓接口性能瞬間提升了。

但使用線程池有個小問題就是:如果服務器重啟了,或者是需要被執行的功能出現異常了,無法重試,會丟數據。

那么這個問題該怎么辦呢?

5.2 mq

使用mq改造之后,接口邏輯如下:對于發站內通知和用戶操作日志功能,在接口中并沒真正實現,它只發送了mq消息到mq服務器。然后由mq消費者消費消息時,才真正的執行這兩個功能。

這樣改造之后,接口性能同樣提升了,因為發送mq消息速度是很快的,我們只需關注業務操作的代碼即可。

6. 避免大事務

很多小伙伴在使用spring框架開發項目時,為了方便,喜歡使用@Transactional注解提供事務功能。

沒錯,使用@Transactional注解這種聲明式事務的方式提供事務功能,確實能少寫很多代碼,提升開發效率。

但也容易造成大事務,引發其他的問題。

下面用一張圖看看大事務引發的問題。

圖片圖片

從圖中能夠看出,大事務問題可能會造成接口超時,對接口的性能有直接的影響。

我們該如何優化大事務呢?

  • 少用@Transactional注解
  • 將查詢(select)方法放到事務外
  • 事務中避免遠程調用
  • 事務中避免一次性處理太多數據
  • 有些功能可以非事務執行
  • 有些功能可以異步處理

關于大事務問題我的另一篇文章《讓人頭痛的大事務問題到底要如何解決?》,它里面做了非常詳細的介紹,如果大家感興趣可以看看。

7. 鎖粒度

在某些業務場景中,為了防止多個線程并發修改某個共享數據,造成數據異常。

為了解決并發場景下,多個線程同時修改數據,造成數據不一致的情況。通常情況下,我們會:加鎖。

但如果鎖加得不好,導致鎖的粒度太粗,也會非常影響接口性能。

7.1 synchronized

在java中提供了synchronized關鍵字給我們的代碼加鎖。

通常有兩種寫法:在方法上加鎖 和 在代碼塊上加鎖。

先看看如何在方法上加鎖:

public synchronized doSave(String fileUrl) {
    mkdir();
    uploadFile(fileUrl);
    sendMessage(fileUrl);
}

這里加鎖的目的是為了防止并發的情況下,創建了相同的目錄,第二次會創建失敗,影響業務功能。

但這種直接在方法上加鎖,鎖的粒度有點粗。因為doSave方法中的上傳文件和發消息方法,是不需要加鎖的。只有創建目錄方法,才需要加鎖。

我們都知道文件上傳操作是非常耗時的,如果將整個方法加鎖,那么需要等到整個方法執行完之后才能釋放鎖。顯然,這會導致該方法的性能很差,變得得不償失。

這時,我們可以改成在代碼塊上加鎖了,具體代碼如下:

public void doSave(String path,String fileUrl) {
    synchronized(this) {
      if(!exists(path)) {
          mkdir(path);
       }
    }
    uploadFile(fileUrl);
    sendMessage(fileUrl);
}

這樣改造之后,鎖的粒度一下子變小了,只有并發創建目錄功能才加了鎖。而創建目錄是一個非常快的操作,即使加鎖對接口的性能影響也不大。

最重要的是,其他的上傳文件和發送消息功能,任然可以并發執行。

當然,這種做在單機版的服務中,是沒有問題的。但現在部署的生產環境,為了保證服務的穩定性,一般情況下,同一個服務會被部署在多個節點中。如果哪天掛了一個節點,其他的節點服務任然可用。

多節點部署避免了因為某個節點掛了,導致服務不可用的情況。同時也能分攤整個系統的流量,避免系統壓力過大。

同時它也帶來了新的問題:synchronized只能保證一個節點加鎖是有效的,但如果有多個節點如何加鎖呢?

答:這就需要使用:分布式鎖了。目前主流的分布式鎖包括:redis分布式鎖、zookeeper分布式鎖 和 數據庫分布式鎖。

由于zookeeper分布式鎖的性能不太好,真實業務場景用的不多,這里先不講。

下面聊一下redis分布式鎖。

7.2 redis分布式鎖

在分布式系統中,由于redis分布式鎖相對于更簡單和高效,成為了分布式鎖的首先,被我們用到了很多實際業務場景當中。

使用redis分布式鎖的偽代碼如下:

public void doSave(String path,String fileUrl) {
  try {
    String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
    if ("OK".equals(result)) {
      if(!exists(path)) {
         mkdir(path);
         uploadFile(fileUrl);
         sendMessage(fileUrl);
      }
      return true;
    }
  } finally{
      unlock(lockKey,requestId);
  }  
  return false;
}

跟之前使用synchronized關鍵字加鎖時一樣,這里鎖的范圍也太大了,換句話說就是鎖的粒度太粗,這樣會導致整個方法的執行效率很低。

其實只有創建目錄的時候,才需要加分布式鎖,其余代碼根本不用加鎖。

于是,我們需要優化一下代碼:

public void doSave(String path,String fileUrl) {
   if(this.tryLock()) {
      mkdir(path);
   }
   uploadFile(fileUrl);
   sendMessage(fileUrl);
}

private boolean tryLock() {
    try {
    String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
    if ("OK".equals(result)) {
      return true;
    }
  } finally{
      unlock(lockKey,requestId);
  }  
  return false;
}

上面代碼將加鎖的范圍縮小了,只有創建目錄時才加了鎖。這樣看似簡單的優化之后,接口性能能提升很多。說不定,會有意外的驚喜喔。哈哈哈。

redis分布式鎖雖說好用,但它在使用時,有很多注意的細節,隱藏了很多坑,如果稍不注意很容易踩中。詳細內容可以看看我的另一篇文章《聊聊redis分布式鎖的8大坑》

7.3 數據庫分布式鎖

mysql數據庫中主要有三種鎖:

  • 表鎖:加鎖快,不會出現死鎖。但鎖定粒度大,發生鎖沖突的概率最高,并發度最低。
  • 行鎖:加鎖慢,會出現死鎖。但鎖定粒度最小,發生鎖沖突的概率最低,并發度也最高。
  • 間隙鎖:開銷和加鎖時間界于表鎖和行鎖之間。它會出現死鎖,鎖定粒度界于表鎖和行鎖之間,并發度一般。

并發度越高,意味著接口性能越好。

所以數據庫鎖的優化方向是:

優先使用行鎖,其次使用間隙鎖,再其次使用表鎖。

趕緊看看,你用對了沒?

8.分頁處理

有時候我會調用某個接口批量查詢數據,比如:通過用戶id批量查詢出用戶信息,然后給這些用戶送積分。

但如果你一次性查詢的用戶數量太多了,比如一次查詢2000個用戶的數據。參數中傳入了2000個用戶的id,遠程調用接口,會發現該用戶查詢接口經常超時。

調用代碼如下:

List<User> users = remoteCallUser(ids);

眾所周知,調用接口從數據庫獲取數據,是需要經過網絡傳輸的。如果數據量太大,無論是獲取數據的速度,還是網絡傳輸受限于帶寬,都會導致耗時時間比較長。

那么,這種情況要如何優化呢?

答:分頁處理。

將一次獲取所有的數據的請求,改成分多次獲取,每次只獲取一部分用戶的數據,最后進行合并和匯總。

其實,處理這個問題,要分為兩種場景:同步調用 和 異步調用。

8.1 同步調用

如果在job中需要獲取2000個用戶的信息,它要求只要能正確獲取到數據就好,對獲取數據的總耗時要求不太高。

但對每一次遠程接口調用的耗時有要求,不能大于500ms,不然會有郵件預警。

這時,我們可以同步分頁調用批量查詢用戶信息接口。

具體示例代碼如下:

List<List<Long>> allIds = Lists.partition(ids,200);

for(List<Long> batchIds:allIds) {
   List<User> users = remoteCallUser(batchIds);
}

代碼中我用的google的guava工具中的Lists.partition方法,用它來做分頁簡直太好用了,不然要巴拉巴拉寫一大堆分頁的代碼。

8.2 異步調用

如果是在某個接口中需要獲取2000個用戶的信息,它考慮的就需要更多一些。

除了需要考慮遠程調用接口的耗時之外,還需要考慮該接口本身的總耗時,也不能超時500ms。

這時候用上面的同步分頁請求遠程接口,肯定是行不通的。

那么,只能使用異步調用了。

代碼如下:

List<List<Long>> allIds = Lists.partition(ids,200);

final List<User> result = Lists.newArrayList();
allIds.stream().forEach((batchIds) -> {
   CompletableFuture.supplyAsync(() -> {
        result.addAll(remoteCallUser(batchIds));
        return Boolean.TRUE;
    }, executor);
})

使用CompletableFuture類,多個線程異步調用遠程接口,最后匯總結果統一返回。

9.加緩存

解決接口性能問題,加緩存是一個非常高效的方法。

但不能為了緩存而緩存,還是要看具體的業務場景。畢竟加了緩存,會導致接口的復雜度增加,它會帶來數據不一致問題。

在有些并發量比較低的場景中,比如用戶下單,可以不用加緩存。

還有些場景,比如在商城首頁顯示商品分類的地方,假設這里的分類是調用接口獲取到的數據,但頁面暫時沒有做靜態化。

如果查詢分類樹的接口沒有使用緩存,而直接從數據庫查詢數據,性能會非常差。

那么如何使用緩存呢?

9.1 redis緩存

通常情況下,我們使用最多的緩存可能是:redis和memcached。

但對于java應用來說,絕大多數都是使用的redis,所以接下來我們以redis為例。

由于在關系型數據庫,比如:mysql中,菜單是有上下級關系的。某個四級分類是某個三級分類的子分類,這個三級分類,又是某個二級分類的子分類,而這個二級分類,又是某個一級分類的子分類。

這種存儲結構決定了,想一次性查出這個分類樹,并非是一件非常容易的事情。這就需要使用程序遞歸查詢了,如果分類多的話,這個遞歸是比較耗時的。

所以,如果每次都直接從數據庫中查詢分類樹的數據,是一個非常耗時的操作。

這時我們可以使用緩存,大部分情況,接口都直接從緩存中獲取數據。操作redis可以使用成熟的框架,比如:jedis和redisson等。

用jedis偽代碼如下:

String json = jedis.get(key);
if(StringUtils.isNotEmpty(json)) {
   CategoryTree categoryTree = JsonUtil.toObject(json);
   return categoryTree;
}
return queryCategoryTreeFromDb();

先從redis中根據某個key查詢是否有菜單數據,如果有則轉換成對象,直接返回。如果redis中沒有查到菜單數據,則再從數據庫中查詢菜單數據,有則返回。

此外,我們還需要有個job每隔一段時間,從數據庫中查詢菜單數據,更新到redis當中,這樣以后每次都能直接從redis中獲取菜單的數據,而無需訪問數據庫了。

圖片圖片

這樣改造之后,能快速的提升性能。

但這樣做性能提升不是最佳的,還有其他的方案,我們一起看看下面的內容。

9.2 二級緩存

上面的方案是基于redis緩存的,雖說redis訪問速度很快。但畢竟是一個遠程調用,而且菜單樹的數據很多,在網絡傳輸的過程中,是有些耗時的。

有沒有辦法,不經過請求遠程,就能直接獲取到數據呢?

答:使用二級緩存,即基于內存的緩存。

除了自己手寫的內存緩存之后,目前使用比較多的內存緩存框架有:guava、Ehcache、caffine等。

我們在這里以caffeine為例,它是spring官方推薦的。

第一步,引入caffeine的相關jar包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.6.0</version>
</dependency>

第二步,配置CacheManager,開啟EnableCaching

@Configuration
@EnableCaching
public class CacheConfig {
    @Bean
    public CacheManager cacheManager(){
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        //Caffeine配置
        Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
                //最后一次寫入后經過固定時間過期
                .expireAfterWrite(10, TimeUnit.SECONDS)
                //緩存的最大條數
                .maximumSize(1000);
        cacheManager.setCaffeine(caffeine);
        return cacheManager;
    }
}

第三步,使用Cacheable注解獲取數據

@Service
public class CategoryService {
   
   @Cacheable(value = "category", key = "#categoryKey")
   public CategoryModel getCategory(String categoryKey) {
      String json = jedis.get(categoryKey);
      if(StringUtils.isNotEmpty(json)) {
         CategoryTree categoryTree = JsonUtil.toObject(json);
         return categoryTree;
      }
      return queryCategoryTreeFromDb();
   }
}

調用categoryService.getCategory()方法時,先從caffine緩存中獲取數據,如果能夠獲取到數據,則直接返回該數據,不進入方法體。

如果不能獲取到數據,則再從redis中查一次數據。如果查詢到了,則返回數據,并且放入caffine中。

如果還是沒有查到數據,則直接從數據庫中獲取到數據,然后放到caffine緩存中。

具體流程圖如下:

圖片圖片

該方案的性能更好,但有個缺點就是,如果數據更新了,不能及時刷新緩存。此外,如果有多臺服務器節點,可能存在各個節點上數據不一樣的情況。

由此可見,二級緩存給我們帶來性能提升的同時,也帶來了數據不一致的問題。使用二級緩存一定要結合實際的業務場景,并非所有的業務場景都適用。

但上面我列舉的分類場景,是適合使用二級緩存的。因為它屬于用戶不敏感數據,即使出現了稍微有點數據不一致也沒有關系,用戶有可能都沒有察覺出來。

10. 分庫分表

有時候,接口性能受限的不是別的,而是數據庫。

當系統發展到一定的階段,用戶并發量大,會有大量的數據庫請求,需要占用大量的數據庫連接,同時會帶來磁盤IO的性能瓶頸問題。

此外,隨著用戶數量越來越多,產生的數據也越來越多,一張表有可能存不下。由于數據量太大,sql語句查詢數據時,即使走了索引也會非常耗時。

這時該怎么辦呢?

答:需要做分庫分表。

如下圖所示:

圖片圖片

圖中將用戶庫拆分成了三個庫,每個庫都包含了四張用戶表。

如果有用戶請求過來的時候,先根據用戶id路由到其中一個用戶庫,然后再定位到某張表。

路由的算法挺多的:

  • 根據id取模,比如:id=7,有4張表,則7%4=3,模為3,路由到用戶表3。
  • 給id指定一個區間范圍,比如:id的值是0-10萬,則數據存在用戶表0,id的值是10-20萬,則數據存在用戶表1。
  • 一致性hash算法

分庫分表主要有兩個方向:垂直和水平。

說實話垂直方向(即業務方向)更簡單。

在水平方向(即數據方向)上,分庫和分表的作用,其實是有區別的,不能混為一談。

  • 分庫:是為了解決數據庫連接資源不足問題,和磁盤IO的性能瓶頸問題。
  • 分表:是為了解決單表數據量太大,sql語句查詢數據時,即使走了索引也非常耗時問題。此外還可以解決消耗cpu資源問題。
  • 分庫分表:可以解決 數據庫連接資源不足、磁盤IO的性能瓶頸、檢索數據耗時 和 消耗cpu資源等問題。

如果在有些業務場景中,用戶并發量很大,但是需要保存的數據量很少,這時可以只分庫,不分表。

如果在有些業務場景中,用戶并發量不大,但是需要保存的數量很多,這時可以只分表,不分庫。

如果在有些業務場景中,用戶并發量大,并且需要保存的數量也很多時,可以分庫分表。

關于分庫分表更詳細的內容,可以看看我另一篇文章,里面講的更深入《阿里二面:為什么分庫分表?》

11. 輔助功能

優化接口性能問題,除了上面提到的這些常用方法之外,還需要配合使用一些輔助功能,因為它們真的可以幫我們提升查找問題的效率。

11.1 開啟慢查詢日志

通常情況下,為了定位sql的性能瓶頸,我們需要開啟mysql的慢查詢日志。把超過指定時間的sql語句,單獨記錄下來,方面以后分析和定位問題。

開啟慢查詢日志需要重點關注三個參數:

  • slow_query_log 慢查詢開關
  • slow_query_log_file 慢查詢日志存放的路徑
  • long_query_time 超過多少秒才會記錄日志

通過mysql的set命令可以設置:

set global slow_query_log='ON'; 
set global slow_query_log_file='/usr/local/mysql/data/slow.log';
set global long_query_time=2;

設置完之后,如果某條sql的執行時間超過了2秒,會被自動記錄到slow.log文件中。

當然也可以直接修改配置文件my.cnf

[mysqld]
slow_query_log = ON
slow_query_log_file = /usr/local/mysql/data/slow.log
long_query_time = 2

但這種方式需要重啟mysql服務。

很多公司每天早上都會發一封慢查詢日志的郵件,開發人員根據這些信息優化sql。

11.2 加監控

為了出現sql問題時,能夠讓我們及時發現,我們需要對系統做監控。

目前業界使用比較多的開源監控系統是:Prometheus。

它提供了 監控 和 預警 的功能。

架構圖如下:

圖片圖片

我們可以用它監控如下信息:

  • 接口響應時間
  • 調用第三方服務耗時
  • 慢查詢sql耗時
  • cpu使用情況
  • 內存使用情況
  • 磁盤使用情況
  • 數據庫使用情況

等等。。。

它的界面大概長這樣子:

圖片圖片

可以看到mysql當前qps,活躍線程數,連接數,緩存池的大小等信息。

如果發現數據量連接池占用太多,對接口的性能肯定會有影響。

這時可能是代碼中開啟了連接忘了關,或者并發量太大了導致的,需要做進一步排查和系統優化。

截圖中只是它一小部分功能,如果你想了解更多功能,可以訪問Prometheus的官網:https://prometheus.io/

11.3 鏈路跟蹤

有時候某個接口涉及的邏輯很多,比如:查數據庫、查redis、遠程調用接口,發mq消息,執行業務代碼等等。

該接口一次請求的鏈路很長,如果逐一排查,需要花費大量的時間,這時候,我們已經沒法用傳統的辦法定位問題了。

有沒有辦法解決這問題呢?

用分布式鏈路跟蹤系統:skywalking。

架構圖如下:

圖片圖片

通過skywalking定位性能問題:

圖片圖片

在skywalking中可以通過traceId(全局唯一的id),串聯一個接口請求的完整鏈路。可以看到整個接口的耗時,調用的遠程服務的耗時,訪問數據庫或者redis的耗時等等,功能非常強大。

之前沒有這個功能的時候,為了定位線上接口性能問題,我們還需要在代碼中加日志,手動打印出鏈路中各個環節的耗時情況,然后再逐一排查。

如果你用過skywalking排查接口性能問題,不自覺的會愛上它的。如果你想了解更多功能,可以訪問skywalking的官網:https://skywalking.apache.org/

責任編輯:武曉燕 來源: 蘇三說技術
相關推薦

2021-11-18 08:20:22

接口索引SQL

2022-10-09 13:36:44

接口性能優化

2024-01-22 13:16:00

接口性能優化本地緩存

2022-02-21 13:27:11

接口性能優化索引命令

2022-04-01 15:17:05

Java開發技巧

2022-08-12 10:41:57

接口性能優化

2023-09-25 13:15:50

SQL數據庫

2015-09-16 14:47:14

Android性能優化代碼

2020-05-17 16:19:59

JavaScript代碼開發

2019-07-18 12:40:49

Java編程語言性能優化

2022-11-24 10:34:05

CSS前端

2024-06-11 00:09:00

JavaScript模式變量

2021-11-10 18:52:42

SQL技巧優化

2021-05-07 16:02:54

Python代碼優化

2021-06-16 10:50:16

Python代碼優化

2022-03-10 08:01:06

CSS技巧選擇器

2009-04-16 16:57:58

DotNetNuke優化網站開發

2020-03-25 08:00:32

Kubernetes節點工作

2011-05-10 17:06:05

SEO

2019-08-21 10:53:29

.NET性能優化
點贊
收藏

51CTO技術棧公眾號

日韩精品一区二区三区中文字幕| 亚洲国产日韩在线观看| 国产伦一区二区三区| 欧美无人高清视频在线观看| 欧美 日韩 国产 在线观看| 国产露脸无套对白在线播放| 亚洲高清激情| 国产一区二区久久精品| 夜夜爽久久精品91| www.成人爱| 国产精品初高中害羞小美女文| 999热视频| 日韩在线 中文字幕| 亚洲澳门在线| 国产丝袜一区二区| 一级片黄色免费| 91美女主播在线视频| 久久久久久97三级| www.久久草| 波多野结衣mp4| 国产一区二区三区四区三区四| 亚洲色图18p| 自拍一级黄色片| 自由日本语热亚洲人| 亚洲精品久久嫩草网站秘色| 欧美一区三区二区在线观看| 国产高中女学生第一次| 日韩成人精品视频| 亚州精品天堂中文字幕| 九九精品视频免费| 禁断一区二区三区在线| 精品av久久707| 中文字幕色网站| 性感美女一区二区在线观看| 亚洲不卡在线观看| 51xx午夜影福利| 国产高清一区在线观看| 99久久免费精品| 91丨九色丨国产| 中文字幕丰满人伦在线| 羞羞答答国产精品www一本 | melody高清在线观看| 成人黄色综合网站| 91夜夜揉人人捏人人添红杏| 国产精品午夜一区二区| 免费日韩精品中文字幕视频在线| 欧美黑人又粗大| 亚洲AV成人无码精电影在线| 欧美日韩一二三四| 亚洲免费视频网站| 亚洲av成人片无码| 9国产精品午夜| 日韩午夜在线播放| 成人三级做爰av| 高清一区二区| 在线不卡一区二区| 中文字幕网av| 51一区二区三区| 日本久久一区二区三区| 麻豆av免费在线| 中文字幕在线直播| 一本一道波多野结衣一区二区| 国产亚洲黄色片| 成人在线免费观看黄色| 亚洲国产综合人成综合网站| 日韩在线视频在线| 久久香蕉av| 午夜伦理一区二区| 欧美女人性生活视频| 三级在线看中文字幕完整版| 精品国产成人在线| 无码人妻精品一区二区三区在线| 国产美女一区视频| 五月婷婷久久丁香| 久色视频在线播放| 在线人成日本视频| 欧美色图在线观看| 三日本三级少妇三级99| 久久伊人久久| 亚洲大胆人体视频| 四虎永久免费影院| 成人久久电影| 精品国产欧美一区二区五十路| 91n在线视频| 午夜久久黄色| 97精品国产97久久久久久| 国产小视频在线免费观看| 日韩精品欧美成人高清一区二区| 国产精品一区久久久| 国产成人三级在线播放| proumb性欧美在线观看| 午夜一区二区三区| fc2ppv国产精品久久| 精品久久久久久电影| 成年人网站大全| www.久久久久爱免| 亚洲国产精品久久久久| 精品成人无码一区二区三区| 无码一区二区三区视频| 欧美精品久久一区二区| 无码视频一区二区三区| 国内精品第一页| 国产一区二区三区av在线| 九色在线播放| 亚洲视频在线一区观看| 免费成人在线视频网站| 久久av影院| 日韩精品免费在线播放| 亚洲av无一区二区三区| 伊人久久婷婷| 国产精选久久久久久| www.亚洲欧美| 国产欧美精品一区| 久草视频国产在线| 香蕉成人在线| 亚洲视频综合网| 久久久精品91| 久久电影网站中文字幕| 精品一区二区三区日本| 久久bbxx| 欧美影视一区二区三区| 亚洲天堂av网站| 91成人免费| 国产精品视频自在线| 先锋av资源站| 一区av在线播放| 午夜免费看视频| 免费欧美激情| 国内久久久精品| 97人妻精品一区二区三区视频| 99精品欧美一区二区三区综合在线| 在线码字幕一区| 国产精品亚洲一区二区三区在线观看| 91精品国产色综合久久ai换脸 | 久久亚洲欧美国产精品乐播| 日本丰满大乳奶| 成人一级视频| 亚洲午夜久久久久久久| 国产欧美一区二区三区在线看蜜臂 | 不卡av电影在线观看| 黄色av一区二区| 91小视频免费看| 国产精品国产对白熟妇| 视频精品二区| 欧美成年人视频网站欧美| 综合久久中文字幕| 国产精品人人做人人爽人人添| 能在线观看的av网站| 欧美亚视频在线中文字幕免费| 欧美国产日韩精品| 亚洲高清视频在线播放| 亚洲激情图片qvod| 永久av免费在线观看| 1024精品久久久久久久久| 成人xxxx视频| 精品国产99久久久久久| 欧美精品少妇一区二区三区| 91成人精品一区二区| 蜜臀av一区二区| 亚洲国产日韩综合一区| 成人黄色免费网站| www国产精品视频| 国产精品永久久久久久久久久| 国产精品久久久久久户外露出 | 亚洲视频在线观看日本a| 99久久久国产精品免费调教网站| 亚洲视频综合网| 中文字幕网址在线| 综合久久久久综合| 国产精品99精品无码视亚| 欧美涩涩视频| 国产综合 伊人色| 亚洲国产成人二区| 在线成人激情视频| 国产模特av私拍大尺度| 亚洲精品视频免费观看| 污污免费在线观看| 三级一区在线视频先锋 | 国产精品亚洲欧美导航| 激情成人四房播| 欧美videos大乳护士334| 日韩免费一级片| 久久久综合九色合综国产精品| 欧美精品aaaa| 欧美大片一区| 久久婷婷开心| 亚洲网站三级| 97视频人免费观看| 国产69精品久久app免费版| 欧美精品777| 日韩欧美中文字幕一区二区| 国产亚洲综合性久久久影院| 日本高清久久久| 国产一区二区三区四区老人| 欧美精品一区二区三区在线四季| 四虎国产精品成人免费影视| 久久久亚洲福利精品午夜| 日本a一级在线免费播放| 欧美女孩性生活视频| 国产在线拍揄自揄拍| 国产婷婷色一区二区三区| 视频区 图片区 小说区| 一本色道久久综合亚洲精品高清| 亚洲国产另类久久久精品极度| 在这里有精品| 国产精品美腿一区在线看| 黄网av在线| 深夜福利一区二区| 人妻少妇精品无码专区| 欧美探花视频资源| 国产五月天婷婷| 国产精品久久久久久久浪潮网站| 午夜影院福利社| 六月丁香综合在线视频| 久在线观看视频| 欧美成人日本| 亚洲一区二区精品在线| 日韩av不卡一区| 亚洲一区二区三区乱码aⅴ蜜桃女| 精品捆绑调教一区二区三区| 日韩中文在线不卡| 你懂的在线观看| 日韩欧美在线观看一区二区三区| 亚洲 欧美 中文字幕| 亚洲国产综合色| 国产av 一区二区三区| 久久久www成人免费毛片麻豆 | 成人毛片免费| 97在线免费观看| 伊人影院在线视频| 日韩视频免费在线| 久蕉在线视频| 亚洲黄色av网站| 精品人妻一区二区三区浪潮在线 | 欧美激情三级免费| 欧美成人性生活视频| 亚洲人成人99网站| 天天干天天操av| 日韩欧美国产三级| 99草在线视频| 欧美人与禽zozo性伦| chinese国产精品| 日韩欧美国产网站| 日韩xxx高潮hd| 亚洲成人av免费| 精品午夜福利视频| 亚洲国产一二三| 美女毛片在线观看| 一区二区三区四区激情| 免费看一级一片| 一卡二卡欧美日韩| 激情四射综合网| 亚洲综合激情另类小说区| 久久久国产成人| 亚洲丶国产丶欧美一区二区三区| 午夜写真片福利电影网| 亚洲免费色视频| 91视频免费在线看| 亚洲大片免费看| 青青草av在线播放| 欧美日韩午夜剧场| 久久国产视频一区| 欧美专区亚洲专区| 一级黄色片在线观看| 91精选在线观看| 精品国产亚洲一区二区麻豆| 精品福利在线导航| 亚洲三区在线播放| 亚洲天堂色网站| 日本精品在线| 九九热视频这里只有精品| 牛牛精品在线| 538国产精品一区二区在线| 裤袜国产欧美精品一区| 国产精品视频一| 91精品麻豆| 国产经典一区二区三区| 天堂日韩电影| 亚洲高清不卡一区| 希岛爱理一区二区三区| 日韩精品在线视频免费观看| 一本色道久久综合亚洲精品高清| www日韩视频| 国产在线播放一区三区四| 久久久久亚洲av无码网站| 91啪亚洲精品| 艳妇荡乳欲伦69影片| 亚洲国产精品影院| 手机av免费观看| 欧美一区二区在线播放| 午夜福利视频一区二区| 一区二区在线视频| bl视频在线免费观看| 国产精品第3页| 中文无码日韩欧| 日韩欧美一区二区三区四区| 亚洲高清影视| 国产成人久久777777| 国产主播一区二区三区| 欧美精品黑人猛交高潮| 亚洲视频免费观看| 91美女免费看| 91精品一区二区三区久久久久久| 婷婷久久久久久| 日韩视频免费看| 免费观看一级欧美片| www.久久艹| 日韩一区二区三区免费播放| 国产va亚洲va在线va| 久久国产精品72免费观看| 国产亚洲无码精品| 亚洲精品自拍动漫在线| 波多野结衣一本一道| 欧美成人性战久久| 午夜小视频在线| 欧美在线视频免费播放| 天堂久久av| 中文字幕一区二区三区乱码| 性高湖久久久久久久久| 无码人妻一区二区三区免费n鬼沢| 国产清纯白嫩初高生在线观看91 | 欧美亚洲国产怡红院影院| 丁香花免费高清完整在线播放 | 国精一区二区三区| 国产精自产拍久久久久久| 一本色道久久综合狠狠躁的番外| 91免费国产精品| 精品一二三四在线| 成人激情五月天| 色999日韩国产欧美一区二区| 懂色av一区二区三区四区| 久久成人一区二区| 欧美视频免费看| 日韩免费一区二区三区| 国产农村妇女精品一二区| 最新日本中文字幕| 一区二区欧美国产| 精品国产av鲁一鲁一区 | 午夜国产福利视频| 在线国产亚洲欧美| 国产黄色在线| 国产精品高潮在线| 国产亚洲电影| www日韩在线观看| 99久久免费视频.com| 五月婷婷激情网| 亚洲精品suv精品一区二区| 美女尤物在线视频| 成人毛片网站| 亚洲精品123区| 欧产日产国产精品98| 午夜天堂影视香蕉久久| 天堂а√在线8种子蜜桃视频| 久久久久久久久久av| 一区二区三区欧洲区| 日产精品久久久久久久蜜臀| 粉嫩久久99精品久久久久久夜 | 欧美三级第一页| 伊人久久一区二区三区| 天天综合天天做天天综合| 亚洲 国产 欧美 日韩| 国产成人精品久久二区二区91| 九一成人免费视频| 久久精品视频91| 国产精品国产三级国产| 国产乱码精品一区二区三区精东 | 女厕盗摄一区二区三区| 久久另类ts人妖一区二区| 视频精品一区二区| 国产精品一区二区亚洲| 欧美一区二区三区四区五区| 亚洲wwwww| 精品国产综合久久| 日本v片在线高清不卡在线观看| 91麻豆制片厂| 91精品国产品国语在线不卡| 免费男女羞羞的视频网站在线观看| 国产伦精品一区二区| 久久精品九九| 裸体武打性艳史| 亚洲国产小视频在线观看| 91精品韩国| 国产 国语对白 露脸 | 影音先锋男人在线| 4hu四虎永久在线影院成人| 黄页网站在线| 日韩在线电影一区| 国产一区日韩二区欧美三区| 日韩经典在线观看| 在线观看免费高清视频97| 国产亚洲亚洲国产一二区| 久久精品国产精品亚洲色婷婷| 亚洲国产精品t66y| av中文字幕免费在线观看| 欧美一区二区三区免费视| re久久精品视频| 制服下的诱惑暮生| 精品久久久一区二区| 在线看免费av| 国产精品久久久久av免费|