在Nginx上编程

Nginx是一款高效的http server,它的第三方扩展性也是非常非常之多。这次有个需求,需要通过链接转换成真正的图片地址,将资源rewrite出去。如果资源不存在将随机一张图片返回。

在开始写的过程中,本来想考虑使用LUA模块,但是这么简单的一个功能,觉得利用rewrite功能就可以了,但是他需要随机,nginx自身是不提供随机数的,所以在官方第三方中找到了一个HttpSetMiscModule模块。

原来是通过rewrite功能,将链接根据需求分割获得相应的参数,然后使用set命令,设定nginx下的环境变量。具体看下面的例子。

    location ^~ /live/screenshots/ {
        valid_referers none blocked *.xx.cn; #设定有效来源
        if ($invalid_referer) {
            return 403;
        }
        expires 1m;
        if ($uri ~ /live/screenshots/(\d+)/(\d+)\.(icon|html|htm|jpeg|jpg|gif|bmp|png|flv|swf|zip|rar|doc|docx|xls|xlsx)) { #针对链接进行分析,取出需要的参数
            set $img_path $document_root/live/screenshots/$1/1.$3; #设定nginx环境变量
            set $roomid $1;
            set $gameid $2;
            set $img_type $3;
        }
        if (-e $img_path) { #判断此文件是否存在
            rewrite ^ /live/screenshots/$roomid/1.$img_type break; #存在就重写链接 
        }
        set_random $rand 1 10; #生成随机数
        set $rand_img /live/capture/$gameid/$rand.jpg;
        set $rand_img_path $document_root$rand_img;
        if (-e $rand_img_path) { #判断随机图片
            rewrite ^ $rand_img break;
        }
        error_page 404 =200 http://img.xx.cn/live/NonePic/none.jpg; #如果没有图片,返回404图片,状态仍然保持200
    }

通过上面的例子和注释,应该大致了解了吧。通过rewrite的if提供的regex以-e -f -d函数功能实现了强大的功能。在利用set功能,将nginx的环境进行配置。这个与写一个php或者其他脚本来比较,速度非常快。

我对PHP框架的一些看法

在去年的时候发过一篇对与Request的理解,这个是整个框架中的一小部分。现在php的框架众多,有直接用C实现的yaf;用php语言实现的框架就更多了,例如cakephp,yii,thinkphp,zendframework等等,举不胜举。以前我写过一个小型的PHP框架Suara,现在仍在公司的项目中使用着,但是我一直想把php5.6之前的新特性融合进去,重新对框架进行开发。这时候就遇到第一个难事,如何下手。看过一些stackoverflow的文章,面对PHP框架所需要的功能,有一些疑惑。我们到底需要给予一个框架怎么样的功能?

抛开PHP,我们可以看下Python,其中web.py,就是一个非常好的例子,在众多框架中,是最简单的,只简简单单的提供了router、view、model、controller和wscgi。而其他那些繁杂的功能,全部用使用者或者插件来实现。这个应该最容易上手的一个web框架,只要有一些python语言的基础,并且看一下文档,你就能开发出你所需要的网站。这是我向谈论的第一点:易用性。同样在python中还有flask。在php中我却没有发现,都需要去阅读繁杂的初学者文档,才能一步一步完成。当然,你服务器上只要有php和apache,写个php文件就能成为网站,你可以称它最快速的,我也没什么可以说的了。

第二点,功能性。作为一个框架,所提供的功能的多少,就表现出该框架有多么的强大和方便。虽然这些框架在第一次上手有那么一点苦难,但是在后续开发中,需要一些功能的时候,就显得异常的方便。比如需要一个mail service,一些框架在底层已经完成了实现,而做为网站开发者,只需要对照着框架的文档,写一些配置文件,就能使用。

第三点,性能。众所周知,越强大的框架,所需要占用的内存是直线上升的。这里需要分成两点来说,如果只是用php输出一个”Hello world”。那么直接写php文件的性能肯定是比框架要来的好,快的多,并发量也多。但是如果是查询一个数据然后显示在页面上,那么直接写php上的不一定就有框架来的好了,毕竟框架自身有cache,防dos等等功能。

