读书笔记:PHP核心技术与最佳实践

第一章  面向对象思想的核心概念


  面向对象是什么:面向对象(OOP)是一种程序设计范型,同事也是一张程序开发方法。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和可扩展性
  面向过程、面向对象以及函数式编程呗称为编程语言中的三大范式(前两者同属于命令式编程)

  类是对象的抽象组织,对象是类的具体存在
  对象就是一堆数据,既然如此,可以把一个对象存储起来,以便需要时用。这就是对象的序列化。
  序列化就是把保存在内存中的各个对象状态(属性)保存起来,并且在需要时可以还原出来

对象序列化后,存储的只是对象的属性。类是由属性和方法组成,而对象则是属性的结合,由同一个类生产的不同对象,拥有各自不同的属性,但共享了类的代码空间中方法区域的代码。

对象是什么?

 对象在PHP中也是变量的一种,所以先看PHP源码对变量的定义:

读书笔记:PHP核心技术与最佳实践_第1张图片

zvalue_value 就是PHP底层的变量类型,zend_object_value obj就是变量中的一个结构,在PHP5中,对象在底层的实现是采取“属性数组+方法数组”来实现的。
读书笔记:PHP核心技术与最佳实践_第2张图片

对象在PHP是一种zend_object_value结构体来存储的。对象在ZEND(PHP底层引擎,类似于java的jvm)中定义如下:

读书笔记:PHP核心技术与最佳实践_第3张图片

ce是存储该对象的类结构,在对象初始化时保存了类的入口,相当于类指针的作用,prop erties是一个Hash Table,用来存放对象属性,guards用来阻止递归调用。类的标准方法在zend/zend_object_handlers.h文件中定义,具体实现则是在zend/zend_object_handlers.c文件中。
 由源码可以知道,对象也是一种很普通的变量,不过是其携带了对象的属性和类的入口

对象和数组

'Tom','age'=>16);
$st=serialize($student);
var_dump($st);


  

对象和数组在内容上一模一样,区别在于:对象还有个指针,指向了它所属的类。在对student对象序列化的时候看到了student这个字符,就标志了对象归属于这个二类,可以立即执行对其执行所包含的方法。

总结:类是定义一系列属性和操作的模板,而对象则把属性进行具体化,然后交给类处理。
     对象就是数据,对象本身不包含方法,但是对象有一个指针指向一个类,有对象必定有一个类何其对呀(但是有种特殊情况,就是标量进行强制类型转换的object,此时php一个称为"孤儿"的stdClass类就会收留这个对象)

PHP魔术方法:
   它们是PHP中的内置方法,手册把这两个方法归到重载。
   1>PHP和java区别:java重载指的一个类中可以定义参数列表不同但名字相同的多个方法,但是PHP只能在一个类中有一个构造方法。PHP的重载指动态创建类属性和方法。因此set和get归到重载里面,一定程度增强了程序的健壮性
   2>实际上,toString方法也是一种序列化,PHP自带的serialize/unserialize也是进行序列化的,但是这组函数序列化时会产生一些无用信息,如属性字符串长度,造成存储空间的无谓浪费,因此可以实现自己的序列化和反序列化方法或者json_encode/json_decode也是个不错的选择,但是为什么echo一个对象就会报语法错误,原因就是echo 本来可以打印一个大型,而且实现了这个接口,但是PHP对其做了限制,只有实现toString后才允许使用,从下面的PHP源代码可以得到验证:

读书笔记:PHP核心技术与最佳实践_第4张图片

继承与多态
  面向对象的优势在于类的复用。继承与多态都是对类进行复用一个是类级别的复用,一个是方法级别的复用。
  组合与继承都是提高代码可重用性的手段,组合偏重整体和局部的关系,而继承偏重父与子的关系

读书笔记:PHP核心技术与最佳实践_第5张图片

