Laravel Facade实现细节考

前两天有人讲Laravel中的Facade的时候,看到了__callStatic的实现,问为何如此,所以查了一番。

现有实现

switch实现

我们在调用Facede的方法的时候,绝大多数都会被__callStatic来处理,Larvel 5.1的__callStatic实现如下:

这个实现是 Taylor Otwell 最初实现的版本

cufa实现

是switch实现的简化版:

unpack实现

注意,Argument Unpacking 特性在php 5.6才实现。

这个是 Laravel 5.3 的实现。

实现对比

代码如下:

表中为在各个情况下调用 Facede::get 100000000次的秒数

参数数量 php70+switch php70+cufa php70+unpack php56+switch php56+cufa php56+unpack
0 33 39 34 74 107 65
1 38 46 37 84 117 75
2 41 48 38 91 118 80
3 44 52 38 108 125 89
4 50 49 39 118 137 94
5 58 50 41 189 146 99
6 61 53 42 194 155 107

结论

可以看到,在PHP 5.6下,switch实现比cufa实现,在参数个数比较少的时候,有很大的优势,估计这个也是作者在第一次实现的时候,选择switch实现,而不是更简单的cufa的原因吧。

另外,unpack在PHP 5.6下更快,但由于当时需要支持PHP 5.3,所以不能使用。在Laravel 5.3中,由于最低PHP版本为 5.6.4 ,所以可以大胆的使用unpack了。

此外,实际上, PHP 7.0下,unpack在没有参数时,比switch要慢,但是个人觉得差别不大。

最后,PHP 7.0 的速度提升还是很明显的。

参考资料

https://github.com/laravel/framework/pull/12120

https://gist.github.com/nikic/6390366

composer中指定依赖分支名的坑

之前只是看着别人写的composer.json,知道如果依赖一个项目的master分之,则在依赖的version中可以写dev-master。我就以为所有对分支的依赖,都是写成dev-<branch_name>。

后来发现,v1.x这种分支名,不能直接使用dev-v1.x 来声明依赖。

查了下,https://getcomposer.org/doc/02-libraries.md#branches

For every branch, a package development version will be created. If the branch name looks like a version, the version will be {branchname}-dev. For example, the branch 2.0 will get the 2.0.x-dev version (the .x is added for technical reasons, to make sure it is recognized as a branch). The 2.0.x branch would also be valid and be turned into 2.0.x-dev as well. If the branch does not look like a version, it will be dev-{branchname}. master results in a dev-master version.

翻译如下:

对于每个分支,对应的包都会创建一个开发版本。如果分支名看起来像一个版本号,则开发版本的名字是{branchname}-dev。

比如,分支2.0则会创建2.0.x-dev版本(后面的 .x 是为了技术原因添加,为了确保这个版本能够被识别为分支)。

2.0.x 分支也会被转换为 2.0.x-dev。如果分支看起来不像一个版本,版本号将会是dev-{branchname}。master分支的版本号是dev-master。


另外,当同时存在1.0和1.0.x分支时,1.0.x-dev指向的是1.0分支。

Laravel Migration 类名重复分析

开发者在开发中一般都会为Migration起类名,最常见的就是AlterUserTable这种名字,但是如果后续的开发者第二次修改表,就有了两个类名相同的Migration了。

这样有什么问题吗?

首先,这个Migration如果和之前类名相同的Migration不在同一批次的话,是可以执行成功的。

接下来看看为什么。

Migration的执行

从Migration的执行来看,\Illuminate\Database\Migrations\Migrator::getMigrationFiles拿到所有的Migration列表,通过和执行过的Migration array_diff之后,获取没有执行的Migration列表,在执行这些Migration的时候会在\Illuminate\Database\Migrations\Migrator::requireFiles方法中require对应的Migration文件,如果恰好两个类名相同的Migration需要执行,那么就会出现错误

Cannot declare class AlterTestTable, because the name is already in use in ./database/migrations/2016_07_20_081952_alter_test_table.php on line 31

当然,如果这些Migration都在不同批次中,那么永远也不会有这个错误发生了嘛。

Migration的回退

