接口并发测试

当多人同时请求一个flask接口时,并发是一件需要考虑的事。

1. postman

postman可以用来测试接口,但是postman是串行的,当一个请求完成后才会发送下一个请求,可以测试连续执行,但不能测试并发。
【补充】:postman以json方式提交post请求,首先在headers中添加属性,接着修改body—>raw,设置为json
接口并发测试_第1张图片

2. jMeter

双击jmeter.bat,jemter需要java环境,即安装jdk,见【https://www.cnblogs.com/hupilan521/p/12460580.html】
配置线程组、HTTP请求后,即可进行并发测试,这里的信息头要添加第1节中【补充】内容。
接口并发测试_第2张图片
查看测试日志,发现flask接口能够接收到请求,但是同时接收的多个请求堵塞在一起返回。
接口并发测试_第3张图片

3. WSGI

  • 考虑到flask接口采用app.run()的开发模式运行,因此修改为WSGI启动,但是WSGI会强行将并发测试请求转为串行方式执行。
from gevent import pywsgi

server = pywsgi.WSGIServer(('0.0.0.0', 13579), app)
server.serve_forever()

4. 线程、进程、并发

详细了解下多线程、多进程、并发的含义,并使用python进行实践测试。

  • 进程:是正在进行的程序
  • 并发:指的是多个事件同时发生,本质是任务间的切换给人的感觉是同时在进行,称之为伪并行, 例如洗衣服和做饭,同时发生了
  • 并行:指的是多个事件同时进行中,例如一个人在写代码另一个人在写书,这两件事件是同时在进行的,一个人是无法真正的并行执行任务
from multiprocessing import Process
from threading import Thread

os.getpid()	# 获取进程号

发现flask接口开启后,一个api同时接收多个请求时,各个请求的进程号一样,当同时发送5次请求时,耗时从一次请求时的0.5s增加为1.3s,且5个请求的耗时相当。
因此,修改api接收到参数后的处理方式,采用multiprocessing.Process()调用函数,可以发现,发送同时发送20次请求时,耗时与一次请求相当。但是当发送25个请求时,耗时开始增加到0.8s。发送30个请求时,耗时增加到1s,推测是多个进程的阻塞。

接口并发测试_第4张图片
接口并发测试_第5张图片

5. 尝试降低单次请求计算耗时

  • 采用grequests查询题目,一次请求耗时从0.5s降为0.2s
  • 采用es客户端查询,一次请求耗时从0.5s降为0.2s,与grequests查询的效率相当
  • 将查询id拼接后,采用OR语句查询,一次请求降为0.15s
  • 将数据导入mysql数据库,采用pymysql查询,一次请求降为0.06s

【补充】:mysql 存储过程,检查执行耗时发现和采用sql执行耗时相当

DROP PROCEDURE select_more_topic;
delimiter ;;
CREATE PROCEDURE select_more_topic(in param1 varchar(10000)) 
BEGIN 
set @id = param1; 
set @sel = concat('select * from anpei_practise_info where _id in (', param1, ')');
prepare stmt from @sel; 
execute stmt;
deallocate prepare stmt;
END;;
delimiter ;

call select_more_topic('"3886785006424822201","3886785006252855617"');

6. 处理flask、es的多次请求堵塞问题

python的多线程、多进程感觉效率提升的用处不大,最终采取的方案是:采用分布式缓存Redis存储数据,从Redis中一次请求的耗时约为0.01,并采用ngix进行负载均衡,将请求轮询发送到3个服务器节点上。
这个做法就是增加了数据库存储的开销,因为目前场景下这种出现多并发查询的情况是很少的,其实不怎么划算。而且Redis数据库的维护还要考虑。

  • 采用进程池分发请求
pool = multiprocessing.Pool(4)
for i in range(10):
    pool.apply_async(main)
pool.close()
pool.join()
  • 采用多线程处理
class ThreadReturn(threading.Thread):
    def __init__(self, func, args, name=''):
        threading.Thread.__init__(self)
        self.name = name
        self.func = func
        self.args = args
        self.result = self.func(*self.args)

    def get_result(self):
        try:
            return self.result
        except Exception as e:
            print(e)
            return None
            
for i in range(1):
    p1 = ThreadReturn(search_mysql, (i,))
    existing_list.append(p1)
for i in existing_list:
    i.start()
for sub in existing_list:
    sub.join()

你可能感兴趣的:(api)