[转]mysql长连接好不好?

PHP的MySQL持久化连接,美好的目标,却拥有糟糕的口碑,往往令人敬而远之。这到底是为啥么。近距离观察后发现,这家伙也不容易啊,要看Apache的脸色,还得听MySQL指挥。
对于作为Apache模块运行的PHP来说,要实现MySQL持久化连接,首先得取决于Apache这个web服务器是否支持Keep-Alive。
Keep-Alive
Keep-Alive是什么东西?它是http协议的一部分,让我们复习一下没有Keep-Alive的http请求,从客户在浏览器输入一个有 效url地址开始,浏览器就会利用socket向url对应的web服务器发送一条TCP请求,这个请求成功一次就得需要来回握三次手才能确定,成功以 后,浏览器利用socket TCP连接资源向web服务器请求http协议,发送以后就等着web服务器把http返回头和body发送回来,发回来后浏览器关闭socket连接, 然后做http返回头和body的解析工作,最后呈现在浏览器上的就是漂亮的页面了。这里面有什么问题呢?TCP连接需要三次握手,也就是来回请求三次方 能确定一个TCP请求是否成功,然后TCP关闭呢?来回需要4次请求才能完成!每次http请求就3次握手,4次拜拜,这来来回回的不嫌累啊,多少时间和 资源都被浪费在socket连接关闭上了,能不能一次socket TCP连接发送多次http请求呢?于是Keep-Alive就应运而生,http/1.0里需要客户端自己在请求头加入 Connection:Keep-alive方能实现,在这里我们只考虑http1.1了,只需要设置一下Apache,让它默认就是Keep- Alive持久连接模式(Apache必须1.2+才能支持Keep-Alive)。在httpd.conf里找到KeepAive配置项,果断设置为 On,MaxKeepAliveRequests果断为0(一个持久TCP最多允许的请求数,如果过小,很容易在TCP未过期的情况下,达到最大连接,那 下次连接就又是新的TCP连接了,这里设置0表示不限制),然后对于mysql_pconnect最重要的选项KeepAliveTimeout设置为 15(表示15秒)。
好了,重启Apache,测试一下,赶紧写行东西:
很简单,获取当前PHP执行者(Apache)的进程号,用浏览器浏览这个页面,看到什么?对,有看到一串进程号数字,15秒内,连续刷新页面, 看看进程号有无变化?木有吧?现在把手拿开,交叉在胸前,度好时间,1秒,2秒,3,…15,16。好,过了15秒了,再去刷新页面,进程号有没有变 化?变了!又是一个新的Apache进程了,为什么15秒后就变成新的进程了?记得我们在Apache里设置的KeepAliveTimeout吗?它的 值就是15秒。现在我们应该大致清楚了,在web服务器默认打开KeepAlive的情况下,客户端第一次http成功请求后,Apache不会立刻断开 socket,而是一直监听来自这一客户端的请求,监听多久?根据KeepAliveTimeout选项配置的时间决定,一旦超过这一时间,Apache 就会断开socket了,那么下次同一客户端再次请求,Apache就会新开一个进程来相应。所以我们之前15内不停的刷新页面,看到的进程号都是一致 的,表明是浏览器请求给了同一个Apache进程。
浏览器是怎么知道不需要重新进行TCP连接就可以直接发送http请求呢?因为http返回头里就会带上Connection:keep- alive,Keep-alive:15两行,意思就是让客户端浏览器明白,这次socket连接我这边还没关闭呢,你可以在15内继续使用这个连接,并 发送http请求,于是乎浏览器就知道应该怎么做了。
PHP怎么做
那么,PHP的MySQL连接资源是怎么被hold住的呢,这需要查看PHP的mysql_pconnect的函数代码,我看了下,大概的做法就 是mysql_pconnect根据当前Apache进程号,生成hash key,找hash表内有无对应的连接资源,没有则推入hash表,有则直接使用。有些代码片段可以说明(具体可查看PHP5.3.8源码 ext/mysql/PHP_mysql.c文件690行PHP_mysql_do_connect函数)
#1.生成hash key user=php_get_current_user();//获取当前PHP执行者(Apache)的进程唯一标识号 //hashed_details就是hash key hashed_details_length = spprintf(&hashed_details, 0, “MySQL__%s_”, user); #2.如果未找到已有资源,就推入hash表,名字叫persistent_list,如果找到就直接使用 if (zend_hash_find(&EG(persistent_list), hashed_details, hashed_details_length+1, (void **) &le)==FAILURE) { … … Z_TYPE(new_le) = le_plink; new_le.ptr = mysql; if (zend_hash_update(&EG(persistent_list), hashed_details, hashed_details_length+1, (void *) &new_le, sizeof(zend_rsrc_list_entry), NULL)==FAILURE) { … … } } else { … … mysql = (PHP_mysql_conn *) le->ptr;//直接使用对应的sql连接资源 … … }
zend_hash_find比较容易看明白,原型是zend_hash_find(hash表,key名,key长,value);如果找到,value就有值了。
MySQL的wait_timeout和interactive_timeout
说完Keep-Alive,该到MySQL家串串门了,说的是mysql_pconnect,怎么能绕开MySQL的设置。影响 mysql_pconnect最重要的两个参数就是wait_timeout和interactive_timeout,它们是什么东西?先撇一边,首先 让我们把上面的代码改动一下PHP代码
以上的代码没啥好解释的,让我们用浏览器浏览这个页面,看到什么?看到两个显眼的数字。一个是MySQL线程号,一个是Apache进程号,好 了,15秒后再刷新这个页面,发现这两个id都变了,因为已经是新的Apache进程了,进程id是新的,hash key就变了,PHP只好重新连接MySQL,连接资源推入persistent list。如果15内刷新呢?Apache进程肯定不变,MySQL线程号会变吗?答案得问MySQL了。首先这个MySQL_thread_id是什么 东西?shell方式登录MySQL后执行命令’show processlist;’,看到了什么?
mysql> show processlist; +—–+——+———–+——+——–+—–+——+—————–+ | Id | User | Host | db | Command| Time| State| Info | +—–+——+———–+——+——–+—–+——+—————–+ | 348 | root | localhost | NULL | Query | 0| NULL | show processlist| | 349 | root | localhost | NULL | Sleep | 2| | NULL | +—–+——+———–+——+——–+—–+——+—————–+
发现了很重要的信息,这个processlist列表就是记录了正在跑的线程,忽略Info列为show processlist那行,那行是你当前shell登录MySQL的线程。PHP连接MySQL的线程就是Id为349那行,如果读者自己做测试,应该 知道这个Id=349在你的测试环境里是另外一个值,我们把这个值和网页里输出的MySQL_thread_id($conn)做做比较,对!他们是一样 的。接下来最重要的是观察Command列和Time列,Command = Sleep,表明什么?表明我们mysql_pconnect连接后就一直在sleep,Time字段就告诉我们,这个线程Sleep了多久,那么 Sleep了多久这个线程才能作废呢?那就是wait_timeout或者interactive_timeout要做的工作了,他们默认的值都是8小 时,天啊,太久了,所以如果说web服务器关掉KeepAlive支持,那个这个processlist很容易就被撑爆,就爆出那个Too many connections的错误了,max_connectiosns配置得再多也没用。为了观察这两个参数,我们可以在MySQL配置文件my.cnf里 设置这两个值,找到[MySQLd]节点,在里面设置多两行
interactive_timeout = 60 wait_timeout = 30
配置完后,重启MySQL,shell登录MySQL,这时候show processlist可以发现只有当前线程。然后运行那个带有mysql_pconnect的PHP页面,再回来MySQL端show processlist可发现,多了一个Commond为Sleep的线程,不停的show processlist(方向键上+enter键)观察Time列的变化2,5,10…14!,突然那个Sleep线程程被kill掉了,咋回事,还 没到30秒呢,噢!忘了修改一下Apache keepalive的参数了,把KeepAliveTimeOut从15改成120(只为观察,才这么改),重启Apache。刷新那个页面,好,开始不 停的show processlist,2..5..10..14,15,..20…26….28,29!线程被kill,这次是因为wait_timeout 起了作用,浏览器那边停了30秒,30内如果浏览器刷新,那这个Time又会从0开始计时。这种连接不属于interactive connection(MySQL shell登录那种连接就属于interactive connection),所以采用了wait_timeout的值。如果mysql_pconnect的第4个参数改改呢
刷新下页面,MySQL那边开始刷show processlist,这回Time > 30也不会被kill,>60才被kill了,说明设置了MySQL_CLIENT_INTERACTIVE,就会被MySQL视为 interactive connection,那么这次PHP的MySQL连接在120秒内未刷新的情况下,何时作废将取决于MySQL的 interactive_timeout的配置值。
总结
PHP的mysql_pconnect要达到功效,首先必须保证Apache是支持keep alive的,其次KeepAliveTimeOut应该设置多久呢,要根据自身站点的访问情况做调整,时间太短,keep alive没啥意义,时间太长,就很可能为一个闲客户端连接牺牲很多服务器资源,毕竟hold住socket监听进程是要消耗cpu内存的。最后 Apache的KeepAliveTimeOut配置得和MySQL的time out配置要有个平衡点,联系以上的观察,假设mysql_pconnect未带上第4个参数,如果Apache的KeepAliveTimeOut设置 的秒数比wait_timeout小,那真正对mysql_pconnect起作用的是Apache而不是MySQL的配置。这时如果MySQL的 wait_timeout偏大,并发量大的情况下,很可能就一堆废弃的connection了,MySQL这边如果不及时回收,那就很可能Too many connections了。可是如果KeepAliveTimeOut太大呢,又回到之前的问题,所以貌似Apache。KeepAliveTimeOu 不要太大,但比MySQL。wait_timeout 稍大,或者相等是比较好的方案,这样可以保证keep alive过期后,废弃的MySQL连接可以及时被回收。

