最新发表

php原生模板引擎性能优化

好久没写博客了,今天来一发。

背景:

基于YAF开发的一个网站,模板引擎使用原生php,同时为了满足需求,自己开发了一个widget的功能,每次调用widget都会引发一次模板渲染。在网站首页会调用同一个widget数十次,在查看xhprof的数据时,发现widget渲染模板耗时较多,主要消耗在加载模板文件上,由于同一个widget使用的模板是同一个,所以希望只加载一次模板来提高执行效率。

ab -n1000 -c50的结果是11.31qps(虚拟机性能差)PHP

思路:

首先想到的是在include文件之前把文件读入内存,放到一个静态变量中,之后再调用时就直接从静态变量中取即可。但问题是把模板文件放到变量中之后,怎么渲染呢?最简单粗暴的办法是使用eval,但是太粗暴了,不想使用。

然后想起来php有一个wrapper的功能,可以注册一个wrapper,比如mem,同样是把模板文件读入内存,然后就可以通过include(‘mem://模板引擎路径’)的方式来加载,大致代码如下:

本文来自leo108's blog

class Ext_Wrapper {
    //存放模板文件内容的静态成员变量
    protected static $_fileArr = array();
    protected $_pos;
    protected $_curFile;

    public function stream_open($path, $mode, $options, &$opened_path) {
        $path = substr($path, 5, strlen($path) - 5);
        //判断模板文件是否已经在变量中,不存在就读取
        if (!isset(self::$_fileArr[$path])) {
            self::$_fileArr[$path] = file_get_contents($path);
        }
        $this->_curFile = $path;
        $this->_pos     = 0;
        return true;
    }
    public function stream_read($count) {
        //直接从静态变量中读数据
        $content = self::$_fileArr[$this->_curFile];
        $ret     = substr($content, $this->_pos, $count);
        $this->_pos += strlen($ret);
        return $ret;
    }
    //其他方法略
}
//注册wrapper
stream_register_wrapper('mem', 'Ext_Wrapper');

ab -n1000 -c50的结果是12.68qps

最后试了一下eval的性能,大致代码如下:

class Ext_View extends Yaf_View_Simple {
    private $tmpPath;
    private $tmpData = array();
    private $include;

    //用于保存模板内容的静态变量
    protected static $_fileArr = array();

    public function display($tplFile, $data = array()) {
        $this->tmpPath = $this->getScriptPath() . '/' . $tplFile;
        if (is_array($data)) {
            $this->tmpData = array_merge($this->tmpData, $data);
        }
        unset($tplFile);
        unset($data);
        extract($this->tmpData, EXTR_OVERWRITE);
        //判断模板文件是否已经在变量中,不存在就读取
        if (!isset(self::$_fileArr[$this->tmpPath])) {
            self::$_fileArr[$this->tmpPath] = file_get_contents($this->tmpPath);
        }
        eval('?>'.self::$_fileArr[$this->tmpPath]);
    }
    //其他代码略
}

ab -n1000 -c50的结果是15.07qps,吓尿了,eval果真是简单粗暴有效

本文来自leo108's blog

sonar中文乱码解决方案

OSC搞了一个代码质量管理系统,用的是sonar,看上去很不错,于是就自己搭建了一个,但是发现代码里的中文全部变了‘?’号。

首先检查代码,确认是UTF8格式无误。

http://leo108.com/pid-2011.asp

再检查代码目录下的sonar-project.properties文件,编码配置sonar.sourceEncoding=UTF-8,也没有错。

sonar-run

然后检查数据库,发现数据库的字符集是latin,于是把所有的表字符集都改成utf8。

重新运行了一下sonar-run,以为能解决了,结果发现还是‘?’号。sonar中文乱码解决方案

检查数据库中的数据,已经是乱码的了,说明在写入的时候就有问题了。

再检查了一下sonar-run的配置,里面的jdbc配置是:

sonar.jdbc.url=jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8

看上去是utf8没错啊……但是我发现里面有个&,这个是&的转义,于是想会不会是这里的问题,把&改成&,再运行sonar-run,显示中文正常!乱码

piwik页面卡住问题追查记录

今天无聊就折腾了下piwik,但是发现有概率页面会504,查看nginx日志是php执行超时,而且第一次504之后,后面的所有页面访问全部都是504,重启php-fpm之后又可以正常访问了。

未经允许严禁转载

于是打开php-fpm的slowlog,在页面504的时候slowlog里也打出了对应的调用栈:

script_filename = /home/www/online/website/piwik/index.php
[0x00007fbec1568f48] session_start() /home/www/online/website/piwik/libs/Zend/Session.php:469
[0x00007fbec1568370] start() /home/www/online/website/piwik/core/Session.php:113
[0x00007fbec1567e68] start() /home/www/online/website/piwik/core/FrontController.php:416
[0x00007fbec1567a08] prepareDispatch() /home/www/online/website/piwik/core/FrontController.php:502
[0x00007fbec15675f8] doDispatch() /home/www/online/website/piwik/core/FrontController.php:84
[0x00007fbec15671d0] dispatch() /home/www/online/website/piwik/core/dispatch.php:34
[0x00007fbec15669a0] +++ dump failed

看来是执行session_start的时候卡住了,php的session是有锁机制的,如果某个页面开启了session,并且该请求尚未结束,则后续该session的请求就会全部锁住,等待之前的请求结束。

也就是说piwki页面卡住的原因是之前的某次请求没有结束,但是是哪个请求没有结束呢?因为没有看piwik的代码,而且504是有概率出现的,并不是访问了某个特定的页面后才504,问题追到这里就遇到了瓶颈。piwik页面卡住问题追查记录

鼓捣了半天之后突然想到,那个没有结束的请求肯定会打在php的slowlog里面,于是重新检查了一下,果真在一堆session的slowlog中发现了不一样的日志:

采集者烂JJ

script_filename = /home/www/online/website/piwik/index.php
[0x00007fbec1568c40] curl_exec() /home/www/online/website/piwik/core/Http.php:483
[0x00007fbec1568798] sendHttpRequestBy() /home/www/online/website/piwik/core/Http.php:94
[0x00007fbec15685e0] sendHttpRequest() /home/www/online/website/piwik/core/Http.php:720
[0x00007fbec1567e00] fetchRemoteFile() /home/www/online/website/piwik/plugins/ExampleRssWidget/RssRenderer.php:46
[0x00007fbec1567be8] get() /home/www/online/website/piwik/plugins/ExampleRssWidget/Controller.php:25
[0x00007fffe0d3cf00] rssPiwik() unknown:0
[0x00007fbec1567788] call_user_func_array() /home/www/online/website/piwik/core/FrontController.php:531
[0x00007fbec1567378] doDispatch() /home/www/online/website/piwik/core/FrontController.php:84
[0x00007fbec1566f50] dispatch() /home/www/online/website/piwik/core/dispatch.php:34
[0x00007fbec1566720] +++ dump failed

看上去是个curl调用http请求,追了下里面的代码,发现是个公共的类,并不是固定请求某个url,这也简单,在curl_exec()之前加个打日志的代码,把curl请求的url记录下来。

piwik页面卡住问题追查记录

于是又随便点了记下piwik的页面,发现有记录了一个url:http://feeds.feedburner.com/Piwik

看上去是rss的url,直接在piwik的grep这个url,结果如下:

http://leo108.com/pid-2007.asp

./plugins/ExampleRssWidget/Controller.php:            $rss = new RssRenderer('http://feeds.feedburner.com/Piwik');
./plugins/ExampleRssWidget/Controller.php:            $rss = new RssRenderer('http://feeds.feedburner.com/PiwikReleases');

是个插件的代码。

本文来自http://leo108.com

进到piwik的后台,直接把ExampleRssWidget这个插件禁用。问题解决!http://leo108.com/pid-2007.asp

PS:中国的国情啊……………………piwik

json格式化、高亮库jsonFormater

JsonFormater

基于jQuery的json格式化、高亮库

核心代码参考天马行空工作室,本人只做了模块化和一些代码优化。

demo

http://leo108.github.io/jsonFormater/

github地址

https://github.com/leo108/jsonFormaterjQuery

使用方式

引入jQuery

<script type="text/javascript" src="jquery-1.7.2.min.js"></script>

注意:jQuery版本要求>=1.7.2JSON

引入jsonFormater.js和jsonFormater.css

<script type="text/javascript" src="jsonFormater.js"></script>
<link href="jsonFormater.css" type="text/css" rel="stylesheet"/>

在html中新增一个空的元素作为显示的容器

例如<div id='container'></div>JsonFormater

调用JsonFormater

$(document).ready(function(){
    var options = {
        dom : '#container' //对应容器的css选择器
    };
    var jf = new JsonFormater(options); //创建对象
    jf.doFormat('{"string":"leo108"}'); //格式化json
});

效果图

效果图

http://leo108.com/pid-1996.asp

详细使用方式

配置

支持的配置以及默认配置如下:

采集者烂JJ

{
    dom: '',          //用于放置的dom的选择器
    singleTab: "  ",  //单个tab
    tabSize: 2,       //缩进数量
    quoteKeys: true,  //key是否用双引号包含
    imgCollapsed: "images/Collapsed.gif", //收起的图片路径
    imgExpanded: "images/Expanded.gif",  //展开的图片路径
    isCollapsible: true //是否支持展开收起
}

方法

var obj = new JsonFormater({dom: '#container'});
obj.doFormat(json) //格式化一个json字符串或者js对象
obj.expandAll()    //全部展开
obj.collapseAll()  //全部收起
obj.collapseLevel(level)  //展开到level层

 

本文来自leo108's blog

WordPress语法高亮增强插件更新至2.5.0版本

WordPress语法高亮增强插件(SyntaxHighlighter++)今日更新至2.5.0版本,此版本是一个bug修复版本,主要是修复在高版本(>=3.7.0)的后台“多媒体”→“媒体库”中,点击编辑任意一个多媒体,多媒体页面会被本插件的输入框遮挡的问题。http://leo108.com/pid-1992.asp

WordPress官方下载地址:http://wordpress.org/extend/plugins/syntax-highlighter-with-add-button-in-editor/

单文件PHP开发框架SinglePHP1.0版发布

单文件PHP框架,羽量级网站开发首选。采集者烂JJ

SinglePHP是一个单文件PHP框架,适用于简单系统的快速开发,提供了简单的路由方式,抛弃了坑爹的PHP模板,采用原生PHP语法来渲染页面,同时提供了widget功能,简单且实用。

SinglePHP

协议MITPHP

github地址:https://github.com/leo108/SinglePHP

http://leo108.com/pid-1986.asp

文档地址:http://leo108.github.io/SinglePHP/

PHP

以前在开发一些极其简单的项目时就比较纠结,不用框架吧代码结构比较乱,还要花时间去写那些数据库连接代码;用框架吧框架代码比业务代码还多,太臃肿了。

所以就想自己搞一个简单的框架,满足简单的网站开发需求即可,在开发的过程中也一直坚持简单的原则,可要可不要的功能一律砍掉。本文来自leo108's blog

很久之前就开始酝酿这个项目,13年初的时候有了雏形,经过一年多的修修改改,也在几个小网站试验过,感觉差不多了,就在这两天把文档和注释完善了一下,今晚算是正式发布了。PHP

IE JS下一个奇怪的特(bu)性(g)

前一阵子统计一个站点的nginx访问日志时发现了大量的404请求,这些请求的url比较奇怪,都是以/undefined/img/开头的图片文件,这些请求还有一个共同点,那就是ua全部都是ie。

首先find一下图片的文件名,发现都是系统中用到的图片,是第三方登录的图片按钮,用不同浏览器去访问,发现只有确实只有较低版本(<=ie8)的ie浏览器会出现红叉叉。js

搜索了下相关的代码,发现这些图片的url是用js拼接出来的,大概代码如下:

var static_file_url = window.static_file_url;
html = '<img src="' + static_file_url + '/img/icon_bdshare_weibo.png" width="28" height="28" />';

而window.static_file_url是在页面头部就赋值了的,也就是说,当拼接url的时候,static_file_url并不是window.static_file_url的值,而是未定义undefined,在ie中alert这个变量,确实是undefined。

采集者烂JJ

同时我发现了另一个奇怪的现象,在同一个js文件中有ajax请求,url也是拼接出来的,大概样子如下:本文来自http://leo108.com

var ajax_url = window.server_url;
$.post(ajax_url, param, function(data){}, 'json');

而在这里的ajax请求却是正常的,alert(ajax_url)出来的值也是window.server_url的值。本文来自leo108's blog

这两个例子唯一的区别就是变量名,前者选择的变量名和window对象下的属性一致,而后者不一致,所以怀疑是这个地方的问题,于是写了一段测试代码

window.test_a = 'a';
window.test_b = 'b';
var test_a = window.test_a;
var test_b2 = window.test_b;
alert(test_a); // undefined
alert(test_b2); // b

所以在ie下的这个特(bu)性(g)真是让人爱(i)不(e)释(qu)手(shi)

redmine项目配置tab扩展插件

Redmine提供的插件hook中,并没有扩展项目配置tab的相关hook。本插件可以让其他插件用十分简便的方式,在项目配置中加入一个或多个tab。

使用方式:

redmine

在插件的init.rb中增加一行:

Redmine::Plugin.register :redmine_polls do
[ ... ]

    add_tab :polls, :partial => 'tab/polls'
end

最终效果如图:

redmine

redmine项目配置tab扩展插件

redmine项目配置tab扩展插件

 

更多用法详见readme本文来自leo108's blog

github地址:https://github.com/leo108/redmine-project-setting-tab

http://leo108.com/pid-1977.asp

git@OSC:http://git.oschina.net/leo108/redmine-project-setting-tab

本文来自leo108's blog