中小企业在我国经济发展中具有重要地位,目前我国的中小企业数量多,地区分布广泛,行业分布跨度大。随着全球经济一体化的发展和电子商务的兴起,中小企业之间的竞争将越来越激烈。网络及电子商务的迅猛发展突破了时间、空间的局限性,给中小企业带来了更多的发展机会,同时也增大了企业之间的竞争强度。这就要求中小企业必须改变企业的经营管理模式,提高企业的运营效率。目前,我国中小企业的信息化水平还很低,相比国外企业,还只处于刚开始始用的阶段。随着技术发展,电脑操作及管理日趋简化,电脑知识日趋普及,同时市场经济快速多变,竞争激烈,企业采用电脑管理进货、库存、销售等诸多环节也已成为趋势及必然。
本系统实现的主要功能模块包括:采购管理,销售管理,库存管理,基础资料管理,系统管理等几个功能模块。
本文首先进行系统的需求分析,得出要建的各个系统模块。采用用SQL2005数据库建立系统运行所要的后台数据库,采用VS2005集成开发环境配合使用C#语言开发一套超市管理系统。
文件:590m.com/f/25127180-496915070-2282fd(访问密码:551685)
以下内容无关:
-------------------------------------------分割线---------------------------------------------
高性能、方便使用的 HTTP(s) 的流量压测工具,结合了多个开源项目开发而成:
redis 的 ae 事件框架
luajit
openssl
http-parser
减少造轮子、复用他人的成功项目,赞👍;我们定制化也走这条路线,代码见此。
要支持 tcp 字节流协议压测,只需要增加一个函数 stream_response,实现见此
– data 的结果为 {“error_code”:0,“error_msg”:"",“data”:{}}
– stream_response 表示使用tcp字节流协议压测,对返回 error_code 进行校验,为0表示状态正常。
function stream_response(data)
local t = json.decode(data)
return t[“error_code”] == 0
end
lua 脚本
wrk 的第一大特色就是支持 lua 脚本,直接对 c 修改进行压测成本比较高:c 的业务开发速度较慢及每次都需要编译。
脚本则克服了开发时间过长的缺点,使用 luajit 速度可以保证在开发和运行的速度中得到一个平衡。
具体的脚本的变量和函数的逻辑见 官方文档,一定要熟读这个文档,精华部分,比其它个人的表述准确非常多。
一些官方文档之外的补充
脚本文件
分为两个文件组成
wrk.lua 内置脚本,提供了基本的 API 和一些变量
命令行 -s <foo.lua>, foo.lua 用户自己使用的脚本文件,为可选项
线程
每个线程都包含一个自己的lua状态机,所以在脚本文件中定义的变量(如自增的请求计数),不同线程的中的结果是不相同的。
线程的结构是一个用户数据(userdata), 在 c 中的定义为 struct thread。关联的 addr 也同样为用户数据,在 c 中的定义为 struct addrinfo,支持取和存的操作(可以存在 = 的左右)
thread:set(key, value),value 不能够为表,在使用 get 操作后会发生 panic,应该是 script_copy_value() 函数中出现栈顶设置错误的问题
加速
如果构造的 request 内容比较耗时的话,优先放在 init() 使用提前生成并且混缓存起来,后面的 request() 直接从缓存的结果中获取。
高性能 && 请求收发逻辑
基于 redis 的 ae 事件框架是。和 ab 不同的是可以充分利用多核资源,减小线程间的切换,以此获得高性能。一般而言,ab 在请求量不是很大的情况下是ok的,但是在请求量到达上w req/s 后,自身就会成为瓶颈。
在每个线程中创建 connections / threads 个连接,并且将这些建立连接的 fd 添加至事件循环中,然后 fd 就绪后,将 readble 和 writeable 函数添加再添加至事件循环中;
writeable
对应着请求发送的逻辑,调用lua接口 reqeust() 获取发送内容就在其中;
在发送完成后会将自身从事件循环中删除,发送(write)可能调用多次,但是一定会等到将缓冲区中的内容全部发送完成,除非发送失败产生错误。
延时发送 delay
delay() 为 lua 的一个可选接口,发挥延迟发送的间隔,单位为毫秒(ms).
当lua脚本中出现了该函数时,writeable 就会从事件循环的文件事件删除自身,并且将 writeable 作为定时任务添加至事件循环中,从而达到延时发送的效果。
readable
对应响应接收的逻辑,对应返回的内容校验,在官方版本中,为 http 请求的解析。对解析的结果进行统计,当判断响应结束时,删除该连接在事件循环中的事件,并且重新进行最初的动作。
我们可以接管这个解析结果的过程,丰富化使用场景。
收发事件简单的时序图
统计
wrk 对两个维度对压测的结果做了统计,结果如下
Thread Stats Avg Stdev Max +/- Stdev
Latency 19.85ms 3.71ms 49.11ms 81.44%
Req/Sec 98.50 16.32 121.00 88.33%
延迟 Latency && 请求速度 Req/Sec
统计每个请求的延迟情况
Avg, 平均延迟
Stdev, 样本标准差
Max, 最大延迟
+/- Stdev, 正负一倍标准差概率
实现
wrk 的实现为通用结构,适用延迟和请求速度的统计
typedef struct {
uint64_t count; // 样本数量
uint64_t limit; // 最大样本变量限制
uint64_t min; // 最小样本变量
uint64_t max; // 最大样本变量
uint64_t data[]; // 索引为样本变量,值为出现的次数
} stats;
limit 防止 data[] 容量不够,也起到一个剔除不满足要求的情况,如延迟超过 limit 后直接归为 timeout 中。
⚠️ 注意 data[] 数据每个元素的值为出现的次数,而不是样本变量。
样本变量统计
_sync* 为编译器的同步函数,wrk 将统计的变量作为一个全局存在,故多个线程内就需要一些同步操作保证正确性。
理论上可以将这些统计变量放在线程内,在所以线程结束后,汇集处理,这里就不要这些同步元语了。不过目前还算简单,这样做问题也不大。
n 为样本变量,stats->data[n] 为该样本变量出现的次数。min 和 max 为之后的统计过程加速。
int stats_record(stats *stats, uint64_t n) {
if (n >= stats->limit) return 0;
__sync_fetch_and_add(&stats->data[n], 1);
__sync_fetch_and_add(&stats->count, 1);
uint64_t min = stats->min;
uint64_t max = stats->max;
while (n < min) min = __sync_val_compare_and_swap(&stats->min, min, n);
while (n > max) max = __sync_val_compare_and_swap(&stats->max, max, n);
return 1;
}
样本标准差
数学公式为
δ=Σ(xi−x¯)2n−1−−−−−−−−−√
wrk 实现如下,L6 处 * stats->data[i] 表示有多个样本变量为 i
1 long double stats_stdev(stats *stats, long double mean) {
2 long double sum = 0.0;
3 if (stats->count < 2) return 0.0;
4 for (uint64_t i = stats->min; i <= stats->max; i++) {
5 if (stats->data[i]) {
6 sum += powl(i - mean, 2) * stats->data[i];
7 }
8 }
9 return sqrtl(sum / (stats->count - 1));
10 }
扩展支持 tcp 压测
由于 wrk 支持 http(s) 的压测,但实际的场景中有很多不是 http 的协议,可以就是很简单的 json 文本协议。
所以这里对 wrk 做一个简单的扩展,支持普通的4层流量压测,功能上支持 json 和 md5。json库使用 yyjson,md5 使用 nginx/md5,充分利用前人的成功经验。