[转]memcache vs memcached?

Here is a quick backgrounder in naming conventions (for those unfamiliar), which explains the frustration by the question asker: For many *nix applications, the piece that does the backend work is called a “daemon” (think “service” in Windows-land), while the interface or client application is what you use to control or access the daemon. The daemon is most often named the same as the client, with the letter “d” appended to it. For example “imap” would be a client that connects to the “imapd” daemon.

This naming convention is clearly being adhered to by memcache when you read the introduction to the memcache module (notice the distinction between memcache and memcached in this excerpt):

Memcache module provides handy procedural and object oriented interface to memcached, highly effective caching daemon, which was especially designed to decrease database load in dynamic web applications.

The Memcache module also provides a session handler (memcache).

More information about memcached can be found at » http://www.danga.com/memcached/.
The frustration here is caused by the author of the PHP extension which was badly named memcached, since it shares the same name as the actual daemon called memcached. Notice also that in the introduction to memcached (the php module), it makes mention of libmemcached, which is the shared library (or API) that is used by the module to access the memcached daemon:

memcached is a high-performance, distributed memory object caching system, generic in nature, but intended for use in speeding up dynamic web applications by alleviating database load.

This extension uses libmemcached library to provide API for communicating with memcached servers. It also provides a session handler (memcached).

