日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

文章來(lái)源:linux性能優(yōu)化實(shí)戰(zhàn)

 

redis 是最常用的鍵值存儲(chǔ)系統(tǒng)之一,常用作數(shù)據(jù)庫(kù)、高速緩存和消息隊(duì)列代理等。Redis 基于內(nèi)存來(lái)存儲(chǔ)數(shù)據(jù),不過,為了保證在服務(wù)器異常時(shí)數(shù)據(jù)不丟失,很多情況下,我們要為它配置持久化,而這就可能會(huì)引發(fā)磁盤 I/O 的性能問題。

今天,我就帶你一起來(lái)分析一個(gè)利用 Redis 作為緩存的案例。

這同樣是一個(gè)基于 Python Flask 的應(yīng)用程序,它提供了一個(gè) 查詢緩存的接口,但接口的響應(yīng)時(shí)間比較長(zhǎng),并不能滿足線上系統(tǒng)的要求。

非常感謝攜程系統(tǒng)研發(fā)部資深后端工程師董國(guó)星,幫助提供了今天的案例。

案例準(zhǔn)備

本次案例還是基于 Ubuntu 18.04,同樣適用于其他的 Linux 系統(tǒng)。我使用的案例環(huán)境如下所示:

機(jī)器配置:2 CPU,8GB 內(nèi)存

預(yù)先安裝 Docker、sysstat 、git、make 等工具,如 apt install docker.io sysstat

今天的案例由 Python 應(yīng)用 +Redis 兩部分組成。其中,Python 應(yīng)用是一個(gè)基于 Flask 的應(yīng)用,它會(huì)利用 Redis ,來(lái)管理應(yīng)用程序的緩存,并對(duì)外提供三個(gè) HTTP 接口:

/:返回 hello redis;

/init/:插入指定數(shù)量的緩存數(shù)據(jù),如果不指定數(shù)量,默認(rèn)的是 5000 條;

緩存的鍵格式為 uuid:

緩存的值為 good、bad 或 normal 三者之一

/get_cache/<type_name>:查詢指定值的緩存數(shù)據(jù),并返回處理時(shí)間。其中,type_name 參數(shù)只支持 good, bad 和 normal(也就是找出具有相同 value 的 key 列表)。

由于應(yīng)用比較多,為了方便你運(yùn)行,我把它們打包成了兩個(gè) Docker 鏡像,并推送到了 Github 上。這樣你就只需要運(yùn)行幾條命令,就可以啟動(dòng)了。

 

今天的案例需要兩臺(tái)虛擬機(jī),其中一臺(tái)用作案例分析的目標(biāo)機(jī)器,運(yùn)行 Flask 應(yīng)用,它的 IP 地址是 192.168.0.10;而另一臺(tái)作為客戶端,請(qǐng)求緩存查詢接口。我畫了一張圖來(lái)表示它們的關(guān)系。

案例篇:Redis響應(yīng)嚴(yán)重延遲,如何解決?

 

接下來(lái),打開兩個(gè)終端,分別 SSH 登錄到這兩臺(tái)虛擬機(jī)中,并在第一臺(tái)虛擬機(jī)中安裝上述工具。

跟以前一樣,案例中所有命令都默認(rèn)以 root 用戶運(yùn)行,如果你是用普通用戶身份登陸系統(tǒng),請(qǐng)運(yùn)行 sudo su root 命令切換到 root 用戶。

到這里,準(zhǔn)備工作就完成了。接下來(lái),我們正式進(jìn)入操作環(huán)節(jié)。

 

案例分析

首先,我們?cè)诘谝粋€(gè)終端中,執(zhí)行下面的命令,運(yùn)行本次案例要分析的目標(biāo)應(yīng)用。正常情況下,你應(yīng)該可以看到下面的輸出:

$ docker run

ec41cb9e4dd5cb7079e1d9f72b7cee7de67278dbd3bd0956b4c0846bff211803

 

然后,再運(yùn)行 docker ps 命令,確認(rèn)兩個(gè)容器都處于運(yùn)行(Up)狀態(tài):

