最近在重构官网,为了支持SEO,整个项目的结构从采用AngularJs的单页面应用结构回归到PHP服务端渲染。问题来了:之前统一调用接口可以保证三端(APP,PC和移动web端)数据同步,现在如果是单独为PC端写一套业务逻辑,需要花费大量的时间精力,且后期维护可能比较困难(需要维护两处)。因此为了最大程度上保证数据同步,决定在通过服务器中转请求原始数据接口,然后渲染页面。经过筛选最终选定了Guzzle框架,随之而来的便是网络请求效率的问题~

这篇文章不是Guzzle的教程,虽然我也是看着文档刚折腾的。

1. 模拟环境

由于首页需要展示很多模块的数据,这意味着需要发送多个请求(大概估算了一下有18个接口)。在开发初期便发现首页打开速度非常慢,当时还以为只是接口效率的问题。后来才发现原因是:Guzzle的默认网络请求是串行的*,这意味着首页打开时间是大于等于等于所有接口请求消耗时间总和的,这是一件完全不能容忍的事情(你能忍受首页打开时间超过8秒?)。

1.1. 接口

下面是为了测试并解决这个问题所搭建的一个接口环境,采用的是node的restify框架。

let Restify = require('restify');

let plugins = require('restify-plugins');

let server = Restify.createServer();

server.use(plugins.acceptParser(server.acceptable));

server.use(plugins.authorizationParser());

server.use(plugins.queryParser());

server.use(plugins.bodyParser({mapParams: true}));

// 等待一段时间后发送响应,模拟接口耗时,根据需要可创建多个接口

server.get("test_2000",(req, res, next)=>{

setTimeout(function (){

res.send("hello after 2s");

}, 2000);

});

server.get("test_1000",(req, res, next)=>{

setTimeout(function (){

res.send("hello after 1s");

}, 1000);

});

// 请求路径 如 localhost:9999/test_100

server.listen(9999, function(){

console.log('%s listening at %s', server.name, server.url);

});

这里的test_1000接口会延迟1s响应,test_2000会延迟2s响应,由于均为本地环境,且接口没有什么复杂的逻辑操作,因此实际的网络请求耗时可估算为代码中指定的延迟时间。

1.2. 请求

接下来在PHP总进行测试,这里使用了Laravel,HTTPModel是对Guzzle进行的封装,后面我们会重写这个基类

namespace App\ApiModel;

use GuzzleHttp\Promise;

class TestModel extends HTTPModel{

public function test_2000(){

$url = "/test_2000";

$data = $this->get($url, []);

return $data;

}

public function test_1000(){

$url = "/test_1000";

$data = $this->get($url, []);

return $data;

}

}

这里是控制器,代码就不贴全了,根据前面的接口,我们估算控制器从接受到浏览器的请求,到返回响应大概需要3s的时间

public function index(Request $request){

$data1 = TestModel::getInstance()->test_1000();

$data2 = TestModel::getInstance()->test_2000();

echo TestModel::getCostTime();

dd($data1);

dd($data2);

}

实际输出为3042ms,刷新页面重复几次测试,尽管每次的结果并不是固定的,但也可以初步认为推断没有错误:整个页面的耗时是由于串行的请求接口造成的。

1.3. 小结

由于平常基本是写Node和前端的接口,在使用PHP的时候下意识的就以为都是异步接口,导致页面打开速度太慢。既然找到了原因,那么就该解决这个问题了。

2. Guzzle并发请求

实际上文档中已经提到了通过异步请求和Promise来实现并发请求,这个确实是文档的时候疏忽了(现在起码有二十多个控制器要修改~)

2.1. 并发请求

为TestModel增加一个测试并发请求的方法

public function test_concurrent(){

$url_1 = "/test_1000";

$url_2 = "/test_2000";

$promises = [

$this->getAsync($url_1, []),

$this->getAsync($url_2, []),

];

// 这里会将请求挂起并等待请求请求结果,对于长期使用异步的前端来讲可能有点陌生,理解成async 和 await即可

$results = Promise\unwrap($promises);

foreach ($results as $res){

$data[] = $this->getResult($res);

}

return $data;

}

同上在控制器中进行测试,理论上响应的时间是由耗时最长的那个接口所决定的,这里应该是2s

public function concurrent(){

$data3 = TestModel::getInstance()->test_concurrent();

echo TestModel::getCostTime();

dd($data3);

}

得到的结果是2044ms,与估算基本相同。当然这里没有考虑接口服务器限制最大并发请求数量的情况,这个后面再提。

2.2. 思考

从上面的两个测试可以看出,确实是由于串行的请求导致了响应时间过慢,而当前的解决办法就是将串行请求,比如由于业务逻辑的不同,页面上的某个数据需要请求多个接口,我们可以在Model中进行异步请求,最终合并结果并返回数据。

