xuadmin 发布的文章

众所周知,高质量的代码可以减轻服务器的压力,同时也能让人赏心悦目。
以下是本人近期总结的优化技巧:

  1. 能不用正则解决的问题,尽量不用正则;
  2. 在includes和requires中使用绝对路径,这样在分析路径花的时间更少;
  3. 使用requiere_once代价比requiere高;
  4. str_replace函数比preg_replace函数快,但strtr函数的效率是str_replace函数的四倍;
  5. 多个分支的条件判断用switch、case优于if、elseif;
  6. 尽量少用@屏蔽错误,这样做效率灰常低;
  7. echo 比 print快,echo 可以打印用逗号分割的字符串;
  8. 执行for循环之前确定最大循环数,而不是每次循环都去计算最大值,可以的话尽量使用foreach代替;
  9. 尽量使用PHP的内置函数;
  10. 并不是所有情况都必须使用面向对象开发,面向对象往往开销很大,每个方法和对象调用都会消耗很多内存;
  11. 如果能将类的方法定义成static,就尽量定义成static,它的速度会提升将近4倍;
  12. 递增一个对象属性,要比递增一个局部变量慢3倍;
  13. 派生类中的方法运行起来要快于在基类中定义的同样的方法;
  14. 用单引号替代双引号引用字符串;
  15. 在可以用file_get_contents替代file、fopen、feof、fgets等系列方法的情况下,尽量用 file_get_contents,因为他的效率高得多;
  16. 尽量做缓存,例如 memcached、redis;
  17. 尽量多用静态HTML页面,少用脚本,服务器解析一个PHP脚本的时间要比一个静态html页面要慢2-10倍;
  18. 尽量少用global变量,对global变量,应该用完就unset()掉;
  19. 禁止过多的循环套循环,嵌套过多的循环会拉低执行效率;
  20. 开启服务器gzip压缩。

一、多阅读手册和源代码
  没什么比阅读手册更值得强调的事了–仅仅通过阅读手册你就可以学习到很多东西,特别是很多有关于字符串和数组的函数。就在这些函数里面包括许多有用的功能,如果你仔细阅读手册,你会经常发现在以往的项目开发过程中,很多时候你在“重复发明轮子”,而实际上你只需要一个核心函数就可以完成相应的功能。手册是你的朋友。另外,现在有很多使用PHP开发的开源程序。为什么不去学习和借鉴呢?下载一份开源的PHP应用程序的源代码,仔细阅读它吧。也许越大的项目越值得去阅读,虽然它们也许有更复杂的结构和系统,但也有更详细的解释文档。
  
二、编写模块化代码
  良好的PHP代码应该是模块化的代码。PHP的面向对象的编程功能是一些特别强大的工具,可以把你的应用程序分解成函数或方法。你应该尽可能多的从你的应用程序的服务器端分开前端的HTML/CSS/JavaScript代码,你也可以在任何PHP框架上遵循MVC(模型-视图-控制器)模式。
  
三、代码编写规范
  良好的PHP代码应该有一套完整的代码编写规范。通过对变量和函数的命名,统一的方法访问数据库和对错误的处理,以及同样的代码缩进方式等来达到编程规范,这样可以使你的代码更具可读性。
 
四、编写可移植代码
  良好的PHP代码应该是可移植的。你可以使用php的现有功能,如魔术引号和短标签。试着了解你的需求,然后通过适应PHP特性来编写代码让代码独立、可移植。
  
五、编写安全代码
  良好的PHP代码应该是安全的。PHP5提供了出色的性能和灵活性。但是安全问题完全在于开发人员。对于一个专业的PHP开发人员来说,深入理解重大安全漏洞是至关重要的,如:跨站点脚本(XSS)、跨站请求伪造(CSRF)、代码注入漏洞、字符编码漏洞。通过使用PHP的特殊功能和函数,如:mysql_real_escape_string等等,你可以编写出安全的代码。
  
六、代码注释
  代码注释是代码的重要组成部分。通过代码注释可以知道该变量或函数是做什么的,这将在今后的代码维护中十分有用。
  
七、使用单引号代替双引号
  字符串始终使用单引号代替双引号,以避免PHP搜索字符串内的变量导致的性能下降。用单引号代替双引号来包含字符串,这样做会更快一些。因为PHP会在双引号包围的字符串中搜寻变量,单引号则不会
  
