xts.so - x2ts
type: channel
link: https://movil.xts.so/x2ts/
items: 批量加载关系对象和属性
items: 在平行空间中运行程序
items: x2ts的事件总线
items: x2ts日志的一些变化
items: x2ts最近的几个大动作

批量加载关系对象和属性
type: item
date: 2017-11-16T15:47:00+08:00
description: Relation Load 的性能问题 长期以来，我们一直使用ORM技术进行数据库操作。相比直接用PDO写SQL并操作结果数组，利用ORM类有诸多好处，其中最好用的两点在于： 相比数组，Model类提供了一个编写getter和setter的地方，可以更灵活地处理和转化数据库中的字段。 利用关系加载技术让可以直接像访问普通属性一样访问相关的Model对象，无需再编写相应SQL。 然而一直以来，上述两种常见的ORM用法，存在严重的性能问题。因为getter和自动关系加载都针对单个对象操作，ORM库常常会为一页的记录生成上百条SQL查询语句，执行起来耗时较长。 比如要查询以下报表： 学员姓名 手机号 所属分校 所在高校 所在班级 负责老师 最近沟通 报表中的学员姓名、手机号来自student表，所属分校来自school表，所在班级来自class表，所在高校来自colleage表，负责老师来自teacher表，最近沟通来自contact_latest表。表关系如下： ----------- | teacher | ----------- ^ | ----------- | school | ----------- ^ | ----------- ------------------ | student | --> | contact_latest | ----------- ------------------ | V ----------- | class | ----------- | V ------------ | colleage | ------------ 每查询一页的报表数据，假设一页50条记录，需要生成两条select student的SQL（一条count一条分页选取，在之后的关系加载过程中，需要再生成250条select语句，用于为每条student记录查询其它相关记录，虽然每条关联记录都是用主键id查询。 用with方法提升性能 引发上述性能问题的关键原因在于，每个ORM记录对象单独做关系加载时，并不知道其它对象也要做相似的关系加载，所以只能生成大量的单条查询SQL。这样在和数据库通讯RTT值高的情况下，性能下降尤其明显。 为了解决这种性能问题，x2ts的从2.5版开始，向Model类新增了一个with方法，它允许我们定义一系列需要批量载入的关系属性，并在下一次many或者sql调用时生效。比如： X::model('student')->with( 'school.master', 'contact_latest', ['class', 'colleage'] )->many('user_id IS NOT NULL', [], 0, 50); 上面这段代码会在载入50条学员记录之后，自动载入和它们关联的分校、老师、班级、高校、联系记录。总共生成6条SQL语句，除student外的5条关联查询SQL，都使用IN查询取得集合数据，有效避免了大量单条查询语句的生成。 with方法接收一个或者多个参数，每个参数是一条关联查询定义，可以是字符串、也可以是数组，还可以是x2ts\db\orm\BatchLoader接口的实例。 with方法的参数说明 字符串描述的是关联关系的名称，可以用点分隔做次级加载。比如： 'school.master' 表示关系载入school属性，然后再载入school的master属性。 数组描述的是多级加载关系。['class', 'colleage']数组等价于'class.colleage'字符串。 有时会对某一级关系对象的多个下级存在加载需求，可以用关联数组实现。比如： ['school' => ['master', 'creator']] 表示关系载入school之后，再载入school的master和creator两个属性。 关联数组的key可以用点分隔表示多级，规则和字符串一样，key必需是数组中的最后一项。比如： ['school.master' => ['role', 'ext_info']] 表示关系载入school后，再载入school的master属性，然后载入master的role和ext_info属性。 这种写法等价于 ['school', 'master' => ['role', 'ext_info']] 多层数组可以相互嵌套，比如： [ 'class.colleage' => [[ 'creator' => [ 'role', 'ext_info', ], ], [ 'master' => 'role' ], 'location', ] ] 表示要载入class属性，以及加载class的colleage属性，然后加载colleage的creator、maste…