$ docker ps
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                             NAMES
2c54eb252d05        feisky/redis-App      "python /app.py"         48 seconds ago      Up 47 seconds                                         app
ec41cb9e4dd5        feisky/redis-server   "docker-entrypoint.s…"   49 seconds ago      Up 48 seconds       6379/tcp, 0.0.0.0:10000->80/tcp   redis

比如,我們切換到第二個(gè)終端,使用 curl 工具,訪問應(yīng)用首頁(yè)。如果你看到 hello redis 的輸出,說(shuō)明應(yīng)用正常啟動(dòng):

接下來(lái),繼續(xù)在終端二中,執(zhí)行下面的 curl 命令,來(lái)調(diào)用應(yīng)用的 /init 接口,初始化 Redis 緩存,并且插入 5000 條緩存信息。這個(gè)過程比較慢,比如我的機(jī)器就花了十幾分鐘時(shí)間。耐心等一會(huì)兒后,你會(huì)看到下面這行輸出:

# 案例插入5000條數(shù)據(jù),在實(shí)踐時(shí)可以根據(jù)磁盤的類型適當(dāng)調(diào)整,比如使用SSD時(shí)可以調(diào)大,而HDD可以適當(dāng)調(diào)小

$ curl http:
{"elapsed_seconds":30.26814079284668,"keys_initialized":5000}

繼續(xù)執(zhí)行下一個(gè)命令,訪問應(yīng)用的緩存查詢接口。如果一切正常,你會(huì)看到如下輸出:

$ curl http:
{"count":1677,"data":["d97662fa-06ac-11e9-92c7-0242ac110002",...],"elapsed_seconds":10.545469760894775,"type":"good"}

我們看到,這個(gè)接口調(diào)用居然要花 10 秒!這么長(zhǎng)的響應(yīng)時(shí)間,顯然不能滿足實(shí)際的應(yīng)用需求。

 

到底出了什么問題呢?我們還是要用前面學(xué)過的性能工具和原理,來(lái)找到這個(gè)瓶頸。

 

不過別急,同樣為了避免分析過程中客戶端的請(qǐng)求結(jié)束,在進(jìn)行性能分析前,

我們先要把 curl 命令放到一個(gè)循環(huán)里來(lái)執(zhí)行。你可以在終端二中,繼續(xù)執(zhí)行下面的命令:

$ while true; do curl http:

接下來(lái),再重新回到終端一,查找接口響應(yīng)慢的“病因”。

最近幾個(gè)案例的現(xiàn)象都是響應(yīng)很慢,這種情況下,我們自然先會(huì)懷疑,是不是系統(tǒng)資源出現(xiàn)了瓶頸。

 

所以,先觀察 CPU、內(nèi)存和磁盤 I/O 等的使用情況肯定不會(huì)錯(cuò)。

我們先在終端一中執(zhí)行 top 命令,分析系統(tǒng)的 CPU 使用情況:

$ top
top - 12:46:18 up 11 days,  8:49,  1 user,  load average: 1.36, 1.36, 1.04
Tasks: 137 total,   1 running,  79 sleeping,   0 stopped,   0 zombie
%Cpu0  :  6.0 us,  2.7 sy,  0.0 ni,  5.7 id, 84.7 wa,  0.0 hi,  1.0 si,  0.0 st
%Cpu1  :  1.0 us,  3.0 sy,  0.0 ni, 94.7 id,  0.0 wa,  0.0 hi,  1.3 si,  0.0 st
KiB Mem :  8169300 total,  7342244 free,   432912 used,   394144 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  7478748 avail Mem
 PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
9181 root      20   0  193004  27304   8716 S   8.6  0.3   0:07.15 python
9085 systemd+  20   0   28352   9760   1860 D   5.0  0.1   0:04.34 redis-server
 368 root      20   0       0      0      0 D   1.0  0.0   0:33.88 jbd2/sda1-8
 149 root       0 -20       0      0      0 I   0.3  0.0   0:10.63 kworker/0:1H
1549 root      20   0  236716  24576   9864 S   0.3  0.3  91:37.30 python3

觀察 top 的輸出可以發(fā)現(xiàn),CPU0 的 iowait 比較高,已經(jīng)達(dá)到了 84%;而各個(gè)進(jìn)程的 CPU 使用率都不太高,最高的 python 和 redis-server ,也分別只有 8% 和 5%。再看內(nèi)存,總內(nèi)存 8GB,剩余內(nèi)存還有 7GB 多,顯然內(nèi)存也沒啥問題。

