wrk 压力测试

wrk负载测试时可以运行在一个或者多核CPU,wrk结合了可伸缩的事件通知系统epoll和kqueue等多线程设计思想。目前wrk可以安装在Linux系统和Mac系统,下面看下wrk在Linux下的安装和用法。

1、压力测试工具wrk安装

[root@localhost /]# yum install git #安装git
[root@localhost wrk]# git clone https://github.com/wg/wrk.git #复制一份wrk源码文件
[root@localhost wrk]# cd /wrk/ #进入wrk源码文件夹
[root@localhost wrk]# mkdir /wrk #编译wrk
[root@localhost wrk]# cp ./wrk /usr/local/bin/ #复制到用户bin目录下

2、压力测试工具wrk用法

[root@localhost wrk]# wrk -t8 -c400 -d30s http://127.0.0.1
Running 30s test @ http://127.0.0.1
8 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 96.88ms 75.53ms 1.93s 97.18%
Req/Sec 554.79 60.39 680.00 78.48%
131271 requests in 30.05s, 97.40MB read
Requests/sec: 4368.91
Transfer/sec: 3.24MB

3、压力测试工具wrk常用参数
-t 线程数
-c HTTP连接数
-d 测试执行时间

我们先来做一个简单的性能测试:

wrk -t12 -c100 -d30s http://www.baidu.com

30秒钟结束以后可以看到如下输出:

Running 30s test @ http://www.baidu.com
12 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 538.64ms 368.66ms 1.99s 77.33%
Req/Sec 15.62 10.28 80.00 75.35%
5073 requests in 30.09s, 75.28MB read
Socket errors: connect 0, read 5, write 0, timeout 64
Requests/sec: 168.59
Transfer/sec: 2.50MB

先解释一下输出: 
12 threads and 100 connections 
这个能看懂英文的都知道啥意思: 用12个线程模拟100个连接. 
对应的参数 -t 和 -c 可以控制这两个参数. 

一般线程数不宜过多. 

核数的2到4倍足够了. 

多了反而因为线程切换过多造成效率降低. 

因为 wrk 不是使用每个连接一个线程的模型, 而是通过异步网络 io 提升并发量. 

所以网络通信不会阻塞线程执行. 

这也是 wrk 可以用很少的线程模拟大量网路连接的原因.

 而现在很多性能工具并没有采用这种方式, 而是采用提高线程数来实现高并发. 

所以并发量一旦设的很高, 测试机自身压力就很大. 

测试效果反而下降. 

下面是线程统计:

Thread Stats Avg Stdev Max +/- Stdev
Latency 538.64ms 368.66ms 1.99s 77.33%
Req/Sec 15.62 10.28 80.00 75.35%

Latency: 可以理解为响应时间, 有平均值, 标准偏差, 最大值, 正负一个标准差占比. 
Req/Sec: 每个线程每秒钟的完成的请求数, 同样有平均值, 标准偏差, 最大值, 正负一个标准差占比. 

一般我们来说我们主要关注平均值和最大值. 标准差如果太大说明样本本身离散程度比较高. 有可能系统性能波动很大. 

接下来:

5073 requests in 30.09s, 75.28MB read
Socket errors: connect 0, read 5, write 0, timeout 64
Requests/sec: 168.59
Transfer/sec: 2.50MB

30秒钟总共完成请求数和读取数据量. 
然后是错误统计, 上面的统计可以看到, 5个读错误, 64个超时. 
然后是所以线程总共平均每秒钟完成168个请求. 每秒钟读取2.5兆数据量. 

可以看到, 相对于专业性能测试工具. wrk 的统计信息是非常简单的. 但是这些信息基本上足够我们判断系统是否有问题了. 

wrk 默认超时时间是1秒. 这个有点短. 我一般设置为30秒. 这个看上去合理一点. 
如果这样执行命令:

1. /wrk -t12 -c100 -d30s -T30s http://www.baidu.com

可以看到超时数就**降低了, Socket errors 那行没有了:

1. Running 30s test @ http://www.baidu.com

2. 12 threads and 100 connections

3. Thread Stats Avg Stdev Max +/- Stdev

4. Latency 1.16s 1.61s 14.42s 86.52%

5. Req/Sec 22.59 19.31 108.00 70.98%

6. 4534 requests in 30.10s, 67.25MB read

7. Requests/sec: 150.61

8. Transfer/sec: 2.23MB

通过 -d 可以设置测试的持续时间. 一般只要不是太短都是可以的. 看你自己的忍耐程度了.

时间越长样本越准确. 如果想测试系统的持续抗压能力, 采用 loadrunner 这样的专业测试工具会更好一点. 

想看看响应时间的分布情况可以加上–latency参数:

1. wrk -t12 -c100 -d30s -T30s –latency http://www.baidu.com

1. Running 30s test @ http://www.baidu.com

2. 12 threads and 100 connections

3. Thread Stats Avg Stdev Max +/- Stdev

4. Latency 1.22s 1.88s 17.59s 89.70%

5. Req/Sec 14.47 9.92 98.00 77.06%

6. Latency Distribution

7. 50% 522.18ms

8. 75% 1.17s

9. 90% 3.22s

10. 99% 8.87s

11. 3887 requests in 30.09s, 57.82MB read

12. Socket errors: connect 0, read 2, write 0, timeout 0

13. Requests/sec: 129.19

14. Transfer/sec: 1.92MB

可以看到50%在0.5秒以内, %75在1.2s 以内. 看上去还不错. 

看到这里可能有人会说了, HTTP 请求不会总是这么简单的, 通常我们会有 POST,GET 等多个 method, 会有 Header, 会有 body 等. 

在我第一次知道有 wrk 这个工具的时候他确实还不太完善, 要想测试一些复杂的请求还有点难度. 现在 wrk 支持 lua 脚本. 在这个脚本里你可以修改 method, header, body, 可以对 response 做一下自定义的分析. 因为是 lua 脚本, 其实这给了你无限的可能. 但是这样一个强大的功能如果不谨慎使用, 会降低测试端的性能, 测试结果也受到影响. 

一般修改method, header, body不会影响测试端性能, 但是操作 request, response 就要格外谨慎了. 

我们通过一些测试场景在看看怎么使用 lua 脚本. 

POST + header + body. 

首先创建一个 post.lua 的文件:

1. wrk.method = "POST"

2. wrk.body = "foo=bar&baz=quux"

3. wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"

就这三行就可以了, 当然 headers 可以加入任意多的内容. 
然后执行:

1. wrk -t12 -c100 -d30s -T30s –script=post.lua –latency http://www.baidu.com

当然百度可能不接受这个 post 请求. 

对 wrk 对象的修改全局只会执行一次. 
通过 wrk 的源代码可以看到 wrk 对象的源代码有如下属性:

1. local wrk = {

2. scheme = "http",

3. host = "localhost",

4. port = nil,

5. method = "GET",

6. path = "/",

7. headers = {},

8. body = nil,

9. thread = nil,

10. }

schema, host, port, path 这些, 我们一般都是通过 wrk 命令行参数来指定. 

wrk 提供的几个 lua 的 hook 函数: 

setup 函数 
这个函数在目标 IP 地址已经解析完, 并且所有 thread 已经生成, 但是还没有开始时被调用. 每个线程执行一次这个函数. 
可以通过thread:get(name), thread:set(name, value)设置线程级别的变量. 

init 函数 
每次请求发送之前被调用. 
可以接受 wrk 命令行的额外参数. 通过 — 指定. 

delay函数 
这个函数返回一个数值, 在这次请求执行完以后延迟多长时间执行下一个请求. 可以对应 thinking time 的场景. 

request函数 
通过这个函数可以每次请求之前修改本次请求的属性. 返回一个字符串. 这个函数要慎用, 会影响测试端性能. 

response函数 
每次请求返回以后被调用. 可以根据响应内容做特殊处理, 比如遇到特殊响应停止执行测试, 或输出到控制台等等.

1. function response(status, headers, body)

2. if status ~= 200 then

3. print(body)

4. wrk.thread:stop()

5. end

6. end

done函数 
在所有请求执行完以后调用, 一般用于自定义统计结果.

1. done = function(summary, latency, requests)

2. io.write("——————————\n")

3. for _, p in pairs({ 50, 90, 99, 99.999 }) do

4. n = latency:percentile(p)

5. io.write(string.format("%g%%,%d\n", p, n))

6. end

7. end

下面是 wrk 源代码中给出的完整例子:

1. local counter = 1

2. local threads = {}

3.

4. function setup(thread)

5. thread:set("id", counter)

6. table.insert(threads, thread)

7. counter = counter + 1

8. end

9.

10. function init(args)

11. requests = 0

12. responses = 0

13.

14. local msg = "thread %d created"

15. print(msg:format(id))

16. end

17.

18. function request()

19. requests = requests + 1

20. return wrk.request()

21. end

22.

23. function response(status, headers, body)

24. responses = responses + 1

25. end

26.

27. function done(summary, latency, requests)

28. for index, thread in ipairs(threads) do

29. local id = thread:get("id")

30. local requests = thread:get("requests")

31. local responses = thread:get("responses")

32. local msg = "thread %d made %d requests and got %d responses"

33. print(msg:format(id, requests, responses))

34. end

35. end

https://sanwen8.cn/p/1d4kAUJ.html

PHP使用Redis来存储session

Redis是一个高性能的key-value数据库,在部分场合可以对关系数据库起到很好的补充作用。现在越来越多的网站为了达到一个更高的可用性把session储存在Memcache、Redis等NoSQL数据库中。
方法一:修改php.ini的设置

session.save_handler = redis
session.save_path = "tcp://127.0.0.1:8089"#Redis服务的地址和端口

方法二:如果不想修改php.ini,可在代码中使用ini_set()方法:修改后重启php-fpm,phpinfo()可以查看到session存储在redis中。

ini_set("session.save_handler","redis");
ini_set("session.save_path","tcp://127.0.0.1:8089");

查看redis存储session的值:如果redis.conf设置了连接密码(requirepass),session的save_path需修改为:tcp://127.0.0.1:8089?auth=requirepass的值。如果选择redis数据库,session.save_path = “tcp://xx.xx.xx.xx:8089?database=11″,诸如此类。

session_start();
$_SESSION['website'] = 'www.leokim.cn';
$redis = new redis();
$redis->connect('192.168.31.102',8089);
echo $_SESSION['website']; //输出www.leokim.cn

Redis高级操作

存入redis数据前必须选择数据库,redis数据库默认16个,下标0~15,默认使用第0个,存放数据前可以使用select N选择数据库。

Redis数据库在redis.conf中配置如下:

#redis.conf#
databases 16

你可以根据实际需要更改数据库个数,但一般不建议修改。

*持久化功能

redis(nosql产品)为了内部数据的安全考虑,会把本身的数据以文件形式保存到硬盘中一份,在服务器重启之后会自动把硬盘的数据恢复到内存(redis)的里边。

数据保存到硬盘的过程就称为“持久化”效果。

Redis快照持久化(snap shotting或称RDB持久化)配置:

该持久化默认开启,一次性把redis中全部的数据保存一份存储在硬盘中,如果数据非常多(10-20G)就不适合频繁进行该持久化操作。快照持久化会根据配置条件定期生成二进制备份文件,默认文件名dump.rdb。

redis.conf 中关于快照的配置:

#快照写入文件名dbfilename dump.rdb
#快照保存目录dir ./
#快照写入频率
save 900 1   
#900 秒内如果超过 1 个 key 被修改,则发起快照保存
save 300 10   
#300秒超过10个key被修改,发起快照
save 60 10000 
#60秒超过10000个key被修改,发起快照
#以上三个备份频率需要同时存在:
#数据变化非常快的时候,就快点做备份(保证数据安全)
#数据变化慢的时候,就慢点做备份(节省服务器资源)

 

手动发起快照持久化:

快照持久化默认开启,并定时执行,你也可以通过redis-cli客户端使用bgsave命令手动发起。

./redis-cli bgsave

Redis AOF(append only file)持久化配置:

本质:把用户执行的每个“写”指令(添加、修改、删除)都备份到文件中,还原数据的时候就是执行具体写指令而已。

AOF默认关闭,默认保存文件名append.aof,默认每秒执行一次,具体参数如下

#开启/关闭AOF
appendonly yes

#保存文件名
appendfilename "appendonly.aof"

#AOF保存频率
# appendfsync always #每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用
appendfsync everysec #每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,推荐
# appendfsync no     #完全依赖 os,性能最好,持久化没保证
#上面三种选项数据安全性及服务性能情况
#数据最安全      服务器性能低  
#数据较安全      服务器性能中等
#数据不安全      服务器性能高(优良)

