天天看點

後端的優化

後端優化分為四個方向

  • 元件配置調優,偏運維
  • 架構調優,偏架構
  • 代碼層面的調優,偏開發
  • CDN 加速

配置調優

以 Nginx、PHP、MySQL 為例。

LNMP中web高并發優化配置以及配置詳解

https://phpartisan.cn/news/55.html

Nginx

從簡單粗暴的角度,就是提高連接配接數。

增加程序數,每個 CPU 配置一個程序。

程序數配置項: worker_processes

CPU 配置項: worker_cpu_affinity ,該選項使得 Nginx 每個程序都執行在不同 CPU

提高單程序允許的最多連接配接數。

配置項: worker_connections

理論上一台機器的最大連接配接數 = worker_processes * worker_connections

PHP-FPM

總體思想是控制程序數。

選項:pm

  • static

    固定程序數。如果是 PHP 專用伺服器,則可以将其設定為固定,并給定一個比較大的值。

  • dynamic (預設)

    根據以下幾個因素變化:

    • 啟動時程序數
    • 最大程序數
    • 至少有多少個空閑程序,少了就建立新空閑程序
    • 至多有多少個空閑程序,多了就銷毀空閑程序

每個 PHP-FPM 程序大緻占用 20 MB 的記憶體,用記憶體除以 20 MB 就是極限數量。但是要注意,如果設定極限數量,在有其他應用占用較大記憶體時,會導緻服務異常。

PHP

去掉沒有用到的擴充。

啟用 OPCache 擴充。

MySQL(InnoDB)

MySQL 的記憶體緩存大小對于性能的影響較大。

MySQL 的緩存分為兩部分:

  • 索引
  • 行記錄

配置項是: innodb_buffer_pool_size

這也是索引不能加太多的原因。索引加太多會導緻索引占用更多的緩存,進而使得行記錄的緩存減少。

索引的更多優化:

  • 索引不要加到重複資料多的列上。

    索引有一個參數 Cardinality,用于評估索引中唯一值的數目的估值。如果該值和表行數的比值小于一定程度,則不會使用索引。

  • 字段太長應使用部分索引。
  • 使用短 ID 作為主鍵。因為輔助索引的葉子節點存儲的是主鍵,如果主鍵太大,會使得輔助索引也變大。是以通常使用自增 ID 而不是 UUID 作為主鍵。
  • 必要情況下建立聯合索引。多條件情況下,單表隻會命中其中一個單列索引。

架構調優

瓶頸主要在資料庫。

使用雙 Nginx 伺服器(或者更多),用上 Keepalived + VIPA 組合確定高可用。

可以設定多個 VIPA ,分布到不同機器上,這些機器互為主備。接着讓域名同時解析到這些 VIPA。這樣可以充分利用多台 Nginx 伺服器,并且保證高可用。

從讀性能和寫性能兩方面入手。

提高讀性能:

  • 添加從機(備援資料),讀寫分離。讀取資料時,從不同的從機讀取。

    一般一主三從,兩從用于提供服務,一從用于背景通路。

    背景通路的服務如果是大資料服務,則可為這台機器設定更多索引來提升讀性能。但會給運維帶來維護的麻煩,是以慎用。通常來說保持與其他伺服器相同的配置。
  • 水準切分。将表中的舊資料轉存到同庫其他表或者其他庫。

    可以優先考慮分庫。因為磁盤滿的時候,還是要把表遷移到其他庫。

  • 垂直切分。将表中不常用的和長度較大的字段拆到另一張表。
  • 冷熱分離。如果隻有近三個月的資料通路量大,則将近三個月的資料盡量放到固态硬碟。将三個月之前的資料放到機械硬碟。
  • 索引外置。把資料備援一份到 Elastic Search 裡面。
  • 外部緩存。業務資料緩存到 Redis 裡面。Cache Aside Pattern。

注:所有資料備援都會帶來資料一緻性的問題。

兩種一緻性問題:

  • 主從不一緻
    • 業務允許時無視不一緻
    • 強制讀主。從庫讀不到時再去主庫讀一次。
    • 選擇性讀主(Redis)。資料更新通知 Redis,毫秒級緩存,查詢前先看更新的資料是否在 Redis 裡面,有則讀主。
  • 緩存不一緻(Redis)

    發生在寫後立即讀。緩存了舊資料。

    通過 binlog 了解主從同步進度,同步完删除緩存。

提高寫性能:

  • 多主多寫

    要解決 ID 沖突的問題。兩種方式:

    • 設定不同起始 ID ,提高自增 ID 步長(會導緻資料庫配置不一緻)
    • 用戶端生成 ID。生成 ID 的方式可以參考分布式 ID 的幾種生成方式。