綜合 top 的信息,最有嫌疑的就是 iowait。所以,接下來(lái)還是要繼續(xù)分析,是不是 I/O 問題。

還在第一個(gè)終端中,先按下 Ctrl+C,停止 top 命令;然后,執(zhí)行下面的 IOStat 命令,查看有沒有 I/O 性能問題:

$ iostat -d -x 1
Device            r/s     w/s     rkB/s     wkB/s   rrqm/s   wrqm/s  %rrqm  %wrqm r_await w_await aqu-sz rareq-sz wareq-sz  svctm  %util
...
sda              0.00  492.00      0.00   2672.00     0.00   176.00   0.00  26.35    0.00    1.76   0.00     0.00     5.43   0.00   0.00

 

觀察 iostat 的輸出,

我們發(fā)現(xiàn),磁盤 sda 每秒的寫數(shù)據(jù)(wkB/s)為 2.5MB,I/O 使用率(%util)是 0。

看來(lái),雖然有些 I/O 操作,但并沒導(dǎo)致磁盤的 I/O 瓶頸。

 

 

排查一圈兒下來(lái),CPU 和內(nèi)存使用沒問題,I/O 也沒有瓶頸,接下來(lái)好像就沒啥分析方向了?

 

碰到這種情況,還是那句話,反思一下,是不是又漏掉什么有用線索了。

你可以先自己思考一下,從分析對(duì)象(案例應(yīng)用)、系統(tǒng)原理和性能工具這三個(gè)方向下功夫,回憶它們的特性,查找現(xiàn)象的異常,再繼續(xù)往下走。

回想一下,今天的案例問題是從 Redis 緩存中查詢數(shù)據(jù)慢。

 

對(duì)查詢來(lái)說(shuō),對(duì)應(yīng)的 I/O 應(yīng)該是磁盤的讀操作,但剛才我們用 iostat 看到的卻是寫操作。

雖說(shuō) I/O 本身并沒有性能瓶頸,但這里的磁盤寫也是比較奇怪的。

 

為什么會(huì)有磁盤寫呢?那我們就得知道,到底是哪個(gè)進(jìn)程在寫磁盤。

要知道 I/O 請(qǐng)求來(lái)自哪些進(jìn)程,還是要靠我們的老朋友 pidstat。在終端一中運(yùn)行下面的 pidstat 命令,觀察進(jìn)程的 I/O 情況:

$ pidstat -d 1
12:49:35      UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s iodelay  Command
12:49:36        0       368      0.00     16.00      0.00      86  jbd2/sda1-8
12:49:36      100      9085      0.00    636.00      0.00       1  redis-server
從 pidstat 的輸出,我們看到,I/O 最多的進(jìn)程是 PID 為 9085 的 redis-server,并且它也剛好是在寫磁盤。這說(shuō)明,確實(shí)是 redis-server 在進(jìn)行磁盤寫。

 

當(dāng)然,光找到讀寫磁盤的進(jìn)程還不夠,我們還要再用 strace+lsof 組合,看看 redis-server 到底在寫什么。

 

接下來(lái),還是在終端一中,執(zhí)行 strace 命令,并且指定 redis-server 的進(jìn)程號(hào) 9085:

 

# -f表示跟蹤子進(jìn)程和子線程,-T表示顯示系統(tǒng)調(diào)用的時(shí)長(zhǎng),-tt表示顯示跟蹤時(shí)間

$ strace -f -T -tt -p 9085