八、转义字符串输出
  使用ENT_QUOTES作参数传递给htmlspecialchars函数,以确保单引号(')也转换成HTML实体,这是一个好习惯。
  
九、使用逗号分隔字符串输出
  通过echo语句输出使用逗号(,)分隔的字符串,要比使用字符串连接操作符(.)的性能更好。
  
十、输出前检查传来的值
  输出前检查传过来的值$_GET['query']。使用isset或empty函数,可以用来检查变量是否为null值。

在服务器中,一个应用程序只要接收到请求就会产生日志记录,日积月累,会占用很多磁盘空间,也不利于日志跟踪。
很多人会自己写sh脚本定时执行去手动分割日志,其实linux本身自带了一个日志分割轮替神器,就是logrotate。
不需要添加定时脚本,也不需要编写维护sh脚本,只需简单几个配置,就可以实现日志分割轮替。

编辑配置文件 vim /etc/logrotate.conf

weekly --时间参数 weekly按周、yearly按年、monthly按月、daily按日
rotate 4  --转存保留多少份日志
create  --指定转存日志权限
dateext
include /etc/logrotate.d
/var/log/wtmp {
    monthly
    create 0664 root utmp
        minsize 1M
    rotate 1
}
/var/log/btmp {
    missingok
    monthly
    create 0600 root utmp
    rotate 1
}

nignx日志分割,新增 /etc/logrotate.d/nginx 文件
vim /etc/logrotate.d/nginx
新增一下内容

/usr/local/nginx/logs/*.log {
       daily  #每日轮替
       rotate 7  #保留7个日志文件
       compress  #对日志进行压缩
       missingok  #如果日志不存在,则忽略改日志的警告信息
       dateext  #使用日期作为日志轮替文件的后缀
       notifempty #如果日志为空文件,则不进行日志轮替
        sharedscripts  #所有文件分割后在执行后面的脚本
        postrotate #脚本开始标识
         kill -USR1 `cat /usr/local/nginx/logs/nginx.pid`
        endscript #脚本结束标识
}

如果是version 2 以上的版本 postrotate/endscript 可以换为

/usr/local/nginx/logs/*.log {
       daily  #每日轮替
       rotate 7  #保留7个日志文件
       compress  #对日志进行压缩
       missingok  #如果日志不存在,则忽略改日志的警告信息
       dateext  #使用日期作为日志轮替文件的后缀
       notifempty #如果日志为空文件,则不进行日志轮替
       copytruncate # 把原日志文件拷贝一份重命名,然后清空原始文件
}

工厂模式

工厂模式是最常用也是最简单的一种设计模式了,不需要用关键字new就可以获取实例,减少代码耦合,提高代码的可扩展性。
例如你想修改某个实例化的类名,不需要去每个实例化的地方改,可以直接在工厂模型修改即可。

简单的例子:

class Factory
{
    public static function get Instance (string $newclass)
    {
        return new $class;
    }
}
$object = Factory::getInstance('class1')

单例模式

单例模式,最大的特点是,在整个应用进程中,只会存在一个该类的实例,一旦创建,就会一直保存在内存中。
一般具备以下几个特征,1、私有的静态变量,用来存放实例,2、私有的构造和克隆方法,3、公有的静态方法对外界提供获取实例入口,俗称三私一公。
最常用的场景应该是数据库模型了,使用单例模式,只需连接一次数据库,节约资源。

简单的例子:

class SingleCase 
{
    private static $_instance;

    private function __construct(){}

    private function __clone(){}

    public static function getInstance()
    {
        return self::$_instance = new self();
    }

}
$instance = SingleCase::getInstance();

观察者模式

当一个对象状态发生变化时,依赖他的对象全部会收到通知,并自动更新。是一种低耦合,非入侵式的通知和更新机制。
一个事件发生后,要执行一连串更新操作,传统的编程方式,就是在事件的代码之后直接加入处理的逻辑。当更新的逻辑越来越多的时候,代码会变得难以维护,观察者模式解决了这个问题。
简单的例子

//主题接口
interface Subject 
{
    public function register(Observer $observer);
    public function notify();
}

// 观察者接口
interface Observer 
{
    public function watch();
}

//主题
class Action implements Subject 
{
    public $_observers = [];
    public function register(Observer $observer)
    {
        $this->_observers[] = $observer; 
    }

    public function notify()
    {
        foreach ($this->_observers as $o) {
            $o->watch();
        }
    }
}


//观察者
class Cat implements Observer
{
    public function watch ()
    {
        echo 'cat watch';
    }
}

class Dog implements Observer 
{
    public function watch()
    {
        echo 'dog watch';
    }
}

class People implements Observer 
{
    public function watch()
    {
        echo 'people watch';
    }
}

//注册动作
$act = new Action();
$act->register(new Cat);
$act->register(new Dog);
$act->register(new People);


//调用实例
$act->notify();

#注册模式
注册模式,解决了全局共享和交换对象问题。已经创建好的对象,挂在到某个全局可以使用的数组上,在需要使用的时候,直接从该数组上获取即可。将对象注册到全局的树上。任何地方直接去访问。

代码示例

class Register
{
    protected static  $objects;
        function set($alias,$object)//将对象注册到全局的树上
        {
            self::$objects[$alias]=$object;//将对象放到树上
        }
        static function get($name){
        return self::$objects[$name];//获取某个注册到树上的对象
    }
    function _unset($alias)
  {
        unset(self::$objects[$alias]);//移除某个注册到树上的对象。
    }
}

适配器模式

将各种不同的函数接口封装成统一的api
例如php的数据库操作有mysql、mysqli、pdo三种,可以使用适配器统一成一个对外接口
首先定义个接口,有几种不同的情况,就写几个类实现该接口。将完成相似功能的函数,统一成一致的方法。

/**
 * 目标角色
 */
interface Target {
 
    /**
     * 源类也有的方法1
     */
    public function sampleMethod1();
 
    /**
     * 源类没有的方法2
     */
    public function sampleMethod2();
}
 
/**
 * 源角色
 */
class Adaptee {
 
    /**
     * 源类含有的方法
     */
    public function sampleMethod1() {
        echo 'Adaptee sampleMethod1 <br />';
    }
}
 
/**
 * 类适配器角色
 */
class Adapter extends Adaptee implements Target {
 
    /**
     * 源类中没有sampleMethod2方法,在此补充
     */
    public function sampleMethod2() {
        echo 'Adapter sampleMethod2 <br />';
    }
 
}
 
class Client {
 
    /**
     * Main program.
     */
    public static function main() {
        $adapter = new Adapter();
        $adapter->sampleMethod1();
        $adapter->sampleMethod2();
 
    }
 
}

数据库锁设计主要是用于处理并发问题,当出现并发访问的时候,数据库需要合理的控制资源的访问规则。

按照所的粒度划分

  1. 全局锁
    全局锁就是对整个数据库实例加锁,mysql提供了一个全局读加锁的方法,命令是:Flush tables with read lock 。当你需要让整个库出于只读状态的时候,可以使用这个命令,之后其他线程的语句会被阻塞,包括增、删、改,数据库结构的修改和更新类食物的提交。典型的使用场景是做全库逻辑备份。
  2. 表锁
    表锁是mysql锁中粒度最大的一种锁,表示当前的操作对整张表加锁,资源开销比行锁少,不会出现死锁的情况,但是发生锁冲突的概率很大。被大部分的mysql引擎支持。表锁的语法是 lock tables … read/write。可以用 unlock tables 主动释放锁,也可以在客户端断开的时候自动释放。
  3. 页锁
    页级锁是mysql中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB支持页级锁。
  4. 行锁
    行级锁是mysql中锁定粒度最小的一种锁,只针对当前操作的行进行加锁,是在引擎层由各个引擎自己实现的,但并不是所有的存储引擎都支持,MyISAM就不支持,InnoDB支持。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。有可能会出现死锁的情况。

按照锁的使用方式划分

  1. 排他锁
    排他锁又称写锁,如果事务T对数据A加上排他锁,那么其他事务不能再对A加任何类型的操作,但是T既能读数据,也能修改数据。
  2. 共享锁
    共享锁又称读锁,是读取操作创建的锁。其他用户可以并发读取数据,但是不能进行修改,知道共享锁释放。

按照锁思想划分

  1. 悲观锁
    悲观锁是指对数据被外界修改保持保守态度,因此在整个数据处理过程,将数据出于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制。对目标记录进行修改前,先试着给它加上锁,如果加锁成功,那么在解锁前,任何其他操作都会抛出异常。
  2. 乐观锁
    乐观锁一般假定数据不会造成冲突,所以只有在数据提交更新的时候,才会正式对数据冲突与否进行检测,如果发现冲突了,则返回错误信息,让用户决定如何操作。