使用workerman 压力测试结果,很满意

CiChenMan



Nginx Server 与 WorkerMan Server For PHP 压测报告
测试要求:
1.相同环境 (linux debian12)
2.相同配置 (4c 8v 40G)
3.相同输出内容 ({"code":200,"msg":"\u6210\u529f","data":
[{"id":1,"name":"cichenman","age":26,"tip":"\u5185\u5b58\u6570\u636e"}]})
4.内网测试 (本地 127.0.0.1)
5.AB 压测 (参数:50W 请求 1000 并发 开启 Keep-Alive)
测试模式:

  1. nginx + 纯静态 输出固定文本
  2. nginx + php 输出固定文本
    3.workerman 输出固定文本

具体代码贴在了评论区

均采用:4 个进程,8G 内存,千兆内网环境测试,测试数据仅供参考,每种测试模式测试 10 次

625 15 1
15个评论

six

你这个压测有问题, 4核服务器 静态输出 workerman 不可能才4万 QPS, 至少10万 +
这是我刚压测的4核腾讯云服务器, 14万QPS
截图

代码

<?php

use Workerman\Connection\TcpConnection;
use Workerman\Protocols\Http\Request;
use Workerman\Worker;
require_once __DIR__ . '/vendor/autoload.php';

$worker = new Worker('http://0.0.0.0:8787');
$worker->count = 4;
$worker->onMessage = function (TcpConnection $connection, Request $request) {
    $connection->send('{"code":200,"msg":"\u6210\u529f","data":[{"id":1,"name":"cichenman","age":26,"tip":"\u5185\u5b58\u6570\u636e"}]}');
};
Worker::runAll();
  • CiChenMan 16天前

    可能是我本地环境问题
    1.我win电脑开的vm虚拟机24核心
    2.电脑后台挂了很多软件
    3.虚拟机安装了宝塔,nginx,mysql
    4.ab测试也是在虚拟机内部直接压测
    5.实际压测程序内部结构很复杂,写了启动类,缓存类,数据库类,并都启动实例化了,
    6.输出形式可能不同,ng是直接返回一个静态文本,php是echo,workerman是调用各种安全检测判断后输出

    感觉问题在我自己,并不是程序问题,应该用一个纯净linux机器安装我写的脚本,并把无用的实例注释掉。

    然后用另一台电脑测试,这样性能应该上去了

  • CiChenMan 16天前

    我用的e5-2673 v3本地电脑,内存频率2133mz

  • CiChenMan 16天前

    你的服务器配置是不是吊打我

  • six 16天前

    我的就是普通的腾讯云的服务器,4核 4G,还是竞价实例,一小时3分钱那种,用完就关

  • CiChenMan 15天前

    好吧,正在优化代码中,现在已经将代码的并发处理到了9万。用了gzip压缩数据,太占用资源

  • CiChenMan 15天前

    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)

CiChenMan

重新在天翼云的2核心业务服务器上进行了测试
环境2C 4G
安装软件:由于是业务服务器安装了MySQL,bt面板,等
压测参数:ab -n 500000 -c 1000 -k http://127.0.0.1/

  • 暂无评论
CiChenMan

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();
  • 暂无评论
CiChenMan

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;
    // 构造函数,可进行一些默认参数赋值等初始化操作
    public function __construct() {
    }
    // 实际运行服务的私有实例方法
    public static function run() {
        $MC = new McServer("0.0.0.0",2207);
        // 创建一个Worker监听指定端口,使用http协议通讯
        $worker = new Worker(self::$ip. ":". self::$port);
        // 启动指定数量的进程对外提供服务
        $worker->count = self::$count;
        // 进程名字
        $worker->name = self::$name;
        // 调用类的静态方法。
        $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();
    }
}
  • 暂无评论
CiChenMan

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() {
    }
    //子进程调用PDO实例化
    public static function PDO() {
        # 实例化数据库类
        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));
    }
    //子进程调用MC客户端实例化 - 链接MC服务端 
    public static function Mclient() {
        return new McClient('0.0.0.0:2207');
    }
    //子进程内部独立的缓存器
    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) {
        //设置当前链接缓冲区大小
        $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));
        }
        //处理静态方式返回情况
        $connection->send(self::xlh_plain($d));
        // $connection->close();
    }
    public static function onClose(TcpConnection $connection) {
    }
    public static function onWorkerStop(Worker $worker) {
    }
}
  • 暂无评论
