Nginx Server 與 WorkerMan Server For PHP 壓測報告
測試要求:
1.相同環(huán)境 (linux debian12)
2.相同配置 (4c 8v 40G)
3.相同輸出內(nèi)容 ({"code":200,"msg":"\u6210\u529f","data":
[{"id":1,"name":"cichenman","age":26,"tip":"\u5185\u5b58\u6570\u636e"}]})
4.內(nèi)網(wǎng)測試 (本地 127.0.0.1)
5.AB 壓測 (參數(shù):50W 請求 1000 并發(fā) 開啟 Keep-Alive)
測試模式:
具體代碼貼在了評論區(qū)
均采用:4 個進程,8G 內(nèi)存,千兆內(nèi)網(wǎng)環(huán)境測試,測試數(shù)據(jù)僅供參考,每種測試模式測試 10 次
可能是我本地環(huán)境問題
1.我win電腦開的vm虛擬機24核心
2.電腦后臺掛了很多軟件
3.虛擬機安裝了寶塔,nginx,mysql
4.ab測試也是在虛擬機內(nèi)部直接壓測
5.實際壓測程序內(nèi)部結(jié)構(gòu)很復(fù)雜,寫了啟動類,緩存類,數(shù)據(jù)庫類,并都啟動實例化了,
6.輸出形式可能不同,ng是直接返回一個靜態(tài)文本,php是echo,workerman是調(diào)用各種安全檢測判斷后輸出
感覺問題在我自己,并不是程序問題,應(yīng)該用一個純凈linux機器安裝我寫的腳本,并把無用的實例注釋掉。
然后用另一臺電腦測試,這樣性能應(yīng)該上去了
Benchmarking 127.0.0.1 (be patient)
Completed 100000 requests
Completed 200000 requests
Completed 300000 requests
Completed 400000 requests
Completed 500000 requests
Completed 600000 requests
Completed 700000 requests
Completed 800000 requests
Completed 900000 requests
Completed 1000000 requests
Finished 1000000 requests
Server Software: workerman
Server Hostname: 127.0.0.1
Server Port: 80
Document Path: /
Document Length: 10 bytes
Concurrency Level: 1000
Time taken for tests: 10.324 seconds
Complete requests: 1000000
Failed requests: 0
Keep-Alive requests: 1000000
Total transferred: 171000000 bytes
HTML transferred: 10000000 bytes
Requests per second: 96861.28 [#/sec] (mean)
Time per request: 10.324 [ms] (mean)
Time per request: 0.010 [ms] (mean, across all concurrent requests)
Transfer rate: 16175.08 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.6 0 27
Processing: 0 10 5.9 9 53
Waiting: 0 10 5.9 9 53
Total: 0 10 5.9 10 53
Percentage of the requests served within a certain time (ms)
50% 10
66% 12
75% 13
80% 14
90% 17
95% 21
98% 26
99% 30
100% 53 (longest request)
重新在天翼云的2核心業(yè)務(wù)服務(wù)器上進行了測試
環(huán)境2C 4G
安裝軟件:由于是業(yè)務(wù)服務(wù)器安裝了MySQL,bt面板,等
壓測參數(shù):ab -n 500000 -c 1000 -k http://127.0.0.1/
run.php
<?php
use Workerman\Config\StartUp;
use Workerman\Lib\FileMonitor\FileMonitorstart;
require_once __DIR__ . '/../vendor/autoload.php';
//new FileMonitorstart();
startUp::$ip = "http://0.0.0.0";
startUp::$port = 80;
startUp::$count = 2;
startUp::$name = "http";
startUp::run();
startUp.php
<?php
namespace Workerman\Config;
use Workerman\Worker;
use Workerman\Lib\MemoryCache\McServer;
use Workerman\Connection\TcpConnection;
use Workerman\Timer;
class StartUp {
public static $ip;
public static $port;
public static $count;
public static $name;
// 構(gòu)造函數(shù),可進行一些默認參數(shù)賦值等初始化操作
public function __construct() {
}
// 實際運行服務(wù)的私有實例方法
public static function run() {
$MC = new McServer("0.0.0.0",2207);
// 創(chuàng)建一個Worker監(jiān)聽指定端口,使用http協(xié)議通訊
$worker = new Worker(self::$ip. ":". self::$port);
// 啟動指定數(shù)量的進程對外提供服務(wù)
$worker->count = self::$count;
// 進程名字
$worker->name = self::$name;
// 調(diào)用類的靜態(tài)方法。
$worker->onWorkerStart = array('\Workerman\Config\Main', 'onWorkerStart');
$worker->onConnect = array('\Workerman\Config\Main', 'onConnect');
$worker->onMessage = array('\Workerman\Config\Main', 'onMessage');
$worker->onClose = array('\Workerman\Config\Main', 'onClose');
$worker->onWorkerStop = array('\Workerman\Config\Main', 'onWorkerStop');
//啟動
Worker::runAll();
}
}
Main.php
<?php
namespace Workerman\Config;
use Workerman\Protocols\Http\Response;
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
use Workerman\Lib\Sql\Pdo;
use Workerman\Config\ConfigGlobal\Config;
use Workerman\Lib\MemoryCache\McClient;
use Workerman\Lib\MemoryCache\MCLIB;
use Workerman\Lib\Data\TypeConversion;
use Workerman\Config\Route;
class Main {
public function __construct() {
}
//子進程調(diào)用PDO實例化
public static function PDO() {
# 實例化數(shù)據(jù)庫類
try {
return new Pdo(Config::Paramete("MySql")->Host,Config::Paramete("MySql")->User,Config::Paramete("MySql")->Pwd,Config::Paramete("MySql")->DB,50);
}
catch(\Exception $e) {
return ($e->getMessage());
}
}
private static function xlh_json($data) {
return new Response(200, [
'Content-Type' => 'application/json',
'Content-Encoding'=>'gzip'
], gzencode(json_encode($data),6));
}
private static function xlh_plain($data) {
return new Response(200, [
'Content-Type' => $data['type'],
'Content-Encoding'=>'gzip',
'Cache-Control'=>'max-age=86400'
],$data['body']);
}
private static function xlh_httperror($data) {
return new Response($data['code'], [
'Content-Encoding'=>'gzip'
], gzencode($data['msg'],6));
}
//子進程調(diào)用MC客戶端實例化 - 鏈接MC服務(wù)端
public static function Mclient() {
return new McClient('0.0.0.0:2207');
}
//子進程內(nèi)部獨立的緩存器
public static function Mc() {
return new MCLIB();
}
public static function onWorkerStart(Worker $worker) {
if (function_exists('opcache_reset')) {
opcache_reset();
}
//加載路由表
require_once(__DIR__.'/RouteConfig.php');
global $PDO,$McClient,$Mc;
$PDO = self::PDO();
$McClient = self::Mclient();
$Mc = self::Mc();
}
public static function onConnect(TcpConnection $connection) {
//設(shè)置當前鏈接緩沖區(qū)大小
$connection->maxSendBufferSize = 5*1024*1024;
}
public static function onMessage(TcpConnection $connection, $request) {
global $PDO,$McClient,$Mc;
$connection->PDO = $PDO;
$connection->McClient = $McClient;
$connection->Mc = $Mc;
$get = $request->get();
$post = $request->post();
$headers = $request->header();
$cookies = $request->cookie();
$files = $request->file();
$host = $request->host(true);
$method = $request->method();
$uri = $request->uri();
$path = $request->path();
$query_string = $request->queryString();
$version = $request->protocolVersion();
$sid = $request->sessionId();
$data = [
"GET" => $get,
"POST" => $post,
"HEADERS" => $headers,
"COOKIES" => $cookies,
"FILES" => $files,
"HOST" => $host,
"METHOD" => $method,
"URL" => $uri,
"PATH" => $path,
"query_string" => $query_string,
"version" => $version,
"session" => $sid
];
$connection->data = TypeConversion::StdClass($data);
$d = Route::Verify($connection);
//處理直接返回文本字符串的情況
if(is_string($d)) {
$d = [
"type"=>"text/html",
"body"=>gzencode($d,6)
];
}
//處理httperror情況
if(isset($d['httperror'])) {
return $connection->send(self::xlh_httperror($d['httperror']));
}
//處理返回接口json但未聲明情況
if(!isset($d['type']) || empty($d['type'])) {
return $connection->send(self::xlh_json($d));
}
//處理靜態(tài)方式返回情況
$connection->send(self::xlh_plain($d));
// $connection->close();
}
public static function onClose(TcpConnection $connection) {
}
public static function onWorkerStop(Worker $worker) {
}
}
Route.php
<?php
namespace Workerman\Config;
use Workerman\Config\RouteConfig;
use Workerman\Lib\FileTypeDetection\FileTypeDetection;
class Route {
//路由加載狀態(tài)
private static $load = false;
//是否加載默認路由
private static $autoroute = true;
//路由表
private static $routes = [
// //指定具體方法路由
"/" => ["App\Controllers\Defaults","index"],
];
public function __construct() {}
//設(shè)置是否加載默認路由
public static function SetAutoRoute($status) {
self::$autoroute = $status;
}
//安全站點
private static $WebHost = [];
//校驗是否符合通行后綴
private static function IsProhibitSuffix($name) {
//禁止訪問的文件后綴
$ProhibitSuffix = [
"php",
"git",
];
$url = $name;
$path = parse_url($url, PHP_URL_PATH);
$pathInfo = pathinfo($path);
$extension = isset($pathInfo['extension'])?$pathInfo['extension']:"plain";
if(in_array($extension,$ProhibitSuffix)) {
return true;
}
return $extension;
}
//獲取文件實際地址完整的
private static function StaticFile($path) {
if($path === "/") {
return __DIR__ ."/../../../StaticFiles".$path."index.html";
}
return __DIR__ ."/../../../StaticFiles".$path;
}
//錯誤定義
public static function CODEERROR(int $code,$msg="<center><h3 style='color:#f30;'>訪問的資源不存在</h3><hr/>Workerman</center>") {
$data = is_string($msg)?$msg:json_encode($msg);
return [
'httperror'=>[
'code'=>$code,
'msg' =>$msg
]
];
}
//以靜態(tài)資源形式訪問
public static function StaticFiles($connection) {
$path = self::StaticFile($connection->data->PATH);
//校驗請求文件合法性
$anquan = self::IsProhibitSuffix($path);
if($anquan===true) {
return self::CODEERROR(403,"安全防火墻:".$connection->data->PATH." - 禁止訪問 - 命中攔截規(guī)則".PHP_EOL);
}
//校驗文件存在否
if (file_exists($path)) {
$d = $connection->Mc->Get($path);
if(!$d) {
$d = [
"type"=> FileTypeDetection::GetMIME($anquan),
"body"=>gzencode(file_get_contents($path), 6)
];
$connection->Mc->Add($path,$d,time()+864000);
}
return $d;
} else {
return self::CODEERROR(404);
}
}
//添加路由
public static function AddRoute($path="",$array=[]) {
if(!self::$load && self::$autoroute) {
self::$load = true;
}
self::$routes[$path] = $array;
}
//添加安全站點
public static function AddWebHost(Array $data=[]){
if(count($data)>0){
foreach ($data as $value) {
// code...
self::$WebHost[] = $value;
}
}
}
//校驗安全站點
public static function IsWebHost(String $host){
if(in_array($host,self::$WebHost)){
return true;
}
return false;
}
# 校驗路由規(guī)則
public static function Verify($connection) {
if(!self::IsWebHost($connection->data->HOST)){
return self::CODEERROR(500,"<center><h3 style='color:#f30;'>未知站點</h3><hr/>Workerman</center>");
}
//請求路徑
$path = $connection->data->PATH;
// echo "請求路徑".$path.PHP_EOL;
$parts = explode('/', $path);
$count = count($parts);
//獲取最終的動作名
$name = $parts[$count-1];
//還原路由鍵值結(jié)構(gòu)
$routekey = "";
foreach ($parts as $index => $value) {
if($count>2) {
// code...
if($value) {
if($index == $count-1) {
$routekey.="/{name}";
} else {
$routekey.="/".$value;
}
}
} else {
// code...
if($value) {
$routekey.="/".$value;
}
}
}
if(!$routekey) {
$routekey = "/";
}
if(!isset(self::$routes[$routekey])) {
$routekey = $path;
}
if(isset(self::$routes[$routekey])) {
//路由存在
$gz = self::$routes[$routekey];
// code...
$stdclass = new $gz[0]();
$methodName = isset($gz[1])?$gz[1]:$name;
if (method_exists($stdclass, $methodName)) {
return $stdclass->$methodName($connection);
//動作存在
} else {
return self::CODEERROR(404);
}
} else {
//路由不存在
//嘗試以靜態(tài)文件形式訪問
return self::StaticFiles($connection);
}
}
}
RouteConfig.php
<?php
use Workerman\Config\Route;
//添加安全站點,只有安全站點可以通過訪問
Route::AddWebHost(["www.wlot.top"]);
//默認路由功能
Route::SetAutoRoute(true);
//添加業(yè)務(wù)路由
Route::AddRoute("/testurl",["App\Controllers\Home","userdata"]);
Route::AddRoute("/testurl/{name}",["App\Controllers\Home"]);
Route::AddRoute("/testurl/v1/userdata",["App\Controllers\Home","userdata"]);
Route::AddRoute("/testurl/v1/userdata/{name}",["App\Controllers\Home"]);
Home.php
<?php
namespace App\Controllers;
use Workerman\Protocols\Http\Response;
use Workerman\Lib\Console\Log;
class Home {
public function __construct() {
}
//開始編寫業(yè)務(wù)代碼
public function userdata($connection) {
return [
"code"=>200,
"msg"=>"OK",
"data"=>[
"nickname"=>"CiChenMan"
],
"time"=>time(),
"other"=>$connection->data
];
}
public function userdatav1($connection) {
return 123;
}
public function test($connection) {
$sql = md5("select * from user;");
$d = $connection->McClient->Get($sql);
if(!$d) {
# 獲取用戶信息
$d = $connection->PDO->selects( "select * from user;");
$connection->McClient->Add($sql,$d,time()+864000);
}
return $d;
}
}
FileTypeDetection.php
<?php
namespace Workerman\Lib\FileTypeDetection;
class FileTypeDetection{
// 初始化
public function __construct(){}
//常見后綴
private static $MIME = [
// 文本文件類型
'html' => 'text/html',
'htm' => 'text/html',
'css' => 'text/css',
'js' => 'application/javascript',
'txt' => 'text/plain',
'xml' => 'application/xml',
// 圖像文件類型
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'svg' => 'image/svg+xml',
// 音頻文件類型
'mp3' => 'audio/mpeg',
'wav' => 'audio/wav',
'ogg' => 'audio/ogg',
// 視頻文件類型
'mp4' => 'video/mp4',
'avi' => 'video/x-msvideo',
'flv' => 'video/x-flv',
'webm' => 'video/webm',
// 腳本文件類型
'php' => 'application/x-httpd-php',
'py' => 'text/x-python',
'rb' => 'application/x-ruby',
// 壓縮文件類型
'zip' => 'application/zip',
'rar' => 'application/x-rar-compressed',
'gz' => 'application/gzip',
// 其他文件類型
'pdf' => 'application/pdf',
'json' => 'application/json',
'ico' => 'image/x-icon',
// 默認未知的
'plain' => 'text/plain'
];
//獲取后綴
public static function GetMIME($name='plain'){
return self::$MIME[$name];
}
}
PDO.php
<?php
namespace Workerman\Lib\Sql;
use Workerman\Timer;
use Workerman\Lib\Console\Log;
// 垃圾的封裝,優(yōu)化空間很大,記得優(yōu)化
class Pdo {
private $pdo;
#賬號
private $user;
#密碼
private $password;
#主機
private $host;
#數(shù)據(jù)庫
private $db;
#心跳
private $m;
#日志
public $callback = array();
#構(gòu)造參數(shù)——初始化使用
public function __construct(string $host="",string $user="",string $password="",string $db="",$m=5) {
$this -> user = $user;
$this -> password = $password;
$this -> host = $host;
$this -> db = $db;
$this -> m = $m;
try {
$this->pdo = new \PDO("mysql:host=".$this->host.";dbname=".$this->db, $this->user, $this->password,array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION));
$this->pdo->exec('SET NAMES utf8mb4');
if(class_exists('\Workerman\Timer')) {
$this->Timer();
}
}
catch(\PDOException $e) {
throw new \Exception(Log::log(300));
}
;
}
//常駐內(nèi)存時?;? private function Timer() {
// 定時任務(wù)
Timer::add($this->m, function() {
$this->selects("show tables");
}
);
}
### 進階sql操作 ###
### sql語句模板預(yù)處理 ###
### 更謹慎的封裝,調(diào)用單個或多個方法 ###
### 實現(xiàn)便捷的數(shù)據(jù)管理 ###
### 支持增刪改查,事務(wù),提交,回滾 ###
### 添加一個隊列方式,將來可能會用 ###
### 多語句查詢事務(wù)封裝 ###
### 根據(jù)業(yè)務(wù)需求自行修改庫 ###
//1.預(yù)處理查詢數(shù)據(jù) 數(shù)據(jù)為空返回[]數(shù)組,有返回[...]
public function selects(string $sql,Array $arr = array()) {
try {
$sth = $this->pdo->prepare($sql);
$sth->execute($arr);
$a = $sth->fetchAll(\PDO::FETCH_ASSOC);
$arr = array();
foreach ($a as $index => $val) {
$arr[$index] = $val;
}
;
return $arr;
}
catch(\PDOException $e) {
throw new \Exception(Log::log(301));
}
;
}
//2.預(yù)處理增加數(shù)據(jù) 增加成功返回true 否 false(false時:可能修改的數(shù)據(jù)玉源數(shù)據(jù)相同,或者元數(shù)據(jù)不存在)
public function insets(string $sql,Array $arr = array()) {
try {
$sth = $this->pdo->prepare($sql);
$sth->execute($arr);
$a = $sth->rowCount();
$newid = $this->pdo->lastInsertId();
return $newid;
}
catch(\PDOException $e) {
// print_r($e);
throw new \Exception(Log::log(301));
}
;
}
//3.預(yù)處理刪除數(shù)據(jù) 成功成功返回true 否false (false時可能元數(shù)據(jù)不存在或者sql有問題)
public function deletes(string $sql,Array $arr = array()) {
try {
$sth = $this->pdo->prepare($sql);
$sth->execute($arr);
$a = $sth->rowCount();
return $a;
}
catch(\PDOException $e) {
throw new \Exception(Log::log(301));
}
;
}
//4.預(yù)處理修改數(shù)據(jù) 成功成功返回true 否false (false時可能元數(shù)據(jù)不存在或者sql有問題)
public function updates(string $sql,Array $arr = array()) {
try {
$sth = $this->pdo->prepare($sql);
$sth->execute($arr);
return $sth->rowCount();
}
catch(\PDOException $e) {
print_r($e);
throw new \Exception(Log::log(301));
}
;
}
### 事務(wù)相關(guān) ###
//1.接受一個多預(yù)處理模板事務(wù)數(shù)據(jù)集 全部執(zhí)行成功返回true 狗則 false
public function start_begin(Array $object) {
try {
/* 開始一個事務(wù),關(guān)閉自動提交 */
$this->pdo->beginTransaction();
//設(shè)置一個記錄日志
$log = [];
//循環(huán)遍歷處理預(yù)處理數(shù)據(jù)
foreach ($object as $index => $sql) {
$sth = $this->pdo->prepare($sql['sql']);
$sth->execute($sql['arr']);
//將每次預(yù)處理的結(jié)果(受到影響的行數(shù))記錄到log中,方便查詢具體運行日志
$log[$index] = $sth->rowCount();
}
;
//提交
$this->pdo->commit();
return $log;
}
catch(\PDOException $e) {
//file_put_contents("sql",$e);
//回滾
$this->pdo->rollBack();
throw new \Exception(Log::log(303));
}
;
}
//開啟事務(wù)
public function beginTransaction() {
$this->pdo->beginTransaction();
}
//執(zhí)行提交
public function commit() {
$this->pdo->commit();
}
//回滾
public function rollBack() {
$this->pdo->rollBack();
}
}
MCLIB.php
<?php
//
// 基于workerman 的常駐內(nèi)存緩存器
//
//
//
namespace Workerman\Lib\MemoryCache;
use Workerman\Timer;
class MCLIB {
// 基本配置參數(shù)信息
private $info = [
"name" => "MC內(nèi)存緩存器",
"describe" => "一個基于 workerman 的常駐內(nèi)存緩存器,快速的在多進程間訪問全局共享數(shù)據(jù)!",
"version" => "1.0.0",
"author" => "CiChenMan",
"email" => "1570442495@qq.com",
"wechat" => "cichenman",
"tel" => "18265556279"
];
public function GetInfo() {
return $this->info;
}
// 默認增加緩存時間 例如查詢到緩存后為了更持久的使用,自動增加的變量時間
private static $AutoTime = 864000;
// 存儲塊大小信息配置
private $StorageBlockSize = [
// 最大 1G 的內(nèi)存存儲塊,超出后會自動清理一些訪問較少的存儲塊,數(shù)據(jù)很大時/內(nèi)存寬裕時可以適當調(diào)整
"MaxSize" => 1 * (1024*1024*1024*1024),
// 是否開啟自動刪除多余內(nèi)存模式
"AutoDel" => false,
];
// 存儲塊索引
private $StorageBlockIndex = [
//存儲條目數(shù)量
"DataLen" => 0,
//存儲占用大小(總量)
"DataSize"=> 0,
//存儲索引基本信息 數(shù)組數(shù)據(jù)對應(yīng)索引
"DataList"=> [
"Key" => [
// "test"
],
"Value" => [
// "test" => [
// "Visits"=>122,
// "Index"=>0,
// //大小
// "Size"=>567,
// //過期時間
// "FailureTime"=> 1701062400
// ]
]
]
];
// 存儲塊
private $StorageBlock = [];
// 初始化
public function __construct($AutoDel=false) {
$this->StorageBlockSize['AutoDel'] = $AutoDel;
if($this->StorageBlockSize['AutoDel']) {
Timer::add(30, function() {
$this->AutoDel();
}
);
}
}
//大小轉(zhuǎn)換
private function formatSize($sizeInBytes) {
$units = array('B', 'KB', 'MB', 'GB', 'TB');
$index = 0;
while ($sizeInBytes >= 1024 && $index < count($units) - 1) {
$sizeInBytes /= 1024;
$index++;
}
return round($sizeInBytes, 2). $units[$index];
}
// 清理一些訪問較少的存儲塊
public function AutoDel() {
$visitsArray = [];
$dataList = $this->StorageBlockIndex['DataList']['Value'];
foreach ($dataList as $key => $value) {
$visitsArray[$key] = $value['Visits'];
}
array_multisort($visitsArray, SORT_DESC, $dataList);
//echo "-------------------------------------------";
// print_r($dataList);
$d = [
"緩存條數(shù)" => $this->StorageBlockIndex['DataLen'],
"緩存占用內(nèi)存" => $this->formatSize($this->StorageBlockIndex['DataSize']),
];
// print_r($d);
}
// 增加新數(shù)據(jù)
// $Key 索引
// $Value 數(shù)據(jù)
// $Time 過期時間
public function Add(string $Key, $Value,int $Time = 0) {
if(!$Key || !$Value) {
return [
"Status" => "FAILL",
"MessAge" => "請檢查存儲數(shù)據(jù)時傳入的參數(shù)是否正確。"
];
}
;
if(isset($this->StorageBlockIndex['DataList']['Value'][$Key])) {
return [
"Status" => "FAILL",
"MessAge" => "添加數(shù)據(jù)的索引在MC中已經(jīng)存在,無需重復(fù)添加。"
];
}
//獲取字節(jié)大小
$strlen = strlen(serialize($Value));
if($this->StorageBlockIndex['DataSize'] + $strlen > $this->StorageBlockSize['MaxSize']) {
return [
"Status" => "FAILL",
"MessAge" => "累計存儲內(nèi)容超過設(shè)置最大限制,無法存入緩存。"
];
}
//寫入數(shù)據(jù)
$this->StorageBlockIndex['DataSize'] = $this->StorageBlockIndex['DataSize'] + $strlen;
++$this->StorageBlockIndex['DataLen'];
$this->StorageBlockIndex['DataList']['Key'][] = $Key;
$this->StorageBlockIndex['DataList']['Value'][$Key] = [
"Visits"=>0,
//下標
"Index"=>count($this->StorageBlockIndex['DataList']['Key']) - 1,
//大小
"Size"=>$strlen,
//過期時間
"FailureTime"=> $Time
];
$this->StorageBlock[$Key] = serialize($Value);
return true;
}
// 刪除數(shù)據(jù)
// $Key 索引
public function Del(string $Key) {
if(!$Key) {
return [
"Status" => "FAILL",
"MessAge" => "請檢查存儲數(shù)據(jù)時傳入的參數(shù)是否正確。"
];
}
;
//寫入數(shù)據(jù)
$this->StorageBlockIndex['DataSize'] = $this->StorageBlockIndex['DataSize'] - $this->StorageBlockIndex['DataList']['Value'][$Key]['Size'];
--$this->StorageBlockIndex['DataLen'];
$index = $this->StorageBlockIndex['DataList']['Value'][$Key]['Index'];
unset($this->StorageBlockIndex['DataList']['Key'][$Key][$index]);
unset($this->StorageBlockIndex['DataList']['Value'][$Key]);
unset($this->StorageBlock[$Key]);
return true;
}
//修改數(shù)據(jù)
// $Key 索引
// $Value 數(shù)據(jù)
// $Time 過期時間
public function UpData(string $Key, $Value,int $Time = 0) {
if(!$Key || !$Value) {
return [
"Status" => "FAILL",
"MessAge" => "請檢查存儲數(shù)據(jù)時傳入的參數(shù)是否正確。"
];
}
;
//獲取新數(shù)據(jù)字節(jié)大小
$newstrlen = strlen(serialize($Value));
//獲取舊數(shù)據(jù)字節(jié)大小
if(!isset($this->StorageBlockIndex['DataList']['Value'][$Key])) {
return [
"Status" => "FAILL",
"MessAge" => "傳入修改數(shù)據(jù)的索引在MC中不存在,請檢查。"
];
}
$oldstrlen = $this->StorageBlockIndex['DataList']['Value'][$Key]['Size'];
if($this->StorageBlockIndex['DataSize'] - $oldstrlen + $newstrlen > $this->StorageBlockSize['MaxSize']) {
return [
"Status" => "FAILL",
"MessAge" => "修改數(shù)據(jù)時,累計存儲內(nèi)容超過設(shè)置最大限制,無法存入緩存,修改失敗。"
];
}
$this->StorageBlockIndex['DataSize'] = $this->StorageBlockIndex['DataSize'] - $oldstrlen + $newstrlen;
$this->StorageBlockIndex['DataList']['Value'][$Key]['Size'] = $newstrlen;
$this->StorageBlockIndex['DataList']['Value'][$Key]['FailureTime'] = $Time;
$this->StorageBlock[$Key] = serialize($Value);
return true;
}
//獲取數(shù)據(jù)
public function Get(string $Key,$AutoData = "") {
if(!isset($this->StorageBlockIndex['DataList']['Value'][$Key])) {
if($AutoData) {
return $AutoData;
} else {
return null;
}
}
//如果過期了就刪除
if($this->StorageBlockIndex['DataList']['Value'][$Key]['FailureTime']-time()<0) {
//echo "過期刪除";
$this->Del($Key);
return $AutoData;
}
$t = $this->StorageBlockIndex['DataList']['Value'][$Key]['FailureTime'];
if($t - self::$AutoTime < self::$AutoTime) {
$this->StorageBlockIndex['DataList']['Value'][$Key]['FailureTime'] = $t + self::$AutoTime;
}
++$this->StorageBlockIndex['DataList']['Value'][$Key]['Visits'];
return unserialize($this->StorageBlock[$Key]) ;
// return $this->StorageBlockIndex['DataList']['Value'][$Key]['Visits'];
}
}
McClient.php - 基于官方的共享組件客戶端
<?php
namespace Workerman\Lib\MemoryCache;
/**
* Global data client.
* @version 1.0.3
*/
class McClient {
/**
* Timeout.
* @var int
*/
public $timeout = 5;
/**
* Heartbeat interval.
* @var int
*/
public $pingInterval = 25;
/**
* Global data server address.
* @var array
*/
protected $_globalServers = array();
/**
* Connection to global server.
* @var resource
*/
protected $_globalConnections = null;
/**
* Cache.
* @var array
*/
protected $_cache = array();
/**
* Construct.
* @param array | string $servers
*/
public function __construct($servers) {
if(empty($servers)) {
throw new \Exception('servers empty');
}
$this->_globalServers = array_values((array)$servers);
}
/**
* Connect to global server.
* @throws \Exception
*/
protected function getConnection($key) {
$offset = crc32($key)%count($this->_globalServers);
if($offset < 0) {
$offset = -$offset;
}
if(!isset($this->_globalConnections[$offset]) || !is_resource($this->_globalConnections[$offset]) || feof($this->_globalConnections[$offset])) {
$connection = stream_socket_client("tcp://{$this->_globalServers[$offset]}", $code, $msg, $this->timeout);
if(!$connection) {
throw new \Exception($msg);
}
stream_set_timeout($connection, $this->timeout);
if(class_exists('\Workerman\Timer') && php_sapi_name() === 'cli') {
$timer_id = \Workerman\Timer::add($this->pingInterval, function($connection)use(&$timer_id) {
$buffer = pack('N', 8)."ping";
if(strlen($buffer) !== @fwrite($connection, $buffer)) {
@fclose($connection);
\Workerman\Timer::del($timer_id);
}
}
, array($connection));
}
$this->_globalConnections[$offset] = $connection;
}
return $this->_globalConnections[$offset];
}
/**
* Add.
* @param string $key
* @throws \Exception
*/
// public function add($key)
// {
// $connection = $this->getConnection($key);
// $this->writeToRemote(array(
// 'cmd' => 'add',
// 'key' => $key
// ), $connection);
// return $this->readFromRemote($connection);
// }
##################################################################
public function Add(string $Key, $Value,int $Time = 0) {
$connection = $this->getConnection($Key);
$this->writeToRemote(array(
'FUN' => 'Add',
'Key' => $Key,
'Value' => $Value,
'Time' => $Time
), $connection);
return $this->readFromRemote($connection);
}
public function Del(string $Key) {
$connection = $this->getConnection($Key);
$this->writeToRemote(array(
'FUN' => 'Del',
'Key' => $Key
), $connection);
return $this->readFromRemote($connection);
}
public function UpData(string $Key, $Value,int $Time = 0) {
$connection = $this->getConnection($Key);
$this->writeToRemote(array(
'FUN' => 'UpData',
'Key' => $Key,
'Value' => $Value,
'Time' => $Time
), $connection);
return $this->readFromRemote($connection);
}
public function Get(string $Key,$AutoData = "") {
$connection = $this->getConnection($Key);
$this->writeToRemote(array(
'FUN' => 'Get',
'Key' => $Key,
'AutoData' => $AutoData
), $connection);
return $this->readFromRemote($connection);
}
public function AutoDel() {
$connection = $this->getConnection('AutoDel');
$this->writeToRemote(array(
'FUN' => 'AutoDel'
), $connection);
return $this->readFromRemote($connection);
}
###################################################################
/**
* Write data to global server.
* @param string $buffer
*/
protected function writeToRemote($data, $connection) {
$buffer = json_encode($data);
//serialize($data);
$buffer = pack('N',4 + strlen($buffer)) . $buffer;
$len = fwrite($connection, $buffer);
if($len !== strlen($buffer)) {
throw new \Exception('writeToRemote fail');
}
}
/**
* Read data from global server.
* @throws Exception
*/
protected function readFromRemote($connection) {
$all_buffer = '';
$total_len = 4;
$head_read = false;
while(1) {
$buffer = fread($connection, 8192);
if($buffer === '' || $buffer === false) {
throw new \Exception('readFromRemote fail');
}
$all_buffer .= $buffer;
$recv_len = strlen($all_buffer);
if($recv_len >= $total_len) {
if($head_read) {
break;
}
$unpack_data = unpack('Ntotal_length', $all_buffer);
$total_len = $unpack_data['total_length'];
if($recv_len >= $total_len) {
break;
}
$head_read = true;
}
}
return unserialize(substr($all_buffer, 4));
}
}
McServer.php - 基于官方共享組件服務(wù)端
<?php
namespace Workerman\Lib\MemoryCache;
use Workerman\Worker;
use Workerman\Lib\MemoryCache\MCLIB;
/**
* Global data server.
*/
class McServer {
/**
* Worker instance.
* @var worker
*/
protected $_worker = null;
// 實例化MC插件
protected $MC;
/**
* Construct.
* @param string $ip
* @param int $port
*/
public function __construct($ip = '0.0.0.0', $port = 2207) {
$worker = new Worker("frame://$ip:$port");
$worker->count = 1;
$worker->name = "MC內(nèi)存緩存器(1.0.0)";
$worker->onWorkerStart = array($this,'onWorkerStart');
$worker->onMessage = array($this, 'onMessage');
$worker->reloadable = false;
$this->_worker = $worker;
}
public function onWorkerStart($worker) {
// 實例化MC內(nèi)存緩存器
$this->MC = new MCLIB(true);
}
/**
* onMessage.
* @param TcpConnection $connection
* @param string $buffer
*/
public function onMessage($connection, $buffer) {
if ($buffer === 'ping') {
return;
}
$data = json_decode($buffer,true);
if (!$buffer ||!isset($data['FUN']) ||!isset($data['Key'])) {
return $connection->close(serialize('錯誤的數(shù)據(jù)'));
}
$FUN = $data['FUN'];
$key = $data['Key'];
// print_r($data);
switch ($FUN) {
case 'Get':
$cachedData = $this->MC->Get($data['Key'],$data['AutoData']);
if ($cachedData!== null) {
return $connection->send(serialize($cachedData));
}
$connection->send('N;');
break;
case'UpData':
$result = $this->MC->UpData($data['Key'],$data['Value'],$data['Time']);
if($result===true) {
return $connection->send('b:1;');
}
$connection->send(serialize($result));
break;
case 'Add':
$result = $this->MC->Add($data['Key'],$data['Value'],$data['Time']);
if ($result === true) {
return $connection->send('b:1;');
}
$connection->send(serialize($result));
break;
case 'Del':
$this->MC->Del($data['Key']);
$connection->send('b:1;');
break;
case 'AutoDel':
$this->MC->AutoDel();
$connection->send('b:1;');
break;
default:
$connection->close(serialize('不符合規(guī)范的調(diào)用'));
}
}
}
你這個壓測有問題, 4核服務(wù)器 靜態(tài)輸出 workerman 不可能才4萬 QPS, 至少10萬 +

這是我剛壓測的4核騰訊云服務(wù)器, 14萬QPS
代碼