\Illuminate\Database\Migrations\MigrationRepositoryInterface::getLast获取最后一次执行的Migration文件名列表,然后在\Illuminate\Database\Migrations\Migrator::runDown方法中将Migration名字resolve到类名(比如 2016_07_20_081952_alter_test_tableAlterTestTable 类,这时候就通过new这个类,运行down方法来回退。

同样的,在这种情况下,如果有两个一样名字的类,则autoload机制只会选择一个。

注意,在这种情况下,即使两个Migration不在同一个批次当中回退,那也会有一个Migration永远不能回退。


  • 这也解释了为什么在本地创建了migration后,运行后,然后rollback的时候提示找不到这个类,而必须要dumpautoload才可以。
  • 由于Migration up的时候是通过Laravel自己解析到代码的,而down的时候是通过composer解析的,这导致了Migration的up和down不对称,确实不怎么好看。
  • 作为一个合格的开发,显然不应该让Migration类名重复!

《硅谷钢铁侠:埃隆·马斯克的冒险人生》

“我们想要会飞的汽车,而不是140个字符。”

  • 马斯克有自己的想法

他需要做的是,制定一个目标,然后尽力实现。

在SpaceX,他知道自己要造便宜的火箭,所以员工必须努力优化每个部件;在特斯拉,员工必须有最好的执行力。

“他(马斯克)总是说,让最基本的物理原理说话。”
“我们首先要决定世界上最好的遮阳板是什么样子的,然后做得比那更好。”

  • 勇敢面对未知

虽然之前马斯克不知道如何发射火箭,但是在SpaceX开始之后,马斯克自学相关知识,甚至问员工问题。

但是这些未知都没有阻止马斯克去造火箭、生产电动汽车,相反,智能门把手和触屏操控系统是一次非常完美的反击。

  • 面对压力越挫越勇

三家公司多次遇到危机,财务危机之下,马斯克亲自审核支出,公关危机之中,马斯克自己写新闻稿来辩驳。

当然,马斯克出色的学习能力、完美的记忆力也是马斯克成功的条件,但这一点,我们没法学习到。


坚持自己的想法非常困难,但实现自己的想法对于个人来说非常重要,但是马斯克对于自己想法的固执、对于员工的苛责实在不是一个好的表达。自己的想法可能伴随着来自众人、来自传统的压力,但是新东西要是一开始就得到了旧世界的承认,那还是新东西吗?

“我告诉他,‘不只是那个家伙错了,我再重申一遍,你也错了。’”

即使在社交场合中,马斯克也可能会突然从餐桌前站起来,不做任何解释,径直走到外面去看星星,仅仅因为他不愿意跟傻瓜待在一起闲聊。

Continue reading 《硅谷钢铁侠:埃隆·马斯克的冒险人生》

如何命名二进制PHP扩展?

Python界有自己的文件名约定https://www.python.org/dev/peps/pep-0427/#file-name-convention,比如distribution-1.0-1-py27-none-any.whl一看就知道自己能不能用到这个whl包。

最近需要发布thrift_protocol扩展,故自己定下一个命名规范,在此记录之。

首先看看php如何判断扩展兼容与否,dl.c:172dl.c:182分别根据zend_api(比如20151012)和build_id(比如API20151012,NTS)来判断是否兼容。

所以我们用build_id+platform就可以表示ABI版本了,加上name和version就得出命名规范:

{distribution}-{version}-{build_id}-{platform}.{so,dll}

比如:

thrift_protocol-1.0-API20151012,NTS-linux_x86_64.so

遗憾的是,github会吞掉英文逗号,所以只能用英文句号替代了。

不过,phpize的时候能看到zend_api,无法看到build_id,算是一个不方便的地方,只能通过php -i | grep 'PHP Extension Build'来变通获取了。

将ngx_brotli编译成动态nginx模块

Brotli是google新出的一个压缩算法,据说比Zopfli的压缩率要高20–26%(Zopfli是google之前发布的一个和Deflate兼容的压缩算法)。

Chrome 49+、Firefox 44+以及Opera 36+都支持Brotli了,所以,是时候尝试一把了。

但是为了支持这个特性,自己编译部署nginx太麻烦了,nginx 1.9.11开始支持动态模块,所以也来尝试一把。

系统需求

本人测试环境ubuntu 14.04, ppa:nginx/stable, nginx/1.10.1

安装libbrotli

按照 libbrotli 的说明,依次执行如下命令安装

为了让系统中使用到libbrotli的程序能够加载so文件,故创建软链:

准备源代码

下载nginx和ngx_brotli的源代码:

编辑ngx_brotli/config文件,在顶部添加一行

configure

在nginx源码目录执行

并make,得到两个so文件:

加载模块

/etc/nginx/nginx.conf文件中添加如下两行,使nginx加载模块:

接下来在http段中添加如下两个配置:

再执行 看看有没有错误,如果没有错误的话那就成功了。

不过,brotli压缩算法只有在https连接中才能生效。

module “/path/to/ngx_http_brotli_static_module.so” is not binary compatible in nginx.conf

这个错误是由于module的signature和nginx的signature不匹配导致的,我的解决办法是,打印出这两个signature,逐位比对,并据此调整configure的参数。

参考 ngx_module.h 比如,第25位表示NGX_HTTP_V2定义与否,此时可以添加--with-http_v2_module来修正signature。

第22位表示NGX_THREADS定义与否,可以添加–with-threads来修正。

第8位和第9位与IPv6有关,只需添加–with-ipv6即可。

至于第30位,只能通过编辑ngx_brotli/config文件,添加have=NGX_HTTP_HEADERS . auto/have来搞定了。

Continue reading 将ngx_brotli编译成动态nginx模块

How to install/upgrade elasticsearch cluster via ansible?

First, we have a playbook:

serial: "50%" means ansbile will run tasks in 50% of hosts (or less).

And, we should add a rule elasticsearch:

  1. we try to disable allocation for cluster upgrade
  2. download elasticsearch’s deb package to local cache
  3. install and configure, restart elasticsearch
  4. wait 9200 port is available, enable allocation, wait for cluster become green.

some vars:

Continue reading How to install/upgrade elasticsearch cluster via ansible?

How to set proxy_next_upstream? and why?

You can add proxy_next_upstream in http, server, location context:

There is something to notice:

  • You should add error, timeout, invalid_header since the upstream didn’t receive this request.
  • You can add http_502, http_503, http_504 because upstream meet network issue.
  • Don’t add http_500 because there is error in application, retries will cause other server is down.(And developer should fix this error, not load balancer)

Continue reading How to set proxy_next_upstream? and why?

How to use Theano? (with OpenCL)

I have a MacBook Pro with AMD display card, so I want Theano use OpenCL.

First, create virtualenv and activate it:

Install some dependencies for Theano and OpenCL:

those dependencies isn’t in setup.py of Theano (or pygpu).

Install Theano (latest development version, so far, 0.8.2 doesn’t support OpenCL):

If github is temporary unavailable, try my personal csdn mirror(CSDN CODE can sync github repo conveniently):

Next step is installing gpuarray for supporting OpenCL:

Now, Theano can use OpenCL to accelerate computing.


We can use Theano’s check1.py to check that OpenCL is available:

Performance:

  • Only use CPU (1.662902 s)

  • CPU over OpenCL (1.057008 s)

  • Intel GPU over OpenCL (0.554572 s)

  • AMD GPU over OpenCL (0.470640 s)


If you using stable(0.8.2) Theano, you will meet this error when you try to use OpenCL:

so, you’d better install development version of Theano.

APT Hash sum mismatch

工作当中遇到此问题,然后偶尔看见此文。

此文是 APT Hash sum mismatch 的翻译。

TL;DR

APT 仓库能够提供未压缩和压缩过的文件格式。常用的几个的压缩格式是 gzip, bzip 和lzma。

apt 的 Bug 在处理lzma文件(.xz)读写的时候会偶尔报告“Hash sum mismatch”错误。

本文提供一种变通方式,另外,在特定版本的 apt 中,该bug已经被修复。

使用如下系统的用户应该升级到系统提供的最新的 apt 版本:

  • Ubuntu Trusty (14.04) 及更新的版本
  • Debian Jessie (8) 及更新的版本

更早系统的用户应该升级系统或者使用如下的变通方法。

目前,我们决定关闭 packagecloud 的所有仓库中对 lzma 格式的支持。元数据仍然能够以 gzip, bzip 以及未压缩的格式来获取。packagecloud 仓库的所有者们并不需要做任何事情。

Hash sum mismatch 变通方法

用户可以通过两种方法强制 apt 不使用 lzma 格式的元数据。

第一种办法就是运行 apt-get 的时候添加额外的命令行参数:

(注意:如果你偏向于使用 bzipped 格式,你也可以指定参数为“bz2”)

或者,你可以在全局的 apt 设置中指定这个选项,这样你就不必每次运行 apt 的时候输入这些。

步骤如下:

  1. 创建 /etc/apt/apt.conf.d/99compression-workaround 文件
  2. 添加这些文本到文件中: Acquire::CompressionTypes::Order:: "gz";

现在,apt-get update 会首先尝试 gzip 格式的元数据。

Hash sum mismatch 在新的 apt 版本中被修复

这个 aptbug 在2014年3月21日被修复(git 提交为c4b113e650dbdbb4c5c9c6f36437c94db6b214d9)

这个修复包含在 apt 1.0以及更新的版本中,apt 1.0在如下系统中可用:

  • Ubuntu Trusty (14.04) 及更新的版本
  • Debian Jessie (8) 及更新的版本

如果你在如上系统中看到了这个错误,你应该确认是是否运行着最新的 apt 。

“Hash sum mismatch”到底意味着什么?

顶级的文件包含着仓库其他部分文件的散列值,这是 apt 仓库的组织元数据的方式。

“Hash sum mismatch”错误提示用户: apt 下载的文件的散列值和元数据中的散列值不匹配。

不幸的是,因为 apt 中的 bug,以 lzma 格式(.xz文件)压缩过的元数据会偶尔下载或(在某些情况下)解压错误,造成文件损坏。

结果,损坏的文件的校验值会导致 apt 产生“Hash sum mismatch”错误。

结论

apt 在处理 lzma 格式中一个不幸的错误 导致了臭名昭著的“Hash sum mismatch”错误,加之散列值会被校验,造成了另最终用户困惑的局面。

Ubuntu Trusty 和 Debian Jessies(或者更新的系统)的用户建议升级到系统中最新的 apt 版本。其他用户建议使用上文提供的变通方法来避免食用 lzma 格式的元数据。

packagecloud 的 APT 仓库作者们不需要做任何事情。我们已经完全关闭了 lzma 格式的元数据,仓库用户并不会被此 bug 影响。