[pid  9085] 14:20:16.826131 epoll_pwait(5, [{EPOLLIN, {u32=8, u64=8}}], 10128, 65, NULL, 8) = 1 <0.000055>
[pid  9085] 14:20:16.826301 read(8, "*2rn$3rnGETrn$41rnuuid:5b2e76cc-"..., 16384) = 61 <0.000071>
[pid  9085] 14:20:16.826477 read(3, 0x7fff366a5747, 1) = -1 EAGAIN (Resource temporarily unavailable) <0.000063>
[pid  9085] 14:20:16.826645 write(8, "$3rnbadrn", 9) = 9 <0.000173>
[pid  9085] 14:20:16.826907 epoll_pwait(5, [{EPOLLIN, {u32=8, u64=8}}], 10128, 65, NULL, 8) = 1 <0.000032>
[pid  9085] 14:20:16.827030 read(8, "*2rn$3rnGETrn$41rnuuid:55862ada-"..., 16384) = 61 <0.000044>
[pid  9085] 14:20:16.827149 read(3, 0x7fff366a5747, 1) = -1 EAGAIN (Resource temporarily unavailable) <0.000043>
[pid  9085] 14:20:16.827285 write(8, "$3rnbadrn", 9) = 9 <0.000141>
[pid  9085] 14:20:16.827514 epoll_pwait(5, [{EPOLLIN, {u32=8, u64=8}}], 10128, 64, NULL, 8) = 1 <0.000049>
[pid  9085] 14:20:16.827641 read(8, "*2rn$3rnGETrn$41rnuuid:53522908-"..., 16384) = 61 <0.000043>
[pid  9085] 14:20:16.827784 read(3, 0x7fff366a5747, 1) = -1 EAGAIN (Resource temporarily unavailable) <0.000034>
[pid  9085] 14:20:16.827945 write(8, "$4rngoodrn", 10) = 10 <0.000288>
[pid  9085] 14:20:16.828339 epoll_pwait(5, [{EPOLLIN, {u32=8, u64=8}}], 10128, 63, NULL, 8) = 1 <0.000057>
[pid  9085] 14:20:16.828486 read(8, "*3rn$4rnSADDrn$4rngoodrn$36rn535"..., 16384) = 67 <0.000040>
[pid  9085] 14:20:16.828623 read(3, 0x7fff366a5747, 1) = -1 EAGAIN (Resource temporarily unavailable) <0.000052>
[pid  9085] 14:20:16.828760 write(7, "*3rn$4rnSADDrn$4rngoodrn$36rn535"..., 67) = 67 <0.000060>
[pid  9085] 14:20:16.828970 fdatasync(7) = 0 <0.005415>
[pid  9085] 14:20:16.834493 write(8, ":1rn", 4) = 4 <0.000250>

觀察一會(huì)兒,有沒有發(fā)現(xiàn)什么有趣的現(xiàn)象呢?

事實(shí)上,從系統(tǒng)調(diào)用來(lái)看, epoll_pwait、read、write、fdatasync 這些系統(tǒng)調(diào)用都比較頻繁。

那么,剛才觀察到的寫磁盤,應(yīng)該就是 write 或者 fdatasync 導(dǎo)致的了。

接著再來(lái)運(yùn)行 lsof 命令,找出這些系統(tǒng)調(diào)用的操作對(duì)象:

$ lsof -p 9085
redis-ser 9085 systemd-network    3r     FIFO   0,12      0t0 15447970 pipe
redis-ser 9085 systemd-network    4w     FIFO   0,12      0t0 15447970 pipe
redis-ser 9085 systemd-network    5u  a_inode   0,13        0    10179 [eventpoll]
redis-ser 9085 systemd-network    6u     sock    0,9      0t0 15447972 protocol: TCP
redis-ser 9085 systemd-network    7w      REG    8,1  8830146  2838532 /data/appendonly.aof
redis-ser 9085 systemd-network    8u     sock    0,9      0t0 15448709 protocol: TCP

現(xiàn)在你會(huì)發(fā)現(xiàn),描述符編號(hào)為 3 的是一個(gè) pipe 管道,5 號(hào)是 eventpoll,7 號(hào)是一個(gè)普通文件,而 8 號(hào)是一個(gè) TCP socket。

結(jié)合磁盤寫的現(xiàn)象,我們知道,只有 7 號(hào)普通文件才會(huì)產(chǎn)生磁盤寫,而它操作的文件路徑是 /data/appendonly.aof,相應(yīng)的系統(tǒng)調(diào)用包括 write 和 fdatasync。

如果你對(duì) Redis 的持久化配置比較熟,看到這個(gè)文件路徑以及 fdatasync 的系統(tǒng)調(diào)用,你應(yīng)該能想到,這對(duì)應(yīng)著正是 Redis 持久化配置中的 appendonly 和 appendfsync 選項(xiàng)。很可能是因?yàn)樗鼈兊呐渲貌缓侠恚瑢?dǎo)致磁盤寫比較多。