Information about libmemcached can be found at » http://tangent.org/552/libmemcached.html.
In summary, both are functionally the same, but they simply have different authors, and the one is simply named more appropriately than the other.
原文:http://serverfault.com/questions/63383/memcache-vs-memcached

[nginx]nginx做反向代理和负载均衡

一 前言
先记录一下nginx和apache的优缺点:
nginx 相对 apache 的优点:
轻量级,同样起web 服务,比apache 占用更少的内存及资源
抗并发,nginx 处理请求是异步非阻塞的,而apache 则是阻塞型的,在高并发下nginx 能保持低资源低消耗高性能
高度模块化的设计,编写模块相对简单
社区活跃,各种高性能模块出品迅速啊

apache 相对nginx 的优点:
rewrite ,比nginx 的rewrite 强大
模块超多,基本想到的都可以找到
少bug ,nginx 的bug 相对较多
超稳定

二 nginx做反向代理
修改部署目录下conf子目录的nginx.conf文件(如nginx-1.5.13\conf\nginx.conf)内容,可调整相关配置。

反向代理配置示例:

location / {
#设置主机头和客户端真实地址,以便服务器获取客户端真实IP
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#禁用缓存
proxy_buffering off;
#设置反向代理的地址
proxy_pass http://192.168.1.1;
}
代理地址根据实际情况修改。
这样访问到该网站的资源都会去192.168.1.1的机器上去取,实现反向代理。
配置 location ~ \.php$可以让不同的资源去不同的机器上获得。