因此,结合上面的三点,要写一个框架,具体需要看它应用的场景,只有最最符合使用场景的框架才是好框架。

除去router、controller、model和view之外,框架中还有一些其他的常见功能:

  1. Autoload自动加载, 这个是在框架中最为核心的,免去了使用include,require引用。若结合namespace,引用与载入就非常的方便。
  2. Exception异常处理
  3. Log 日志记录
  4. Cache 缓存处理,主要是file、redis、memcache
  5. 邮件

一个框架,需要你很多很多知识,以及丰富的开发经历,才能对框架有一个全局控制。

php对象中最基础的东东 — Object

在开发php框架的时候,我们总需要一个最最基础的类,而这个类负责着最最基础的东西,在OO中,Object就是最原始,最底层的结构。为何需要Object?Object是一个对象,这个对象中它含有属性、方法。通过所需要的对象捏合在一起,就成了一个东西。 就比方说家里的照明,它就包含有2个Object:开关和灯泡。 开关拥有链接电源和断开电源的功能,而灯泡具有亮和灭的属性,两者结合在一起就成了家里的最简单的照明系统。 同样,在PHP中需要各种各样的类,组合起来产生一个为人服务的功能。

PHP的Class默认是有__get, __set, __isset, __unset, __call这些基础的magic, 但是如果你不在class中实现这些方法的时候,是不会自动启用的。

1. __get
当$object->property 就会自动触发,这样就把$object的属性(类变量)完全暴露出来了,为了安全期间,只有拥有get{name}方法的可以读取到

2. __set
当$object->property = $value 就会自动触发,会将值直接赋予到该属性上。 同上, 只有拥有set{name}方法的才能进行属性设置。 这样避免了一个object的完全暴露

在object中,只要让property能读取,那么就一定有设定的方法。因此,只要property拥有get属性,那么他同时也要拥有set属性

3. __isset
当使用islet时会触发,用于判断$object是否有property

4. __unset
当使用unset是会触发,将值赋值为null

5. __call
当调用$obejct->xx(), 如果调用失败的时候,会触发__call

6. hasMethod
判断该$object是否拥有此方法

7. hasPerporty
判断该$object是否拥有此属性,一般通过判断 公有property是否可以读取、是否可以赋值 或者 私有属性是否存在(property_exists)

好了,上面已经把大致的Object基础讲了一边,下面是基本实现

class Object {
   //获得当前Object的类名
   public static function className() {
      return get_called_class();
   }

   public function __get() {
       $getter = 'get' . $name;
        if (method_exists($this, $getter)) {
           return $this->$getter();
        } elseif (method_exists($this, 'set' . $name)) {
           throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
        } else {
           throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
        }
   }

   public function __set() {
       $setter = 'set' . $name;
        if (method_exists($this, $setter)) {
           return $this->$setter();
        } elseif (method_exists($this, 'get' . $name)) {
           throw new InvalidCallException('Getting read-only property: ' . get_class($this) . '::' . $name);
        } else {
           throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
        }
   }
   
   public function __isset($name) {
        $getter = 'get' . $name;
        if (method_exists($this, $getter)) {
           return $this->$getter() !== null;
        } else {
           return false;
        }
    }

   public function __unset($name) {
        $setter = 'set' . $name;
        if (method_exists($this, $setter)) {
           return $this->$setter(null);
        } else {
           throw new InvalidCallException('Unsetting read-only property: ' . get_class($this) . '::' . $name);
        }
    }

    public function hasProperty($name, $checkVars = true) {
        return $this->canGetProperty($name, $checkVars) || $this->canSetProperty($name, false);
    }

    public function canGetProperty($name, $checkVars) {
       return method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name);
    }

    public function canSetProperty($name, $checkVars) {
       return method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name);
    }

    public function hasMethod($name) {
       return method_exists($this, $name);
    }
}

这上面只有最最基础的Object,在现实中,Object可能会有各种各样的变化,因此我们需要在实际情况下,对Object进行改进。

