后端的優化


后端優化分為四個方向

  • 組件配置調優,偏運維
  • 架構調優,偏架構
  • 代碼層面的調優,偏開發
  • 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

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

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

MySQL(InnoDB)

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

提高讀性能:

  • 添加從機(冗余數據),讀寫分離。讀取數據時,從不同的從機讀取。
    一般一主三從,兩從用於提供服務,一從用於后台訪問。

    后台訪問的服務如果是大數據服務,則可為這台機器設置更多索引來提升讀性能。但會給運維帶來維護的麻煩,所以慎用。通常來說保持與其他服務器相同的配置。

  • 水平切分。將表中的舊數據轉存到同庫其他表或者其他庫。
    可以優先考慮分庫。因為磁盤滿的時候,還是要把表遷移到其他庫。
  • 垂直切分。將表中不常用的和長度較大的字段拆到另一張表。
  • 冷熱分離。如果只有近三個月的數據訪問量大,則將近三個月的數據盡量放到固態硬盤。將三個月之前的數據放到機械硬盤。
  • 索引外置。把數據冗余一份到 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,減少接口調用的耗時。

MySQL(InnoDB)

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

在執行語句前,先用 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. 避免數據類型隱式轉換
    隱式轉換會使索引失效


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM