安装

1
2
3
4
5
sudo yum groupinstall 'Development Tools'
sudo yum install -y openssl-devel git
git clone https://github.com/wg/wrk.git wrk
cd wrk
make

环境要求:Linux、Mac

特性:很少的线程压出大并发量
原理:使用操作系统特定高性能 IO 机制,select、epoll、kquue等,复用 redis 的 ae 异步事件驱动框架

缺点:仅支持单机压测

同类型测试工具:JMeter, LoadRunner

测试结果

./wrk -c 1 -t 1 -d 1 http://www.baidu.com

将可执行文件移动到 /usr/local/bin 位置

sudo cp wrk /usr/local/bin

wrk 参数说明

  • -t 需要模拟的线程数
  • -c 需要模拟的连接数
  • -timeout 超时时间
  • -d 测试持续时间

结果说明

  • Latency 响应时间
  • Req/Sec 每个线程每秒完成的请求数
  • Avg 平均
  • Max 最大
  • Stdev 标准差
  • +/- Stdev 正负一个标准差占比

高级用法

搭配 lua 使用:启动阶段、运行阶段和结束阶段,对压测进行个性化

setup()

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

init()

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

delay()

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

request()

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

response()

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

done()

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

自定义 Lua 脚本中可访问的变量以及方法

1
2
3
4
5
6
7
8
9
10
wrk = {
scheme = "http",
host = "localhost",
port = 8080,
method = "GET",
path = "/",
headers = {},
body = nil,
thread = <userdata>,
}

Lua

安装

1
2
3
4
5
wget http://www.lua.org/ftp/lua-5.3.4.tar.gz
tar -zxvf lua-5.3.4.tar.gz -C ../
cd lua-5.3.0
make linux test
make install

winodw 安装 Lua

本站下载地址 LuaForWindows_v5.1.4-46

执行 lua

方式①
1
2
3
4
5
lua xxx.lua 

或者

lua -i
方式②
1
2
3
4
--xxx.lua 起始位置
#!/usr/local/bin/lua

... 代码

为脚本添加可执行权限,执行脚本:

cd 脚本目录 -> ./xxx.lua

退出交互式编程

Ctrl+D、Ctrl+C、os.exit()

基本语法

Lua 是一个区分大小写的编程语言

注释

- - 单行注释
- -[[
多行注释
- -]]
*推荐使用
- -[=[多行注释]=]

标识符

定义变量,[a-Z_] + [a-Z_0-9],不建议使用 _ + [A-Z],Lua 保留字

全局变量

默认情况下,变量总认为是全局的,删除全局变量,将变量设为 nil

1
b = nil

数据类型

nil、boolean、number、string、userdata、function、thread、table

nil:空,任何值设为 nil,等价于删除
boolean:(false、nil) => false,其余都是 true
number:双精度实浮点数
string:‘...’ “...” [[...]]
function:C、Lua编写函数
userdata:C数据结构
thread:最主要的线程是协同程序(coroutine)
table:关联数组,数组的索引(数字、字符串、表类型),创建通过’构造表达式’完成,索引从’1’开始,长度会自动增长,没初始的 table 都是 nil

类型检查:type(print)

[注意]
数字字符串数字 做运算,优先转数值类型
字符串相连:'..'
#:计算字符串的长度,其实是字符串所占的字节数长

table

1
2
3
4
5
6
7
8
9
10
11
tbl = {'apple', 'pear', 'orange', 'banana'}

for k,v in pairs(tbl) do
print(k .. ":" .. v)
end

输出结果:
1:apple
2:pear
3:orange
4:banana

注意:#table 返回第一个 nil 前值的下标

线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停

变量

全局变量、局部变量、表达式中的域

local:声明局部变量,生命周期(从声明位置 -> 语句块结束)

多变量赋值:a, b = 10, 2*x,同时赋值,不会进行变量传递

局部变量的好处:

  1. 避免命名冲突
  2. 访问局部变量的速度比全局变量更快

函数

可变参数: '...'

1
2
3
4
5
6
7
8
9
10
function add(...)

local s = 0
for i,v in ipairs{...} do
s = s + v
end
return s
end

print(add(2,3,4,5,6))

获取可变参数数量的方式:

  1. #{…}
  2. select(“#”, …)

多返回值的函数赋值时,只有最后一个函数才会展开:local b,c,d,e = add(),add()

运算符优先级

1
2
3
4
5
6
7
8
^
not - (unary)
* / %
+ -
..
< > <= >= ~= ==
and
or

字符串操作方法

upper、lower

string.gsub(操作的字符串,被替换的字符,要替换的字符,替换次数)

string.gsub("aaaa","a","z", 3)

string.find(操作的字符串,要查找的字符,从何位置开始):返回第一次符合要求的索引

string.char(arg):整型数字转字符连接
string.byte(arg,int[返回第几个,默认第一个]):字符转整数值

string.rep(操作字符串,重复次数)

string.match

string.sub

字符串格式化

string.format()

%c:数字,转ascii码
%d,%i:数字,转有符号整数
%o:数字,转8进制
%u:数字,转无符号整数
%x:数字,转十六进制,使用小写字母
%X:数字,转十六进制,使用大写字母

%e:数字,转科学计数,小写字母e
%E:数字,转科学计数,大写字母E

%f:数字,转浮点数
%q:字符串,转可被Lua编译器读入的格式
%s:字符串,按照给定参数格式转

迭代器

Lua中迭代器支持指针类型,分为无状态、有状态两种迭代器

  • pairs 和 ipairs 区别

pairs:迭代 table,可以遍历表中所有key,可以返回 nil
ipairs:迭代数组,不能返回 nil,遇到则退出

三个值:迭代器,状态常量,控制变量

Table 常用操作

table.concat(数据,连接符,起始位置,结束位置)

table.insert(数据,插入位置,插入值)

table.maxn

table.remove

table.sort

模块及包

Lua 模块是由变量、函数等已知元素组成的 table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module = {}

module.constant = '常量'

function module.add()
io.write('公共函数')
end

local function locfun()
print('私有函数')
end

function module.plus()
locfun()
end

return module

加载模块

1
2
3
4
5
require("module")
module.plus()

=> local m = require("module")
m.plus()

加载机制

  1. 自定义的模块,不是放在哪个文件目录都行
  2. require 有自己的文件路径加载策略
  3. require 用于搜索 Lua 文件的路径存放在全局变量 package.path,Lua 启动时,环境变量 LUA_PATH 初始化,如果找不到环境变量,编译时定义的默认路径初始化

LUA_PATH:根目录 .profile.bashrc

1
2
3
4
#LUA_PATH
export LUA_PATH = "~/lua/?.lua;;" ;; 新加的路径后加上原来的默认路径

source ~/.profile

C包

使用前必须先加载并连接,实现方式通过动态连接库机制(loadlib()

local f = loadlib(path【绝对地址】, “luaopen_socket”【初始化函数】)

  • 使用 require 函数加载 C 库

修改 stub 文件对应二进制库的实际路径,将 stub 文件所在的目录加入到 LUA_PATH

元表 Metatable

支持多 table 操作

setmetatable(tbale, metatable):设置元表,存在‘__metatable ’,则报错
getmetatable(table):获取元表

__index

__index = [tbale, function(table, key)]

Lua 查找元素的规则步骤:

  1. 表中查找,& 返回 | 继续
  2. 判断表中是否有元表,& 继续| 返回 nil
  3. 判断元表有没有 __index 方法,& (表,重复1,2,3;函数,返回值) | 返回 nil

__newindex

为表添加操作符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function table_maxn(t)
local mn = 0
for k,v in pairs(t) do
if mn < k then
mn = k
end
end
return mn
end

mytab = setmetatable({1,2,3,4}, {
__add = function(mytab, newtab)
for i = 1, table_maxn(newtab) do
table.insert(mytab, table_maxn(mytab) + 1, newtab[i])
end
return mytab
end
})

sectab = {4,5,6}
mytab = mytab + sectab
for k,v in ipairs(mytab) do
print(k .. ' : ' .. v)
end

__add => +
__sub => -
__mul => *
__div => /
__mod => %
__unm => -
__concat => ..
__eq => ==
__lt => <
__le => <=

__Call 调用值时调用该方法

__tostring 修改表的输出行为

协同程序

Lua 协同程序与线程比较类似:拥有独立的堆栈、独立的局部变量、独立的指令指针,与其它协同程序,共享全局变量等

线程、协同程序的区别:
  1. 拥有多线程的程序可以同时运行几个线程,协同程序需要彼此协作运行
  2. 任一指定时候,只有一个协同程序运行,运行中的协同程序只有被挂起的时候才会挂起
  3. 协同程序类似同步的多线程,在等待同一个线程锁的多线程类似协同

语法

coroutine.create() suspended
coroutine.resume() running 返回值 boolean
coroutine.yield():挂起 suspended
coroutine.status():状态【dead、suspended、running】
coroutine.wrap()
coroutine.running()
return dead

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
local npro

function productor()
local i = 0
while true do
i = i + 1
send(i)
end
end

function consumer()
while true do
local i = receive()
print(i)
end
end

function receive()
local status, value = coroutine.resume(npro)
return value
end

function send(x)
coroutine.yield(x)
end

npro = coroutine.create(productor)
consumer()

coroutine.creat、coroutine.wrap 区别

create 返回协同程序,类型 thread,使用 resume 调用
wrap 返回普通函数,类型 function,不可以使用 resume 调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
function yieldReturn(arg) return arg end

co_yieldtest = coroutine.create(
function()
print("启动协程状态"..coroutine.status(co_yieldtest))
print("--")
coroutine.yield()
coroutine.yield(1)
coroutine.yield(print("第3次调用"))
coroutine.yield(yieldReturn("第4次调用"))
return 2
end
)

print("启动前协程状态"..coroutine.status(co_yieldtest))
print("--")

for i = 1,6 do
print("第"..i.."次调用协程:", coroutine.resume(co_yieldtest))
print("当前协程状态"..coroutine.status(co_yieldtest))
print("--")
end

输出结果如下:

启动前协程状态suspended
--
启动协程状态running
--
1次调用协程: true
当前协程状态suspended
--
2次调用协程: true 1
当前协程状态suspended
--
3次调用
3次调用协程: true
当前协程状态suspended
--
4次调用协程: true4次调用
当前协程状态suspended
--
5次调用协程: true 2
当前协程状态dead
--
6次调用协程: false cannot resume dead coroutine
当前协程状态dead
--

I/O 操作

读取、处理文件

简单模式:

单输入文件 -> 单输出文件

安全模式:

文件句柄,面向对象的形式

打开文件

file = io.open(filename, [mode])

mode 值:

r、r+:只读,文件必须存在
w、w+:只写,文件存在则内容清空,文件不存在则创建
a、a+:追加,文件存在则追加内容,文件不存在则创建

b:二进制模式
+:可读模式

常用方法

io.open
io.input
io.close
io.output
io.write
io.tmpfile
io.type
io.flush
io.lines

1
2
3
file = io.input('path', 'r')
io.input(file)
print(io.read())

完全模式

1
2
3
4
5
file = io.open('path', 'r')
print(file:read())

file:close()
file:write('str')

file:seek() 设置、获取文件位置
file:flush() 向文件写入缓冲中的所有数据

1
2
3
4
5
6
7
for line in io.lines('path') do
print(line)
end


file:seek('end', -100)
print(file:read('*a'))
  • *n 读取一个数字返回
  • *a 从当前位置读取整个文件
  • *| 读取下一行
  • number 返回指定个数的字符串

错误处理

  • 语法错误
  • 运行错误

处理错误

assert()、error()

pcall()、xpcall()、debug()

1
2
3
4
5
6
7
8
9
10
function myfunction ()
n = n/nil
end

function myerrorhandler( err )
print( "ERROR:", err )
end

status = xpcall( myfunction, myerrorhandler )
print( status)

Debug

debug()
getfenv()
gethook()
getinfo()
debug.getlocal()
getmetatable()
getregistry()
getupvalue()
sethook()
setlocal()
setmetatable()
setupvalue()
trackback()

调试类型

命令行调试器有:RemDebug、clidebugger、ctrace、xdbLua、LuaInterface - Debugger、Rldb、ModDebug

图形界调试器有:SciTE、Decoda、ZeroBrane Studio、akdebugger、luaedit

垃圾回收

collectgarbage(‘collect|count|restart|setpause|setstepmul|setp|stop’)

一次完整的垃圾收集循环、以K字节数返回使用总内参数、重启垃圾收集器、将arg设为收集器的间歇率、单步运行垃圾收集器、停止

面向对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

Shape = {area = 0}

function Shape:new (o,side)
o = o or {}
setmetatable(o, self)
self.__index = self
side = side or 0
self.area = side*side;
return o
end

function Shape:printArea ()
print("面积为 ",self.area)
end

-- 创建对象
myshape = Shape:new(nil,10)
myshape:printArea()

-- 派生类
Square = Shape:new()

function Square:new (o,side)
o = o or Shape:new(o,side)
setmetatable(o, self)
self.__index = self
return o
end

function Square:printArea ()
print("正方形面积为 ",self.area)
end

-- 创建对象
mysquare = Square:new(nil,10)
mysquare:printArea()

Rectangle = Shape:new()

function Rectangle:new (o,length,breadth)
o = o or Shape:new(o)
setmetatable(o, self)
self.__index = self
self.area = length * breadth
return o
end

function Rectangle:printArea ()
print("矩形面积为 ",self.area)
end

-- 创建对象
myrectangle = Rectangle:new(nil,10,20)
myrectangle:printArea()

数据库访问

LuaSQL

支持的数据库有:ODBC, ADO, Oracle, MySQL, SQLite 和 PostgreSQL

LuaRocks 安装方法

1
2
3
4
5
wget http://luarocks.org/releases/luarocks-2.2.1.tar.gz
tar zxpf luarocks-2.2.1.tar.gz -C ../
cd luarocks-2.2.1
./configure; sudo make bootstrap
sudo luarocks install luasocket

安装数据库

1
2
3
4
5
luarocks install luasql-sqlite3
luarocks install luasql-postgres
luarocks install luasql-mysql
luarocks install luasql-sqlite
luarocks install luasql-odbc
*安装 mysql 报错*
luarocks install luasql-mysql MYSQL_INCDIR=/usr/include/mysql MYSQL_LIBDIR=/usr/local/mysql/lib
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

-- 连接 mysql

luasql = require 'luasql.mysql'
env = luasql.mysql

conn = env:connect('db_name', 'user', 'password', 'ip', '3306')
conn:execute'set names utf8'

cur = conn:execute('select * from md_tag')

row = cur:fetch({}, 'a')

file = io.open('md_tag.txt', 'w+')

while row do
var = string.format('%d %s\n', row.md_id, row.m_name)
print(var)
file:write(var)
row = cur:fetch(row, 'a')
end

file:close()
conn:close()
env:close()

luarocks几个常用命令

build
download
help
list
install
make
pack
path
remove
search
show
unpack