三 nginx负载均衡
nginx 的 upstream默认是以轮询的方式实现负载均衡,这种方式中,每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
另外一种方式是ip_hash:每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
负载均衡配置示例:
upstream backend {
#ip_hash;
server 192.168.1.251;
server 192.168.1.252;
server 192.168.1.247;
}
server {
listen 80;
server_name trffweb;
location / {
#反向代理的地址
proxy_pass http://backend;
}
}
Upstream命名和服务器地址根据实际情况修改。

[nginx]accesslog分析

nginx 产生的Log一般在nginx.conf中进行配置,如默认的Log格式:

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log  main;

一条典型的accesslog:
101.226.166.254 – – [21/Oct/2013:20:34:28 +0800] “GET /movie_cat.php?year=2013 HTTP/1.1” 200 5209 “http://www.baidu.com” “Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; MDDR; .NET4.0C; .NET4.0E; .NET CLR 1.1.4322; Tablet PC 2.0); 360Spider”
下面我们来说说这一行记录的意思:
1)101.226.166.254:(用户IP)
2)[21/Oct/2013:20:34:28 +0800]:(访问时间)
3)GET:http请求方式,有GET和POST两种
4)/movie_cat.php?year=2013:当前访问的网页是动态网页,movie_cat.php即请求的后台接口,year=2013为具体接口的参数
5)200:服务状态,200表示正常,常见的还有,301永久重定向、4XX表示请求出错、5XX服务器内部错误
6)5209:传送字节数为5209,单位为byte
7)”http://www.baidu.com”:refer:即当前页面的上一个网页
8)”Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; MDDR; .NET4.0C; .NET4.0E; .NET CLR 1.1.4322; Tablet PC 2.0); 360Spider”: agent字段:通常用来记录操作系统、浏览器版本、浏览器内核等信息

通过某些脚本分析Log,就可以获取全站的PV,某个链接的PV,然后进行统计分析

另外某些工具如:goAccess能够很好的分析日志,并且产生可视化的Html图表,比较直观好用
在 CentOS 6.5 上安装编译 GoAccess 时需要的工具和库:
# yum groupinstall ‘Development Tools’
# yum install glib2 glib2-devel ncurses-devel
在 Ubuntu 12.04 上安装编译 GoAccess 时需要的工具和库:
$ sudo apt-get install build-essential
$ sudo apt-get install libglib2.0-dev libncursesw5-dev
下载 GoAccess 的源代码、编译和安装:
$ wget http://downloads.sourceforge.net/project/goaccess/0.7.1/goaccess-0.7.1.tar.gz
$ tar -xzvf goaccess-0.7.1.tar.gz
$ cd goaccess-0.7.1/
$ ./configure –enable-geoip –enable-utf8
$ make
$ sudo make install
最后
vim ~/.goaccessrc
写入

date_format %d/%b/%Y:%T %z
log_format %h - - [%d] "%r" %s %b "%R" "%u"

使用方法:
生成html: goaccess -f access.log -a > report.html
直接查看: goaccess -f access.log

[php]通过单例模式看self,static,$this的区别

单例模式比较简单,下面是一种写法:

    public static function getInstance() {
        static $i = null;  //注:声明时不能是函数或者计算
        (is_null($i)) && ($i = new static());
        return $i;
    }

这里想说new static() 和 self()的区别
官方资料这样写:
self refers to the same class in which the new keyword is actually written.
static, in PHP 5.3’s late static bindings, refers to whatever class in the hierarchy you called the method on.
self是当前类,而static取决你调用方法的层次
例子如下:

class A {
    public static function className(){
        echo __CLASS__;
    }

    public static function test(){
        self::className();
    }
}

class B extends A{
    public static function className(){
        echo __CLASS__;
    }
}

B::test();  //A
class A {
    public static function className(){
        echo __CLASS__;
    }

    public static function test(){
        static::className();
    }
}

class B extends A{
    public static function className(){
        echo __CLASS__;
    }
}
B::test(); //B

对于$this 和self
this是指向当前对象的指针,self是指向当前类的指针
也有网友这样总结:
Use $this to refer to the current object. Use self to refer to the current class. In other words, use $this->member for non-static members, use self::$member for static members.
只要注意在静态函数或者静态变量上的使用,就能应对一般的问题