从方法复用角度,如果两个类具有很多相同的代码和方法,可以从这两个类中抽象出一个父类,提供公共方法,然后这两个类作为子类提供个性方法。这时用继承更好
而组合就没有这么多限制。组合之间的类可以关系(体现为复用代码)很小,甚至于没有关系
两个如何抉择: 标准就是 低耦合(模块与模块之间尽可能独立存在,模块与模块之间的接口尽量少而简单)
按照这个思想,继承与组合两者都可以使用的情况下,更倾向于组合,因为:
 1>继承破坏了封装性
   例如鸟为父类,有羽毛和飞翔方法,子类天鹅和鸭子,显然鸭子不需要飞翔这个方法,但作为子类却可以无区别使用这个方法,破坏了类封装性。
 2>继承是紧耦合的
   使得父类和子类捆绑在一起。组合仅通过唯一接口和外部进行通信,耦合度低于继承
 3>继承扩展负责
   锁着继承层数增加和子类增加,设计大量方法重写。使用组合,可以根据类型约束,实现动态组合减少代码
 4>和恰当继承可以能违反显示世界逻辑
   比如:人作为父类,雇员、经理、学生为子类,存在这一问题,经理一定是雇员,学生也可能是雇员,而是用继承的话一个人无法拥有多个角色。而使用组合就可以较好解决这个问题

   但是组合也有缺点,在创建组合对象时,组合需要一一创建局部对象,这一定程度增加了一些代码,而继承不需要,子类自动有父类方法,扩展简单

   对于不是专门用于被继承的类,或者禁止被继承,也就是用final修饰符
   总结:底层代码多用组合,顶层/业务层代码多用继承。前者可以提高效率,避免对象臃肿。后者可以提高灵活性,让业务使用更方便

多态
  弱语言的PHP里多态与传统强类型语言里面多态在实现和概念上有一些区别,总结:多态指同一类对象在运行时的具体化,PHP语言是弱类型的,实现多态更简单、更灵活,类型转换不是多态,PHP中子类和父类中,因为没有对象转型机制,把派生类对象复制给基类对象然后在调用函数时动态改变其执行,子类无法向上转型为父类,从而失去多态最典型的特征,多态的本质就是if...else,只不过实现的层级不同
  class a{}
  class b extends a{}

  function do($obj){
   $obj->方法();
  }

   do(new a());
   do(new b());
  判断传入对象所属的类不同调用其同名的方法

 

面向接口编程

fly();
        }
    }
$obj=new m();
$obj->demo(new plain());
$obj->demo(new car());


当调用car失败是因为不存在fly方法,但是plain确实可以实现的,所以在PHP里只关心是否可以实现,而并不关系接口语法是否正确,但是在java中,接口不仅规范接口的实现者,也调用接口的执行者,不允许调用接口中本不存在的方法
由于PHP是弱类型,且强调灵活,所以不推荐大规模使用接口,而是仅在部分'内核'代码中使用接口,因为PHP中接口失去很多接口应该具有的语义

  总结接口的几个概念:
    接口作为一种规范和契约存在,作为规范,接口应该保证可用性;作为契约,接口应该保证可控性
    接口只是一个生命,一旦使用interface关键字,就应该实线它。可以由程序员实现(外部接口),也可以由系统实现(内部接口)。接口本身什么都不做,但是它可以告诉我们它能做什么

  PHP接口不足:
    一个是没有契约限制
    二是缺少足够多的内部接口(SPL库里面的lterator迭代器接口,能够使对象用于foreach结构)
    lterator接口原型如下:

读书笔记:PHP核心技术与最佳实践_第6张图片

    如果一个类实现了lterator接口就必须实现这五个方法,为什么一个类实现了lterator迭代器,其对象就可以被用作foreach对象呢,其实很简单,就是对PHP实例对象使用foreach,会检查这个实例有没有实现lterator接口,如果实现了,就会通过内置方法或使用实现类的方法模拟foreach语句,和之前的toString方法很像

   但是PHP的这种内部接口比较少

面向对象设计的五大原则:
    单一职责原则(减少类之间的耦合、提高类的复用性)
    
    接口隔离原则
    
    开发-封闭原则:一个模块在扩展性方面应该是开发的,而在更改性方面应该是封闭的
    
    里氏替换原则:只要针对继承的设计原则,因为继承与派生是OOP的一个主要特性,能够减少代码的重复编程实现,从而实现系统中的代码复用。父类的方法都要在子类中实现或重写,并且派生类只实现其抽象类中声明的方法,而不应当给出多余的方法定义或实现
      如果A、B两个类违反了设计,通常做法是创建一个新的抽象类C,作为两个具体类的超类,将A和B的共同行为移动到C,从而解决A和B行为不一致的问题,
      包括上面说的使用多态实现隐藏基类和派生类对象的区别,以及使用组合的方式解决继承中的基类与派生类中的不符合语意的情况。但是PHP对LSP的支持并不好,因为缺乏向上转型等概念,只能通过一些曲折的方法实现
       abstract class Cache{
     
    public abstract function set($key,$values,$expire=60);
    public abstract function get($key);
    public abstract function del($key);
    public abstract function delAll();
    public abstract function has($key);//检查是否有
 }