MySQL1: 如何剖析性能

最近正好在阅读《高性能MySQL》,同时在公司做一次分享会,在这次分享会中我将会从几个方面开展

  1. 如何剖析性能
  2. 正确使用数据类型
  3. 如何创建正确的索引
  4. 优化查询

一、为什么需要性能优化

衡量性能一个重要原则:完成某件任务所需要的时间度量。性能即响应时间。对于数据库服务器,我们关注的是它查询或者其他操作语句(SELECT, UPDATE, INSERT)的响应时间。其次,我们有个误区,认为mysql占用CPU越低越好,这个其实不然,如果在有效的查询中,mysql能够很好的利用CPU资源,即时在100%的情况下,也能非常块的响应,那么我们需要考虑另外一方面的优化。另外,我们只把性能优化看作提升每秒查询量(即吞吐量),对查询的优化可以让服务器每秒执行更多的查询,因为每条查询所执行的时间更加的短了。

我们需要做优化,首先应该进行对数据有个精准的测量,要知道为什么会响应如此之慢。所以本文将具体探讨如何测量以及数据优化。

二、对应用程序进行性能剖析

我们不能只针对mysql服务器进行剖析,在很多情况下应用导致的性能问题也不少,其性能瓶颈可能有很多因素:

  • 外部资源,比如调用了其他web服务
  • 应用需要处理大量的数据,比如分析二进制数据
  • 在循环里执行昂贵的操作。比如滥用正则表达式
  • 使用了低效的算法