接下來(lái)就驗(yàn)證一下這個(gè)猜測(cè),我們可以通過 Redis 的命令行工具,查詢這兩個(gè)選項(xiàng)的配置。

繼續(xù)在終端一中,運(yùn)行下面的命令,查詢 appendonly 和 appendfsync 的配置:

$ docker exec -it redis redis-cli config get 'append*'
1) "appendfsync"
2) "always"
3) "appendonly"
4) "yes"

從這個(gè)結(jié)果你可以發(fā)現(xiàn),appendfsync 配置的是 always,而 appendonly 配置的是 yes。

這兩個(gè)選項(xiàng)的詳細(xì)含義,你可以從 Redis Persistence 的文檔中查到,這里我做一下簡(jiǎn)單介紹。

Redis 提供了兩種數(shù)據(jù)持久化的方式,分別是快照和追加文件。

快照方式,會(huì)按照指定的時(shí)間間隔,生成數(shù)據(jù)的快照,并且保存到磁盤文件中。

  • 為了避免阻塞主進(jìn)程,Redis 還會(huì) fork 出一個(gè)子進(jìn)程,來(lái)負(fù)責(zé)快照的保存。這種方式的性能好,無(wú)論是備份還是恢復(fù),都比追加文件好很多。

不過,它的缺點(diǎn)也很明顯。在數(shù)據(jù)量大時(shí),fork 子進(jìn)程需要用到比較大的內(nèi)存,保存數(shù)據(jù)也很耗時(shí)。所以,你需要設(shè)置一個(gè)比較長(zhǎng)的時(shí)間間隔來(lái)應(yīng)對(duì),比如至少 5 分鐘。這樣,如果發(fā)生故障,你丟失的就是幾分鐘的數(shù)據(jù)。

  • 追加文件,則是用在文件末尾追加記錄的方式,對(duì) Redis 寫入的數(shù)據(jù),依次進(jìn)行持久化,所以它的持久化也更安全。

此外,它還提供了一個(gè)用 appendfsync 選項(xiàng)設(shè)置 fsync 的策略,確保寫入的數(shù)據(jù)都落到磁盤中,具體選項(xiàng)包括 always、everysec、no 等。

  • always 表示,每個(gè)操作都會(huì)執(zhí)行一次 fsync,是最為安全的方式;
  • everysec 表示,每秒鐘調(diào)用一次 fsync ,這樣可以保證即使是最壞情況下,也只丟失 1 秒的數(shù)據(jù);
  • 而 no 表示交給操作系統(tǒng)來(lái)處理。

回憶一下我們剛剛看到的配置,appendfsync 配置的是 always,意味著每次寫數(shù)據(jù)時(shí),都會(huì)調(diào)用一次 fsync,從而造成比較大的磁盤 I/O 壓力。

當(dāng)然,你還可以用 strace ,觀察這個(gè)系統(tǒng)調(diào)用的執(zhí)行情況。比如通過 -e 選項(xiàng)指定 fdatasync 后,你就會(huì)得到下面的結(jié)果:

$ strace -f -p 9085 -T -tt -e fdatasync
strace: Process 9085 attached with 4 threads
[pid  9085] 14:22:52.013547 fdatasync(7) = 0 <0.007112>
[pid  9085] 14:22:52.022467 fdatasync(7) = 0 <0.008572>
[pid  9085] 14:22:52.032223 fdatasync(7) = 0 <0.006769>
...

從這里你可以看到,每隔 10ms 左右,就會(huì)有一次 fdatasync 調(diào)用,并且每次調(diào)用本身也要消耗 7~8ms。

 

 

 

不管哪種方式,都可以驗(yàn)證我們的猜想,配置確實(shí)不合理。這樣,我們就找出了 Redis 正在進(jìn)行寫入的文件,也知道了產(chǎn)生大量 I/O 的原因。

 

不過,回到最初的疑問,為什么查詢時(shí)會(huì)有磁盤寫呢?按理來(lái)說(shuō)不應(yīng)該只有數(shù)據(jù)的讀取嗎?這就需

 

要我們?cè)賮?lái)審查一下 strace -f -T -tt -p 9085 的結(jié)果。