但是,考虑另外一个问题:一个页面可能需要调用多个接口,这些接口由不同的Model抽象,这意味着不同Model接口的调用仍旧是串行的,而我们最终需要的效果应当是将某个控制器方法中的所有请求都进行并发处理,这样才能最大程度上减少接口调用所带来的网络延迟。

也就是说,我们需要实现的是:先由不同的模型准备接口请求,然后再统一发起请求。下面我们尝试着对Guzzle进行封装并实现这个需求。

3. 封装Guzzle

虽然具体的方向已经确定了,但还有很多细节需要整理:

请求由不同的模型生成,最后统一发送,这里可以使用单例实现。

需要保证模型在控制器中的调用习惯(即同步代码),这是因为需要根据返回的数据处理部分业务逻辑,可以使用闭包和数据引用传递实现

3.1. 初步实现

首先实现请求基类

use GuzzleHttp\Client;

use GuzzleHttp\Cookie\CookieJar;

use GuzzleHttp\Promise;

class HTTPModel{

protected $hostname = "localhost:9999";

protected $client;

protected static $count = 0;

protected static $start_at;

// 用来保存异步请求

protected static $asyncRequest = [];

private static $instances = [];

// 实例化guzzle client对象

private function __construct(){

// 统计请求耗时

self::$start_at = self::getMicrTime();

$this->client = new Client([

'base_uri' => "http://{$this->hostname}/index.php",

'timeout' => 3.0,

]);

}

// 单例

public final static function getInstance(){

$name = get_called_class();

if (!isset(self::$instances[$name])) {

self::$instances[$name] = new static();

}

return self::$instances[$name];

}

// 异步请求

public function getAsync($url, $opts, $callback){

self::$count++;

$async = $this->client->getAsync($url, $opts);

// 这里只是简单处理将请求和回调函数关联在一起

$obj = new \stdClass();

$obj->request = $async;

$obj->callback = $callback;

// 将请求保存起来

self::$asyncRequest[] = $obj;

return $async;

}

// 统一发送请求

public function startAsync(){

$asyncRequest = self::$asyncRequest;

foreach ($asyncRequest as $request){

$promises[] = $request->request;

}

// 发送请求,等待响应

$results = Promise\unwrap($promises);

foreach ($results as $index=>$res){

$data = $this->getResult($res);

$callback = $asyncRequest[$index]->callback;

// 执行回调函数

$callback($data);

}

// 清空请求队列

self::$asyncRequest = [];

}

// 还有一些其他的工具方法,暂时不列出来了

}

然后我们还是定义一个TestModel,用于接口测试,由于在模型类中需要处理数据,因此在这里声明回调函数用于处理响应结果,其中$result是控制器中的引用变量,用于在控制器中获取结果

class TestModel extends HTTPModel{

public function test_2000_c(&$result){

$url = "/test_2000";

return $this->getAsync($url, [], function($data)use(&$result){

$result = $data;

});

}

public function test_1000_c(&$result){

$url = "/test_1000";

return $this->getAsync($url, [], function($data)use(&$result){

$result = $data;

});

}

}

最后在控制器中调用

class TestController extends Controller{

public function async(){

// 预先定义引用变量

$data['data1'] = [];

$data['data2'] = [];

// 生成请求

TestModel::getInstance()->test_1000_c($data['data1']);

TestModel::getInstance()->test_2000_c($data['data2']);

// 发送并发请求

TestModel::getInstance()->startAsync();

// 查看结果

// echo TestModel::getCostTime(); // 2026ms

// var_dump($data1); // hello after 1s

// var_dump($data2); // hello after 2s

// 输出数据

return view("test",$data);

}

}

这样貌似就达到了我们的目的,其中核心的思想是通过单例来维护所有模型需要调用的接口请求(这样就不用纠结于到底是哪个模型需要调用接口),然后手动触发网络请求。

很明显,这比串行调用接口的效率要高很多,尤其是需要调用很多接口的页面(比如首页)。而在控制器中,对数据的处理还基本维持了串行调用的同步习惯(只是需要手动调用startAsync而已)。

4. 总结

我对PHP并不是很了解,实际上这只是针对于三端数据统一所做的尝试,使用服务器中转调用第三方接口肯定会影响效率,后面这里的接口可能会被重构掉。事实上我当我把之前的串行代码替换之后,提高的速度也并不是那么明显,貌似真正的性能瓶颈在数据库那里,这完完全全是后端的事儿了~