如果要求实现文件、memcache等机制缓存,只要继承这个抽象类并实现其抽象方法就可以了
     
     依赖倒置原则:上层模块不应该依赖于下层模块,他们共同依赖于一个抽象(父类不能依赖子类),抽象类不能依赖于具体,具体应该要依赖于抽象
       为什么要依赖于接口:因为接口体现对问题的抽象,一般相对稳定或者变化不频繁,而具体是多变的。所以依赖抽象是实现代码扩展和运行期内绑定(多态)的基础:只要实现了该抽象类的子类,都可以被类的使用者使用。 

     working();
    }
}
class wB{
    private $e;
    public function set(e $e){
         $this->e=$e;
    }
    public function work(){
        $this->e->working();
    }

}
$wa=new wA;
$wa->work();
$wb=new wB;
$wb->set(new t);
$wb->work();


 在wA中,work方法依赖于t实现;在wB中,work依赖于抽象,这样可以把需要的对象通过参数传入。在wB中t实例通过set传入,实现了工厂模式。由于这个实现是硬编码的,为了实现代码的进一步扩展把这个依赖关系写入配置文件中,指明wB需要一个t对象,专门由一个程序检查配置是否正确(所依赖类文件是否存在)以及加载配置中所依赖的实现。这个检测程序,就成为IOC容器
   实际上IOC是依赖倒置原则的同义词,DI即依赖注入和DS是IOC的两个实现
   在经典的J2EE设计里,通常把DAO和Service分为接口层和实现层,然后在配置文件里进行依赖关系的配置,这是最常见的DIP的应用。
   但是这样设置存在一个问题,配置文件越来越大,其间关系越来越复杂,同样逃脱不了随着应用和业务的改变,不断修改代码的噩梦(配置文件改了,代码也会改变)
    php也有把依赖关系写入配置文件里,通过配置文件产生需要对象,但是这样的代码是为了实现而实现。在spring领配置文件配置的不仅仅是一个类运行时的依赖关系,还可以实现事务管理、AOP、延迟加载等,而PHP试下能上吗的特性消耗是巨大的。
    就PHP这动态脚本遇见更强调快速开发、逻辑清晰、代码简单,如果附加了这种设计模式的框架,从技术实现和运行效率来看都是不可取。
      依赖倒置的核心原则是解耦,如果脱离这个原则,那就是本末倒置
      PHP目前还没有一个比较完善的IOC容器
      如何满足DIP:每个较高层次类都为了它所需要的服务提出一个接口声明,较底层次类实现这个接口。每个高层次类通过该抽象接口使用服务。

正则表达式:
 正则表达式的实现有多种引擎(如非确定性有穷自动机NFA、确定性有穷自动机DFA)

PHP网络技术及应用:
   HTTP是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器模型,通常承载于TCP协议之上,有时也承载于TLS或SSL协议层之上,就成为HTTPS

   HTTP/2的多路复用特性,使其可以在一个连接上同时打开多个流,双向传输数据,并且同域名下所有通信都可以在单个连接上完成,这个连接可以承载任意数量的双向数据流。同时,HTTP/2引入了二进制分帧层,将HTTP/1.1的请求和响应拆分成颗粒度更细的帧,从而实现了优先级、流量控制和Server Push等功能。
 但是目前没有规定它基于TLS部署,没有安全层的HTTP/2称为H2C,但是出于安全考虑,当前所有浏览器都支出HTTP/2
   HTTP/2协议不同于HTTP/1的文件格式报文,HTTP/2传输的都是二进制帧,但是沿用了HTTP版本的绝大部分语义,上层应用根本感知不到HTTP/2的存在

   
      如果client使用HTTP1.1但如果不希望使用长连接,需要在header指明connection为close,如果Server不想支持长连接,需要response说明connection为close

      Cookie:分两种,一种是客户端向服务器发送的,使用Cookie报头,用来标记一些信息;另一种是服务器发送浏览器的,报头为set cookie。两者的区别是cookie的value可以有多个cookie值,并且不需要显示指定domain等。而SetCookie报头里一条记录只能由于一个Cookie的value。需要指明domain、path等

  HTTP应用:模拟灌水机器人

   读书笔记:PHP核心技术与最佳实践_第7张图片

 在整个过程,浏览器扮演的角色始终是一个忠实的执行者。只要遵循HTTP协议和服务器进行交互,就实现了一个简单的浏览器,这个浏览器只提供数据的收发而不提供解析功能。对于服务器端的代码,无法判断是真的还是虚拟的