read(8, "*2rn$3rnGETrn$41rnuuid:53522908-"..., 16384)

write(8, "$4rngoodrn", 10)

read(8, "*3rn$4rnSADDrn$4rngoodrn$36rn535"..., 16384)

write(7, "*3rn$4rnSADDrn$4rngoodrn$36rn535"..., 67)

write(8, ":1rn", 4)

 

細(xì)心的你應(yīng)該記得,根據(jù) lsof 的分析,文件描述符編號(hào)為 7 的是一個(gè)普通文件 /data/appendonly.aof,而編號(hào)為 8 的是 TCP socket。而觀察上面的內(nèi)容,8 號(hào)對(duì)應(yīng)的 TCP 讀寫,是一個(gè)標(biāo)準(zhǔn)的“請(qǐng)求 - 響應(yīng)”格式,即:

從 socket 讀取 GET uuid:53522908-… 后,響應(yīng) good;

再?gòu)?socket 讀取 SADD good 535… 后,響應(yīng) 1。

對(duì) Redis 來(lái)說(shuō),SADD 是一個(gè)寫操作,所以 Redis 還會(huì)把它保存到用于持久化的 appendonly.aof 文件中。

觀察更多的 strace 結(jié)果,你會(huì)發(fā)現(xiàn),每當(dāng) GET 返回 good 時(shí),隨后都會(huì)有一個(gè) SADD 操作,這也就導(dǎo)致了,明明是查詢接口,Redis 卻有大量的磁盤寫。

 

到這里,我們就找出了 Redis 寫磁盤的原因。不過,在下最終結(jié)論前,我們還是要確認(rèn)一下,8 號(hào) TCP socket 對(duì)應(yīng)的 Redis 客戶端,到底是不是我們的案例應(yīng)用。

 

 

我們可以給 lsof 命令加上 -i 選項(xiàng),找出 TCP socket 對(duì)應(yīng)的 TCP 連接信息。不過,由于 Redis 和 Python 應(yīng)用都在容器中運(yùn)行,我們需要進(jìn)入容器的網(wǎng)絡(luò)命名空間內(nèi)部,才能看到完整的 TCP 連接。

注意:下面的命令用到的 nsenter 工具,可以進(jìn)入容器命名空間。如果你的系統(tǒng)沒有安裝,請(qǐng)運(yùn)行下面命令安裝 nsenter:

docker run --rm -v /usr/local/bin:/target jpetazzo/nsenter

還是在終端一中,運(yùn)行下面的命令:

# 由于這兩個(gè)容器共享同一個(gè)網(wǎng)絡(luò)命名空間,所以我們只需要進(jìn)入app的網(wǎng)絡(luò)命名空間即可

$ PID=$(docker inspect --format {{.State.Pid}} app)
# -i表示顯示網(wǎng)絡(luò)套接字信息
$ nsenter --target $PID --net -- lsof -i
COMMAND    PID            USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
redis-ser 9085 systemd-network    6u  IPv4 15447972      0t0  TCP localhost:6379 (LISTEN)
redis-ser 9085 systemd-network    8u  IPv4 15448709      0t0  TCP localhost:6379->localhost:32996 (ESTABLISHED)
python    9181            root    3u  IPv4 15448677      0t0  TCP *:http (LISTEN)
python    9181            root    5u  IPv4 15449632      0t0  TCP localhost:32996->localhost:6379 (ESTABLISHED)

這次我們可以看到,redis-server 的 8 號(hào)文件描述符,對(duì)應(yīng) TCP 連接 localhost:6379->localhost:32996。

其中, localhost:6379 是 redis-server 自己的監(jiān)聽端口,自然 localhost:32996 就是 redis 的客戶端。

 

再觀察最后一行,localhost:32996 對(duì)應(yīng)的,正是我們的 Python 應(yīng)用程序(進(jìn)程號(hào)為 9181)。

歷經(jīng)各種波折,我們總算找出了 Redis 響應(yīng)延遲的潛在原因。總結(jié)一下,我們找到兩個(gè)問題。

 

第一個(gè)問題,Redis 配置的 appendfsync 是 always,這就導(dǎo)致 Redis 每次的寫操作,都會(huì)觸發(fā) fdatasync 系統(tǒng)調(diào)用。今天的案例,沒必要用這么高頻的同步寫,使用默認(rèn)的 1s 時(shí)間間隔,就足夠了。