在平行空间中运行程序
type: item
date: 2017-05-03T17:54:00+08:00
description: 介绍 思考这样一个实例：老师答疑时要下发通知给学员的App。把老师提交的答案保存到数据库只需50ms，调用第三方的WebService接口完成推送却需要等待500ms乃至数秒！我们希望老师可以马上看到答案提交成功，并且开始为下一位学员答疑，不必等待App推送完成。x2ts框架提供了一个新的功能来达成这一目的，你可以提交一段代码，让这段代码在独立的进程中运行，不会阻塞php-fpm。用起来就像这样： X::parallel(function($userId, $title, $message) { $ret = X::curl->post(...); //请求推送接口 X::logger()->info($ret); })->run( $askSubject->user_id, $askSubject->author . '回答了你的问题', $askSubject->first_ask ); 例子中的代码把调用推送接口的代码写在了一个匿名函数中，然后调用run方法把参数传过去，这个匿名函数会在另一个进程中运行。这里的“另一个进程”，就是Parallel Runner的守护进程。 使用说明 想要使用Parallel Runner首先要用composer引入库： composer require x2ts/x2ts-parallel 然后在配置文件中加入组件配置： 'parallel' => [ 'class' => \x2ts\parallel\Runner::class, 'singleton' => true, 'conf' => [ 'name' => 'sk-parallel', 'sock' => '/var/run/sk/parallel.sock', 'pid' => '/var/run/sk/parallel.pid', 'lock' => '/var/run/sk/parallel.lock', 'workerNum' => 16, 'backlog' => 128, 'maxRequest' => 500, ], ], 在X类中加入parallel方法申明 /** * @method static x2ts\parallel\Runner parallel(Closure $func = null) */ class X extends x2ts\ComponentFactory {} 编写Parallel Runner启动脚本： <?php // cliroot/parallel.php require_once dirname(__DIR__) . '/webroot/xts.php'; X::parallel()->start(); 最后，启动Parallel Runner守护进程： mkdir /var/run/sk chown -R www-data:www-data /var/run/sk php cliroot/parallel.php 现在就可以像前面的例子一样，把程序放到Parallel Runner中运行了。 限制 因为匿名函数其实是在Parallel Runner进程中运行，所以必然会有一些限制： 不能使用use关键字引入外层变量，因为在另一个进程中没有此匿名函数的执行上下文。 不能使用$this变量，理由和上一条相同，但可以在内部的匿名类定义中使用$this，此时$this指向该匿名类的实例。 不能按引用传递变量，因为变量实际会通过IPC的方式发到另一个进程，按引用传递变量是没有意义的。 不能传递不可被序列化的变量，因为要通过IPC的方式发送到另一进程，resource和不可序列化的object不能作为参数传递。 不能期待返回值，因为程序在另一个进程中运行，执行结果只能通过修改数据库、Redis等方式反映，所有return的数据都会被丢弃。 不能期待echo输出，因为程序在另一个进程中运行，echo输出的内容并不会显示在页面上，因为是守护进程，也不会输出到屏幕，需要输出信息供阅读和调试，请通过日志输出。

x2ts的事件总线
type: item
date: 2017-05-03T16:34:00+08:00
description: 有的时候我们会需要在主线流程之外处理一些支线任务，比如用户注册的主线任务是检查和保存用户的各种资料，同时存在支线任务是给邀请此用户来注册的另一用户发送一条邀请成功通知。直观的写法是把发送通知的代码直接追加在注册程序的保存命令之后，这样的写法一个还不打紧，再多写几个就会破坏现有程序的可维护性，比如注册就赠送一个体验产品订单，还有注册成功要向统计系统发个消息通知…… 基于事件模型来写这种支线程序可以让代码组织得更好。注册部分的代码只专注于注册该干的事情——检查数据、创建用户对象、设置用户资料、写入数据库，然后触发“用户注册”事件，后面的通知、馈赠、统计等支线行为，应该由监听用户注册事件的一众程序来处理，它们可以写在关联事件监听器的地方，也可以写在相应业务的模块里。 function notifyInvitor(event\user\Register $ev) { $user = $ev->user; if ($user->invitation) { //发送通知给邀请的用户 } } X::bus() ->on('user.register', 'notifyInvitor') ->on('user.register', ['Product', 'presentTrial']) ->on('user.register', ['Statistic', 'userRegister']; 在用户注册的主业务代码中，只需要触发此事件即可： //各种检查和赋值 $user->save(); X::bus()->dispatch(new event\user\Register([ 'dispatcher' => $this, 'user' => $user, ])); x2ts框架的事件也已转向使用事件总线。所谓事件总线，意思是所有事件都在挂载在同一个对象上，并且所有事件都在同一个对象上触发，这样一来，开发事件处理函数的人无需了解事件触发的程序细节，只要根据文档选择适当的事件，即可开发回调函数，完成支线任务。所有的x2ts框架事件，都定义在x2ts开头的命名空间下；所有的应用层事件，都定义在event开头的命名空间下，代码保存在项目的protected/event目录中。 许多时候事件处理代码只是一些小函数，没有完整的class或者模块承载，为了更好的管理这些函数，项目加入了一个event\Setup类，位于protected/event/Setup.php里，在这个类里可以写静态方法作为事件监听器函数。还有一个静态方法setup()负责注册这些事件监听器。事件监听写的书写原则是为每一个任务编写一个静态方法，名称和要做的事情相符，比如notifyInvitor表示此方法会发送通知给邀请人，sendAskAnsweredNotify表示发送答疑回复通知等等。

x2ts日志的一些变化
type: item
date: 2017-05-03T14:32:00+08:00
description: 这次重构x2ts框架的时候，日志模块也在大调之列。最主要的变化当属放弃了原来的直接写入日志文件的做法，改为使用monolog作为日志处理模块。monolog把记录日志这件事划分成了Logger、Handler、Formatter三个部分。Logger负责和应用程序对接，提供诸如notice、warning这样的简便接口，也提供addRecord这样的通用接口。Handler负责日志的处理和输出，可以输出到stderr，也可以把文件、网络、命名管道、syslog、消息队列、PHPConsole等等作为输出日志的目标。Formatter负责日志输出前的格式化，可以把日志文本和元信息（比如时间、进程号等）格式化成指定的样子。 以前日志函数在x2ts\Toolkit中提供，一个是log还有一个是trace，现在Logger是一个独立的x2ts组件类，它是核心组件之一，不论是否在配置中定义始终都可以使用（如果没有定义输出目标，默认会输出到stderr）。在日志消息等级方面，以前的DEBUG、NOTICE、WARNING、ERROR共4个级别被扩展成monolog所定义的8个级别：DEBUG、INFO、NOTICE、WARNING、ERROR、CRITICAL、ALERT、EMERGENCY。 调用日志的方式也改为使用logger组件： X::logger()->info($someMessage); 为了方便日志在各种Handler中输出，原来日志中夹带的Unix Console颜色字符从日志中去掉了，现在的日志文件只有纯文本数据。如果想看带颜色的日志需要使用开发机上的colorlog脚本。 tail -f app.log | colorlog 现在日志的输出等级依然是debug模式输出DEBUG以上的日志，非debug模式输出NOTICE级别以上的日志。在新的级别体系中，INFO级别的日志可以用于区分框架输出的trace log

x2ts最近的几个大动作
type: item
date: 2017-04-07T02:30:00+08:00
description: 最近在将x2ts由git submodule使用方式重构到composer上，顺便检讨之前设计不合理的地方并加以修订。预计将会有以下几大调整： 使用composer管理依赖 遵循PSR-1开发规范 使用PSR-4 autoload机制 日志模块基于monolog实现 配置不再保存于static变量中，迁移到Configuration类 加入事件支持和全局事件总线 路由器切分为路由和规则两部分 Router和Action类中的事件改向全局事件总线触发 除MVC外的组件迁移到单独的库中，作为可选模块使用 加入Unit Test