分析PHP性能的工具非常多,比如facebook开发的xhprof(https://github.com/facebook/xhprof),xdebug。

三、剖析MySQL查询

分析服务端的负载是很有价值的,因为在服务端可以有效地审计效率低下的查询。定位和优化“坏”的查询能够显著的提升应用性能。
第一种方法,慢查询日志最初只捕捉比较“慢”的查询,在5.1版本之后,做了增强,现在只要设置long_query_time为0就能捕获所有的查询。
第二种方法,通过TCP抓包,然后根据MySQL的客户端/服务端通信协议进行分析,在linux下可以使用tcpdump,windows下可以使用wiresharke。
在分析单条查询,我们可以借助系统自身的SHOW PROFILE、SHOW STATUS两种方式。

1. SHOW PROFILE

这命令是在5.1时候引用进来的,而且默认是禁用的,我们可以通过修改服务器变量启用它。

mysql>SET profiling = 1;

设置完毕后,将会检测其耗费的时间和其他一些查询执行状态变更相关数据。 当一条查询提交给服务器时,服务端将会建立一张临时表,将所有信息全部记录进去。

mysql>select * from pre_forum_post LIMIT 0, 3000;
3000 rows in set (0.16 sec)

然后使用SHOW PROFILES查看有什么结果。

mysql>SHOW PROFILES;
+----------+------------+--------------------------------------------+
| Query_ID | Duration   | Query                                      |
+----------+------------+--------------------------------------------+
|        1 | 0.01831450 | select * from pre_forum_post LIMIT 0, 3000 |
+----------+------------+--------------------------------------------+
1 rows in set, 1 warning (0.00 sec)

要具体看一条数据我们可以这样。

mysql> SHOW PROFILE FOR QUERY 1;
+----------------------+----------+
| Status               | Duration |
+----------------------+----------+
| starting             | 0.000068 |
| checking permissions | 0.000009 |
| Opening tables       | 0.000037 |
| init                 | 0.000031 |
| System lock          | 0.000015 |
| optimizing           | 0.000007 |
| statistics           | 0.000015 |
| preparing            | 0.000011 |
| executing            | 0.000004 |
| Sending data         | 0.018052 |
| end                  | 0.000017 |
| query end            | 0.000009 |
| closing tables       | 0.000013 |
| freeing items        | 0.000014 |
| cleaning up          | 0.000015 |
+----------------------+----------+
15 rows in set, 1 warning (0.00 sec)

EXPLAIN

mysql> EXPLAIN select * from pre_forum_post LIMIT 0, 3000;
+----+-------------+----------------+------+---------------+------+---------+------+----------+-------+
| id | select_type | table          | type | possible_keys | key  | key_len | ref  | rows     | Extra |
+----+-------------+----------------+------+---------------+------+---------+------+----------+-------+
|  1 | SIMPLE      | pre_forum_post | ALL  | NULL          | NULL | NULL    | NULL | 36873047 | NULL  |
+----+-------------+----------------+------+---------------+------+---------+------+----------+-------+
1 row in set (0.00 sec)

mysql> EXPLAIN select * from pre_forum_post as a LEFT JOIN pre_forum_thread as b ON a.tid = b.tid GROUP BY a.author ORDER BY b.tid;
+----+-------------+-------+--------+---------------+---------+---------+--------------+----------+---------------------------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref          | rows     | Extra                           |
+----+-------------+-------+--------+---------------+---------+---------+--------------+----------+---------------------------------+
|  1 | SIMPLE      | a     | ALL    | NULL          | NULL    | NULL    | NULL         | 36873047 | Using temporary; Using filesort |
|  1 | SIMPLE      | b     | eq_ref | PRIMARY       | PRIMARY | 3       | plu_dx.a.tid |        1 | NULL                            |
+----+-------------+-------+--------+---------------+---------+---------+--------------+----------+---------------------------------+
2 rows in set (0.02 sec)

四、诊断间隙性的问题

间隙性处问题是很难诊断的,它可能因为:

  1. 应用服务器与MySQL服务器出现短暂的网络延迟
  2. memcached或者redis中的数据过期,导致这些请求全部需要从MySQL中获取
  3. DNS查询偶尔会有超时
  4. 互斥锁争用,或者内部删除查询缓存的算法效率太低
  5. 并发超过一定的阀值

是因为查询所导致的问题还是服务器?我们可以使用SHOW GLOBAL STATUS、SHOW PROCESSLIST、查询日志等等手段。

PHP Routing Part 1:Request


php开发中,如果不是mvc模式,在规划路径的时候通常使用目录结构,或者在根目录下建立有意义的php文件。
例如:

http://www.example.com/user/login.php
http://www.example.com/user.php?actinon=login

但使用MVC模式写站点的的时候,由于php的特性,必须要一个router管理及派发。在谈router之前,首先从request开始谈起.

Request, 请求。用户输入网址按回车,php收到的客户端数据就是Request。我们通过分析request,知道当前用户需要访问哪个页面,哪个动作,哪些数据等等信息。通常情况下request只包含header,但在put状态同时还会包含body部分。

一、 header

在求请求头中,会包含用户浏览器传递到服务端的各种信息,如果用户ip地址、正在访问的url、userAgent、cookie等等参数。可以通过firefox的firebug或者Chrome的调试工具, 点击network。可以查看到当前页的Request信息。如下图所示:
php_request_header_info

header在php中如果访问到?可以从$_SERVER数组中读取HTTP_{$key}的信息。

二、 body

body只有在两种请求状态下会出现:put、delete。并且不是表单提交状态。这一点非常重要,如果是表单提交,那么可以从$_POST中读取。下面会具体讲到。
如何读取?非常简单,直接通过php://input就能读取到数据流。

$fh = fopen("php://input", "r");
$content = stream_get_contents($fh);
fclose($fh);

这种主要出现在resetFul服务中,客户端直接向服务端put/delete json或者xml格式的数据。

三. $_GET

在这里插入一个题外话,在php中一个特殊的数据$_REQUEST,它同时包含了$_GET和_$POST中的数据,如果直接通过$_REQUEST数据是不安全的,此时你没法分清用户传来的数据是post还是get,极大的降低了服务端的安全性。所以不推荐使用$_REQUEST代替$_GET和$_POST读取用户传递的数据信息。
在php.ini中开启magic_quotes_gpc,需要在分析前stripslash。$_GET数据来自于链接?后面的一串字符。以xxx[=yy]形式出现,中间使用‘&’符号相连接。xxx为key,yy为value。在php中就展现成一个数据形式。

array(
'xxx' => 'yy',//xxx=yy
'aaa' => '',//aa
);

在MVC中特别需要注意。一般情况下,一个站点会将域名‘/’后面的字符串全部传给index.php。那么$_GET中就只包含了这个信息。这里要注意一点,这时候链接中带有?后面的字符串,紧紧是字符串,它不会被php自动转化成$_GET中的项。我们只需要从字符串中提取‘?’后面的数据 /user/login?returnurl=http://www.example.com。然后通过parse_str函数分析,然后与$_GET进行合并,

if (ini_get('magic_quotes_gpc') === '1') {
	$query = stripslashes_deep($_GET);
} else {
	$query = $_GET;
}
if (strpos($url, '?') !== false) {
	list(, $querystr) = explode('?', $url);
	parse_str($querystr, $queryArgs);
	$query += $queryArgs;
}

四. $_POST

$_POST只用通过表单,并且表单属性中的method设为post。

五. $_FILES

文件上传处理。文件上传通过页面上表单,并且form属性需要设置encrypt=multipart/form-data,同时form中需要包含一个input type="file",php $_FILES中才会有数据。上传后的数据存储在/tmp(linux)或者/APPDATA/temp(windows)目录下,这个文件在15分钟后会被系统自动回收。

在下一篇会讲到Router,分析url地址,根据设定的规则,自动加载controller文件,控制response,在通过view渲染网页向客户端输出网页。

hex2bin for PHP

在php5.4中, 已经有了hex2bin函数, 可以快速的将hex转为二进制.而在低于5.4的版本中如何处理? 可以通过pack进行转换

function hex2bin($hexstr) {
$n = strlen($hexstr);
$sbin=””;
$i=0;
while($i<$n){ $a =substr($hexstr,$i,2); $c = pack("H*",$a); if ($i==0){ $sbin=$c; } else { $sbin.=$c; } $i+=2; } return $sbin; } [/php]

使用C开发包裹第三方的PHP扩展

一. 快速上手

建立php扩展, 我们可以直接使用源代码目录下的ext_skel生成一个初步的框架. 第一步我们需要给他函数定义文件, 该函数定义文件定义了扩展对外提供的函数原形。函数定义文件的一般格式是一个函数一行。你可以定义可选参数和使用大量的PHP类型,包括: bool, float, int, array等。

resource SFileOpenArchive(string name, int priority, int flags)

保存为myfunctions.def文件至PHP原代码目录树下。

该是通过扩展骨架(skeleton)构造器运行函数定义文件的时机了。该构造器脚本叫ext_skel,放在PHP原代码目录树的ext/目录下(PHP原码主目录下的README.EXT_SKEL提供了更多的信息)。假设你把函数定义保存在一个叫做myfunctions.def的文件里,而且你希望把扩展取名为myfunctions,运行下面的命令来建立扩展骨架.

./ext_skel --extname=myfunctions --proto=myfunctions.def

Read more »

自定义stdout

在python中, 有时候你需要捕捉print的输出保存到一个指定的文件下面. 一般的情况下, print实际上是调用的sys.stdout. 如果你需要需要对print进行捕捉. 你只要需要hook sys.stdout即可.

例如我们将所有的print都保存到StringIO下, 只需要写成

from StringIO import StringIO
import sys
old_stdout = sys.stdout
sys.stdout = mystdout = StringIO()

#获取内部值
sys.stdout = old_stdout
print mystdout.getvalue()
mystdout.close()

但是当你的print中有unicode字符的时, 使用StringIO会报TyperError或者UnicodeError之类的错误.这个时候我们需要进行一下改写.

from StringIO import StringIO
import sys, itertools

class Logger(object):
  def __init__(self):
    self.log = StringIO();
    self.terminal = sys.stdout

  def write(self, message):
    self.log.write(self.safestr(message))

  def safestr(self, obj, encoding='utf-8'):
    #包含能正确转换
    if isinstance(obj, unicode):
      return obj.encode(encoding)
    elif isinstance(obj, str):
      return obj
    elif hasattr(obj, 'next'):
      return itertools.imap(self.safestr, obj)
    else:
      return str(obj)

  def close(self):
    self.log.close()

  def output(self):
    self.terminal.write(self.log.getvalue())

old_stdout = sys.stdout
sys.stdout = Logger()

print "Hello"

print u"测试"

#将所有catch的输出到terminal
sys.stdout.output()

"""
Hello
测试
"""
sys.stdout.close()

在print调用write使用, 我们将message进行一次转码. 在通过调用StringIO().getvalue方法的时候, 就不会报错了.

如果你想将写入文件, 只需要在self.log这部分进行一下改动即可.

self.log = open("debug.log", "a")

bash脚本小记1

1. Bash中的数组
注意如果你头部使用了”#!/bin/sh”可能会不支持. 这个时候你需要改成”#!/bin/bash”
在bash中定义一个数组 myarray = (a b c)
里面每个元素之间用空格隔开.
计算当前数组的数量, ${#myarray[@]}
获取当前数组所以元素 ${myarray[@]}
或者其中某一个元素 ${myarray[index]} 例如第一个元素即${myarray[0]}

遍历数据

for el in "${myarray[@]}"; do echo "$el"; done 

1.1 数组转成字符串(相当于 join, concat这类js函数)

FOO=( a b c )
SAVE_IFS=$IFS
IFS=","
FOOJOIN="${FOO[*]}"
IFS=$SAVE_IFS
echo $FOOJOIN

2. Mysql
使用mysql命令行 将select的数据转成数据
只需要

MYSQL_BIN=`which mysql`
DUMPARGS="-u $DB_USER -p$DB_PASSWD --skip-triggers --compact --skip-extended-insert --no-create-info $DATABASE"
list=($($MYSQL_BIN $QUERY_ARGS 'SELECT aid FROM dede_addonarticle order by aid DESC LIMIT 0, 1000'))

echo ${#list[@]}
echo ${list[@]}

3 Mysqldump

mysqldump -u $DB_USER -p$DB_PASSWD --skip-triggers --compact --skip-extended-insert --no-create-info $DATABASE   table --where=""

在git中使用Python打补丁包


有时候, 我们写完一个项目需要对其进行打包. 打完成包非常简单, 我们可以使用git archive这个命令. 但是有时候我们需要对项目的一些特殊文件进行过滤. 这个时候, 可能会说这些文件可以放在.gitingore中. 我需要告诉的时, 在开发项目的时候, 有些文件也需要在git仓中, 以方便其他小组的人使用. 另一种情况, 我们需要打一个差异包出来, 如果没有任何特殊要求, 可以直接使用tar zxvf xxx-patch.tar.gz `git diff –name-only`进行打包. 但是你这个仓中含有很多个submodule时候, 使用这种方法, 会遇到一个很不幸的事, 不管这个子模块修改了一个, 还是多个文件, 他都会将这个子模块全部打包进去. 因此, 我们需要一个好的打包工具, 解决以上问题. 目前我是采用python + pygit2的方式. 同时你系统环境中要有git.

1. 获取git仓中tags列表

pygit2有一个已知的bug, 你给你的项目打tag, 如果打tag时候没有加上message. pygit2是无法读取到的.
读取git仓中的tag非常的简单. 代码如下

def get_tags(self):
 self.tags = [];
 print "=== Parsing package version";
 data = self.repo.listall_references();

 for item in data:
  ref = self.repo.lookup_reference(item);
  if (ref.type == pygit2.GIT_OBJ_COMMIT):
   oid = ref.oid;
   ref_obj = self.repo[oid];
   if isinstance(ref_obj, pygit2.Tag):
    self.tags.append(ref_obj);
   else:
    continue;
 self.tags.sort(lambda x,y: cmp(y.tagger.time, x.tagger.time));

获取tag之后, 我们可以使用tag下属性timer, 根据时间先后次序进行排序. 这样你想取最后一个tag作为目标进行打包 只要self.tags[0](倒序)就能直接获取到. 同样你需要获取两个版本的差异. 只要git diff tag[0]..tag[1]
Read more »