[php]Yii框架下的curl component

php的curl用法网上介绍的很多了,这里整理一下自己的写法

<?php

class EApi extends CComponent {

    const ERROR_INVALID_TOKEN   = 20001;
    const ERROR_MISSING_PARAM   = 20002;
    const ERROR_INVALID_PARAM   = 20003;
    const ERROR_EMPTY_RESPONSE  = 20004;

    const CACHE_DURATION = 3600;

    public $accessToken;
    public $connectTimeout = 1;
    public $timeout = 5;

    protected $_currentUrlId;
    protected $_urls;
    protected $_ch;

    public function init() {
        //非必须
        $ua = "api/0.1"; defined("APP_MODE") && $ua .= "-".APP_MODE;

        $opt = array(
            CURLOPT_RETURNTRANSFER  => TRUE,         // return web page
            CURLOPT_HEADER          => FALSE,        // don't return headers
            CURLOPT_FOLLOWLOCATION  => FALSE,        // follow redirects
            CURLOPT_ENCODING        => "",           // handle all encodings
            CURLOPT_USERAGENT       => $ua,
            CURLOPT_AUTOREFERER     => TRUE,         // set referer on redirect
            CURLOPT_CONNECTTIMEOUT  => $this->connectTimeout,   // timeout on connect
            CURLOPT_TIMEOUT         => $this->timeout,          // timeout on response
            CURLOPT_SSL_VERIFYHOST  => 0,            // don't verify ssl
            CURLOPT_SSL_VERIFYPEER  => FALSE,        //
            CURLOPT_VERBOSE         => FALSE,        //
        );

        $this->_ch = curl_init();
        curl_setopt_array($this->_ch, $opt);
    }

    public function getApiUrl() {
        if (empty($this->_urls)) {
            return null;
        }
        if ($this->_currentUrlId === null) {
            $this->_currentUrlId = array_rand($this->_urls);
        }
        return $this->_urls[$this->_currentUrlId];
    }

    public function clearApiUrl() {
        unset($this->_urls[$this->_currentUrlId]);
        unset($this->_currentUrlId);
    }

    public function setUrls($urls) {
        $this->_urls = array();
        foreach ($urls as $url) {
            if ("http" != parse_url($url, PHP_URL_SCHEME)) {
                Yii::warning("lianjia-feed server is wrong", $url, "application.linajia-feed");
                continue;
            }
            $this->_urls[] = $url;
        }
    }

    public function query($name, array $params=array(), $post = array()) {

        $urlPrefix = $this->getApiUrl();
        if (empty($urlPrefix)) {
            Yii::warning("no valid lianjia-feed server url exists", null, "application.lianjia-feed");
            return false;
        }
        $query = http_build_query($params);
        $url = $urlPrefix . $name . (strpos($urlPrefix . $name, "?") ? "&" : "?") . $query;
        
        $cacheKey = sprintf("%s:%s?%s", "key", $name, $query);
        $ret = Yii::app()->cache->get($cacheKey);
        if ($ret) {
            return $ret;
        }
//         var_dump($url);
//         die();
        $ch = $this->_ch;
        curl_setopt($ch, CURLOPT_URL, $url);
        if($post) {
            $post = http_build_query($post);
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
        }
        $raw = curl_exec($ch);
        $errno = curl_errno($ch);

        if ($errno == CURLE_COULDNT_CONNECT) {
            Yii::warning("Couldn't connect server.", $this->_urls[$this->_currentUrlId]);
            unset($this->_urls[$this->_currentUrlId]);
            $this->_currentUrlId = null;
            return $this->query($name, $params);
        }

        if ($errno != CURLE_OK) {
            $errstr = curl_error($ch);
            Yii::fatal("HTTP Error: cURL[{$errno}] {$errstr}", null, "application.lianjia-feed");
            return false;
        }

        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        Yii::info(sprintf("HTTP recieved %.1fKB in %.1fms ",
            curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD)/1024,
            curl_getinfo($ch, CURLINFO_TOTAL_TIME)*1000), null, "application.lianjia-feed");