CiChenMan

Route.php

<?php
namespace Workerman\Config;
use Workerman\Config\RouteConfig;
use Workerman\Lib\FileTypeDetection\FileTypeDetection;
class Route {
    //路由加载状态
    private static $load = false;
    //是否加载默认路由
    private static $autoroute = true;
    //路由表
    private static $routes = [
        // //指定具体方法路由
        "/" => ["App\Controllers\Defaults","index"],
    ];

    public function __construct() {}
    //设置是否加载默认路由
    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
                    ]    
                ];
    }
    //以静态资源形式访问
    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." - 禁止访问 - 命中拦截规则".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;
    }
    # 校验路由规则
    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];
        //还原路由键值结构
        $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 {
            //路由不存在
            //尝试以静态文件形式访问
            return self::StaticFiles($connection);
        }
    }
}
  • 暂无评论
CiChenMan

RouteConfig.php

<?php
use Workerman\Config\Route;

//添加安全站点,只有安全站点可以通过访问
Route::AddWebHost(["www.wlot.top"]);

//默认路由功能
Route::SetAutoRoute(true);

//添加业务路由
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"]);
  • 暂无评论
CiChenMan

Home.php

<?php
namespace App\Controllers;
use Workerman\Protocols\Http\Response;
use Workerman\Lib\Console\Log;
class Home {
    public function __construct() {
    }
    //开始编写业务代码
    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;
    }
}
  • 暂无评论
CiChenMan

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];
    }
}
  • 暂无评论
CiChenMan

PDO.php

<?php
namespace Workerman\Lib\Sql;
use Workerman\Timer;
use Workerman\Lib\Console\Log;
// 垃圾的封装,优化空间很大,记得优化
class Pdo {
    private $pdo;
    #账号
            private $user;
    #密码
            private $password;
    #主机 
            private $host;
    #数据库
            private $db;
    #心跳
            private $m;
    #日志
            public $callback = array();
    #构造参数——初始化使用
            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));
        }
        ;
    }
    //常驻内存时保活
    private function Timer() {
        // 定时任务
        Timer::add($this->m, function() {
            $this->selects("show tables");
        }
        );
    }
    ### 进阶sql操作                        ###
            ### sql语句模板预处理                  ###
            ### 更谨慎的封装,调用单个或多个方法   ###
            ### 实现便捷的数据管理                 ###
            ### 支持增删改查,事务,提交,回滚      ###
            ### 添加一个队列方式,将来可能会用     ###
            ### 多语句查询事务封装                 ###
            ### 根据业务需求自行修改库             ###
            //1.预处理查询数据 数据为空返回[]数组,有返回[...]
    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.预处理增加数据 增加成功返回true 否 false(false时:可能修改的数据玉源数据相同,或者元数据不存在)
    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.预处理删除数据 成功成功返回true 否false (false时可能元数据不存在或者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.预处理修改数据 成功成功返回true 否false (false时可能元数据不存在或者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));
        }
        ;
    }
    ### 事务相关   ###
            //1.接受一个多预处理模板事务数据集 全部执行成功返回true 狗则 false
    public function start_begin(Array $object) {
        try {
            /* 开始一个事务,关闭自动提交 */
            $this->pdo->beginTransaction();
            //设置一个记录日志
            $log = [];
            //循环遍历处理预处理数据
            foreach ($object as $index => $sql) {
                $sth = $this->pdo->prepare($sql['sql']);
                $sth->execute($sql['arr']);
                //将每次预处理的结果(受到影响的行数)记录到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));
        }
        ;
    }
    //开启事务
    public function beginTransaction() {
        $this->pdo->beginTransaction();
    }
    //执行提交
    public function commit() {
        $this->pdo->commit();
    }
    //回滚
    public function rollBack() {
        $this->pdo->rollBack();
    }
}
  • 暂无评论
CiChenMan

MCLIB.php