array_get_headers():取得服务器响应一个HTTP请求所发送的所有标头,通常用此函数请求一个URL,根据返回数据判断状态码是否为200,判断资源是否存在
file():包括fopen、file_get_contents等,可以用来操作文件,也可以请求一个网络资源
socket:通过socket发送和请求数据。包括但不限于HTTP协议
cURL:模拟浏览器和服务器进行交互
header:PHP用用此函数发送原始的HTTP头。这个函数之前不能有输出以及空格
这里使用http_build_query(生成 URL-encode 之后的请求字符串)和file等系列函数发送HTTP请求

读书笔记:PHP核心技术与最佳实践_第8张图片

模拟灌水机器人:
  $data=array('author'=>'1','test'=>'ss');
  $data=http_build_query($data);
  $opts=array(
  'http'=>array(
    'method'=>'POST',
    'header'=>'Content-type:application/x-www.form-urlencoded\r\n',"Content-Length:".strlen($data)."\r\n",
    'content'=>$data,
    'User-Agent:Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36'."\r\n",
    'Referer:http:/ssss/111/'."\r\n"//服务器请求的原始资源的URL',
    'cookie:PHPSESSION=12ssdds2sdd'."\r\n"
  )
  );
  $context=stream_context_create($opts); //创建资源流
  $html=$file_get_contents('http:/ssss/111/con',false,$context);

  3.垃圾信息防御:
     1>IP限制。其原理在于IP难以伪造,即使是对于拨号用户,虽然IP可变,但这也会增加攻击的工作量
     2>验证码
     3>Token和表单欺骗。通过增加隐藏的表单值或者故意对程序混淆表单值,进而打到判断真实用户还是软件提交的目的
     4>审核机制

 Cookie:
   Cookie是远程浏览器端存储数据并以此跟踪和识别用户的机制。从实现上说,Cookie是存储在客户端上的一小段数据,浏览器通过HTTP协议和服务器端进行Cookie交互
   Bool setcookie();
   1>键值  2>值   3>有效时间 4>有效目录,默认(/整个域名)  5>作用域名,默认本域名下
   6>是否进行加密传输,默认false,true只有使用https 7>是否只使用http访问cookie,如果为1、true,客户端js无法操作这个cookie,可以减少xss攻击风险
   注意:当php在当前页设置的cookie不会立即生效,要等到下一个页面才能看到。这是由于设置的这个页面里的cookie由服务器传递给客户浏览器,在下一个页面浏览器才能把cookie从客户的机器里获取传回服务器,如果js设置,立即生效
   但是cookie不是越多越好,它会增加宽带,一旦cookie被设置在域下,则请求该域名下的一个资源,浏览器和服务器之间都可能存在cookie的上行和下行流量。

   Cookie跨域与p3p、CORS协议
     目前最流行的单点登录。最简单的方式使用p3p协议。
      什么是跨域,指的是超过了浏览器的同源策略:
       协议相同
       域名相同
       端口相同
      如果是非同源这三个行为无法使用:
       ajax、DOM无法获得、cookie、localStorage和IndexDB无法读取
 

 Session:
   指一种持续性、双向的连接。Session可以通过URL重写、COOkie,通过Cookie中存储sessionID实现Session传递,通过一个PHPSESSID的Cookie和服务器联系,session通过sessionID判断客户端用户的
   即使禁止了Cookie也可以使用表单提交的方式:
       echo "new1
";
    $a=session_name();
    $b=session_id();
    
    echo "new2
";

    new php
    $sessionName=session_name();
    $sessionID=$_GET[$sessionName];

    session_id($sessionID);
    print_r($_COOKIE);
    var_dump($_SESSION);

  如果没用设置Session的生命周期,SessionID存储在内存中,关闭浏览器后该ID自动注销;重新请求该页面,会重新注册一个sessionID。如果客户端没用禁用Cookie,Cookie在启动Session会话的时候会存储SessionID和Session生存期的角色
 