php guzzle并发,使用Guzzle并发请求接口相关推荐

  1. PHP guzzle异步请求数据,怎么在PHP中使用Guzzle执行POST和GET请求

    怎么在PHP中使用Guzzle执行POST和GET请求 发布时间:2021-02-17 08:01:14 来源:亿速云 阅读:67 作者:Leah 怎么在PHP中使用Guzzle执行POST和GET请 ...

  2. PHP项目中使用Guzzle执行POST和GET请求

    以往在项目中要用到第三方接口时会用到封装好的curl执行请求,现在有了更好的解决方案--Guzzle. 下面是官方介绍: Guzzle是一个PHP的HTTP客户端,用来轻而易举地发送请求,并集成到我们 ...

  3. php:项目中使用Guzzle执行POST和GET请求

    以往在项目中要用到第三方接口时会用到封装好的curl执行请求,现在有了更好的解决方案--Guzzle. 下面是官方介绍: Guzzle是一个PHP的HTTP客户端,用来轻而易举地发送请求,并集成到我们 ...

  4. Jmeter 并发测试下让登录接口只执行一次

    1.创建一个线程组(Thread Group) 设置20个并发如图: 2.添加一个组件吞[吐量控制器](Throughput Contrller),选择总数计算(Total Executions) 3 ...

  5. java web 大并发服务器_计算-服务器最大并发量-http协议请求-以webSphere服务器为例-考虑线程池...

    请求的处理流程 广域网上有大量的并发用户同时访问web服务器,web服务器传递请求给应用服务器(web容器),web容器传递请求给ejb容器,然后ejb容器发送数据库连接请求给数据库. 请求的处理流程 ...

  6. 【高并发】高并发分布式锁架构解密,不是所有的锁都是分布式锁!!

    来自:冰河技术 写在前面 最近,很多小伙伴留言说,在学习高并发编程时,不太明白分布式锁是用来解决什么问题的,还有不少小伙伴甚至连分布式锁是什么都不太明白.明明在生产环境上使用了自己开发的分布式锁,为什 ...

  7. ajax调用接口很慢,nodejs 请求接口在高并发下耗时很大,而单个请求非常快

    情况: request.js 库请求接口, express.js 做 server ,实现了 curl http://localhost:8080/proxy-api 本地一个地址,在 router ...

  8. axios请求接口http_使用axios请求接口,几种content-type的区别详解

    axios的使用 安装(一般使用框架的话, 脚手架都集成了) $ npm install axios 请求示例 // POST axios.post('/user', { firstName: 'Fr ...

  9. 网站高并发及高并发架构详解

    高并发是指在同一个时间点,有很多用户同时的访问URL地址,比如:淘宝的双11,双12,就会产生高并发,如贴吧的爆吧,就是恶意的高并发请求,也就是DDOS攻击,再屌丝点的说法就像玩撸啊撸被ADC暴击了一 ...

  10. java设计模式并发_[高并发Java 七] 并发设计模式

    [高并发Java 七] 并发设计模式 [高并发Java 七] 并发设计模式 为什么80%的码农都做不了架构师?>>> 在软件工程中,设计模式(design pattern)是对软件设 ...

最新文章

  1. Linux ext3grep 恢复数据
  2. Echart..js插件渲染报错 data.length1?
  3. 理论塔板数 matlab,matlab作图法计算精馏理论板数
  4. android package.xml,Android自动化编译设置AndroidManifest.xml中package值(包名)
  5. 7-95 倒数第N个字符串 (15 分)
  6. Ubuntu 16.04 更新源
  7. 安卓应用安全指南 5.6.2 密码学 规则书
  8. springboot+activiti工作流mybatis冲突解决办法
  9. tomcat下面的starup.bat的作用
  10. DS18B20驱动编程
  11. 主成分分析碎石图_主成分分析大全
  12. 马斯克的“星链”会不会威胁中国太空安全?肯定会!
  13. 【FlinkX】两个issue分析:reader和writer的通道数不一致+获取JobId
  14. 菜鸟、小白在autojs和冰狐智能辅助之间如何选择?
  15. 海外网红营销是战略还是战术?从“PDCA循环”层面规划营销
  16. 在MySQL中group by 是什么意思
  17. 练习-Java类和对象之对象组合之求圆锥体表面积
  18. Pytorch深度学习(五):加载数据集以及mini-batch的使用
  19. 拼音读数字(难度系数:1颗星)
  20. android批量上传图片(模仿QQ空间和微信发表说说)

热门文章

  1. 关于find_busiest_group函数提现出的Linux性能问题
  2. 百度地图LV1.5实践项目开发工具类bmap.util.jsV1.3
  3. 东南亚跨境电商ERP怎么选?萌店长ERP,含大数据分析的免费erp系统
  4. 做产品也要造概念,讲故事,用优雅的措辞美化自己
  5. Requirement diagram
  6. 巧用Scrum与Kanban
  7. 硬件钱包 Ledger使用教程
  8. vue项目为什么选择svg图标
  9. Linux系统自动更新时间命令的详细说明
  10. 【DG】基于同一个主机建立物理备库和逻辑备库 (三)