阿弟:從300qps至3000qps的優化之路
原文連結:http://www.postgres.cn/news/viewone/1/283
原作者:阿弟 創作時間:2017-08-16 23:40:18
前言
現在的併發文章動不動就係統架構,虛得不行。 網友阿弟寫的這篇反而從零開始步步實操,是篇好文章,相當值得借鑑 週末基於這模式做個併發實踐,到時步驟也釋出到掘金好參考對比
背景
一個車貨匹配專案,初步估計有100萬司機。支援每秒500次查詢最新貨源列表,業務高峰時每分鐘大約10萬司機(包括大量無註冊司機)線上查詢最新的貨源訂單。即1500qps,當然還有很多低頻率的業務,如每秒幾筆貨源訂單。
環境準備: 測試資源是線下開發機,非生產環境
資料庫伺服器
CPU:至強 4核 單執行緒
RAM:32G
硬碟:7200轉SAS 1T 2個做了raid1
OS:Centos 6.4
網路卡:1000M
IP:192.168.1.6
應用伺服器
CPU:至強 4核 單執行緒
RAM:32G
硬碟:7200轉SAS 1T 2個做了raid1
OS:Centos 7.0
網路卡:1000M
IP:192.168.1.10
交換機
H3C 1000M
平臺初步技術方案
基於公司目前技術儲備,我們可以選擇使用的元件有nginx/apache/php/go/pgbouncer/pg,這些元件可以組合的架構還是很豐富的,原則上能滿足業務需求的情況下,架構是越簡單越好。複雜的架構出問題的機率越高
但很明顯,整個系統的瓶頸肯定是在各種Api與資料庫通訊這一環節上面,這是優化的重中之重
測試環境服務元件版本號
PostgreSQL 9.4.1、nginx-1.6.2、apache-2.2.31、php-5.5.38、pgbouncer-1.5.4、golang-1.7.3
1、PostgreSQL 9.4.1
編譯安裝
wget https://ftp.postgresql.org/pub/source/v9.4.1/postgresql-9.4.1.tar.gz
./configure --prefix=/home/ad/pgsql
gmake -j 4
gmake install
複製程式碼
初始化
su postgres
/home/ad/pgsql/bin/initdb -D /home/ad/data -U postgres -E utf8 --locale=c -W
複製程式碼
引數配置
listen_addresses = '*'
port = 9410
max_connections = 2000
superuser_reserved_connections = 3
shared_buffers = 6048MB
work_mem = 2MB
maintenance_work_mem = 512MB
autovacuum_work_mem = 512MB
synchronous_commit = on
checkpoint_segments = 32
checkpoint_timeout = 5min
checkpoint_completion_target = 0.5
checkpoint_warning = 30s
wal_level = minimal #單機模式
effective_cache_size = 32GB
log_destination = 'csvlog'
logging_collector = on
log_min_duration_statement = 1000
log_line_prefix = '%a %r %u %d %p'
log_statement = 'ddl'
track_activity_query_size = 4096
autovacuum = on
log_autovacuum_min_duration = 0
autovacuum_max_workers = 3
複製程式碼
其它引數全部先預設
2、apache-2.2.31
編譯安裝
./configure --prefix=/home/ad/apache-2.2.31 --enable-rewrite --enable-deflate
--enable-expires --enable-headers --enable-modules=most --with-mpm=worker --enable-so
複製程式碼
選擇worker-mpm模式
引數配置
Listen 8812
LoadModule php5_module modules/libphp5.so #這個是編譯好php後自動加上去的
User apache
Group apache
ServerName 127.0.0.1:8812
< Directory />
Options FollowSymLinks
AllowOverride None
Order deny,allow
allow from all
</Directory>
<IfModule mime_module>
AddType application/x-httpd-php .php
</IfModule>
Include conf/extra/httpd-mpm.conf
Include conf/extra/httpd-default.conf
複製程式碼
其它引數全部先預設
3、php-5.5.38
編譯安裝
wget http://cn2.php.net/get/php-5.5.38.tar.gz/from/this/mirror
mv mirror php-5.5.38.tar.gz
tar zxf php-5.5.38.tar.gz
cd php-5.5.38
./configure --prefix=/home/ad/php --with-apxs2=/home/ad/apache/bin/apxs
--with-pgsql=/home/ad/pgsql --enable-fpm
gmake -j 4
gmake install
複製程式碼
引數配置
error_reporting = E_ALL & ~E_NOTICE
track_errors = Off
max_execution_time = 30
memory_limit = 128M
display_errors = On #生產環境不建議開啟
log_errors = On
複製程式碼
4、nginx-1.6.2
編譯安裝
wget http://nginx.org/download/nginx-1.6.2.tar.gz
tar zxf nginx-1.6.2.tar.gz
cd nginx-1.6.2
./configure --prefix=/home/ad/nginx --with-http_ssl_module --with-http_spdy_module
--with-http_stub_status_module --with-pcre
複製程式碼
引數配置
user nginx nginx;
worker_processes 4;
worker_cpu_affinity 0001 0010 0100 1000;
worker_rlimit_nofile 65535;
error_log logs/error.log;
pid logs/nginx.pid;
events {
use epoll;
worker_connections 65535;
}
http {
include mime.types;
default_type application/octet-stream;
upstream openapi{
server 192.168.1.10:8812
}
server {
listen 6001;
server_name 192.168.1.10;
location / {
proxy_pass http://openapi;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
複製程式碼
5、pgbouncer-1.5.4
pgbouncer連線池也裝在應用伺服器上面
編譯安裝
安裝pgbouncer需要libevent包
wget https://github.com/downloads/libevent/libevent/libevent-2.0.19-stable.tar.gz
tar zxf libevent-2.0.19-stable.tar.gz
cd libevent-2.0.19-stable
./configure --prefix=/home/ad/libevent
gmake -j 4
gmake installl
--載入libevent動態庫
echo '/home/ad/libevent/lib' > /etc/ld.so.conf.d/libevent.conf
ldconfig
wget http://pgfoundry.org/frs/download.php/3393/pgbouncer-1.5.4.tar.gz
tar zxf pgbouncer-1.5.4
cd pgbouncer-1.5.4
./configure --prefix=/home/ad/pgbouncer/ --with-libevent=/home/ad/libevent/
gmake -j 4
gmake install
複製程式碼
引數配置
執行引數檔案/home/ad/pgbouncer/pgbouncer.ini
[databases]
car_goods_matching = host=192.168.1.6 port=9410 dbname=car_goods_matching
user=postgres password=pgsql
[pgbouncer]
listen_port = 9410
listen_addr = *
auth_type = md5
auth_file = /home/ad/pgbouncer/user.txt
logfile = /home/ad/pgbouncer/pgbouncer.log
pidfile = /home/ad/pgbouncer/pgbouncer.pid
admin_users = pgb_admin
pool_mode = session
max_client_conn = 65535
default_pool_size = 1024
client_idle_timeout = 60
idle_transaction_timeout = 30
複製程式碼
使用者及口令檔案/home/ad/pgbouncer/user.txt
"pgb_admin" "pgsql"
複製程式碼
6、golang-1.7.3
直接安裝
yum install golang -y複製程式碼
測試資料及指令碼
1、資料庫環境
--建立測試庫
create database car_goods_matching;
--建立使用者表並生成100萬使用者資料,使用者註冊時間隨機分佈
CREATE TABLE public.users
(
id serial not null unique,nickname text not null,mobileno text not null unique,createtime timestamp not null default now()
);
COMMENT ON TABLE public.users IS '使用者表';
COMMENT ON COLUMN public.users.id IS 'id號';
COMMENT ON COLUMN public.users.nickname IS '呢稱';
COMMENT ON COLUMN public.users.mobileno IS '手機號';
COMMENT ON COLUMN public.users.createtime IS '註冊時間';
--生成使用者資料
INSERT INTO public.users(nickname,mobileno,createtime) select
t::text,t::text,'2015-12-01'::timestamp + (random()*31449600)::integer *
interval '1 seconds' FROM generate_series(13800000001,13801000000) as t;
--建立索引
CREATE INDEX users_createtime_idx ON users USING BTREE(createtime);
--建立訂單表並生成1000萬訂單資料,使用者下單時間隨機分佈
CREATE TABLE orders
(
id serial not null unique,createtime timestamp not null default now(),users_id integer not null,goods_name text not null
);
COMMENT ON TABLE public.orders IS '運力需求訂單表';
COMMENT ON COLUMN public.orders.id IS 'id號';
COMMENT ON COLUMN public.orders.createtime IS '下單時間';
COMMENT ON COLUMN public.orders.users_id IS '使用者id號';
COMMENT ON COLUMN public.orders.goods_name IS '貨源名稱';
--這是生成訂單的函式,每天30000訂單
CREATE OR REPLACE FUNCTION orders_make(a_date date) RETURNS TEXT AS
$$
BEGIN
INSERT INTO orders(users_id,goods_name,createtime)
SELECT
users.id,md5(random()::Text),a_date::timestamp + (random()*86399)::integer * interval '1 seconds'
FROM
users
WHERE
users.createtime < a_date
ORDER BY
random()
LIMIT 30000
;
RETURN 'OK';
END;
$$
LANGUAGE PLPGSQL;
COMMENT ON FUNCTION orders_make(a_date date) IS '生成訂單資料';
--呼叫函式生成資料,時間比較長,耐心等
SELECT orders_make('2015-12-01'::date + t) FROM generate_series(0,365) as t;
--把生成的資料序號重新生成
create sequence order_id_seq_tmp;
copy (select nextval('order_id_seq_tmp'),createtime,users_id,goods_name from orders
order by createtime) to '/home/pg/orders.txt';
truncate table orders;
copy orders from '/home/pg/orders.txt';
SELECT SETVAL('orders_id_seq',(select id FROM orders ORDER BY id DESC LIMIT 1));
DROP sequence order_id_seq_tmp;
--建立索引
CREATE INDEX orders_createtime_idx ON orders USING BTREE(createtime);
CREATE INDEX orders_users_id_idx ON orders USING BTREE(users_id);
--資料生成後記得做一下統計
vacuum analyze
複製程式碼
2、測試程式
--除錯效能使用hello.php
<?php
echo "hello world!!";
?>
--獲取訂單列表api介面goodslist.php
<?php
FUNCTION isint($val,$empty_is_true=FALSE)
{
$val=strval($val);
IF ($val=="" && !$empty_is_true)
{
RETURN FALSE;
}
ELSEIF($val=="" && $empty_is_true)
{
RETURN TRUE;
}
IF (STRVAL(INTVAL($val))!=STRVAL($val))
{
RETURN FALSE;
}
ELSE
{
RETURN TRUE;
}
}
//生成json字串
FUNCTION openapi_json_encode($data)
{
if (version_compare(PHP_VERSION,'5.4.0','>='))
{
RETURN json_encode($data,JSON_UNESCAPED_UNICODE);
}
else
{
RETURN json_encode($data);
}
}
IF (!isint($_GET['offset']))#偏移量
{
$_GET['offset']="0";
}
IF ($_GET['pool']=="1") #連線池
{
$conn=pg_connect("host=192.168.1.10 port=9410 dbname=car_goods_matching
user=pgb_admin password=pgsql");
}
ELSE #直連
{
$conn=pg_connect("host=192.168.1.6 port=9410 dbname=car_goods_matching
user=postgres password=pgsql");
}
$sql="SELECT
orders.id,orders.createtime,orders.goods_name,users.nickname,users.mobileno
FROM
orders
INNER JOIN users ON orders.users_id=users.id
ORDER BY
orders.id DESC
OFFSET
".$_GET['offset']."
LIMIT 10
" ;
$sql_result=pg_query($conn,$sql) ;
$data['return_code']="FAIL";
$data['data']= pg_fetch_all($sql_result);
IF ($data['data'])
{
$data['return_code']="SUCCESS";
}
ECHO openapi_json_encode($data);
pg_close($conn);
?>
複製程式碼
效能校準
1、apache+php
短連線測試
配置apache,關閉長連線配置
vim /home/ad/apache/conf/extra/httpd-default.conf
KeepAlive Off
MaxKeepAliveRequests 100
KeepAliveTimeout 5
複製程式碼
重啟服務
/home/ad/apache/bin/apachectl restart複製程式碼
在另外一臺電腦上使用wrk工具進行測試
wrk -t4 -c8 -d60s http://192.168.1.10:8812/hello.php
Running 1m test @ http://192.168.1.10:8812/hello.php
4 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 432.30us 768.90us 36.84ms 99.35%
Req/Sec 3.51k 186.91 4.02k 85.21%
837934 requests in 1.00m,135.05MB read
Requests/sec: 13942.51
Transfer/sec: 2.25MB
複製程式碼
使用atop檢視伺服器的資源使用情況
http連線斷開等待情況
netstat -n | grep -i TIME_WAIT | wc -l
6000
複製程式碼
長連線測試
非全程長連線
vim /home/ad/apache/conf/extra/httpd-default.conf
KeepAlive On
MaxKeepAliveRequests 100 #連線超過100次請求就結束
KeepAliveTimeout 5 #連線超過5秒就結束
複製程式碼
跑wrk測試
wrk -t4 -c8 -d60s http://192.168.1.10:8812/hello.php
Running 1m test @ http://192.168.1.10:8812/hello.php
4 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.96ms 21.25ms 444.92ms 99.33%
Req/Sec 5.91k 748.39 7.90k 80.88%
1405959 requests in 1.00m,201.38MB read
Requests/sec: 23393.64
Transfer/sec: 3.35MB
複製程式碼
使用atop檢視伺服器的資源使用情況
全程長連線
vim /home/ad/apache/conf/extra/httpd-default.conf
KeepAlive On
MaxKeepAliveRequests 1000000
KeepAliveTimeout 60
複製程式碼
跑wrk測試
wrk -t4 -c8 -d60s http://192.168.1.10:8812/hello.php
Running 1m test @ http://192.168.1.10:8812/hello.php
4 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 356.51us 722.91us 37.48ms 99.30%
Req/Sec 6.34k 315.97 6.91k 93.21%
1513403 requests in 1.00m,216.49MB read
Requests/sec: 25213.26
Transfer/sec: 3.61MB
複製程式碼
使用atop檢視伺服器的資源使用情況
從上面測試的結果來看,在高併發,高壓力的情況下,程式複用可以提高一倍的系統效能。短連線時產生了大量的TIME_WAIT程式,產生的原因主要是客戶端不斷的連線和斷開,在高併發的情況下,伺服器遲遲沒收到客戶端斷開連線的第四次握手訊號造成的,這些程式會一直佔用locale port,導致本地埠號給消耗光了。可以設定下面的核心引數來提高locale port的使用效率
net.ipv4.tcp_syncookies = 1 #表示開啟SYN Cookies,可防範少量SYN攻擊,預設為0
net.ipv4.tcp_tw_reuse = 1 #允許將TIME-WAIT sockets重新用於新的TCP連線,預設是0
net.ipv4.tcp_tw_recycle = 1 #表示開啟TCP連線中TIME-WAIT sockets的快速回收,預設是0
net.ipv4.ip_local_port_range = 1024 65535 #增大locale port數量,預設是從32768開始
不過這幾個引數是治標不治本,最終還是要回到提升api本身的處理能力.另外對於動態的api介面業務,如果你的客戶端直接與apache進行通訊,現實中基本都是短連線業務,所以用apache+php跑個13000已經是極限了。
2、nginx+apache+php
nginx與apache短連線通訊
下面我們來看看nginx+apache+php在短連線的情況下,locale port是怎樣給消耗光的,nginx+apache+php見上面的配置
使用wrk工具對nginx進行壓力測試
wrk -t4 -c8 -d60s http://192.168.1.10:6001/hello.php
Running 1m test @ http://192.168.1.10:6001/hello.php
4 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 13.02ms 89.80ms 1.00s 97.80%
Req/Sec 2.24k 1.00k 3.20k 82.52%
112423 requests in 1.00m,17.06MB read
Socket errors: connect 0,read 0,write 76,timeout 41
Non-2xx or 3xx responses: 41
Requests/sec: 1871.76
Transfer/sec: 290.91KB
複製程式碼
使用atop檢視伺服器的資源使用情況
在測試到一半的時候再開啟一個終端,測試一下伺服器是否還有接受請求的能力,發現連線超時,使用top檢視系統負載情況,竟然沒負載
curl http://192.168.1.10:6001/
curl: (7) Failed connect to 192.168.1.10:6001; 連線超時
複製程式碼
1871次/秒的處理能力,還有timeout情況,是不是懷疑nginx有問題,其實這不是nginx的問題,主要是nginx處理能力太強了,而apache跑php弱了,導致程式一直佔用socket port,我們只需要做一下這樣的調整,伺服器的處理能力就上來了
nginx與apache長連線通訊
upstream openapi{
server 192.168.1.10:8812
keepalive 1000000;
}
server {
listen 6001;
server_name 192.168.1.10;
location / {
proxy_pass http://openapi;
proxy_http_version 1.1; #nginx預設是http1.0協議,http1.0不支援長連線
proxy_set_header Connection "";
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
複製程式碼
wrk -t4 -c8 -d60s http://192.168.1.10:6001/hello.php
Running 1m test @ http://192.168.1.10:6001/hello.php
4 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.07ms 9.61ms 258.20ms 99.47%
Req/Sec 4.07k 267.78 5.77k 88.93%
969902 requests in 1.00m,147.02MB read
Requests/sec: 16138.11
Transfer/sec: 2.45MB複製程式碼
使用atop檢視伺服器的資源使用情況
客戶端與nginx長連線通訊,nginx與apache長連線通訊
wrk -t4 -c8 -d60s http://192.168.1.10:6001/hello.php
Running 1m test @ http://192.168.1.10:6001/hello.php
4 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 627.20us 2.94ms 115.11ms 99.32%
Req/Sec 4.18k 352.11 6.90k 81.97%
998513 requests in 1.00m,151.41MB read
Requests/sec: 16614.20
Transfer/sec: 2.52MB
複製程式碼
看上去資料變化不大,但是TIME_WAIT量明顯減少了
netstat -n | grep -i TIME_WAIT|wc -l
97
複製程式碼
由於nginx和apache是跑在同一個節點上,cpu互相爭用,所以併發數下來,如果跑在不同的機器上,nginx代理的損耗其實是非常小的,看看下面的測試就會知道
nginx與apache長連線通訊(不同節點)
nginx位於應用伺服器
apache位於資料庫伺服器
wrk -t4 -c8 -d60s http://192.168.1.10:6001/hello.php
Running 1m test @ http://192.168.1.10:6001/hello.php
4 threads and 8 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 433.26us 1.32ms 54.39ms 99.11%
Req/Sec 5.92k 561.30 6.53k 90.67%
1414121 requests in 1.00m,213.08MB read
Requests/sec: 23567.56
Transfer/sec: 3.55MB
複製程式碼
# 架構 連線數 系統cpu使用量 系統cpu佔比 使用者cpu使用量 使用者cpu佔比 time_wait佔用數
1 apache短連線 12392 99 24.75% 149 0.3725 6000
2 apache長連線 24186 110 27.50% 245 0.6125 2500
3 apache全程長連線 25213 110 27.50% 227 0.5675 8
4 nginx+apache短連線 1871 2 0.50% 1 0.0025 6000
5 nginx+apache長連線 16138 115 28.75% 183 0.4575 4800
6 nginx長連線+apache長連線 16614 123 30.75% 196 0.49 97
複製程式碼
從上面的測試資料來看,建議高併發簡單業務的介面採用第5種方案,如果是web網頁與介面混合使用的,使用第6種模式
四、業務測試
為了測試更接近於實際業務場景,這次使用http_load工具,http_load使用單程式併發多個執行緒進行併發測試,訪問nginx時採用的是非長連線訪問,並且可以設定每秒訪問介面次數,http_load測試特別說明
單程式限制每秒1000次訪問
引數為-r 334(超過1000的三分之一)時,實際上能跑到500次/秒的訪問量
引數為-r 501(超過1000的二分之一)時,測試端cpu會跑滿
所以跑每秒500次訪問時引數是-r 334,跑每秒1000次訪問時啟用兩個-r 334 http_load程式來測試,依此類推
模擬每秒100次訪問,持續訪問300秒
http_load -r 100 -s 300 /home/ad/urls.txt複製程式碼
模擬每秒500次訪問,持續訪問300秒
http_load -r 334 -s 300 /home/ad/urls.txt複製程式碼
因為測試工具佔用cpu資源還是比較大,需要另外一臺主機上執行,這樣測試更準確
1、nginx+apache+php+pgsql
[root@cqs ~]# echo 'http://192.168.1.10:6001/goodslist.php?pool=0&offset=0'
> /home/ad/urls.txt
複製程式碼
測試結果
每秒100次訪問
[root@cqs ~]# http_load -r 100 -s 300 /home/ad/urls.txt
29999 fetches,96 max parallel,4.42485e+07 bytes,in 300 seconds
1475 mean bytes/connection
99.9967 fetches/sec,147495 bytes/sec
msecs/connect: 0.471398 mean,2.057 max,0.23 min
msecs/first-response: 12.5897 mean,992.857 max,7.751 min
HTTP response codes:
code 200 -- 29999
複製程式碼
其它略,下面是測試結果對比表
資料庫伺服器壓力對比
次數/秒 系統cpu使用量 系統cpu佔比 使用者cpu使用量 使用者cpu佔比 總使用量 總佔比
100 37 9.25% 35 8.75% 72 18.00%
200 73 18.25% 70 17.50% 143 35.75%
300 115 28.75% 104 26.00% 219 54.75%
400 148 37.00% 140 35.00% 288 72.00%
複製程式碼
應用伺服器壓力對比
次數/秒 time_wait佔用數 load average 502次數 佔比 locale port
100 6000 0.2 0
200 6000 0.2 0
300 6000 0.3 0
400 6000 0.5 20430 17.03% 消耗完
複製程式碼
從上面的資料來看,qps為400時資料庫伺服器就出現了不穩定情況,而且應用伺服器的locale port會被消耗光。通過對資料的對比,發現資料庫伺服器sys_cpu比user_cpu佔用量還要大,原因與上面應用服務效能測試時一致,就是在高併發時,程式不斷連線和關閉過度消耗了cpu資源。應用伺服器locale port給佔完,導致http請求失敗。得想辦法讓應用程式與pg服務連執行緒序可以複用,減少資料庫伺服器的cpu壓力才行,上pgbouncer連線池吧。
下面先說一下,怎樣查詢pg程式有沒複用,使用捉包工具tcpdump捉取兩次獨立請求時連線資料庫的local port ,如port一致則就是程式複用,如下所示
在資料庫伺服器上的一個終端輸入下面命令
tcpdump -n -i em1 dst 192.168.1.6複製程式碼
在測試機上連續輸入下面命令二次,非連線池連線
crul 'http://192.168.1.10:6001/goodslist.php?pool=0&offset=0'複製程式碼
在tcpdump終端你會看到下面的資訊,對應的locale port不一致
17:11:01.816414 IP 192.168.1.10.61048 > 192.168.1.6.pyrrho: Flags [.],ack 1505,win 37,length 0
17:11:03.250386 IP 192.168.1.10.61053 > 192.168.1.6.pyrrho: Flags [S],seq 724980759,win 14600,options [mss 1460,nop,sackOK,wscale 9],length 0
17:11:03.250594 IP 192.168.1.10.61053 > 192.168.1.6.pyrrho: Flags [.],ack 3440127706,win 29,length 0
複製程式碼
在測試機上連續輸入下面命令二次,連線池連線
crul 'http://192.168.1.10:6001/goodslist.php?pool=1&offset=0'
複製程式碼
在tcpdump終端你會看到下面的資訊,對應的locale port一致
17:14:35.184299 IP 192.168.1.10.61068 > 192.168.1.6.pyrrho: Flags [P.],seq 92:449,ack 332,win 31,length 357
17:14:35.189180 IP 192.168.1.10.61068 > 192.168.1.6.pyrrho: Flags [P.],seq 449:466,ack 1504,length 17
17:14:35.229312 IP 192.168.1.10.61068 > 192.168.1.6.pyrrho: Flags [.],ack 1584,length 0
複製程式碼
2、nginx+apache+php+pgbouncer+pgsql
[root@cqs ~]# echo 'http://192.168.1.10:6001/goodslist.php?pool=1&offset=0'
> /home/ad/urls.txt
複製程式碼
每秒100次訪問
[root@cqs ~]# http_load -p 100 -r 100 -s 300 /home/ad/urls.txt
29999 fetches,13 max parallel,in 300.001 seconds
1475 mean bytes/connection
99.9962 fetches/sec,147494 bytes/sec
msecs/connect: 0.546002 mean,5.094 max,0.244 min
msecs/first-response: 2.80047 mean,129.88 max,2.012 min
HTTP response codes:
code 200 -- 29999
複製程式碼
其它略,下面是測試結果對比表
資料庫伺服器壓力對比
次數/秒 系統cpu使用量 系統cpu佔比 使用者cpu使用量 使用者cpu佔比 總使用量 總佔比
100 1 0.25% 9 2.25% 10 2.50%
200 2 0.50% 20 5.00% 22 5.50%
300 2 0.50% 32 8.00% 34 8.50%
400 3 0.75% 49 12.25% 52 13.00%
複製程式碼
應用伺服器壓力對比
次數/秒 time_wait佔用數 load average 502次數 佔比 locale port
100 6000 0.1 0
200 6000 0.2 0
300 6000 0.4 0
400 6000 0.5 18487 15.41% 消耗完
複製程式碼
從上面的資料來看,qps為400時資料庫伺服器壓力非常的小,但應用伺服器與資料庫伺服器的通訊效率太低,導致應用伺服器的locale port被消耗光,http服務拒絕連線,瓶頸在應用與連線池的通訊效率上,只能水平擴充套件多一些應用伺服器節點了,
下面看看雙應用伺服器節點的效能是不是會提高
3、nginx負載雙節點(apache+php)+pgbouncer+pgsql
nginx.conf配置如下
首先在資料庫節點上也裝上apache/php/pgbouncer
接著修改應用節點上的nginx.conf配置,如下
upstream openapi{
server 192.168.1.10:8812 weight=6;
server 192.168.1.6:8812 weight=4;
keepalive 1000000;
}
複製程式碼
重啟nginx服務
/home/ad/nginx/sbin/nginx -s reload複製程式碼
測試開始
[root@cqs ~]# echo 'http://192.168.1.10:6001/goodslist.php?pool=1&offset=0' >
/home/ad/urls.txt
複製程式碼
每秒100次訪問
[root@cqs ~]# http_load -r 100 -s 300 /home/ad/urls.txt
29999 fetches,38 max parallel,147495 bytes/sec
msecs/connect: 0.681952 mean,11.494 max,0.258 min
msecs/first-response: 3.68105 mean,403.703 max,2.131 min
HTTP response codes:
code 200 -- 29999
複製程式碼
其它略,下面是測試結果對比表
次數/秒 系統cpu使用量 系統cpu佔比 使用者cpu使用量 使用者cpu佔比 總使用量 總佔比
100 4 1.00% 12 3.00% 16 4.00%
200 6 1.50% 23 5.75% 29 7.25%
300 9 2.25% 34 8.50% 43 10.75%
400 13 3.25% 48 12.00% 61 15.25%
500 15 3.75% 60 15.00% 75 18.75%
600 20 5.00% 78 19.50% 98 24.50%
複製程式碼
應用伺服器壓力對比
次數/秒 time_wait佔用數 load average 502次數 佔比 locale port
100 4800 0.2 0
200 6000 0.2 0
300 6000 0.3 0
400 6000 0.4 0
500 6000 0.6 0
600 6000 0.6 2725 1.51% 消耗完
複製程式碼
從上面的資料來看,應用伺服器水平擴充套件之後,併發數也跟著上來了,而且資料庫伺服器的壓力還是在可以接受的範圍之內,雖然滿足了業務的初步請求,但還是無法對付峰值請求,如果想處理更大的併發量,要不再水平擴充套件應用伺服器,另一個就是想辦法提高“應用程式與pg”的通訊效率,下面使用go來實現這些架構,看看是不是會提高。
4、基於go的後端服務架構
這是go的服務端程式
car_goods_matching.go
package main
import (
"fmt"
"github.com/valyala/fasthttp"
"github.com/jackc/pgx"
"os"
"strconv"
"encoding/json"
)
var pool *pgx.ConnPool
type Row struct {
Id int `json:"id"`
Createtime string `json:"createtime"`
Goods_name string `json:"goods_name"`
Nickname string `json:"nickname"`
Mobileno string `json:"mobileno"`
}
type Data struct {
Return_code string `json:"return_code"`
Rows []Row `json:"data"`
}
func httpHandle(w *fasthttp.RequestCtx) {
offset := string(w.QueryArgs().Peek("offset"))
if offset == "" {
offset = "0"
}
sql := `
SELECT
orders.id,orders.createtime::text,users.mobileno
FROM
orders
INNER JOIN users ON orders.users_id=users.id
ORDER BY
orders.id DESC
OFFSET
` + offset + `
LIMIT 10
`
rows,err := pool.Query(sql)
checkErr(err)
defer rows.Close()
w.SetContentType("text/html")
var data Data = Data{}
data.Rows = make([]Row,0)
data.Return_code="FAIL"
for rows.Next() {
var row Row
err = rows.Scan( &row.Id,&row.Createtime,&row.Goods_name,&row.Nickname,&row.Mobileno )
checkErr(err)
data.Rows = append(data.Rows,row)
}
if len(data.Rows)>0 {
data.Return_code="SUCCESS"
}
ret,_ := json.Marshal(data)
fmt.Fprintf(w,"%s",string(ret))
}
func main() {
var err error
poolnum,err := strconv.Atoi(os.Args[1])
checkErr(err)
connPoolConfig := pgx.ConnPoolConfig{
ConnConfig: pgx.ConnConfig{
Host: "192.168.1.6",User: "postgres",Password: "pgsql",Database: "car_goods_matching",Port: 9410,},MaxConnections: poolnum,}
pool,err = pgx.NewConnPool(connPoolConfig)
checkErr(err)
if err := fasthttp.ListenAndServe("0.0.0.0:8091",httpHandle); err != nil {
fmt.Println("start fasthttp fail:",err.Error())
}
}
func checkErr(err error) {
if err != nil {
panic(err)
}
}
複製程式碼
啟動服務,配置8個連線數
go run car_goods_matching.go 8 複製程式碼
測試開始
[root@cqs ~]# echo 'http://192.168.1.10:8091/?offset=0' >/home/ad/urls.txt
複製程式碼
每秒100次訪問
[root@cqs ~]# http_load -r 100 -s 300 /home/ad/urls.txt
29999 fetches,6 max parallel,4.57185e+07 bytes,in 300 seconds
1524 mean bytes/connection
99.9966 fetches/sec,152395 bytes/sec
msecs/connect: 0.601967 mean,7.028 max,0.245 min
msecs/first-response: 2.2164 mean,64.464 max,1.707 min
HTTP response codes:
code 200 -- 29999
複製程式碼
其它略,下面是測試結果對比表
資料庫伺服器壓力對比
次數/秒 系統cpu使用量 系統cpu佔比 使用者cpu使用量 使用者cpu佔比 總使用量 總佔比
100 1 0.25% 8 2.00% 9 2.25%
200 2 0.50% 18 4.50% 20 5.00%
300 2 0.50% 26 6.50% 28 7.00%
400 2 0.50% 35 8.75% 37 9.25%
500 3 0.75% 42 10.50% 45 11.25%
600 3 0.75% 52 13.00% 55 13.75%
1000 8 2.00% 71 17.75% 79 19.75%
1500 8 2.00% 118 29.50% 126 31.50%
2000 11 2.75% 145 36.25% 156 39.00%
3000 17 4.25% 214 53.50% 231 57.75%
複製程式碼
應用伺服器壓力對比
次數/秒 time_wait佔用數 load average 502次數 佔比 socket佔用
100 5200 0.1 0
200 6000 0.1 0
300 6000 0.1 0
400 6000 0.2 0
500 6000 0.3 0
600 6000 0.3 0
1000 6000 0.3 0
1500 6000 0.3 0
2000 6000 0.6 0
3000 6000 0.6 0
複製程式碼
從上面的測試資料我們可以看到基於go的服務端應用程式由於每次處理業務時都不需要與應用程式進行通訊,也不需要與資料庫進行連線,所以其sys cpu消耗的極其的低,編譯型程式,執行效率也高,在併發到3000的qps時,資料庫的cpu負載才過一半。由此可見使用Go服務端應用來支撐業務簡單的高併發應用是非常適合的方案。
為了更友好的對外通訊,統一api入口,我們使用nginx做web服務入口,再代理跑各種應用,現在來看看nginx代理跑go效能是否也能滿足
5、基於nginx+go的後端服務架構
nginx上的配置,記得配置nginx與go通訊採用長連線哦
#增加一個openapi_goodslist分組,最後的nginx.conf配置如下
user nginx nginx;
worker_processes 4;
worker_cpu_affinity 0001 0010 0100 1000;
worker_rlimit_nofile 65535;
error_log logs/error.log;
pid logs/nginx.pid;
events {
use epoll;
worker_connections 65535;
}
http {
include mime.types;
default_type application/octet-stream;
upstream openapi{
#ip_hash;
server 192.168.1.10:8812
#其它節點
keepalive 1000000;
}
# goodslist介面應用
upstream openapi_goodslist{
server 192.168.1.10:8091 ;
#其它節點
keepalive 1000000;
}
server {
listen 6001;
server_name 192.168.1.10;
#配置訪問路由
location /openapi/goodslist{
proxy_pass http://openapi_goodslist;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
location / {
proxy_pass http://openapi;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
複製程式碼
下面是測試結果對比表
資料庫伺服器壓力對比
次數/秒 系統cpu使用量 系統cpu佔比 使用者cpu使用量 使用者cpu佔比 總使用量 總佔比
100 1 0.25% 8 2.00% 9 2.25%
200 2 0.50% 18 4.50% 20 5.00%
300 2 0.50% 26 6.50% 28 7.00%
400 2 0.50% 35 8.75% 37 9.25%
500 3 0.75% 42 10.50% 45 11.25%
600 3 0.75% 52 13.00% 55 13.75%
1000 9 2.25% 68 17.00% 77 19.25%
1500 10 2.50% 112 28.00% 122 30.50%
2000 13 3.25% 141 35.25% 154 38.50%
3000 19 4.75% 210 52.50% 229 57.25%
複製程式碼
應用伺服器壓力對比
次數/秒 time_wait佔用數 load average 502次數 佔比 socket佔用
100 5200 0.1 0
200 6000 0.1 0
300 6000 0.1 0
400 6000 0.2 0
500 6000 0.3 0
600 6000 0.4 0
1000 6000 0.4 0
1500 6000 0.4 0
2000 6000 0.8 0
3000 6000 0.8 0
複製程式碼
加了nginx代理之後對資料庫的效能沒影響,只是應用伺服器的cpu開銷大了一點,這個損耗是在可接受範圍之內的,而且也可以將nginx與應用服務節點進行分離到不同的節點上面。
五、測試結果對比
序號 專案 qps峰值 300次/秒cpu壓力
1 nginx+apache+php+pgsql 300 219
2 nginx+apache+php+pgbouncer+pgsql 300 34
3 nginx+apache+php+pgbouncer+pgsql雙節點 500 43
4 基於go框架的實現 3000 28
5 nginx+go 3000 28
複製程式碼
各種框架處理qps峰值--值越大越好
各種框架處理300qps時cpu壓力--值越小越好
以上是對於高併發,業務極其簡單的介面在通訊架構上的一種優化方式,具體採用哪種構架來搭建你們的應用,需要具體業務具體優化,靈活搭配就是,另外還要考慮運維的成本。