Session入库:
    Session以文件的形式存放在本地硬盘的一个目录中,所以当Session比较多的适合,磁盘读取文件就会比较慢(一般2000个文件时候),于是想到把Session分目录存放
      如何分目录存放
        在php.ini中:session.save_path = "N;/path" 
          N表示要设置的目录级数
          MODE 表示目录权限属性 默认600 win不用设置
          /path:表示Session文件存放的根目录路径
          比如 session.save_path = "2;/tmp/phpsession"
           以phpsession作为根目录,该目录下进行两级目录散列,每一级目录分别以0-9和a-z共36个字复名,这样目录可以达到36的36次方
           注意:Session回收是被动的,为了保证过期的Session能被正常回收可以修改PHP配置文件session.gc_divisor提高回收率,或者设置一个变量判断是否过去,但是对于设置分级目录存储的Session,PHP不会自动回收,需要自己实现其回收机制 


    对于访问量大的站点,使用默认的Session存储方式并不合适,较优的方法是存放在数据库、内存表、APC等,解决这个方案就是session_set_save_handler。(在别的课程有详细说明),然后再大流量下入库效率不高,可以使用内存数据库
 

SESSION集群下的处理:

      由于每一个会话Cookie对应服务器后端的一个sessionID,也就是说Session和具体的服务器绑定的,一旦后端IP轮询切换,会话COOKIE找不到对应的Session

 1>依然使用集群,但集群的轮询策略使用hash ip
     将每一个客户与后端服务器绑定,这样一来,用户的会话始终落在一台固定的服务器上。这样成本最小的方案,只需要修改Nginx的配置。(如果是比较老的架构,这个是成本最小的)
      upstream bckend{
        ip_hash;
        server backend1.example.com;
        server backend2.example.com;
      }
      存在的问题:一台服务器挂了,切换到另外一台服务器,对应的会话也会丢失
                 如果前端是CDN的话,客户端IP变化的频率可以能是很高的,有可能一个小时或更短时间旧编号一次(在校园网网络环境下很容易产生这种情况)
  2>使用Session复制方案
     让集群的每天服务器都存储整个集群所有服务器上全部的Session。如果某一台服务器挂掉,用户请求被负载的其他Web服务器上,而且由于Session被复制了,不存在会话丢失

      存在问题:需要耗费系统资源和网络开销,尤其是Web服务器多的时候或者Session数据量大的时候,这点特别明显
      因为Session复制本身比较麻烦,尤其对于大于两台服务器的集群中,
      (这种方案是很早一些服务器,比如tomcat的做法。由于php之前一直服务于中小型网站,早起php还有没这种企业级开发的集群部署,所有PHP业界没有这种做法)
   3>Nginx的sticky方案
     这种是方案一得补充,可以会话死死放在其中一台服务器上,可以避免cdn的ip突变,但是存在服务器挂掉的问题
   4>NoSQL的集中存储方案
     基于redis的session集中存储。目前比较流行的方案,但是有以下问题
       a.redis有单点,并且redis的引入增加了系统复杂度,要解决这个单点就要使用redis的集群方案,比如codis

       b.用户量打的情况,用来连接redis的类库可能存在瓶颈,比如性能不稳定、高并发下容易挂
       但是配置比较简单,比如php只要安装predis扩展然后在php.ini下配置
       vi /etc/php.ini
       session.save_handler=redis
       session.save_path="tcp://192.168.1.22:6379?timout=15"

   5>放弃session,使用cookies
  

 PHP与数据库基础


   数据库只是不仅是DBA的知识范围,也是程序员必须掌握的。由于程序员更接近业务,更容易知道应用的评价,所以他们更容易犯错

   1.什么是PDO
      PHP针对每周数据库都有一个独立的模块、一组独立的函数,这样的结构和设计让PHP兼容多种数据库变得困难。一旦将一个应用移到另一种数据库环境中,或者需要添加新的数据库支持,就不得不重新编写和数据库的操作。通常编写多个类,用适配器模式实现
        PDO提供一个通用接口访问多种数据库,即抽象的数据模型支持连接多种数据库
        Mysqli:提欧共了过程化和面向对象两种风格的API,增加了预编译和参数绑定的特性
        PDO:为PHP定义一个访问数据的轻量、持久的接口。实现PDO接口的每一种数据库驱动都能以正则扩展的形式把这字的特色表现出来
      注意:PDO只是抽象的接口类,利用PDO本身并不能实现任何数据库操作,必须使用一个特定的数据库PDO驱动访问数据库
       在php.ini
       ;extension=php_pdo.dll
    2.数据库优化

     基本语句优化
      1>避免在列上进行运算,这样会导致索引失效
        select * from where year(d)>=2011  改为:select * from where d>='2011-01-01'
      2>使用join时,应该用小结果集驱动大结果集。同时吧复杂的join查询拆分成多个Query,因为join多个表,可能导致更多的锁定和堵塞
      3>注意like模糊查询的使用
      4>select *这种语句
      5>使用批量插入语句节省交互
      6>limit的基数比较大的时候使用between
       select * from a as b order by id limit 100000,10 改为:select * from a as b where id between 100000 and 100010 order by id
       建议用between或者where代替limit,但是between也有缺陷,如果id中件断行或者中间部分id不读取的情况,总读取的数量会少于预计数量
       在读取比较后面的数据可以通过desc数据反向查找,以减少前段数据的扫描
      7>避免使用NULL
      8>不要使用count(id),而应该是count(*)
      9>不要做无谓的排序操作,而尽可能在索引中完成排序

     索引与性能分析
       MYSQL的执行计划:就是在一条select语句前放上关键词explain,我们可知道:
         什么时候为表加入索引
         优化器是否已最佳次序连接表
       为了强制优化器对一个select语句使用特定联结次序,需要增加一个straight_join