分庫:

  • 單 key

    場景:使用者表查詢比登入多

    其他字段如果要加速,則專門做一個單字段到 UID 的映射表(可放入緩存加速)

  • 1 對多

    場景:使用者查訂單比訂單查使用者多

    使用者訂單。訂單 ID 攜帶使用者 ID 的資訊。讓同一個使用者的訂單落在同一個庫。

  • 多對多

    場景:關注與粉絲。

    建立兩個庫,分别用其中一個字段作為分庫依據。

  • 多 key

    場景:買家比賣家查訂單多,查訂單比查使用者多

    忽略最少的部分,退化為 1 對多。

    架構不能為 1% 的性能而帶來 20% 甚至更高的複雜性。

服務

無狀态化,可根據需要橫向擴容。

用 JWT(Json Web Token)驗證身份。

檔案存儲放分布式檔案存儲上面,如 MinIO。

代碼層面

分為:

  • 減少連接配接次數
  • 多線程/多程序
  • 緩存
  • 資料庫

例如項目中有一個子產品,要傳輸腳本到目标機器上執行。分為兩步:

  1. 傳輸腳本
  2. 執行

要建立兩次連接配接。

優化方式:将腳本 base64_encode,然後把執行指令拼接在後面。

echo "base64_encoded string" | base64 -d -i > /usr/local/src/xxx.sh; bash xxx.sh "param0";
           

碰到有多個耗時任務,為每個任務建立一個新的線程或者程序執行。

分為應用内緩存和外置緩存。

應用内緩存有些場景需要自己維護多台機器之間的緩存資訊,根據情況使用。

外置緩存(如 Redis/Memcached)。

将請求外部接口的資料緩存到 Redis,減少接口調用的耗時。

總體思想是盡可能減少資料量,盡可能早結束查詢,盡可能命中索引,盡可能減小鎖的粒度。

在執行語句前,先用 Explain 檢視執行計劃,盡量命中索引,避免全表掃描。

  1. 盡量避免使用 select *,需要多少字段拿多少字段
  2. 非唯一索引盡量使用 limit
  3. 使用索引來代理 limit 處理分頁

    limit 會掃描前面不要的資料,然後逐一抛棄。在 Where 裡面指定 ID 範圍會更快。

    業務層提供上一頁和下一頁的操作,避免使用者一次跳多頁。URL 要使用 after_xxx ,避免使用者直接修改 page。例如 GitHub 的 release 清單界面。

  4. 用 Union 替代 OR

    注:MySQL 的優化器會嘗試使用索引合并來自動優化 OR。

  5. 當資料集不會重複時,用 Union All 替代 Union
  6. 聯合索引最左比對原則
  7. 聯合索引在範圍查詢的字段後就不會再走索引了
  8. 删除由最左比對原則覆寫的索引
  9. 使用 like 時,避免把 % 放前面

    放在前面不走索引。

  10. 使用 Where 加更精确的條件限制來減少傳輸的資料量

    以前見過判斷使用者登入使用者名密碼的時候,把整個使用者表查出來再逐一判斷的代碼。

  11. 避免對索引列使用 MySQL 内置函數。
  12. 優先使用 Inner Join 而不是其他 Join。
  13. 如果使用 Left Join 或者 Right Join,驅動表資料量盡可能小。
  14. 避免在索引列上使用不等号。如果索引能用範圍掃描,則使用範圍操作符。

    例如 a != 1,轉化為 a < 1 AND a > 1。

  15. 大量資料使用批量分塊插入資料

    其中一個影響因素是鎖。一個事務插入已知數量的多條資料,隻需擷取一次鎖。

  16. 使用覆寫索引

    使用索引就能擷取想要的值,不需要從資料表中讀。

    用于輔助索引。

    因為索引的執行順序是:

    • 用輔助索引找到主鍵
    • 通過主鍵索引找到資料

      如果 select 的值隻包括輔助索引和主鍵,則使用覆寫索引。

  17. 盡量不要在 select 字段多的時候使用 Distinct
  18. 批量删除資料要謹慎
    • 分批操作。
    • 如果全部資料删除,且不需要恢複,則使用 truncate 。
    • 如果不是全部删除,則把保留的資料插入到新表,再整個删除舊表。

    批量删除會加鎖

    批量删除過程要寫 undo 日志,一旦復原,需要更多時間

  19. 避免資料類型隐式轉換

    隐式轉換會使索引失效

後端的優化

本文采用知識共享署名 2.5 中國大陸許可協定進行許可。歡迎轉載,演繹或用于商業目的,但是必須保留本文的署名 schaepher(包含連結)。如您有任何疑問或者授權方面的協商,請給我留言。

繼續閱讀