<?php
// 
//  基于workerman 的常驻内存缓存器
//  
// 
// 
namespace Workerman\Lib\MemoryCache;
use Workerman\Timer;
class MCLIB {
    //  基本配置参数信息
    private  $info = [
            "name" => "MC内存缓存器",
            "describe" => "一个基于 workerman 的常驻内存缓存器,快速的在多进程间访问全局共享数据!",
            "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 的内存存储块,超出后会自动清理一些访问较少的存储块,数据很大时/内存宽裕时可以适当调整
    "MaxSize"   =>      1 * (1024*1024*1024*1024),
            // 是否开启自动删除多余内存模式
    "AutoDel"   =>      false,
        ];
    //  存储块索引
    private  $StorageBlockIndex = [
            //存储条目数量
    "DataLen" => 0,
            //存储占用大小(总量)
    "DataSize"=> 0,
            //存储索引基本信息 数组数据对应索引
    "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();
            }
            );
        }
    }
    //大小转换
    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 = [
                    "缓存条数" => $this->StorageBlockIndex['DataLen'],
                    "缓存占用内存" => $this->formatSize($this->StorageBlockIndex['DataSize']),
                ];
        // print_r($d);
    }
    //  增加新数据 
    //  $Key    索引
    //  $Value  数据
    //  $Time   过期时间
    public  function Add(string $Key, $Value,int $Time = 0) {
        if(!$Key || !$Value) {
            return [
                            "Status"    =>  "FAILL",
                            "MessAge"   =>  "请检查存储数据时传入的参数是否正确。"
                        ];
        }
        ;
        if(isset($this->StorageBlockIndex['DataList']['Value'][$Key])) {
            return [
                            "Status"    =>  "FAILL",
                            "MessAge"   =>  "添加数据的索引在MC中已经存在,无需重复添加。"
                        ];
        }
        //获取字节大小
        $strlen = strlen(serialize($Value));
        if($this->StorageBlockIndex['DataSize'] + $strlen > $this->StorageBlockSize['MaxSize']) {
            return [
                            "Status"    =>  "FAILL",
                            "MessAge"   =>  "累计存储内容超过设置最大限制,无法存入缓存。"
                        ];
        }
        //写入数据
        $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;
    }
    //  删除数据
    //  $Key    索引
    public  function Del(string $Key) {
        if(!$Key) {
            return [
                            "Status"    =>  "FAILL",
                            "MessAge"   =>  "请检查存储数据时传入的参数是否正确。"
                        ];
        }
        ;
        //写入数据
        $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;
    }
    //修改数据
    //  $Key    索引
    //  $Value  数据
    //  $Time   过期时间
    public  function UpData(string $Key, $Value,int $Time = 0) {
        if(!$Key || !$Value) {
            return [
                            "Status"    =>  "FAILL",
                            "MessAge"   =>  "请检查存储数据时传入的参数是否正确。"
                        ];
        }
        ;
        //获取新数据字节大小
        $newstrlen = strlen(serialize($Value));
        //获取旧数据字节大小
        if(!isset($this->StorageBlockIndex['DataList']['Value'][$Key])) {
            return [
                            "Status"    =>  "FAILL",
                            "MessAge"   =>  "传入修改数据的索引在MC中不存在,请检查。"
                        ];
        }
        $oldstrlen = $this->StorageBlockIndex['DataList']['Value'][$Key]['Size'];
        if($this->StorageBlockIndex['DataSize'] - $oldstrlen + $newstrlen > $this->StorageBlockSize['MaxSize']) {
            return [
                            "Status"    =>  "FAILL",
                            "MessAge"   =>  "修改数据时,累计存储内容超过设置最大限制,无法存入缓存,修改失败。"
                        ];
        }
        $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;
    }
    //获取数据
    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'];
    }
}
  • 暂无评论
CiChenMan

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));
    }
}
  • 暂无评论
CiChenMan

McServer.php - 基于官方共享组件服务端

<?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内存缓存器(1.0.0)";
        $worker->onWorkerStart = array($this,'onWorkerStart');
        $worker->onMessage = array($this, 'onMessage');
        $worker->reloadable = false;
        $this->_worker = $worker;
    }
    public function onWorkerStart($worker) {
        // 实例化MC内存缓存器
        $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('错误的数据'));
        }
        $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('不符合规范的调用'));
        }
    }
}
  • 暂无评论
shiroi

虽然但是不是很懂你想表达什么

lsmir2

不明觉厉.这得是什么场景啊?

  • CiChenMan 1天前

    网站

  • lsmir2 6小时前

    你的缓存类 如果多进程的情况下,是不是会有问题.他不是共享缓存.

CiChenMan

220
积分
0
获赞数
0
粉丝数
22天前 加入
×
🔝