读书笔记:PHP核心技术与最佳实践_第9张图片

       各属性的含义:id:查询序列号
        select_type:查询的类型,包括普通查询、联合查询和子查询
    读书笔记:PHP核心技术与最佳实践_第10张图片

读书笔记:PHP核心技术与最佳实践_第11张图片

     数据库配置优化

        三种存储引擎的特点:

   读书笔记:PHP核心技术与最佳实践_第12张图片
    mysql在高并发下的性能瓶颈很明显,主要原因就是锁定机制导致的堵塞。而innodb在锁定机制上采用了行锁,不同于mylsam的表级锁,行级锁在锁定上带来的消耗大于表级锁,但是在系统并发访问量较高的时候,innodb整体性能远大于myisam。同事innodb的索引不仅缓存索引本身,也缓存数据,所以innodb需要内存,在这个时代,内存很廉价

      存储引擎的选择方法:
     通过数据库中执行show global status,查看读写比。当读写比达到10:1的时候,就认为以写为主的数据库
     选择基本原则如下:
    采用myisam:  R/W>100:1,且updata相对较少、并发不高,不需要事务、表数据量小、硬盘资源有限
    innoDB:R/W比较小,频繁更新大字段、表数据量超过1千万,并发高、安全性和可用性要求高
  配置优化:
   1>关闭不必要的二进制文件和慢查询日志,仅在内存足够或者开发调试时打开它们
     show variables like '%slow%';
     show global status like '%slow%'
   2>适度使用query cache
   3>增加mysql允许的最大连接数
     show variables like 'max_connections'
     对于innoDB,需要注意innodb_buffer_poll_size参数
   4>从表中删掉大量行后,可运行optimize table tableName 进行碎片整理
   mysql瓶颈措施:
     1>增加mysql配置中buffer和cache的数值,增加服务器cpu数量和内存大小
     2>使用第三方引擎或衍生版本。如percona在性能和功能上比mysql有明显提升、mariaBD在innodb引擎性能比mysql优秀。以上都是针对mysql的innodb引擎。
      innodb每次提交事务时,为了保证数据已经持久化到磁盘,需要调整一次fsync告知文件系统,将可能在缓存总数据刷新到磁盘。而fsync操作本身消耗较多I/O资源,响应较慢,如果每次事务提交都单独做fsync操作,将是系统TPS的一个瓶颈,所以在mysql 5以后处于分布式考虑,为了保证innodb内部commit和msql日志顺序一致,innodb被迫放弃支持group commit,直到mariaDB,才解决这个问题
     3>对数据库进行分区分表,减少表面积
     4>使用nosql复制解决问题
     5>使用中间件做数据查分和分布式部署(cobar)
     6>使用数据库连接池技术。数据库是线程模式的,可以支持更多的并发连接数。mysql能支持远比oracle更多的连接,但是oracle通常使用数据库连接池技术来复用连接。那么为什么mysql使用常用的数据库连接池技术呢?因为mysql的锁机制不完善,程序的一些问题都可能导致mysql数据库连接阻塞,在大并发情况下,会浪费很多连接资源和反复连接的消耗。使用数据库连接池可以让连接进行排队和复用,缓解高兵发的缓解压力
  

