連接池是什么
我們常見的池很多,比如內(nèi)存池,線程池,對(duì)象池,連接池等。顧名思義,池子干的事情都是一樣的,把一類相同的事物放到一個(gè)池里面,已備不時(shí)之需,好比我們的蓄水池一樣,把平日多余的水儲(chǔ)蓄起來,一方面防止洪水到來時(shí)候?qū)ο掠卧斐珊闈碁?zāi)害,另一方面還可以合理灌溉資源利用,比如還可以水力發(fā)電。同樣連接池是把已經(jīng)已經(jīng)建立好的連接放入一個(gè)池子,當(dāng)請(qǐng)求到來可以直接拿來使用,這樣就即解決了頻繁的創(chuàng)建關(guān)閉連接帶來的開銷,也保護(hù)了后端服務(wù),防止同時(shí)有大量連接涌入,造成危害。
連接池的種類
其實(shí)也就是連接池的使用場(chǎng)景
可以是一個(gè)獨(dú)立部署的服務(wù),通過套接字提供代理服務(wù)。例如我們的常用的mysqlproxy。
可以是一個(gè)服務(wù)內(nèi)部進(jìn)程間共享的連接池,這種相對(duì)更加輕量,可以理解為項(xiàng)目級(jí)別,只對(duì)內(nèi)提供服務(wù)。
進(jìn)程內(nèi)的連接池,更加輕量,當(dāng)前進(jìn)程內(nèi)的線程或者協(xié)程可以使用。
今天我們這里要介紹的是進(jìn)程內(nèi)的連接池,我們以PHP為例,使用協(xié)程并發(fā)的場(chǎng)景來觀察連接池的作用效果。
首先我們要心里琢磨,我們連接池的連接作用
減少客戶端使用連接時(shí),創(chuàng)建和銷毀連接的時(shí)間和系統(tǒng)資源開銷,這里涉及到TCP的三次握手也四次揮手,還有TCP的慢啟動(dòng)預(yù)熱。
避免極端情況大量連接直接涌入后端服務(wù),對(duì)整個(gè)系統(tǒng)服務(wù)造成危害。
但同時(shí)也有一些缺點(diǎn),比如空閑狀態(tài)下也要維護(hù)一定數(shù)量的連接,占用客戶端和服務(wù)端的資源,這里可以根據(jù)實(shí)際需求動(dòng)態(tài)調(diào)配連接數(shù),達(dá)到效率和資源利用的平衡。哪有一點(diǎn)資源不占用,還想系統(tǒng)高效穩(wěn)定的事情,建個(gè)水壩還得占片地,護(hù)壩人間斷性的職守呢。
心中的明鏡
又進(jìn)入我們的提前思考環(huán)節(jié),例如我們要提供100QPS的服務(wù)用戶查詢服務(wù),后端DB是Redis(也可以是mysql,我們這里只是假設(shè),實(shí)際上redis的單機(jī)處理能力是10w/s這個(gè)數(shù)量級(jí)),我可以先事先創(chuàng)建好100個(gè)redis連接,每個(gè)請(qǐng)求到來拿一個(gè)連接使用,請(qǐng)求結(jié)束后再歸還到連接池中。但是萬一有超過預(yù)期并發(fā)量的連接應(yīng)該怎么辦呢,一般可以排隊(duì)處理或者降級(jí)處理。排隊(duì)時(shí)等待當(dāng)前服務(wù)進(jìn)程空閑后再處理,當(dāng)然這會(huì)增加客戶端的響應(yīng)時(shí)間。降級(jí)處理是返回其他的數(shù)據(jù),不走DB請(qǐng)求。
下面秀出我們的基礎(chǔ)代碼,這里只是演示功能,沒有對(duì)模塊做進(jìn)一步封裝。
Step 1
最簡(jiǎn)單的http服務(wù)器
<?php class MyServer { public $server; function __construct() { $server = new Swoole\Http\Server("127.0.0.1", 9501); $this->server = $server; } function request($request, $response) { $redis = new redis; $redis->connect("127.0.0.1", 6379); $val = $redis->get("key"); $response->end("<h1>Hello Swoole redis val $val #" . rand(1000, 9999) . ""); } function start() { $this->server->on('request', [$this, "request"]); $this->server->set([ 'worker_num' => 1 ]); $this->server->start(); } } (new MyServer())->start();
我們使用 swoole process 多進(jìn)程模式,只開啟一個(gè)進(jìn)程為方便調(diào)試,運(yùn)行腳本后
$ ps -ef | grep -v grep |grep server1.php shiguan+ 30587 8251 0 20:37 pts/11 00:00:00 php server1.php shiguan+ 30588 30587 0 20:37 pts/11 00:00:00 php server1.php shiguan+ 30590 30588 0 20:37 pts/11 00:00:00 php server1.php
我們可以發(fā)現(xiàn)三個(gè)進(jìn)程,熟悉swoole的同學(xué)都知道30590進(jìn)程是工作進(jìn)程.
- 我們?cè)诿钚袌?zhí)行 curl 'http://127.0.0.1:9501' 可以得到服務(wù)器反饋 <h1>Hello Swoole redis val value2 #6642
- 然后我們通過lsof -p 30590 查看工作進(jìn)程打開的文件描述符, 發(fā)現(xiàn)并沒有redis的連接.這是為什么呢?自問自答一波,因?yàn)樵趐hp的執(zhí)行流程中,所有局部變量在退出當(dāng)前作用域時(shí),都會(huì)進(jìn)行釋放,也就是16行建立連接的$redis對(duì)象,在執(zhí)行完畢當(dāng)前請(qǐng)求后進(jìn)行了釋放,我們可以通過strace進(jìn)一步驗(yàn)證
$ sudo strace -s 1000 -p 30590 strace: Process 30590 attached epoll_wait(3, [{EPOLLIN, {u32=4, u64=12884901892}}], 4096, -1) = 1 read(4, "\2\0\0\0N\0\0\0\0\0\0\0\3\0\0\0GET / HTTP/1.1\r\nHost: 127.0.0.1:9501\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n\r\n", 425952) = 94 brk(0x55bdabaae000) = 0x55bdabaae000 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 7 fcntl(7, F_GETFL) = 0x2 (flags O_RDWR) fcntl(7, F_SETFL, O_RDWR|O_NONBLOCK) = 0 connect(7, {sa_family=AF_INET, sin_port=htons(6379), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress) poll([{fd=7, events=POLLIN|POLLOUT|POLLERR|POLLHUP}], 1, 60000) = 1 ([{fd=7, revents=POLLOUT}]) getsockopt(7, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 fcntl(7, F_SETFL, O_RDWR) = 0 setsockopt(7, SOL_TCP, TCP_NODELAY, [1], 4) = 0 setsockopt(7, SOL_SOCKET, SO_KEEPALIVE, [0], 4) = 0 poll([{fd=7, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 0 (Timeout) sendto(7, "*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n", 22, MSG_DONTWAIT, NULL, 0) = 22 poll([{fd=7, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 0 (Timeout) poll([{fd=7, events=POLLIN|POLLERR|POLLHUP}], 1, 60000) = 1 ([{fd=7, revents=POLLIN}]) recvfrom(7, "$6\r\nvalue2\r\n", 8192, MSG_DONTWAIT, NULL, NULL) = 12 close(7) = 0 sendto(4, "\2\0\0\0\305\0\0\0\0\0\0\0\0\0\0\0HTTP/1.1 200 OK\r\nServer: swoole-http-server\r\nConnection: keep-alive\r\nContent-Type: text/html\r\nDate: Tue, 22 Oct 2019 12:43:10 GMT\r\nContent-Length: 44\r\n\r\n<h1>Hello Swoole redis val value2 #4823", 213, 0, NULL, 0) = 213 brk(0x55bdab88e000) = 0x55bdab88e000 epoll_wait(3, [{EPOLLIN, {u32=4, u64=12884901892}}], 4096, -1) = 1 read(4, "\2\0\0\0\0\0\0\0\0\0\4\0\3\0\0\0", 425952) = 16 sendto(4, "\2\0\0\0\0\0\0\0\0\0\4\0\0\0\0\0", 16, 0, NULL, 0) = 16 epoll_wait(3,
我們可以發(fā)現(xiàn)連接到redis的fd,在執(zhí)行完recv以后close(7)關(guān)閉了連接.
話外音: 行走江湖重要招式,lsof -p pid, strace -p pid
到這里和連接池沒有半毛錢關(guān)系,因?yàn)檫@個(gè)服務(wù)是短連接,每次處理請(qǐng)求需要?jiǎng)?chuàng)建連接,關(guān)閉連接,對(duì)應(yīng)有tcp的三次握手和四次揮手等老生長談的問題,具體可以參考我們郭新華老師在Swoole微課程中的視頻教程.
Step 2
感受一下長連接,我們可以通過將連接對(duì)象的變量賦值給類屬性的簡(jiǎn)單操作,增加其引用計(jì)數(shù),從而使得請(qǐng)求結(jié)束后不能對(duì)對(duì)象進(jìn)行釋放.
<?php class MyServer { public $server; public $pool; function __construct() { $server = new Swoole\Http\Server("127.0.0.1", 9501); $this->server = $server; } function request($request, $response) { $redis = new redis; $redis->connect("127.0.0.1", 6379); $this->pool[] = $redis; $val = $redis->get("key"); $response->end("<h1>Hello Swoole redis val $val #" . rand(1000, 9999) . ""); } function start() { $this->server->on('request', [$this, "request"]); $this->server->set([ 'worker_num' => 1 ]); $this->server->start(); } } (new MyServer())->start();
通過簡(jiǎn)單的代碼修改,然后通過 lsof -p 查看工作進(jìn)程打開的文件描述符
... php 31598 shiguangqi 4u unix 0x0000000000000000 0t0 6577793 type=DGRAM php 31598 shiguangqi 5u unix 0x0000000000000000 0t0 6577794 type=DGRAM php 31598 shiguangqi 6u a_inode 0,13 0 11932 [signalfd] php 31598 shiguangqi 7u IPv4 6579223 0t0 TCP localhost:48048->localhost:6379 (ESTABLISHED)
我們可以發(fā)現(xiàn)在最下方真的有打開的redis連接,同樣也可以strace來跟蹤請(qǐng)求的系統(tǒng)調(diào)用,這里我們省去.這個(gè)代碼是我們每次請(qǐng)求都去創(chuàng)建新的連接,沒有任何復(fù)用,基本無法使用.
Step 3
漸入佳境,我們想要的是可以重復(fù)利用的一個(gè)連接池,有幾種選擇
當(dāng)請(qǐng)求到來的時(shí)候,嘗試從連接池中獲取連接對(duì)象,如果連接池為空,創(chuàng)建連接對(duì)象,請(qǐng)求結(jié)束的時(shí)候,歸還至連接池.
進(jìn)程啟動(dòng)的時(shí)候,創(chuàng)建固定數(shù)量的連接對(duì)象,當(dāng)請(qǐng)求到來的時(shí)候,嘗試從連接池中獲取連接對(duì)象,如果連接池為空,繼續(xù)等待或者服務(wù)降級(jí); 不為空的話正常服務(wù),請(qǐng)求結(jié)束的時(shí)候,歸還至連接池.
我們這里選擇第一種方式,每個(gè)方式都各有優(yōu)勢(shì),我們可根據(jù)自己情況進(jìn)行取舍,下面是動(dòng)態(tài)創(chuàng)建連接的實(shí)例代碼
<?php class MyServer { public $server; public $pool; function __construct() { $server = new Swoole\Http\Server("127.0.0.1", 9501); $this->server = $server; $this->pool = new \SplQueue(); } function request($request, $response) { if ($this->pool->count() > 0) { $redis = $this->pool->pop(); } else { $redis = new redis; $redis->connect("127.0.0.1", 6379); } $val = $redis->get("key"); $response->end("<h1>Hello Swoole redis val $val #" . rand(1000, 9999) . ""); $this->pool->push($redis); } function start() { $this->server->on('request', [$this, "request"]); $this->server->set([ 'worker_num' => 1 ]); $this->server->start(); } } (new MyServer())->start();
我們這里實(shí)現(xiàn)了連接的動(dòng)態(tài)創(chuàng)建和復(fù)用,可以通過strace來驗(yàn)證發(fā)現(xiàn),兩次連續(xù)的請(qǐng)求,第一次會(huì)創(chuàng)建連接,第二次會(huì)復(fù)用我們的fd
$ sudo strace -s 1000 -p 1001 [sudo] shiguangqi 的密碼: strace: Process 1001 attached brk(0x556c8955b000) = 0x556c8955b000 epoll_wait(3, [{EPOLLIN, {u32=4, u64=12884901892}}], 4096, -1) = 1 read(4, "\1\0\0\0N\0\0\0\0\0\0\0\3\0\0\0GET / HTTP/1.1\r\nHost: 127.0.0.1:9501\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n\r\n", 425952) = 94 mmap(NULL, 2101248, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f61bd028000 socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP) = 7 close(7) = 0 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 7 fcntl(7, F_GETFL) = 0x2 (flags O_RDWR) fcntl(7, F_SETFL, O_RDWR|O_NONBLOCK) = 0 connect(7, {sa_family=AF_INET, sin_port=htons(6379), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress) poll([{fd=7, events=POLLIN|POLLOUT|POLLERR|POLLHUP}], 1, 60000) = 1 ([{fd=7, revents=POLLOUT}]) getsockopt(7, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 fcntl(7, F_SETFL, O_RDWR) = 0 setsockopt(7, SOL_TCP, TCP_NODELAY, [1], 4) = 0 setsockopt(7, SOL_SOCKET, SO_KEEPALIVE, [0], 4) = 0 poll([{fd=7, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 0 (Timeout) sendto(7, "*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n", 22, MSG_DONTWAIT, NULL, 0) = 22 poll([{fd=7, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 1 ([{fd=7, revents=POLLIN}]) recvfrom(7, "$", 1, MSG_PEEK, NULL, NULL) = 1 poll([{fd=7, events=POLLIN|POLLERR|POLLHUP}], 1, 60000) = 1 ([{fd=7, revents=POLLIN}]) recvfrom(7, "$6\r\nvalue2\r\n", 8192, MSG_DONTWAIT, NULL, NULL) = 12 getpid() = 1001 getpid() = 1001 fcntl(4, F_GETFL) = 0x802 (flags O_RDWR|O_NONBLOCK) fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK) = 0 sendto(4, "\1\0\0\0\305\0\0\0\0\0\0\0\0\0\0\0HTTP/1.1 200 OK\r\nServer: swoole-http-server\r\nConnection: keep-alive\r\nContent-Type: text/html\r\nDate: Tue, 22 Oct 2019 13:17:45 GMT\r\nContent-Length: 44\r\n\r\n<h1>Hello Swoole redis val value2 #6094", 213, 0, NULL, 0) = 213 munmap(0x7f61bd028000, 2101248) = 0 epoll_wait(3, [{EPOLLIN, {u32=4, u64=12884901892}}], 4096, -1) = 1 read(4, "\1\0\0\0\0\0\0\0\0\0\4\0\3\0\0\0", 425952) = 16 sendto(4, "\1\0\0\0\0\0\0\0\0\0\4\0\0\0\0\0", 16, 0, NULL, 0) = 16 epoll_wait(3, [{EPOLLIN, {u32=4, u64=12884901892}}], 4096, -1) = 1 read(4, "\2\0\0\0N\0\0\0\0\0\0\0\3\0\0\0GET / HTTP/1.1\r\nHost: 127.0.0.1:9501\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n\r\n", 425952) = 94 brk(0x556c8977b000) = 0x556c8977b000 poll([{fd=7, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 0 (Timeout) sendto(7, "*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n", 22, MSG_DONTWAIT, NULL, 0) = 22 poll([{fd=7, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 0 (Timeout) poll([{fd=7, events=POLLIN|POLLERR|POLLHUP}], 1, 60000) = 1 ([{fd=7, revents=POLLIN}]) recvfrom(7, "$6\r\nvalue2\r\n", 8192, MSG_DONTWAIT, NULL, NULL) = 12 sendto(4, "\2\0\0\0\305\0\0\0\0\0\0\0\0\0\0\0HTTP/1.1 200 OK\r\nServer: swoole-http-server\r\nConnection: keep-alive\r\nContent-Type: text/html\r\nDate: Tue, 22 Oct 2019 13:17:46 GMT\r\nContent-Length: 44\r\n\r\n<h1>Hello Swoole redis val value2 #6308", 213, 0, NULL, 0) = 213 epoll_wait(3, [{EPOLLIN, {u32=4, u64=12884901892}}], 4096, -1) = 1 read(4, "\2\0\0\0\0\0\0\0\0\0\4\0\3\0\0\0", 425952) = 16 sendto(4, "\2\0\0\0\0\0\0\0\0\0\4\0\0\0\0\0", 16, 0, NULL, 0) = 16 epoll_wait(3,
可以驗(yàn)證在38行,第二次請(qǐng)求的時(shí)候,并沒有重新創(chuàng)建連接,完全符合我們程序預(yù)期
Step 4
重點(diǎn)來了,通過觀察系統(tǒng)調(diào)用我們可以發(fā)現(xiàn),以上的例子我們使用的是單進(jìn)程同步模式,也就是不支持單進(jìn)程的并發(fā)處理.到這里協(xié)程的威力要出來了,我們可以支持單進(jìn)程并發(fā)(php對(duì)多線程的支持不好,幾乎沒人使用php的ZTS版本)
重點(diǎn)又又來了,我們需要做的只需要在(new MyServer())->start();前增加一行代碼
Swoole\Runtime::enableCoroutine(); (new MyServer())->start();
接下來觀察工作進(jìn)程兩次請(qǐng)求的系統(tǒng)調(diào)用
$ sudo strace -s 1000 -p 1747 strace: Process 1747 attached brk(0x55f6d8c9e000) = 0x55f6d8c9e000 epoll_wait(3, [{EPOLLIN, {u32=4, u64=12884901892}}], 4096, -1) = 1 read(4, "\1\0\0\0N\0\0\0\0\0\0\0\3\0\0\0GET / HTTP/1.1\r\nHost: 127.0.0.1:9501\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n\r\n", 425952) = 94 mmap(NULL, 2101248, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3edce28000 socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC, IPPROTO_IP) = 7 fcntl(7, F_GETFL) = 0x2 (flags O_RDWR) fcntl(7, F_SETFL, O_RDWR|O_NONBLOCK) = 0 setsockopt(7, SOL_TCP, TCP_NODELAY, [1], 4) = 0 setsockopt(7, SOL_TCP, TCP_NODELAY, [1], 4) = 0 connect(7, {sa_family=AF_INET, sin_port=htons(6379), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress) brk(0x55f6d8cc0000) = 0x55f6d8cc0000 epoll_ctl(3, EPOLL_CTL_ADD, 7, {EPOLLOUT, {u32=7, u64=25769803783}}) = 0 brk(0x55f6d8ca0000) = 0x55f6d8ca0000 epoll_wait(3, [{EPOLLOUT, {u32=7, u64=25769803783}}], 4096, 60000) = 1 epoll_ctl(3, EPOLL_CTL_DEL, 7, NULL) = 0 getsockopt(7, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 setsockopt(7, SOL_TCP, TCP_NODELAY, [1], 4) = 0 setsockopt(7, SOL_SOCKET, SO_KEEPALIVE, [0], 4) = 0 recvfrom(7, 0x7f3ee07b69ef, 1, MSG_PEEK, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable) sendto(7, "*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n", 22, 0, NULL, 0) = 22 recvfrom(7, 0x7f3ee07b69ef, 1, MSG_PEEK, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable) recvfrom(7, "$6\r\nvalue2\r\n", 8192, 0, NULL, NULL) = 12 getpid() = 1747 getpid() = 1747 fcntl(4, F_GETFL) = 0x802 (flags O_RDWR|O_NONBLOCK) fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK) = 0 sendto(4, "\1\0\0\0\305\0\0\0\0\0\0\0\0\0\0\0HTTP/1.1 200 OK\r\nServer: swoole-http-server\r\nConnection: keep-alive\r\nContent-Type: text/html\r\nDate: Tue, 22 Oct 2019 13:27:04 GMT\r\nContent-Length: 44\r\n\r\n<h1>Hello Swoole redis val value2 #3338", 213, 0, NULL, 0) = 213 munmap(0x7f3edce28000, 2101248) = 0 epoll_wait(3, [{EPOLLIN, {u32=4, u64=12884901892}}], 4096, -1) = 1 read(4, "\1\0\0\0\0\0\0\0\0\0\4\0\3\0\0\0", 425952) = 16 sendto(4, "\1\0\0\0\0\0\0\0\0\0\4\0\0\0\0\0", 16, 0, NULL, 0) = 16 epoll_wait(3, [{EPOLLIN, {u32=4, u64=12884901892}}], 4096, -1) = 1 read(4, "\2\0\0\0N\0\0\0\0\0\0\0\3\0\0\0GET / HTTP/1.1\r\nHost: 127.0.0.1:9501\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n\r\n", 425952) = 94 brk(0x55f6d8ec0000) = 0x55f6d8ec0000 recvfrom(7, 0x7f3ee07b69ef, 1, MSG_PEEK, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable) sendto(7, "*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n", 22, 0, NULL, 0) = 22 recvfrom(7, "$", 1, MSG_PEEK, NULL, NULL) = 1 recvfrom(7, "$6\r\nvalue2\r\n", 8192, 0, NULL, NULL) = 12 sendto(4, "\2\0\0\0\305\0\0\0\0\0\0\0\0\0\0\0HTTP/1.1 200 OK\r\nServer: swoole-http-server\r\nConnection: keep-alive\r\nContent-Type: text/html\r\nDate: Tue, 22 Oct 2019 13:28:50 GMT\r\nContent-Length: 44\r\n\r\n<h1>Hello Swoole redis val value2 #1576", 213, 0, NULL, 0) = 213 brk(0x55f6d8ca0000) = 0x55f6d8ca0000 epoll_wait(3, [{EPOLLIN, {u32=4, u64=12884901892}}], 4096, -1) = 1 read(4, "\2\0\0\0\0\0\0\0\0\0\4\0\3\0\0\0", 425952) = 16 sendto(4, "\2\0\0\0\0\0\0\0\0\0\4\0\0\0\0\0", 16, 0, NULL, 0) = 16 epoll_wait(3,
可以發(fā)現(xiàn)是通過epoll_wait來監(jiān)聽redis句柄的讀寫事件.
但是有個(gè)問題,如果當(dāng)前時(shí)間有大量的請(qǐng)求涌入,會(huì)建立大量的redis連接,對(duì)后端服務(wù)造成殺傷,我們來通過ab壓測(cè)演示一下
$ ab -c 1000 -n 10000 'http://127.0.0.1:9501/' This is ApacheBench, Version 2.3 <$Revision: 1807734 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 127.0.0.1 (be patient) Completed 1000 requests Completed 2000 requests Completed 3000 requests Completed 4000 requests Completed 5000 requests Completed 6000 requests Completed 7000 requests Completed 8000 requests Completed 9000 requests Completed 10000 requests Finished 10000 requests Server Software: swoole-http-server Server Hostname: 127.0.0.1 Server Port: 9501 Document Path: / Document Length: 44 bytes Concurrency Level: 1000 Time taken for tests: 0.512 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 1920000 bytes HTML transferred: 440000 bytes Requests per second: 19528.96 [#/sec] (mean) Time per request: 51.206 [ms] (mean) Time per request: 0.051 [ms] (mean, across all concurrent requests) Transfer rate: 3661.68 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 3 5.8 2 39 Processing: 1 5 6.3 4 61 Waiting: 1 4 6.3 3 61 Total: 4 7 9.2 5 64 Percentage of the requests served within a certain time (ms) 50% 5 66% 6 75% 6 80% 6 90% 7 95% 38 98% 44 99% 56 100% 64 (longest request)
然后通過查看通過壓測(cè)建立了多少連接
$ lsof -p 2323 | grep 'localhost:6379 (ESTABLISHED)' | wc -l 129
這里只是本地使用redis執(zhí)行最簡(jiǎn)單的操作,如果請(qǐng)求IO時(shí)間較長,連接不能及時(shí)釋放,會(huì)建立更多的連接.這里會(huì)對(duì)后端造成不可預(yù)估的殺傷.有沒有什么辦法可以限制并發(fā)數(shù),對(duì)服務(wù)資源進(jìn)行控制呢,答案是肯定的.我們可以使用channel來限制并發(fā).具體channel的使用和原理請(qǐng)參考Twosee的課
Step 5
<?php class MyServer { public $server; public $pool; public $chan; function __construct() { $server = new Swoole\Http\Server("127.0.0.1", 9501); $this->server = $server; $this->pool = new \SplQueue(); } function request($request, $response) { $this->chan->push(true); if ($this->pool->count() > 0) { $redis = $this->pool->pop(); } else { $redis = new redis; $redis->connect("127.0.0.1", 6379); } $val = $redis->get("key"); $response->end("<h1>Hello Swoole redis val $val #" . rand(1000, 9999) . ""); $this->pool->push($redis); $this->chan->pop(); } function workerStart($server, $worker_id) { echo "worker start $worker_id\n"; Swoole\Runtime::enableCoroutine(); $this->chan = new Swoole\Coroutine\Channel(10); } function start() { $this->server->on('request', [$this, "request"]); $this->server->on('workerStart', [$this, "workerStart"]); $this->server->set([ 'worker_num' => 1 ]); $this->server->start(); } } (new MyServer())->start();
我們對(duì)上面的代碼進(jìn)行多次壓測(cè),
$ ab -c 1000 -n 10000 'http://127.0.0.1:9501/' This is ApacheBench, Version 2.3 <$Revision: 1807734 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 127.0.0.1 (be patient) Completed 1000 requests Completed 2000 requests Completed 3000 requests Completed 4000 requests Completed 5000 requests Completed 6000 requests Completed 7000 requests Completed 8000 requests Completed 9000 requests Completed 10000 requests Finished 10000 requests Server Software: swoole-http-server Server Hostname: 127.0.0.1 Server Port: 9501 Document Path: / Document Length: 44 bytes Concurrency Level: 1000 Time taken for tests: 0.477 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 1920000 bytes HTML transferred: 440000 bytes Requests per second: 20949.87 [#/sec] (mean) Time per request: 47.733 [ms] (mean) Time per request: 0.048 [ms] (mean, across all concurrent requests) Transfer rate: 3928.10 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 4 17 4.0 18 24 Processing: 8 28 5.5 29 43 Waiting: 5 23 6.0 23 39 Total: 27 46 3.9 46 56 Percentage of the requests served within a certain time (ms) 50% 46 66% 47 75% 48 80% 49 90% 50 95% 52 98% 53 99% 53 100% 56 (longest request)
發(fā)現(xiàn)最多只會(huì)有10個(gè)連接,這里的連接數(shù)是我們進(jìn)行硬編碼設(shè)置.
$ lsof -p 5093 | grep 'localhost:6379 (ESTABLISHED)' | wc -l 10
總結(jié)
需要說明的是,我們的實(shí)例代碼只是演示,很多地方并不嚴(yán)謹(jǐn)而且沒有模塊化的封裝,例如redis連接的建立沒有檢查成功,也沒有處理redis請(qǐng)求的失敗重連,還有很多細(xì)節(jié)需要完善.在生產(chǎn)環(huán)境當(dāng)中,需要對(duì)外部的每一項(xiàng)資源保持警惕,不信任.連接很可能被服務(wù)端切斷.這里也沒有涉及到用戶提交的參數(shù)等過程.
我們通過漸進(jìn)式的演進(jìn),來驗(yàn)證長連接的作用和使用方式,并且學(xué)會(huì)通過channel掌控并發(fā)能力,保護(hù)當(dāng)前服務(wù)的資源,包括后端的資源,使我們的服務(wù)穩(wěn)定,健壯.想要了解更多swoole教程歡迎關(guān)注編程學(xué)習(xí)網(wǎng)
掃碼二維碼 獲取免費(fèi)視頻學(xué)習(xí)資料
- 本文固定鏈接: http://www.wangchenghua.com/post/8395/
- 轉(zhuǎn)載請(qǐng)注明:轉(zhuǎn)載必須在正文中標(biāo)注并保留原文鏈接
- 掃碼: 掃上方二維碼獲取免費(fèi)視頻資料