        if ($code != 200) {
            Yii::fatal("HTTP Error: HTTP[{$code}]", null, "application.lianjia-feed");
            Yii::debug("HTTP Error Response:", $raw, "application.lianjia-feed");
            return false;
        }

        if (empty($raw)) {
            Yii::fatal("HTTP Error: Empty response", null, "application.lianjia-feed");
            return false;
        }

        $res = json_decode($raw, true);       
        
        Yii::app()->cache->set($cacheKey, $res["data"], self::CACHE_DURATION);
        return $res["data"];
    }
    
    //接口地址及其参数
    public function getApi($get, $post) {
        //第二个,第三个为get及其post参数
        return $this->query("feed/getNotifyNum", array("get" => $get), array("post" => $post));
    }

另外在config中添加

        'api' => array(
            'class' => 'ext.EApi',
            'urls'  => array(
                'http://172.0.0.1:8090/',  //IP地址
            ),     
        ),

使用的时候直接调用

Yii::app()->api->getApi($get, $post);

[php]打印字符a-z的问题

php打印字符a~z,先看个例子

        for($i = 'a'; $i <= 'z'; $i ++) {
            echo ($i)." " ;
        }

好像没什么问题,打印结果却是:
a b c d e f g h i j k l m n o p q r s t u v w x y z aa ab ac ad ae af ag ah ai aj ak al am an ao ap aq ar as at au av aw ax ay az ba bb …. (省略)yy yz

经过好心人提醒,原来php是用26进制,类似于十进制的a=1,b=2….z=25那么aa=26。在做$i++时候符合这个规则。
而做比较时候,’aa’和’z’比较时,还是按照字符串比较的规则,首字母大的则大,所以’aa’还是小于’z’.最后就是出现以上结果。

下面附上几二种简单的方式:

for($i=ord('a'); $i<ord('z'); $i++) {
    echo chr($i);
}
echo implode(',', range('a', 'z'));

[php]生成验证码API

网站很多时候需要用到验证码,PHP生成验证码也很方便,这里用到了php的GD库,GD库,是php处理图形的扩展库,GD库提供了一系列用来处理图片的API,使用GD库可以处理图片,或者生成图片。