数据库设计


   范式与反范式
    1>核心业务使用范式。类似交易,强调数据安全和一致性。
    2>弱一致性需求-反ACID。对数据性要求不高场合,比如人数统计,静态页等。NoSQL都是基于弱一致性需求,降低数据完整性和一致性获取效率
    3>空间换时间,冗余换效率。数据量大的时候联表查询比较费时,需要冗余,冗余表如何处理?一般是定期转储。例如很少有人查询3年前某个月记录,因为只要看报表就行
    4>避免不必要的冗余

 数据库分区: 就是把一个数据表的文件和索引分散存储在不同物理文件中。5.1版本以上才支持分区
    MySQL支持的分区类型:
     range分区:基于一个给定的连续区间范围(区间要求连续并且不能重叠),把数据分配到不同的分区
     list分区:类似于range分区,区别在于list分区是居于枚举出的值列表分区,range是基于给定的连续区间范围分区
     hash分区:基于给定的分区个数,把数据分配到不同的分区
     key分区:类似于hash分区
     注意:无论哪种分区,要么你分区表上没有主键/唯一键,要么分区表的主键/唯一键都必须包含分区键,也就是说不能使用主键/唯一键字段之外的其它字段分区。

     MySQL分区的有限主要包括以下4个方面

    1>.和单个磁盘或者文件系统分区相比,可以存储更多数据
    2>优化查询。在where子句中包含分区条件时,可以只扫描必要的一个或者多个分区来提高查询效率;同时在涉及sum()和count()这类聚合函数的查询时,可以容易的在每个分区上并行处理,最终只需要汇总所有分区得到的结果
    3>对于已经过期或者不需要保存的数据,可以通过删除与这些数据有关的分区来快速删除数据
    4>跨多个磁盘来分散数据查询,以获得更大的查询吞吐量
    分区和水平分表功能类似,将一个大表的数据分割到多张小表中去,由于查询不需要全表扫描了,只需要扫描某些分区,所以分区能提高查询速度。

    水平分表需要用户预先手动显式创建出多张分表(如tbl_user0, tbl_user1, tbl_user2),在物理上实实在在的创建多张表,通过客户端代理(Sharding-JDBC等)或者中间件代理(Mycat等)来实现分表逻辑。

    分区是MySQL的一个插件Plugin功能,将一张大表的数据在数据库底层分成多个分区文件(如tbl_user#P#p0.ibd, tbl_user#P#p1.ibd, tbl_user#P#p2.ibd),和水平分表不同的是分区不需要显式的创建“分表”,数据库会自动创建分区文件的,同时索引也是分区的,用户看到的只是一张普通的表,其实是对应的是多个分区,这个是对用户是屏蔽的、透明的,在使用上和使用一张表完全一样,不需要借助任何功能来实现。分区是一种逻辑上的水平分表,在物理层面还是一张表
    
    分表的应用
     分区是吧一个逻辑表分成了几个物理文件后进行存储,而分表是把原先一个表分成几个表,进行分表查询,可以union活着做一个视图
      分表:
       垂直切分:
       水平切分:切分到另外一个数据库或表中
       对于一些流量统计系统,其数据量比较大,并且对过往数据的关注度不高,这时按年、月、日、进行分表,将每日统计信息放在一个日期命名的表中。或者按照增量进行分表,比如超过100万数据,放入第二个表中。还可以按照Hash进行分表,但是按照日期或者取模余数最常见,也是最容易扩展
        场景一:实现分页、统计、查询
                    $user='demo',$unino%5;
            $sql="select * from ",$user;
            //查询全部
            $array=('demo1','demo2','demo3','demo4');
            foreach($array as $a){
                $sql.="select * from",$a,"union";
            }
            $sql=substr($sql, 0,-5);
        ?>
        分页的话在这个大集合上做limit即可
        场景二:流量监控系统,数据胖蛋,需要按天分表
    
    MYSQL的高级应用
      视图:实际上等价于一条SQL语句,只是把一连串的SQL语句缩写了。实际上mysql中,视图等价于依赖SQL查询语句,MYSQL在处理视图查询时候,只是把视图展开成定义的语句,并在其上查询。因此不可能提高查询效率,其只是到另一个查询语句的映射。
      视图只是一个虚拟表,只包含定义,不含有数据
      报表的SQL语句复杂,能与一个煎蛋的视图代替较长的SQL
      注意:SELECT语句不能包含FROM子句中的子查询,不能引用系统或用户变了,不能引用预处理语句参数,在存储子程序内,定义不能引用子程序参数或局部变量

 关于缓存:
  为什么使用缓存?
    在业务的复杂性和并发的增多,数据库每秒接受并处理的请求次数有限,用缓存可以利用有限的资源提供尽可能大的吞吐量
      减少了计算量,缩短请求流程(减少网络I/O或者硬盘I/O),在标准的流程中任何环节都可以被切断,请求可以从缓存里取到数据直接返回。这样节约了时间、提高速度、节省了资源
       一个PHP的WEB程序可以缓存的:
         底层有CUP缓存、磁盘文件系统缓存
         应用层有Zend虚拟机的缓存,有NoSQL内存缓存,有opcache这类基于OPcode字节码缓存
         数据库有Table Cache、 Query Cache
         Servlet容器层有Apache缓存
         应用层有Smarty实现文件缓存
         基于HTTP协议和浏览器自身实现的浏览器缓存
  缓存三个要素  命中率、缓存更新策略、
      缓存更新策略:FIFO:最新进入缓存的数据在空间不够的会被先清除
                   LFU:最少使用的元素会被清除,缓存的元素有一个hit属性,属性小的先被清除
                   FRU:最近最少使用的元素被清除。缓存的元素有一个时间戳。

      文件缓存:模板的作用就是把动态的PHP代码编译成静态的HTML文件,当下次读取的时候不再编译直接读取静态文件
      尽管文件存储收到磁盘I/O效率较低的影响,但是在Web中,类似于模板编译后存储静态文件这个做法有很大优势。用较小的I/O换去Web服务器与数据库的交互,以及PHP解释引擎对脚本进行解释的消耗,得到了较大回报

   HTTP协议中的缓存使用:
     可以在HTML页面利用meta tag和在PHP程序通过header来控制缓存生成
        header('Cache-Control:max-age=86400,must-revalidate');//
        header('Last-Modified:'.gmdate('D,d M Y H: i: s').'GMT');
        header('Expires:'.gmdate('D,d M Y H: i: s',time()+'86400').'GMT');
        echo '我不刷新';

    使用    go请求 

    窍门:
       1>不经常改变的拖/页面启用缓存,使用Cache Control:max age属性设置一个较长过期时间
       2>避免使用POST,POST模式返回内容大部分缓存服务器不会保存,如果通过GET模式发送可以缓存下来
       3>不要在URL加入针对每个用户的识别信息:除非内容针对不同用户

    Web服务器缓存
      Nginx:Ngingx配合PHP的FastCGI模式,利用PHP优势,具有极大的负载能力,可以实现传统的缓存以及基于proxy_cache的缓存

内存数据库 redis:
String 类型:

  buf数组:字符串实体,保存字符串的内容
  len:记录buf大小
  free:记录buf还有多少可用空间
  可以把图片和视频文件等一些静态文件保存到string中
List类型:
  双端链表,存放微博中我关注的列表或者记录所有回帖ID
Set类型:
   无序集合,通过Hash Table实现,查找和删除元素时间复杂度为O1。优点就是快速查找元素是否存在,记录不能重复数据,例如记录投票,以日期为key查询用户ID是否存在
Sorted Set类型
  和set相似,都是string类型元素集合,不同于sorted set 是有序集合,而Sorted set通过一个double类型的证书score进行排序。sorted set通过跳表和hash Table组合完成。前者负责排序,后者负责保存数据,例如进行顶帖次数排序,可以把顺序值设置从有序集合的score值,具体指设置为对应value,用户每次按顶帖按钮,只需要执行zadd修改score值
Hash类型:
  每一个key对应一个hash table,增删改时间复杂度为O1,适合于存储对象,例如用户信息对象,把用户ID作为key,可以把用户信息保存到Hash类型中

读书笔记:PHP核心技术与最佳实践_第13张图片

 

 

你可能感兴趣的:(PHP模块,读书笔记)