AOF策略设置为always或者everysec时,后台处理进程(后台保存或者AOF日志重写)会执行大量的I/O操作,在某些Linux配置中会阻止过长的fsync()请求。注意现在没有任何修复,即使fsync在另外一个线程进行处理。

为了减缓这个问题,可以设置下面这个参数no-appendfsync-on-rewrite:

#redis.conf#
no-appendfsync-on-rewrite yes

为AOF备份文件做优化压缩处理:

AOF记录用户的每个操作,但是有些操作例如对某个key的多个同类型操作是可以合并为一个,从而做到压缩文件大小的效果。例如多次incr一个整型key,可以直接合并为set key N。

压缩优化(AOF重写)命令:

./redis-cli bgrewriteaof

AOF自动重写:

当AOF文件增长到一定大小的时候Redis能够调用 BGREWRITEAOF 对日志文件进行重写,它是这样工作的:Redis会记住上次进行些日志后文件的大小(如果从开机以来还没进行过重写,那日子大小在开机的时候确定)基础大小会同现在的大小进行比较。如果现在的大小比基础大小大制定的百分比,重写功能将启动,同时需要指定一个最小大小用于AOF重写,这个用于阻止即使文件很小但是增长幅度很大也去重写AOF文件的情况,设置 percentage 为0就关闭这个重写特性。

#redis.conf
#auto-aof-rewrite-percentage 100 
auto-aof-rewrite-min-size 64mb

手动持久化相关指令:

save (客户端命令行内)前台备份数据(快照保存)
bgsave 异步保存数据到磁盘(快照保存)
lastsave 返回上次成功保存到磁盘的unix时间戳
shutdown  同步保存到服务器并关闭redis服务器
bgrewriteaof  当日志文件过长时优化AOF日志文件存储
redis 127.0.0.1:6379> SAVE #前台备份数据
./redis-cli  bgrewriteaof
./redis-cli  bgsave
./redis-cli -h 127.0.0.1 -p 6379 bgsave   #手动发起快照

数据恢复

如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 快照保存目录 并启动服务即可。要查看快照保存目录,可以查看redis.conf的dir配置。

 

*Redis主从模式

为了降低每个redis服务器的负载,可以多设置几个,并做主从模式,一个服务器负载“写”(添加、修改、删除)数据,其他服务器负载“读”数据,主服务器数据会“自动”同步给从服务器

905655-20160422212934835-967833038.png

Redis主从复制特点

1.master可以有多个slave

2.除了多个slave连接到master外,slave也可以连接到其他slave,形成网状结构

3.可以让slave做读请求,master做写操作 

 

配置主服务器: 

(假定局域网IP为192.168.1.101)

#redis.conf#

#配置主服务器密码
requirepass admin123

#自定义端口
port 6379

 配置从服务器:

#redis.conf#

#主服务器连接密码
masterauth admin123

#自定义端口
port 6380

#设置成为192.168.1.101的从服务器
slaveof 192.168.1.101 6379

#取消从服务器只读
slave-read-only no

重新开启redis-server 服务,则从服务器设置成功。

905655-20160423130828023-2066105402.png

在一台服务器上部署多个Redis服务节点(启动多端口,运行多实例)方式: 

假设你只有一台电脑,但又想实现redis的主从部署,你考虑使用诸如6380,6381…等端口创建从节点,那么问题来了,改怎么设置呢?

 如果要在一台服务器上运行多个redis实例,必须满足以下3点:

  1. 使用不同的端口

  2. 分别配置每个实例(服务)的pid和log文件

  3. RDB和AOF持久化到每个实例(服务)的rdb和aof文件中

每个实例必须对应一个配置文件,配置文件按照上面3点设置:

cp redis.conf redis_6380.conf
vi redis_6380.conf
port 6380 #设置端口
pidfile /var/run/redis/redis_6380.pid #设置pid文件
logfile /var/log/redis/redis_6380.log #设置log文件
dbfilename dump_6380.rdb #设置RDB持久化文件
appendfilename "appendonly_6380.aof" #设置aof持久化文件

 其他配置文件按此修改,至于从节点配置,回上面看。

启动多个redis实例:

