Markdown

[心得]一些工作經驗回顧

第一份工作持續了兩年多,雖然所在的單位系統已經上線 N 年了,非常穩定,剩餘的工作都是維護居多。

但因為人數持續增長,也陸續面臨了各種奇怪的挑戰,常常要翻 LOG 查問題跟上面的人報告可以有哪些解法。

在找這些問題學到了不少,加上常常需要跟 DBA 還有 DC 打交道,多少增廣見聞。

礙於全部的筆記跟資料都留在公司電腦了,趁著休息的這段時間,
剛好慢慢回顧一下兩年來的一些心得,並陸續整理在這篇文章。




一些工作經驗回顧

機器選擇

Q: 如何評估新服務機器量
A: 估當前需求,評估使用次數,讀得多,寫得多,呼叫頻率為何?是否離峰時段跟尖峰差距很大。
再評估未來一到兩年需求可能會增長的量,各別壓測WEB與DB,看是否會遇到瓶頸,決定好後把服務簡單搭起來完整壓測 WEB+DB 再次評估。
單台 WEB 機器+單台 DB 機器,WEB 遇到瓶頸:
解決方案:
1.加 WEB 機器,導入利用 nginx 做 loadbalance
  • 優點: 簡單、便宜
  • 缺點: 未來瓶頸變成 MYSQL、擴展性低、可用性低
2.拆服務、拆機器
  • 優點: 性能高、擴展性高,未來可以依功能拆服務依服務加機器、可用性高
  • 缺點: 須對服務本身拆分難度高、WEB 機與 DB 數量需要較多,較昂貴

機器的 loadbalance:

有錢: 利用機器例如 F5 在Layer4(連線層)、Layer7(應用層)做掉
沒錢: Nginx
NGINX 方式有幾種算法
1.round-robin(default)
2.least connected load balancing
3.weighted load balancing
4.ip_hash
其中1~3方法無法保證同個使用者用到同一台。
另外曾經遇到使用最少連線時未設定上限,
導致瞬間大流量全部灌到同一台上掛掉,故設定上要小心。

其他:

worker_connections 、
client_max_body_size、
keepalive_timeout…

資料庫:

MYSQL
  1. loadbalance
  2. 開服務之前可跑 warmup
  3. sharding 可利用id或是地區等
  4. connection pool 減少開連線成本
  5. 依照業務拆DB,但MYSQL要考量JOIN與跨DB transactions問題
  6. 拆表
  • 垂直分表,一張表中有很多欄位不常用,將欄位拆出去,但要使用完整資料可能會多一道手續
  • 水平分表
    • 以 user_id,timestamp 之類去分範圍儲存,例如11000、10012000。
    • 一開始就切好數量,並以 user_id 之類的值去 hash 來決定儲存的表,但之後再次遇到問題會比較麻煩。
    • 水平拆分後要 join 需多次 join 後再合併。
    • count 也要進行相對多次,解決方法可以再開一張表紀錄更新某類型的表總數,但如果這個值是需要很精確一值頻繁更新的話會對DB造成另一個負擔。
    • order by 也要分很多段。
  1. 定期切+清理 partition
  2. 可以把一些固定性的資料例如明細或報表等只保留幾個月,由程式定期將舊資料移至 column base database 例如 infobright,撈取大量資料或是老舊資料時切換連線即可,優點是SELECT非常快速,但缺點是要進行 UPDATE 或 DELETE 非常非常慢。
Redis
  1. warmup,開服務前可先將固定已知資料預先塞入(如全球IP等)。
  2. redis cluster 缺點是無法直接scan,需要分台掃
  3. sharding,缺點是單線程沒備份掛掉很麻煩,最好是放置消失也沒關係的資料
  4. connection pool
  5. 要救回資料有幾種,有固定或可回推資料可以從db或log建回來,否則可考慮使用RDB、AOF設定來還原,RDB還是寫在memory中所以機器也掛的話一樣無法復原,優點是機器沒掛的話復原很快,且檔案小。AOF備份的話優點是每次寫入成功後就將該命令寫到檔案中,但缺點是檔案大,還原時間長。Redis 4.0 之後提供 RDB-AOF 混用的方案。
MYSQL 優化
  • 欄位型態是否合適
    • 數字:
      bigint(8byte)、int(4byte)、smallint(2byte)、tinyint(1byte)
      曾遇到使用int但 auto_increment 頂到上限,後來改成 bigint unsinged 實際範圍變成 1 ~ 2^64-1。
      但是相對的若是不需要那麼大則使用tinyint即可,例如年齡,boolean等。
      另外能用數字存的盡量別用字串。
    • 字串:
      char、varchar、tiniytext、text、mediumtext,
      其中 char 為固定大小指定多少就是多少不夠會補空白,適合用在確定長度的資料。

其他:

char 是固定的,開多大就是多大。
varchar 則是浮動的,
以 latin1 編碼下每個字元為 1byte 來比較,結果如下
ValueCHAR(4)Storage RequiredVARCHAR(4)Storage Required
‘’’ ’4 bytes‘’1 byte
‘ab’'ab ’4 bytes‘ab’3 bytes
‘abcd’‘abcd’4 bytes‘abcd’5 bytes
‘abcdefgh’‘abcd’4 bytes‘abcd’5 bytes
但通常資料庫會使用 utf8mb4_unicode_ci 以應付一些表情符號等,而 utf8mb4 一個字是 4byte,mysql varchar 最大長度為64k(2^16-1 = 65535 byte),考量到使用 utf8mb4,最多只能開到 VARCHAR(16383)。

SLOW QUERY 查詢

Type Possible keys Key Rows Scanned Duration (seconds) Extra info Rows returned
  • id:執行順序,id越大的越先被完成執行
  • select_type:
  • table 哪張表執行的,也有可能為以下命名。
    – unionM,N: The row refers to the union of the rows with id values of M and N.
    – derivedN: The row refers to the derived table result for the row with an id value of N. A derived table may result, for example, from a subquery in the FROM clause.
    – subqueryN: The row refers to the result of a materialized subquery for the row with an id value of N. See Section 8.2.2.2, “Optimizing Subqueries with Materialization”.
  • partitions
  • type: const > eq_reg > ref > range > index > ALL。
  • possible_keys:可用索引
  • key:實際用的index,沒有中的話就是NULL
  • key_len: INDEX 長度,通常越短越好
  • ref: All rows with matching index values are read from this table for each combination of rows from the previous tables. ref is used if the join uses only a leftmost prefix of the key or if the key is not a PRIMARY KEY or UNIQUE index (in other words, if the join cannot select a single row based on the key value). If the key that is used matches only a few rows, this is a good join type.
  • rows:查詢時使用到的行數
  • filtered:需要讀到的row占總數百分比
  • extra: 通常都先用這個當指標來優化,主要看有沒有 filesort 跟 temporary 產生。
    • 當出現 Using filesort 代表query中做了額外的 sort,可能是 order by 跟 index 條件相衝。
    • 當出現 Using temporary 代表query過程中需要開臨時表,通常發生在 order by 與 group by 條件用到不同欄位時。
  • ignore index、force index
    曾經遇到 mysql,選擇判斷使用 merge index 後反而比較慢,這時候可以視情況拔掉 index,或是改為比較快的 index。

留言