从零开始造轮子 ,一个常驻内存极简的 PHP 框架 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
tanszhe

从零开始造轮子 ,一个常驻内存极简的 PHP 框架

  •  
  •   tanszhe 2018 年 12 月 21 日 5878 次点击
    这是一个创建于 2681 天前的主题,其中的信息可能已经有所发展或是发生改变。

    One - 一个极简的基于 swoole 常驻内存框架

    背景

    在用过laravel框架,发现它的路由数据库 ORM确实非常好用,但是整体确实有点慢,执行到控制器大于需要耗时 30ms 左右。于是打算做一个拥有非常好用的路由和 orm 又非常简单的框架。所以你会发现 one 框的路由ORM有 laravel 的影子。但也有一些自己的特色,例如ORM支持自动化缓存(自动化读、写、刷新)保持与数据库同步,对外使用无感知。one 框架也支持在 fpm 下运行,在fpm下框架自身整体耗时在 1ms 左右。

    hello world

    安装

    composer create-project lizhichao/one-app app cd app php App/swoole.php 

    测试

    curl http://127.0.0.1:8081/ 

    主要功能

    • RESTful 路由
    • 中间件
    • websocket/tcp/http ……任意协议路由
    • ORM 模型
    • 统一的 session 处理
    • mysql 连接池
    • redis 连接池
    • 协程 tcp-client 连接池
    • HTTP/TCP/WEBOSCKET/UDP 服务器
    • 缓存
    • 进程间内存共享
    • RPC( http,tcp,udp)
    • 日志
    • RequestId 跟踪

    路由

     Router::get('/', \App\Controllers\IndexController::class . '@index'); // 带参数路由 Router::get('/user/{id}', \App\Controllers\IndexController::class . '@user'); // 路由分组 Router::group(['namespace'=>'App\\Test\\WebSocket'],function (){ // websocket 路由 Router::set('ws','/a','TestController@abc'); Router::set('ws','/b','TestController@bbb'); }); // 中间件 Router::group([ 'middle' => [ \App\Test\MixPro\TestMiddle::class . '@checkSession' ] ], function () { Router::get('/mix/ws', HttpController::class . '@ws'); Router::get('/mix/http', HttpController::class . '@http'); Router::post('/mix/http/loop', HttpController::class . '@httpLoop'); Router::post('/mix/http/send', HttpController::class . '@httpSend'); }); 

    orm 模型

    定义模型

    namespace App\Model; use One\Database\Mysql\Model; // 模型里面不需要指定主键,框架会缓存数据库结构 // 自动匹配主键,自动过滤非表结构里的字段 class User extends Model { // 定义模型对应的表名 CONST TABLE = 'users'; // 定义关系 public function articles() { retrn $this->hasMany('id',Article::class,'user_id'); } // 定义事件 // 是否开启自动化缓存 // …… } 

    使用模型

    fpm下数据库连接为单列, 在swoole模式下数据库连接自动切换为连接池

    // 查询一条记录 $user = User::find(1); // 关联查询 $user_list = User::whereIn('id',[1,2,3])->with('articles')->findAll()->toArray(); // 更新 $r = $user->update(['name' => 'aaa']); // 或者 $r = user::where('id',1)->update(['name' => 'aaa']); // $r 为影响记录数量 

    缓存

    // 设置缓存 Cache::set('ccc',1); // 获取 Cache::get('ccc'); // 或者 缓存 ccc 过期 10s 在 tag1 下面 Cache::get('ccc',function (){ return '缓存的信息'; },10,['tag1']); // 刷新 tag1 下的所有缓存 Cache::flush('tag1'); 

    HTTP/TCP/WEBOSCKET/UDP 服务器

    启动一个 websocket 服务器, 添加 http 服务监听, 添加 tcp 服务监听

     [ // 主服务器 'server' => [ 'server_type' => \One\Swoole\OneServer::SWOOLE_WEBSOCKET_SERVER, 'port' => 8082, // 事件回调 'action' => \One\Swoole\Server\WsServer::class, 'mode' => SWOOLE_PROCESS, 'sock_type' => SWOOLE_SOCK_TCP, 'ip' => '0.0.0.0', // swoole 服务器设置参数 'set' => [ 'worker_num' => 5 ] ], // 添加监听 'add_listener' => [ [ 'port' => 8081, // 事件回调 'action' => \App\Server\AppHttpPort::class, 'type' => SWOOLE_SOCK_TCP, 'ip' => '0.0.0.0', // 给监听设置参数 'set' => [ 'open_http_protocol' => true, 'open_websocket_protocol' => false ] ], [ 'port' => 8083, // 打包 解包协议 'pack_protocol' => \One\Protocol\Text::class, // 事件回调 'action' => \App\Test\MixPro\TcpPort::class, 'type' => SWOOLE_SOCK_TCP, 'ip' => '0.0.0.0', // 给监听设置参数 'set' => [ 'open_http_protocol' => false, 'open_websocket_protocol' => false ] ] ] ]; 

    RPC

    像调用本项目的方法一样调用远程服务器的方法。跨语言,跨机器。

    服务端

    启动 rpc 服务,框架已经内置了各个协议的 rpc 服务,添加到到上面配置文件的action即可。列如: 支持http调用,又支持tpc调用。

    // http 协议 rpc 服务 [ 'port' => 8082, 'action' => \App\Server\RpcHttpPort::class, 'type' => SWOOLE_SOCK_TCP, 'ip' => '0.0.0.0', 'set' => [ 'open_http_protocol' => true, 'open_websocket_protocol' => false ] ], // tpc 协议 rpc 服务 [ 'port' => 8083, 'action' => \App\Server\RpcTcpPort::class, 'type' => SWOOLE_SOCK_TCP, 'pack_protocol' => \One\Protocol\Frame::class, // tcp 打包 解包协议 'ip' => '0.0.0.0', 'set' => [ 'open_http_protocol' => false, 'open_websocket_protocol' => false, 'open_length_check' => 1, 'package_length_func' => '\One\Protocol\Frame::length', 'package_body_offset' => \One\Protocol\Frame::HEAD_LEN, ] ] 

    添加具体服务到 rpc, 例如有个类Abc

     class Abc { private $a; // 初始值 public function __construct($a = 0) { $this->a = $a; } // 加法 public function add($a, $b) { return $this->a + $a + $b; } public function time() { return date('Y-m-d H:i:s'); } // 重新设初始值 public function setA($a) { $this->a = $a; return $this; } } 

    Abc添加到 rpc 服务

     // 添加 Abc 到 rpc 服务 RpcServer::add(Abc::class); // 如果你不希望把 Abc 下的所有方法都添加到 rpc 服务,也可以指定添加。 // 未指定的方法客户端无法调用. //RpcServer::add(Abc::class,'add'); // 分组添加 //RpcServer::group([ // // 中间件 在这里可以做 权限验证 数据加解密 等等 // 'middle' => [ // TestMiddle::class . '@aa' // ], // // 缓存 如果设置了 当以同样的参数调用时 会返回缓存信息 不会真正调用 单位:秒 // 'cache' => 10 //], function () { // RpcServer::add(Abc::class); // RpcServer::add(User::class); //}); 

    客户端调用

    为了方便调用我们建立一个映射类( one 框架可自动生成)

     class ClientAbc extends RpcClientHttp { // rpc 服务器地址 protected $_rpc_server = 'http://127.0.0.1:8082/'; // 远程的类 不设置 默认为当前类名 protected $_remote_class_name = 'Abc'; } 

    调用 rpc 服务的远程方法, 和调用本项目的方法一样的。你可以想象这个方法就在你的项目里面。

     $abc = new ClientAbc(5); // $res === 10 $res = $abc->add(2,3); // 链式调用 $res === 105 $res = $abc->setA(100)->add(2,3); // 如果把上面的模型的 User 添加到 rpc // RpcServer::add(User::class); // 下面运行结果和上面一样 // $user_list = User::whereIn('id',[1,2,3])->with('articles')->findAll()->toArray(); 

    上面是通过 http 协议调用的。你也可以通过其他协议调用。例如 Tpc 协议

     class ClientAbc extends RpcClientTcp { // rpc 服务器地址 protected $_rpc_server = 'tcp://127.0.0.1:8083/'; // 远程的类 不设置 默认为当前类名 protected $_remote_class_name = 'Abc'; } 

    其中类 RpcClientHttp,RpcClientTcp在框架里。
    你也可以复制到任何其他地方使用。

    更多

    github

    QQ 交流群: 731475644

    15 条回复    2019-11-26 18:44:07 +08:00
    zgcwkj
        1
    zgcwkj  
       2018 年 12 月 21 日
    表示看不懂,重新定义 零 !
    Exceptions
        2
    Exceptions  
       2018 年 12 月 21 日
    30ms 在么测出来的?
    WuwuGin
        3
    WuwuGin  
       2018 年 12 月 21 日
    我想说,想常驻内存,为什么要用 PHP ?
    tanszhe
        4
    tanszhe  
    OP
       2018 年 12 月 21 日
    @WuwuGin 为什么不能用?
    @Exceptions 实际测的
    madxzb
        5
    madxzb  
       2018 年 12 月 21 日
    感觉不错,希望补全一下模板和 php-fpm 使用方式
    tanszhe
        6
    tanszhe  
    OP
       2018 年 12 月 21 日
    @madxzb 完全支持 php-fpm,使用方法和常规的 mvc 一模一样,不用更改任何代码就可用在 swoole 下运行
    控制器直接使用 $this->display() 加载模板
    https://github.com/lizhichao/one-demo 一些列子
    KgM4gLtF0shViDH3
        7
    KgM4gLtF0shViDH3  
       2018 年 12 月 21 日 via iPhone
    php 开发速度确实快,就是 swoole 装起来太麻烦了
    madxzb
        8
    madxzb  
       2018 年 12 月 21 日
    @tanszhe 报如下错误
    Fatal error: Uncaught Error: Class 'Redis' not found in /Users/xuzhibin/Work/htdocs/one-demo-master/vendor/lizhichao/one/src/Cache/Redis.php:67
    Stack trace:
    #0 /Users/xuzhibin/Work/htdocs/one-demo-master/vendor/lizhichao/one/src/Swoole/Pools.php(99): One\Cache\Redis->createRes()
    #1 /Users/xuzhibin/Work/htdocs/one-demo-master/vendor/lizhichao/one/src/Swoole/Pools.php(70): One\Cache\Redis->getFpmRes('default')
    #2 /Users/xuzhibin/Work/htdocs/one-demo-master/vendor/lizhichao/one/src/Cache/Redis.php(76): One\Cache\Redis->pop()
    #3 /Users/xuzhibin/Work/htdocs/one-demo-master/vendor/lizhichao/one/src/Facades/Facade.php(22): One\Cache\Redis->get('8506ff60db3b3c4...', Object(Closure), 2592000)
    #4 /Users/xuzhibin/Work/htdocs/one-demo-master/vendor/lizhichao/one/src/Http/Router.php(31): One\Facades\Facade::__callStatic('get', Array)
    #5 /Users/xuzhibin/Work/htdocs/one-demo-master/App/swoole.php(16): One\Http\Router::loadRouter()
    #6 {main}
    thrown in /Users/xuzhibin/Work/htdocs/one-demo-master/vendor/lizhichao/one/src/Cache/Redis.php on line 67
    tanszhe
        9
    tanszhe  
    OP
       2018 年 12 月 21 日 via Android
    @madxzb 没有安装 redis 扩展, 你可以把 cache 的驱动还为 file
    GuangXiN
        10
    GuangXiN  
       2018 年 12 月 22 日
    看完主要功能,这不是一个极简框架
    run2
        11
    run2  
       2018 年 12 月 23 日   1
    @tanszhe #9 你可以把选用的 lib 加到 suggest 里 https://getcomposer.org/doc/04-schema.md#suggest
    tanszhe
        12
    tanszhe  
    OP
       2018 年 12 月 24 日
    @sobigfish 好建议
    akagishigeru
        13
    akagishigeru  
       2018 年 12 月 24 日 via iPhone
    @bestkayle pecl install swoole 一个命令啊! so easy
    oklqaz
        14
    oklqaz  
       2018 年 12 月 25 日
    我看到了 Yii 的影子
    CODEWEA
        15
    CODEWEA  
       2019 年 11 月 26 日
    不错,从新定义从零开始
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     999 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 83ms UTC 22:50 PVG 06:50 LAX 15:50 JFK 18:50
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86