内容简介:使用swoole 4.x协程版本后,系统资源利用率提高很多,与此同时发现很多开发习惯已经不同自从看了王晶老师(半桶水)在微信公众号php饭米粒 里发表的文章后忍不住手痒跟随教程认真轮了一个php框架 磨刀石项目,旨在深入了解协程,在试玩过程中碰到很多有意思的事情。今天感觉很经典分享一例设计如下:
使用swoole 4.x协程版本后,系统资源利用率提高很多,与此同时发现很多开发习惯已经不同
自从看了王晶老师(半桶水)在微信公众号 php 饭米粒 里发表的文章后忍不住手痒跟随教程认真轮了一个php框架 磨刀石项目,旨在深入了解协程,在试玩过程中碰到很多有意思的事情。今天感觉很经典分享一例
设计如下:
<?php namespace WhetStone\Stone\Driver; /** * 触发式连接池 * 空余连接数不够才会增长连接数 * 连接到达上限会阻塞等待指定秒数后抛异常 */ abstract class Pool { private $_pool = NULL; protected $_config = NULL; //整体最多连接数 private $_maxObjCount = 50; //正在被调用个数 private $_invokeObjCount = 0; ///////////////////////////////////////////////////// //获取对象,对象必须自带重连 /** * Fend_Pool constructor. * @param int $maxObjCount 最大连接数 * @param float $waitTimeout 连接池满等待超时时间 * @param array $config 数据连接配置 */ public function __construct($maxObjCount = 50, $waitTimeout = 3.0, $config = array()) { $this->_maxObjCount = $maxObjCount; $this->_waitDelay = $waitTimeout; $this->_config = $config; $this->_pool = new \Swoole\Coroutine\Channel($maxObjCount); //一次性创建好所有连接 //失败一个会异常抛出 //桶哥建议,好处是请求响应时间稳定 for ($count = 0; $count < $maxObjCount; $count++) { $obj = $this->getDriverObj(); $this->_pool->push($obj); } } //对象报错时会调用这个函数整理错误 /** * 对象调用操作 * @param string $name 动作名称 * @param array $arguments 参数 * @return mixed * @throws */ public function __call($name, $arguments) { $obj = null; try { $obj = $this->fetchObj(); $result = call_user_func_array(array( $obj, $name ), $arguments); $this->recycleObj($obj); return $result; } catch (\Exception $e) { $this->onError($obj, $e); throw $e; } } ///////////////////////////////////////////////////// private function fetchObj() { //pool is empty and have idle space if ($this->_pool->isEmpty() && ($this->_invokeObjCount + $this->_pool->length() < $this->_maxObjCount)) { $obj = $this->getDriverObj(); //这里有坑!!!!!后续讲解 $this->_invokeObjCount++; return $obj; } //channel have obj //fetch obj by 3 second wait $obj = $this->_pool->pop(3.0); if ($obj !== FALSE) { //increase count $this->_invokeObjCount++; return $obj; } //fetch fail max arrive throw new \Exception("fetch pool obj fail... please increase the connection pool size.", -123); } /** * 获取数据对象,return对象即可 * @return mixed */ abstract public function getDriverObj(); private function recycleObj($obj) { if (empty($obj)) { return; } //decrease count $this->_invokeObjCount--; return $this->_pool->push($obj); } abstract public function onError($obj, $e); }
从上图可以看到我做了一个父类,给各种驱动继承后回调方式传回不同对象,以此实现一个池管理,当有命令调用的时候直接通过__call方法将请求传递到obj内。
子类如果使用可以用如下方式使用
<?php namespace WhetStone\Stone\Driver\Redis; /** * 基础新版 Redis 驱动做的 * 新版本明确服务器不可回应时抛出异常 * * Class Redis * @package WhetStone\Stone\Driver\Redis */ class Redis { private $config = null; private $dbName = ""; private $redis = null; public function __construct($dbname, $config) { $this->dbName = $dbname; $this->config = $config; //do connect $this->reconnect(); } //if connection is broken reconnect public function checkConnection() { try { if ($this->redis->ping() != "+PONG") { $this->reconnect(); } } catch (\Exception $e) { $this->reconnect(); } } public function reconnect() { $this->redis = new \Redis(); //connect the server $ret = $this->redis->connect($this->config["host"], $this->config["port"], $this->config["timeout"] ?? 0); if (!$ret) { throw new \Exception("connect Redis Server fail:" . $this->redis->getLastError(), -24); } $this->redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_RETRY); $this->redis->setOption(\Redis::OPT_READ_TIMEOUT, -1); //prefix if (isset($this->config["prefix"]) && !empty($this->config["prefix"])) { $this->redis->setOption(\Redis::OPT_PREFIX, $this->config["prefix"]); } //auth if (isset($this->config["auth"]) && !empty($this->config["auth"])) { if ($this->redis->auth($this->config["auth"]) == FALSE) { throw new \Exception("Redis auth fail.dbname:" . $this->dbName, -23); } } //db if (isset($this->config["db"]) && !empty($this->config["db"])) { $this->redis->select($this->config["db"]); } } public function __call($name, $arguments) { //check is work well $this->checkConnection(); //do the cmd,如果刚检测完还报错,那。。。 return call_user_func_array(array($this->redis, $name), $arguments); } }
实际运行当中却碰到了一个奇怪的问题,我在pool中规定连接池对象只能有50个,但是使用ab压测-c 1000的时候发现实际会超过我规定的数量,并且请求出现卡死现象。
经过深入及swoole作者之一twose帮助,通过gdb发现代码卡在channel->push上
invoke count:236 pool len:0 invoke count:237 pool len:0 invoke count:238 pool len:0 invoke count:239 pool len:0 invoke count:240 pool len:0 invoke count:241 pool len:0 invoke count:242 pool len:0 invoke count:243 pool len:0 invoke count:244 pool len:0 invoke count:245 pool len:0 invoke count:246 pool len:0 invoke count:247 pool len:0 invoke count:248 pool len:0
反向推理后发现关键问题在这里:
private function fetchObj() { //pool is empty and have idle space if ($this->_pool->isEmpty() && ($this->_invokeObjCount + $this->_pool->length() < $this->_maxObjCount)) { try { $this->_invokeObjCount++; //此处是关键,一定要放在getDriverObj前面 $obj = $this->getDriverObj(); } catch (\Exception $e) { $this->_invokeObjCount--; throw $e; } return $obj; } //channel have obj //fetch obj by 3 second wait $obj = $this->_pool->pop(3.0); if ($obj !== FALSE) { //increase count $this->_invokeObjCount++; return $obj; } //fetch fail max arrive throw new \Exception("fetch pool obj fail... please increase the connection pool size.", -123); }
故障原因为:并发请求1000时,1000个请求会同时调用池获取对象,这时new redis及connect会自动进入协程,由于计数没有先添加,其他并发的请求拿到的已经分配的_invokeObjCount个数没有超过指定数量,所以导致了1000个redis对象被new及connect,最终导致channel超过了之前规定的50…
虽然swoole4.x的协程底层只有一个线程,但是一定要识别出哪里会自动协程……
这个连接池有很大缺点,刚才讨论后发现还需要改进,如pipline,事务会有问题,稍晚会继续介绍新版本
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 聊聊 OkHttp 实现 WebSocket 细节,包括鉴权和长连接保活及其原理
- 聊聊OkHttp实现WebSocket细节,包括鉴权和长连接保活及其原理!
- MQTT Essential 细节笔记总结(深入理解MQTT细节)
- MetInfo 7.0.0 20200326 细节优化补丁,主要优化商城相关细节
- MetInfo7.0.0 20200407 细节优化补丁,修复编辑及手机端细节
- php 的小细节
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Processing语言权威指南
Casey Reas、Ben Fry / 张静 / 电子工业出版社 / 2013-10-1 / 139.00
本书介绍了可视化艺术中的计算机编程概念,对开源编程语言Processing作了非常详尽的阐述。学生、艺术家、设计师、建筑师、研究者,以及任何想编程实现绘画、动画和互动的人都可以使用它。书中的大部分章节是短小的单元,介绍Processing的语法和基本概念(变量、函数、面向对象编程),涵盖与软件相关的图像处理、绘制,并且给出了大量简短的原型程序,配以相应的过程图像与注释。书中还有一些访谈文章,与动画......一起来看看 《Processing语言权威指南》 这本书的介绍吧!