生成验证码的代码如下

    public function actionGetVerifyCode() {
        $width = 80;
        $height = 36;
        $length = 4;
        $codelen = 4;
        //生成验证码
        $charset = 'abcdefghkmnprstuvwxyzABCDEFGHKMNPRSTUVWXYZ23456789';    //随机因子
        $code = substr(str_shuffle($charset), 0, $codelen);
        //绘制图片
        $width = ($length * 10 + 10) > $width ? $length * 10 + 10 : $width;

        $img = imagecreatetruecolor($width, $height);
        $r = array(225, 255, 255, 223);
        $g = array(225, 236, 237, 255);
        $b = array(225, 236, 166, 125);
        $key = mt_rand(0, 3);

        $backColor = imagecolorallocate($img, $r[$key], $g[$key], $b[$key]);    //背景色(随机)
        $borderColor = imagecolorallocate($img, 100, 100, 100);                    //边框色
        imagefilledrectangle($img, 0, 0, $width - 1, $height - 1, $backColor);
        imagerectangle($img, 0, 0, $width - 1, $height - 1, $borderColor);
        $stringColor = imagecolorallocate($img, mt_rand(0, 200), mt_rand(0, 120), mt_rand(0, 120));
        //生成干扰素
        for ($i = 0; $i < 5; $i++) {
            imagearc($img, mt_rand(-10, $width), mt_rand(-10, $height), mt_rand(30, 300), mt_rand(20, 200), 55, 44, $stringColor);
        }
        for ($i = 0; $i < 25; $i++) {
            imagesetpixel($img, mt_rand(0, $width), mt_rand(0, $height), $stringColor);
        }
        for ($i = 0; $i < $length; $i++) {
            imagestring($img, 10, $i * 20 + 5, mt_rand(1, 8), $code{$i}, $stringColor);
        }
        //输出图片
        header("Content-type: image/png");
        setcookie("validReg", md5(strtolower($code)), time() + 600, '/');
        imagepng($img);
        imagedestroy($img);
        //返回验证码
    

使用验证码时,只要取cookie中的validReg即可

        $cookieCode = isset($_COOKIE['validReg']) ? $_COOKIE['validReg'] : "";
        $verifyCode = isset($_POST['verifyCode']) ? $_POST['verifyCode'] : "";
        if ($cookieCode != md5(strtolower($verifyCode))) {
            $this->_processJson(array('errno' => -1, 'error' => '验证码错误'));
            return;
        }

PHP常用的魔术方法

常用的魔术方法有:__Tostring () __Call() __autoLoad() __ clone() __GET() __SET() __isset() __unset()

1.__Tostring() 用于定义输出对象引用时调用 常用于打印一些对象的信息 必须有返回值
eg:有一个persion类
Persion per =new persion()
Echo per; //直接调用会出错
我们可以在类的定义中添加__tostring()方法
Function __Tostring()
{
$str=this->$name.this->age;
Return $str;
}
2.__clone()对象的复制
引用赋值
$per1=$per2; 而这在内存中只有一块地址
而$per1=clone $per2 这时有两块内存地址

3.__call()方法 当调用类实例中不存在的函数时自动执行
如果试图调用类中不存在的函数,会出现语法错误,为了能够友好的提示
我们可以在类中声明Call()方法;
Function __call($funName,$argu)
{
Echo “名为”.$funName.”参数为”.printf($argh).”的函数不存在”,
}
4.__autoLoad 自动加载使用的类文件 该函数是在引用的页面添加
我们都使用过这样情况,在页面中需要调用其他php文件,我们需要使用include方法
但是如果有几十个页面需要引用,未免太过繁琐,我们可以在该页面中使用autoload方法
Function __autoload($className)
{
Include $className.”.php”;
}
这样凡是引用到其他类的地方,都会自动引用该类文件 前提类文件的名称必须是 类名.php
5.__GET() 访问类中私有属性
如果类中的属性设置为私有属性,在类的实例中是无法访问的,但怎样才能访问呢?
我们就可以使用__GET()
Eg :
类中有
Class person
{
Private $name;
Private $age;
}
实例化 person per=new person()
Per->$name; //这样是取不到值的
但是如果我们在类中增加__GET方法
Function __GET($proName)
{
Return this->$proName;
}
我们再次调用Per->$name 就可以访问了
这样做有人会提出疑问了,这样可以直接访问私有变量,和声明成公有的有什么区别呢?
如果声明成公有的,我们可以任意读取,如果是私有,如果我们增加了get方法,那么每次调用私有属性都会调用GET方法的内容,这样我们就可以在get方法中增加一些逻辑处理。
6.__SET()设置类中的私有属性
原理同上,我们可以再类中添加__SET()函数,每当通过调用类实例给私有属性赋值,都会执行__SET函数 ,函数原型:
Function __SET($proName,$value)
{
This->$proName=$value;
}
既然是方法赋值,我们就可以做一些逻辑处理
7.__isset() 判断类中私有属性或方法是否存在时自动调用
首先我们先介绍一下isset 方法,该方法用于判定属性和方法是否存在,但是我们无法通过类类实例判断类中的某个私有属性是否存在
如果我们使用isset(per->$name);//返回值是false,但是$name属性的确存在,怎么解决呢?
解决方法:
1.将$name定义为私有属性
2.
在类定义中添加
Function __isset($proName)
{
Return isset(this->$proName);//再类内部是可以访问私有属性的
}
这样的话我们再次调用isset($name);返回值就为true了;
8.__unset()清除类中私有变量时自动调用
与之结合的是unset() unset方法可以删除属性,当我们需要删除类中属性的时候,如果是公有属性我们可以直接
删除,但是如果是私有我们只通过该方法就无法实现了
怎样实现呢我们可以使用__unset()方法实现该功能我们需要在类中添加
Function __unset($proName)
{
Unset(this->$proName);
}
现在我们再调用unset($name);就可以删除person类中的私有属性$name了

Redis,memcache,apc,apcu,yac介绍和比较

一 Redis和memcache
关于Redis和memcache的比较已经很多了,这里随意摘抄一篇文章:
http://www.open-open.com/lib/view/open1409643182369.html
大概有一下结论:
应该说Memcached和Redis都能很好的满足解决我们的问题,它们性能都很高,总的来说,可以把Redis理解为是对Memcached的拓展,是更加重量级的实现,提供了更多更强大的功能。具体来说:

1.性能上:
性能上都很出色,具体到细节,由于Redis只使用单核,而Memcached可以使用多核,所以平均每一个核上Redis在存储小数据时比
Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis最近也在存储大数据的性能上进行优化,但是比起 Memcached,还是稍有逊色。

2.内存空间和数据量大小:
MemCached可以修改最大内存,采用LRU算法。Redis增加了VM的特性,突破了物理内存的限制。

3.操作便利上:
MemCached数据结构单一,仅用来缓存数据,而Redis支持更加丰富的数据类型,也可以在服务器端直接对数据进行丰富的操作,这样可以减少网络IO次数和数据体积。

4.可靠性上:
MemCached不支持数据持久化,断电或重启后数据消失,但其稳定性是有保证的。Redis支持数据持久化和数据恢复,允许单点故障,但是同时也会付出性能的代价。

5.应用场景:
Memcached:动态系统中减轻数据库负载,提升性能;做缓存,适合多读少写,大数据量的情况(如人人网大量查询用户信息、好友信息、文章信息等)。
Redis:适用于对读写效率要求都很高,数据处理业务复杂和对安全性要求较高的系统(如新浪微博的计数和微博发布部分系统,对数据安全性、读写要求都很高)。

需要慎重考虑的部分

1.Memcached单个key-value大小有限,一个value最大只支持1MB,而Redis最大支持512MB
2.Memcached只是个内存缓存,对可靠性无要求;而Redis更倾向于内存数据库,因此对对可靠性方面要求比较高
3.从本质上讲,Memcached只是一个单一key-value内存Cache;而Redis则是一个数据结构内存数据库,支持五种数据类型,因此Redis除单纯缓存作用外,还可以处理一些简单的逻辑运算,Redis不仅可以缓存,而且还可以作为数据库用
4.新版本(3.0)的Redis是指集群分布式,也就是说集群本身均衡客户端请求,各个节点可以交流,可拓展行、可维护性更强大。

Redis是数据的持久化,也是一种nosql,它可以把需要的数据提前按照key-value存储好,这样用户取数据时,可以直接从key中取出一系列的数据,避免频繁的查表。而memcache可以把用户频繁取出来的key cache住,这样连redis都不用去取了。

二 apc和memcache

由于PHP的特性,每次执行完页面之后,所有运行中的对象都会被释放,所以APC和Memcached就可以用来在脚本、进程之间共享、缓存数据。

APC是PHP的一个扩展,会加载在PHP的进程中,除了可以将PHP代码解释成OPCode保存在内存中之外,还能在PHP的进程之间使用共享内存(系统内核的数据结构)来保存数据,而且完全透明

而Memcached是一个外部的服务,要通过tcp或udp的网络协议来共享/缓存数据

Memcached好处是可以在多台机器之间共享、缓存数据,或者是与其他非php应用共享数据,但由于使用网络协议进行交互,而且在交互过程中需要对php对象进行序列化、反序列化等,延迟较直接集成在PHP进程中的APC大很多

Facebook同时使用了APC和Memcache作了两层缓存

APC更多时候用作Opcode Cache, 而MemCache是用作CotentCache. APC用作Content Cache的时候, 它是单机Cache. 而MemCache可以实现多机共享. 所以类似一些Session共享问题, 就只能用类似MemCache的缓存.
另外因为设计问题, 如果你使用APC缓存易变的内容, 可能会造成缓存内容不同步.

三 apc和apcu
原来的APC:Alternative PHP Cache,包含了System Cache Entries 、Per-Directory Entries、User Cache Entries三大块。
而现在的APCu:APCu – APC User Cache,只包含一个User Cache Entries。
所以是个精简版

四 yac
关于yac直接看鸟哥的博客:
http://www.laruence.com/2013/03/18/2846.html
具体yac的描述如下:
yac is a shared memory user data cache for PHP. it can be used to replace APC or local memcached.

yac is lockless, that means, it is very fast, but there could be a chance you will get a wrong data(depends on how many key slots are allocated and how many keys are stored), so you’d better make sure that your product is not very sensitive to that.
为PHP提供共享用户数据,可以替代APC或者本地memcached,它的特性是无锁,非常快,但是可能有数据错误,所以推荐使用大键值内存。