第二個(gè)問題,Python 應(yīng)用在查詢接口中會(huì)調(diào)用 Redis 的 SADD 命令,這很可能是不合理使用緩存導(dǎo)致的。

對(duì)于第一個(gè)配置問題,我們可以執(zhí)行下面的命令,把 appendfsync 改成 everysec:

$ docker exec -it redis redis-cli config set appendfsync everysec
OK

改完后,切換到終端二中查看,你會(huì)發(fā)現(xiàn),現(xiàn)在的請(qǐng)求時(shí)間,已經(jīng)縮短到了 0.9s:

{..., "elapsed_seconds":0.9368953704833984,"type":"good"}

而第二個(gè)問題,就要查看應(yīng)用的源碼了。點(diǎn)擊 Github ,你就可以查看案例應(yīng)用的源代碼:

def get_cache(type_name):

'''handler for /get_cache'''

for key in redis_client.scan_iter("uuid:*"):

value = redis_client.get(key)

if value == type_name:

redis_client.sadd(type_name, key[5:])

data = list(redis_client.smembers(type_name))

redis_client.delete(type_name)

return jsonify({"type": type_name, 'count': len(data), 'data': data})

 

果然,Python 應(yīng)用把 Redis 當(dāng)成臨時(shí)空間,用來(lái)存儲(chǔ)查詢過程中找到的數(shù)據(jù)。不過我們知道,這些數(shù)據(jù)放內(nèi)存中就可以了,完全沒必要再通過網(wǎng)絡(luò)調(diào)用存儲(chǔ)到 Redis 中。

$ while true; do curl http:

{...,"elapsed_seconds":0.16034674644470215,"type":"good"}

你可以發(fā)現(xiàn),解決第二個(gè)問題后,新接口的性能又有了進(jìn)一步的提升,從剛才的 0.9s ,再次縮短成了不到 0.2s。

當(dāng)然,案例最后,不要忘記清理案例應(yīng)用。你可以切換到終端一中,執(zhí)行下面的命令進(jìn)行清理:

小結(jié)

今天我?guī)阋黄鸱治隽艘粋€(gè) Redis 緩存的案例。

我們先用 top、iostat ,分析了系統(tǒng)的 CPU 、內(nèi)存和磁盤使用情況,不過卻發(fā)現(xiàn),系統(tǒng)資源并沒有出現(xiàn)瓶頸。這個(gè)時(shí)候想要進(jìn)一步分析的話,該從哪個(gè)方向著手呢?

通過今天的案例你會(huì)發(fā)現(xiàn),為了進(jìn)一步分析,就需要你對(duì)系統(tǒng)和應(yīng)用程序的工作原理有一定的了解。

比如,今天的案例中,雖然磁盤 I/O 并沒有出現(xiàn)瓶頸,但從 Redis 的原理來(lái)說(shuō),查詢緩存時(shí)不應(yīng)該出現(xiàn)大量的磁盤 I/O 寫操作。

順著這個(gè)思路,我們繼續(xù)借助 pidstat、strace、lsof、nsenter 等一系列的工具,找出了兩個(gè)潛在問題,一個(gè)是 Redis 的不合理配置,另一個(gè)是 Python 應(yīng)用對(duì) Redis 的濫用。找到瓶頸后,相應(yīng)的優(yōu)化工作自然就比較輕松了。

思考

最后給你留一個(gè)思考題。從上一節(jié) MySQL 到今天 Redis 的案例分析,你有沒有發(fā)現(xiàn) I/O 性能問題的分析規(guī)律呢?如果你有任何想法或心得,都可以記錄下來(lái)。

當(dāng)然,這兩個(gè)案例這并不能涵蓋所有的 I/O 性能問題。你在實(shí)際工作中,還碰到過哪些 I/O 性能問題嗎?你又是怎么分析的呢?

歡迎在留言區(qū)和我討論,也歡迎把這篇文章分享給你的同事、朋友。我們一起在實(shí)戰(zhàn)中演練,在交流中進(jìn)步。

分享到:
標(biāo)簽:響應(yīng) Redis
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定