./redis-server redis_6380.conf
./redis-server redis_6381.conf

 配置好从节点后,./redis-cli 开启客户端,auth 密码 登入,再输入info命令,可以查看redis主节点状态,其中该主节点的从节点信息如下:

# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=1037,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=1037,lag=0
master_repl_offset:1037
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:1036

*关于Redis详细配置信息,可以参考以下博客:

1Redis配置文件详解 

2Redis配置文件详解 

http://www.cnblogs.com/GaZeon/p/5422078.html

Centos7下配置Redis开机自启动

设置redis.conf中daemonize为yes,确保守护进程开启。

编写开机自启动脚本

基本原理为: 
系统开机启动时会去加载/etc/init.d/下面的脚本,通常而言每个脚本文件会自定义实现程序的启动;若想将新的程序开机自启动,只需在该目录下添加一个自定义启动程序的脚本,然后设置相应规则即可。 
如在这里我们在/etc/init.d/下新建一个 redis 的脚本,开机启动时会去加载执行该脚本。

vim /etc/init.d/redis

在该脚本中添加一下内容:

参数要根据自己的配置改

# chkconfig: 2345 10 90  
#redis服务必须在运行级2,3,4,5下被启动或关闭,启动的优先级是90,关闭的优先级是10。
# description: Start and Stop redis   
PATH=/usr/local/bin:/sbin:/usr/bin:/bin  
export PATH 
REDISPORT=6379 #端口号,这是默认的,如果你安装的时候不是默认端口号,则需要修改
EXEC=/usr/local/redis/bin/redis-server  
#redis-server启动脚本的位置,你如果忘了可以用find或whereis找到   
REDIS_CLI=/usr/redisbin/redis-cli  
#redis-cli客户端启动脚本的位置,你如果忘了可以用find或whereis找到   
PIDFILE=/run/redis.pid   #这个也可以用find或whereis找到
CONF="/usr/local/redis/etc/redis.conf"  #redis.conf配置文件的位置,你如果忘了可以用find或whereis找到
AUTH="1234"  
case "$1" in   
        start)   
                if [ -f $PIDFILE ]   
                then   
                        echo "$PIDFILE exists, process is already running or crashed."  
                else  
                        echo "Starting Redis server..."  
                        $EXEC $CONF   
                fi   
                if [ "$?"="0" ]   
                then   
                        echo "Redis is running..."  
                fi   
                ;;   
        stop)   
                if [ ! -f $PIDFILE ]   
                then   
                        echo "$PIDFILE exists, process is not running."  
                else  
                        PID=$(cat $PIDFILE)   
                        echo "Stopping..."  
                       $REDIS_CLI -p $REDISPORT  SHUTDOWN    
                        sleep 2  
                       while [ -x $PIDFILE ]   
                       do  
                                echo "Waiting for Redis to shutdown..."  
                               sleep 1  
                        done   
                        echo "Redis stopped"  
                fi   
                ;;   
        restart|force-reload)   
                ${0} stop   
                ${0} start   
                ;;   
        *)   
               echo "Usage: /etc/init.d/redis {start|stop|restart|force-reload}" >&2  
                exit 1  esac

写完后保存退出

设置可执行权限:

chmod 755 redis

启动测试:

/etc/init.d/redis start

启动成功会提示如下信息:

Starting Redis server...
Redis is running...

使用redis-cli测试:

[root@localhost ~]# /usr/local/redis/bin/redis-cli127.0.0.1:6379> set foo bar
OK127.0.0.1:6379> get foo"bar"127.0.0.1:6379> exit

设置开机自启动:

chkconfig redis on

关机重启测试:

reboot

开机完之后可以用 redis-cli 测试,或者用 ps -ef | grep redis 看看redis 是否在运行中

centos php7安装redis拓展

wget https://codeload.github.com/phpredis/phpredis/zip/php7
unzip php7
cd phpredis-php7

这个地方要用php的phpize来生成configure

我试了几次都不成功 make失败

研究了一会发现linux上貌似默认执行的phpize是php5版本的 按路径调用php7的phpize就可以了

./configure
make && make install

修改php.ini配置 加上redis扩展 

//vim php.ini 添加 extension=redis.so
extension=redis.so

重启php-fpm 查看phpinfo就可以看到拓展已经安装成功了

blob.png