首页 >后端开发 >php教程 > 正文

php面试的总结

原创2018-04-21 11:46:5203639
这篇文章介绍的内容是关于php面试的总结,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下

面试问题

1. explain

内容导航

  • id

  • select_type

  • table

  • type

  • possible_keys

  • key

  • key_len

  • ref

  • rows

  • Extra


环境准备

MySQL版本:

白俊遥博客

创建测试表

白俊遥博客

CREATE TABLE people(     
id bigint auto_increment primary key,     
zipcode char(32) not null default '',     
address varchar(128) not null default '',     
lastname char(64) not null default '',     
firstname char(64) not null default '',     
birthdate char(10) not null default '' );
 CREATE TABLE people_car(    
  people_id bigint,    
   plate_number varchar(16) not null default '',     
   engine_number varchar(16) not null default '',     
   lasttime timestamp );

白俊遥博客

插入测试数据

白俊遥博客

insert into people (zipcode,address,lastname,firstname,birthdate)
values ('230031','anhui','zhan','jindong','1989-09-15'), ('100000','beijing','zhang','san','1987-03-11'), ('200000','shanghai','wang','wu','1988-08-25') 
insert into people_car (people_id,plate_number,engine_number,lasttime) 
values (1,'A121311','12121313','2013-11-23 :21:12:21'), (2,'B121311','1S121313','2011-11-23 :21:12:21'), (3,'C121311','1211SAS1','2012-11-23 :21:12:21')

白俊遥博客

创建索引用来测试

alter table people add key(zipcode,firstname,lastname);

EXPLAIN 介绍

先从一个最简单的查询开始:

Query-1 explain select zipcode,firstname,lastname from people;

白俊遥博客

EXPLAIN输出结果共有id,select_type,table,type,possible_keys,key,key_len,ref,rows和Extra几列。

id

Query-2 explain select zipcode from (select * from people a) b;

白俊遥博客

id是用来顺序标识整个查询中SELELCT 语句的,通过上面这个简单的嵌套查询可以看到id越大的语句越先执行。该值可能为NULL,如果这一行用来说明的是其他行的联合结果,比如UNION语句:

Query-3 explain select * from people where zipcode = 100000 union select * from people where zipcode = 200000;

白俊遥博客

select_type

SELECT语句的类型,可以有下面几种。

SIMPLE

最简单的SELECT查询,没有使用UNION或子查询。见Query-1

PRIMARY

在嵌套的查询中是最外层的SELECT语句,在UNION查询中是最前面的SELECT语句。见Query-2Query-3

UNION

UNION中第二个以及后面的SELECT语句。 见Query-3

DERIVED

派生表SELECT语句中FROM子句中的SELECT语句。见Query-2

UNION RESULT

一个UNION查询的结果。见Query-3

DEPENDENT UNION

顾名思义,首先需要满足UNION的条件,及UNION中第二个以及后面的SELECT语句,同时该语句依赖外部的查询。

Query-4 explain select * from people where id in  (select id from people where zipcode = 100000 union select id from people where zipcode = 200000 );

白俊遥博客

Query-4中select id from people where zipcode = 200000的select_type为DEPENDENT UNION。你也许很奇怪这条语句并没有依赖外部的查询啊。

这里顺带说下MySQL优化器对IN操作符的优化,优化器会将IN中的uncorrelated subquery优化成一个correlated subquery(关于correlated subquery参见这里)。

SELECT ... FROM t1 WHERE t1.a IN (SELECT b FROM t2);

类似这样的语句会被重写成这样:

SELECT ... FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.b = t1.a);

所以Query-4实际上被重写成这样:

Query-5 explain select * from people o where exists  (
select id from people where zipcode = 100000 and id = o.id union select id from people where zipcode = 200000  and id = o.id);

白俊遥博客

题外话:有时候MySQL优化器这种太过“聪明” 的做法会导致WHERE条件包含IN()的子查询语句性能有很大损失。可以参看《高性能MySQL第三版》6.5.1关联子查询一节

SUBQUERY

子查询中第一个SELECT语句。

Query-6 explain select * from people  where id =  (select id from people where zipcode = 100000);

白俊遥博客

DEPENDENT SUBQUERY

和DEPENDENT UNION相对UNION一样。见Query-5

除了上述几种常见的select_type之外还有一些其他的这里就不一一介绍了,不同MySQL版本也不尽相同。

table

显示的这一行信息是关于哪一张表的。有时候并不是真正的表名。

Query-7 explain select * from (select * from (select * from people a) b ) c;

白俊遥博客

可以看到如果指定了别名就显示的别名。

<derivedN>N就是id值,指该id值对应的那一步操作的结果。

还有<unionM,N>这种类型,出现在UNION语句中,见Query-4

注意:MySQL对待这些表和普通表一样,但是这些“临时表”是没有任何索引的。

type

type列很重要,是用来说明表与表之间是如何进行关联操作的,有没有使用索引。MySQL中“关联”一词比一般意义上的要宽泛,MySQL认为任何一次查询都是一次“关联”,并不仅仅是一个查询需要两张表才叫关联,所以也可以理解MySQL是如何访问表的。主要有下面几种类别。

const

当确定最多只会有一行匹配的时候,MySQL优化器会在查询前读取它而且只读取一次,因此非常快。const只会用在将常量和主键或唯一索引进行比较时,而且是比较所有的索引字段。people表在id上有一个主键索引,在(zipcode,firstname,lastname)有一个二级索引。因此Query-8的type是const而Query-9并不是:

Query-8 explain select * from people where id=1;

白俊遥博客

Query-9 explain select * from people where zipcode = 100000;

白俊遥博客

注意下面的Query-10也不能使用const table,虽然也是主键,也只会返回一条结果。

Query-10 explain select * from people where id >2;

白俊遥博客

system

这是const连接类型的一种特例,表仅有一行满足条件。

Query-11 explain select * from (select * from people where id = 1 )b;

白俊遥博客

<derived2>已经是一个const table并且只有一条记录。

eq_ref

eq_ref类型是除了const外最好的连接类型,它用在一个索引的所有部分被联接使用并且索引是UNIQUE或PRIMARY KEY。

需要注意InnoDB和MyISAM引擎在这一点上有点差别。InnoDB当数据量比较小的情况type会是All。我们上面创建的people 和 people_car默认都是InnoDB表。

Query-12 explain select * from people a,people_car b where a.id = b.people_id;

白俊遥博客

我们创建两个MyISAM表people2和people_car2试试:

白俊遥博客

CREATE TABLE people2(     
id bigint auto_increment primary key,     
zipcode char(32) not null default '',     
address varchar(128) not null default '',     
lastname char(64) not null default '',     
firstname char(64) not null default '',     
birthdate char(10) not null default '' )
ENGINE = MyISAM; CREATE TABLE people_car2(     
people_id bigint,     
plate_number varchar(16) not null default '',     
engine_number varchar(16) not null default '',     
lasttime timestamp 
)ENGINE = MyISAM;

白俊遥博客

Query-13 explain select * from people2 a,people_car2 b where a.id = b.people_id;

白俊遥博客

我想这是InnoDB对性能权衡的一个结果。

eq_ref可以用于使用 = 操作符比较的带索引的列。比较值可以为常量或一个使用在该表前面所读取的表的列的表达式。如果关联所用的索引刚好又是主键,那么就会变成更优的const了:

Query-14 explain select * from people2 a,people_car2 b where a.id = b.people_id and b.people_id = 1;

白俊遥博客

ref

这个类型跟eq_ref不同的是,它用在关联操作只使用了索引的最左前缀,或者索引不是UNIQUE和PRIMARY KEY。ref可以用于使用=或<=>操作符的带索引的列。

为了说明我们重新建立上面的people2和people_car2表,仍然使用MyISAM但是不给id指定primary key。然后我们分别给id和people_id建立非唯一索引。

reate index people_id on people2(id); create index people_id on people_car2(people_id);

然后再执行下面的查询:

Query-15 explain select * from people2 a,people_car2 b where a.id = b.people_id and a.id > 2;

白俊遥博客

Query-16 explain select * from people2 a,people_car2 b where a.id = b.people_id and a.id = 2;

白俊遥博客

Query-17 explain select * from people2 a,people_car2 b where a.id = b.people_id;

白俊遥博客

Query-18 explain select * from people2 where id = 1;

白俊遥博客

看上面的Query-15,Query-16和Query-17,Query-18我们发现MyISAM在ref类型上的处理也是有不同策略的。

对于ref类型,在InnoDB上面执行上面三条语句结果完全一致。

fulltext

链接是使用全文索引进行的。一般我们用到的索引都是B树,这里就不举例说明了。

ref_or_null

该类型和ref类似。但是MySQL会做一个额外的搜索包含NULL列的操作。在解决子查询中经常使用该联接类型的优化。(详见这里)。

Query-19 mysql> explain select * from people2 where id = 2 or id is null;

白俊遥博客

Query-20 explain select * from people2 where id = 2 or id is not null;

白俊遥博客

注意Query-20使用的并不是ref_or_null,而且InnnoDB这次表现又不相同(数据量大的情况下有待验证)。

index_merger

该联接类型表示使用了索引合并优化方法。在这种情况下,key列包含了使用的索引的清单,key_len包含了使用的索引的最长的关键元素。关于索引合并优化看这里。

unique_subquery

该类型替换了下面形式的IN子查询的ref:

value IN (SELECT primary_key FROM single_table WHERE some_expr)

unique_subquery是一个索引查找函数,可以完全替换子查询,效率更高。

index_subquery

该联接类型类似于unique_subquery。可以替换IN子查询,但只适合下列形式的子查询中的非唯一索引:

value IN (SELECT key_column FROM single_table WHERE some_expr)

range

只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引。key_len包含所使用索引的最长关键元素。在该类型中ref列为NULL。当使用=、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN或者IN操作符,用常量比较关键字列时,可以使用range:

Query-21 explain select * from people where id = 1 or id = 2;

白俊遥博客

注意在我的测试中:发现只有id是主键或唯一索引时type才会为range。

这里顺便挑剔下MySQL使用相同的range来表示范围查询和列表查询。

explain select * from people where id >1;

白俊遥博客

 explain select * from people where id in (1,2);

白俊遥博客

但事实上这两种情况下MySQL如何使用索引是有很大差别的:

我们不是挑剔:这两种访问效率是不同的。对于范围条件查询,MySQL无法使用范围列后面的其他索引列了,但是对于“多个等值条件查询”则没有这个限制了。

——出自《高性能MySQL第三版》

index

该联接类型与ALL相同,除了只有索引树被扫描。这通常比ALL快,因为索引文件通常比数据文件小。这个类型通常的作用是告诉我们查询是否使用索引进行排序操作。

Query-22 explain select * from people order by id;

白俊遥博客

至于什么情况下MySQL会利用索引进行排序,等有时间再仔细研究。最典型的就是order by后面跟的是主键。

ALL

最慢的一种方式,即全表扫描。

总的来说:上面几种连接类型的性能是依次递减的(system>const),不同的MySQL版本、不同的存储引擎甚至不同的数据量表现都可能不一样。

possible_keys

possible_keys列指出MySQL能使用哪个索引在该表中找到行。

key

key列显示MySQL实际决定使用的键(索引)。如果没有选择索引,键是NULL。要想强制MySQL使用或忽视possible_keys列中的索引,在查询中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。

key_len

key_len列显示MySQL决定使用的键长度。如果键是NULL,则长度为NULL。使用的索引的长度。在不损失精确性的情况下,长度越短越好 。

ref

ref列显示使用哪个列或常数与key一起从表中选择行。

rows

rows列显示MySQL认为它执行查询时必须检查的行数。注意这是一个预估值。

Extra

Extra是EXPLAIN输出中另外一个很重要的列,该列显示MySQL在查询过程中的一些详细信息,包含的信息很多,只选择几个重点的介绍下。

Using filesort

MySQL有两种方式可以生成有序的结果,通过排序操作或者使用索引,当Extra中出现了Using filesort 说明MySQL使用了后者,但注意虽然叫filesort但并不是说明就是用了文件来进行排序,只要可能排序都是在内存里完成的。大部分情况下利用索引排序更快,所以一般这时也要考虑优化查询了。

Using temporary

说明使用了临时表,一般看到它说明查询需要优化了,就算避免不了临时表的使用也要尽量避免硬盘临时表的使用。

Not exists

MYSQL优化了LEFT JOIN,一旦它找到了匹配LEFT JOIN标准的行, 就不再搜索了。

Using index

说明查询是覆盖了索引的,这是好事情。MySQL直接从索引中过滤不需要的记录并返回命中的结果。这是MySQL服务层完成的,但无需再回表查询记录。

Using index condition

这是MySQL 5.6出来的新特性,叫做“索引条件推送”。简单说一点就是MySQL原来在索引上是不能执行如like这样的操作的,但是现在可以了,这样减少了不必要的IO操作,但是只能用在二级索引上,详情点这里。

Using where

使用了WHERE从句来限制哪些行将与下一张表匹配或者是返回给用户。

注意:Extra列出现Using where表示MySQL服务器将存储引擎返回服务层以后再应用WHERE条件过滤。

EXPLAIN的输出内容基本介绍完了,它还有一个扩展的命令叫做EXPLAIN EXTENDED,主要是结合SHOW WARNINGS命令可以看到一些更多的信息。一个比较有用的是可以看到MySQL优化器重构后的SQL。

Ok,EXPLAIN了解就到这里,其实这些内容网上都有,只是自己实际操练下会印象更深刻。下一节会介绍SHOW PROFILE、慢查询日志以及一些第三方工具。


2. author 2.0 授权方式

OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版。

本文对OAuth 2.0的设计思路和运行流程,做一个简明通俗的解释,主要参考材料为RFC 6749。

白俊遥博客

一、应用场景

为了理解OAuth的适用场合,让我举一个假设的例子。

有一个"云冲印"的网站,可以将用户储存在Google的照片,冲印出来。用户为了使用该服务,必须让"云冲印"读取自己储存在Google上的照片。

白俊遥博客

问题是只有得到用户的授权,Google才会同意"云冲印"读取这些照片。那么,"云冲印"怎样获得用户的授权呢?

传统方法是,用户将自己的Google用户名和密码,告诉"云冲印",后者就可以读取用户的照片了。这样的做法有以下几个严重的缺点。

(1)"云冲印"为了后续的服务,会保存用户的密码,这样很不安全。

(2)Google不得不部署密码登录,而我们知道,单纯的密码登录并不安全。

(3)"云冲印"拥有了获取用户储存在Google所有资料的权力,用户没法限制"云冲印"获得授权的范围和有效期。

(4)用户只有修改密码,才能收回赋予"云冲印"的权力。但是这样做,会使得其他所有获得用户授权的第三方应用程序全部失效。

(5)只要有一个第三方应用程序被破解,就会导致用户密码泄漏,以及所有被密码保护的数据泄漏。

OAuth就是为了解决上面这些问题而诞生的。

二、名词定义

在详细讲解OAuth 2.0之前,需要了解几个专用名词。它们对读懂后面的讲解,尤其是几张图,至关重要。

(1) Third-party application:第三方应用程序,本文中又称"客户端"(client),即上一节例子中的"云冲印"。

(2)HTTP service:HTTP服务提供商,本文中简称"服务提供商",即上一节例子中的Google。

(3)Resource Owner:资源所有者,本文中又称"用户"(user)。

(4)User Agent:用户代理,本文中就是指浏览器。

(5)Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。

(6)Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。

知道了上面这些名词,就不难理解,OAuth的作用就是让"客户端"安全可控地获取"用户"的授权,与"服务商提供商"进行互动。

三、OAuth的思路

OAuth在"客户端"与"服务提供商"之间,设置了一个授权层(authorization layer)。"客户端"不能直接登录"服务提供商",只能登录授权层,以此将用户与客户端区分开来。"客户端"登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。

"客户端"登录授权层以后,"服务提供商"根据令牌的权限范围和有效期,向"客户端"开放用户储存的资料。

四、运行流程

OAuth 2.0的运行流程如下图,摘自RFC 6749。

白俊遥博客

(A)用户打开客户端以后,客户端要求用户给予授权。

(B)用户同意给予客户端授权。

(C)客户端使用上一步获得的授权,向认证服务器申请令牌。

(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。

(E)客户端使用令牌,向资源服务器申请获取资源。

(F)资源服务器确认令牌无误,同意向客户端开放资源。

不难看出来,上面六个步骤之中,B是关键,即用户怎样才能给于客户端授权。有了这个授权以后,客户端就可以获取令牌,进而凭令牌获取资源。

下面一一讲解客户端获取授权的四种模式。

五、客户端的授权模式

客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0定义了四种授权方式。

  • 授权码模式(authorization code)

  • 简化模式(implicit)

  • 密码模式(resource owner password credentials)

  • 客户端模式(client credentials)

六、授权码模式

授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。

白俊遥博客

它的步骤如下:

(A)用户访问客户端,后者将前者导向认证服务器。

(B)用户选择是否给予客户端授权。

(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。

(D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。

(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。

下面是上面这些步骤所需要的参数。

A步骤中,客户端申请认证的URI,包含以下参数:

  • response_type:表示授权类型,必选项,此处的值固定为"code"

  • client_id:表示客户端的ID,必选项

  • redirect_uri:表示重定向URI,可选项

  • scope:表示申请的权限范围,可选项

  • state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。

下面是一个例子。

 GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz         &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1 Host: server.example.com

C步骤中,服务器回应客户端的URI,包含以下参数:

  • code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。

  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。

下面是一个例子。

 HTTP/1.1 302 Found Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA           &state=xyz

D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:

  • grant_type:表示使用的授权模式,必选项,此处的值固定为"authorization_code"。

  • code:表示上一步获得的授权码,必选项。

  • redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。

  • client_id:表示客户端ID,必选项。

下面是一个例子。

 POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

E步骤中,认证服务器发送的HTTP回复,包含以下参数:

  • access_token:表示访问令牌,必选项。

  • token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。

  • expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。

  • refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。

  • scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。

下面是一个例子。

      HTTP/1.1 200 OK      Content-Type: application/json;charset=UTF-8      Cache-Control: no-store      Pragma: no-cache      {        "access_token":"2YotnFZFEjr1zCsicMWpAA",        "token_type":"example",        "expires_in":3600,        "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",        "example_parameter":"example_value"      }

从上面代码可以看到,相关参数使用JSON格式发送(Content-Type: application/json)。此外,HTTP头信息中明确指定不得缓存。

七、简化模式

简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。

白俊遥博客

它的步骤如下:

(A)客户端将用户导向认证服务器。

(B)用户决定是否给于客户端授权。

(C)假设用户给予授权,认证服务器将用户导向客户端指定的"重定向URI",并在URI的Hash部分包含了访问令牌。

(D)浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。

(E)资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌。

(F)浏览器执行上一步获得的脚本,提取出令牌。

(G)浏览器将令牌发给客户端。

下面是上面这些步骤所需要的参数。

A步骤中,客户端发出的HTTP请求,包含以下参数:

  • response_type:表示授权类型,此处的值固定为"token",必选项。

  • client_id:表示客户端的ID,必选项。

  • redirect_uri:表示重定向的URI,可选项。

  • scope:表示权限范围,可选项。

  • state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。

下面是一个例子。

     GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz         &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1     Host: server.example.com

C步骤中,认证服务器回应客户端的URI,包含以下参数:

  • access_token:表示访问令牌,必选项。

  • token_type:表示令牌类型,该值大小写不敏感,必选项。

  • expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。

  • scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。

  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。

下面是一个例子。

      HTTP/1.1 302 Found      Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA                &state=xyz&token_type=example&expires_in=3600

在上面的例子中,认证服务器用HTTP头信息的Location栏,指定浏览器重定向的网址。注意,在这个网址的Hash部分包含了令牌。

根据上面的D步骤,下一步浏览器会访问Location指定的网址,但是Hash部分不会发送。接下来的E步骤,服务提供商的资源服务器发送过来的代码,会提取出Hash中的令牌。

八、密码模式

密码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。

在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。

白俊遥博客

它的步骤如下:

(A)用户向客户端提供用户名和密码。

(B)客户端将用户名和密码发给认证服务器,向后者请求令牌。

(C)认证服务器确认无误后,向客户端提供访问令牌。

B步骤中,客户端发出的HTTP请求,包含以下参数:

  • grant_type:表示授权类型,此处的值固定为"password",必选项。

  • username:表示用户名,必选项。

  • password:表示用户的密码,必选项。

  • scope:表示权限范围,可选项。

下面是一个例子。

      POST /token HTTP/1.1      Host: server.example.com      Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW      Content-Type: application/x-www-form-urlencoded      grant_type=password&username=johndoe&password=A3ddj3w

C步骤中,认证服务器向客户端发送访问令牌,下面是一个例子。

      HTTP/1.1 200 OK      Content-Type: application/json;charset=UTF-8      Cache-Control: no-store      Pragma: no-cache      {        "access_token":"2YotnFZFEjr1zCsicMWpAA",        "token_type":"example",        "expires_in":3600,        "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",        "example_parameter":"example_value"      }

上面代码中,各个参数的含义参见《授权码模式》一节。

整个过程中,客户端不得保存用户的密码。

九、客户端模式

客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。

白俊遥博客

它的步骤如下:

(A)客户端向认证服务器进行身份认证,并要求一个访问令牌。

(B)认证服务器确认无误后,向客户端提供访问令牌。

A步骤中,客户端发出的HTTP请求,包含以下参数:

  • granttype:表示授权类型,此处的值固定为"clientcredentials",必选项。

  • scope:表示权限范围,可选项。

      POST /token HTTP/1.1      Host: server.example.com      Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW      Content-Type: application/x-www-form-urlencoded      grant_type=client_credentials

认证服务器必须以某种方式,验证客户端身份。

B步骤中,认证服务器向客户端发送访问令牌,下面是一个例子。

      HTTP/1.1 200 OK      Content-Type: application/json;charset=UTF-8      Cache-Control: no-store      Pragma: no-cache      {        "access_token":"2YotnFZFEjr1zCsicMWpAA",        "token_type":"example",        "expires_in":3600,        "example_parameter":"example_value"      }

上面代码中,各个参数的含义参见《授权码模式》一节。

十、更新令牌

如果用户访问的时候,客户端的"访问令牌"已经过期,则需要使用"更新令牌"申请一个新的访问令牌。

客户端发出更新令牌的HTTP请求,包含以下参数:

  • granttype:表示使用的授权模式,此处的值固定为"refreshtoken",必选项。

  • refresh_token:表示早前收到的更新令牌,必选项。

  • scope:表示申请的授权范围,不可以超出上一次申请的范围,如果省略该参数,则表示与上一次一致。

下面是一个例子。

      POST /token HTTP/1.1      
      Host: server.example.com      
      Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW      
      Content-Type: application/x-www-form-urlencoded      
      grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA

(完)


3. yii 配置文件

yii2框架的安装我们在之前文章中已经提到下面我们开始了解YII2框架

强大的YII2框架网上指南:http://www.yii-china.com/doc/detail/1.html?postid=278或者

http://www.yiichina.com/doc/guide/2.0

Yii2的应用结构:
白俊遥博客

目录篇:

白俊遥博客


白俊遥博客
advance版本的特点是:根目录下预先分配了三个模块,分别是前台、后台、控制台模块。1.backend它主要用于管理后台,网站管理员来管理整个系统。
白俊遥博客
assets 目录用于存放前端资源包PHP类。 这里不需要了解什么是前端资源包,只要大致知道是用于管理CSS、js等前端资源就可以了。config 用于存放本应用的配置文件,包含主配置文件 main.php 和全局参数配置文件 params.php 。models views controllers 3个目录分别用于存放数据模型类、视图文件、控制器类。这个是我们编码的核心,也是我们工作最多的目录。widgets 目录用于存放一些常用的小挂件的类文件。tests 目录用于存放测试类。web 目录从名字可以看出,这是一个对于Web服务器可以访问的目录。 除了这一目录,其他所有的目录不应对Web用户暴露出来。这是安全的需要。runtime 这个目录是要求权限为 chmod 777 ,即允许Web服务器具有完全的权限, 因为可能会涉及到写入临时文件等。 但是一个目录并未对Web用户可见。也就是说,权限给了,但是并不是Web用户可以访问到的。

2.frontend白俊遥博客
我们的目标最终用户提供的主要接口的前端应用。其实,前台和后台是一样的,只是我们逻辑上的一个划分.。
好了,现在问题来了。对于 frontend backend console 等独立的应用而言, 他们的内容放在各自的目录下面,他们的运作必然用到Yii框架等 vendor 中的程序。 他们是如何关联起来的?这个秘密,或者说整个Yii应用的目录结构的秘密, 就包含在一个传说中的称为入口文件的地方。


<?phpdefined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');
require(__DIR__ . '/../../vendor/autoload.php');
require(__DIR__ . '/../../vendor/yiisoft/yii2/Yii.php');
require(__DIR__ . '/../../common/config/bootstrap.php');
require(__DIR__ . '/../config/bootstrap.php');
$config = yii\helpers\ArrayHelper::merge(
require(__DIR__ . '/../../common/config/main.php'),
require(__DIR__ . '/../../common/config/main-local.php'),
require(__DIR__ . '/../config/main.php'),
require(__DIR__ . '/../config/main-local.php'));
$application = new yii\web\Application($config);$application->run();


3.console控制台应用程序包含系统所需要的控制台命令的。
白俊遥博客


下面是全局公共文件夹4.common白俊遥博客

其中:

  • config 就是通用的配置,这些配置将作用于前后台和命令行。

  • mail 就是应用的前后台和命令行的与邮件相关的布局文件等。

  • models 就是前后台和命令行都可能用到的数据模型。 这也是 common 中最主要的部分。


公共的目录(Common)中包含的文件用于其它应用程序之间共享。例如,每一个应用程序可能需要访问该数据库的使用 ActiveRecord。因此,我们可以将AR模型类放置在公共(common)的目录下。同样,如果在多个应用程序中使用了一些辅助(helper )或部件类(widget ),我们也应该把这些放置在公共目录(common)下,以避免重复的代码。 正如我们将很快解释,应用程序也可以共享一部分的共用配置。因此,我们还可以存储config目录下共同的常见配置。
当开发一个大型项目开发周期长,我们需要不断调整数据库结构。出于这个原因,我们还可以使用数据库迁移(DB migrations )功能来保持跟踪数据库的变化。我们将所有 DB migrations(数据库迁移)目录同样都放在公共(common)目录下面。

5.environment每个Yii环境就是一组配置文件, 包含了入口脚本 index.php和各类配置文件。 其实他们都放在/environments 目录下面.
白俊遥博客
从上面的目录结构图中,可以看到,环境目录下有3个东东:

  • 目录 dev

  • 目录 prod

  • 文件 index.php

其中, dev 和 prod 结构相同,分别又包含了4个目录和1个文件:

  • frontend 目录,用于前台的应用,包含了存放配置文件的 config 目录和存放web入口脚本的web 目录

  • backend 目录,用于后台应用,内容与 frontend 相同

  • console 目录,用于命令行应用,仅包含了 config 目录,因为命令行应用不需要web入口脚本, 因此没有 web 目录。

  • common 目录,用于各web应用和命令行应用通用的环境配置,仅包含了 config 目录, 因为不同应用不可能共用相同的入口脚本。 注意这个 common 的层级低于环境的层级,也就是说,他的通用,仅是某一环境下通用,并非所有环境下通用。

  • yii 文件,是命令行应用的入口脚本文件。

对于分散于各处的 web 和 config 目录而言,它们也是有共性的。

  • 凡是 web 目录,存放的都是web应用的入口脚本,一个 index.php 和一个测试版本的index-test.php

  • 凡是 config 目录,存放的,都是本地配置信息 main-local.php 和 params-local.php


6.vendor 白俊遥博客 vendor 。 这个目录从字面的意思看,就是各种第三方的程序。 这是Composer安装的其他程序的存放目录,包含Yii框架本身,也放在这个目录下面。 如果你向composer.json 目录增加了新的需要安装的程序,那么下次调用Composer的时候, 就会把新安装的目录也安装在这个 vendor 下面。

下面也是一些不太常用的文件夹7.vagrant 白俊遥博客8.tests 白俊遥博客

入口文件篇:

1、入口文件路径:

http://127.0.0.1/yii2/advanced/frontend/web/index.php

每个应用都有一个入口脚本 web/index.PHP,这是整个应用中唯一可以访问的 PHP 脚本。一个应用处理请求的过程如下:

1.用户向入口脚本 web/index.php 发起请求。
2.入口脚本加载应用配置并创建一个应用实例去处理请求。
3.应用通过请求组件解析请求的路由。
4.应用创建一个控制器实例去处理请求。
5.控制器创建一个操作实例并针对操作执行过滤器。
6.如果任何一个过滤器返回失败,则操作退出。
7.如果所有过滤器都通过,操作将被执行。
8.操作会加载一个数据模型,或许是来自数据库。
9.操作会渲染一个视图,把数据模型提供给它。
10.渲染结果返回给响应组件。
11.响应组件发送渲染结果给用户浏览器

可以看到中间有模型-视图-控制器 ,即常说的MVC。入口脚本并不会处理请求,而是把请求交给了应用主体,在处理请求时,会用到控制器,如果用到数据库中的东西,就会去访问模型,如果处理请求完成,要返回给用户信息,则会在视图中回馈要返回给用户的内容。

2、为什么我们访问方法会出现url加密呢?

白俊遥博客

我们找到文件:vendor/yiisoft/yii2/web/UrlManager.php

 return "$baseUrl/{$route}{$anchor}"; } else {  $url = "$baseUrl?{$this->routeParam}=" . urlencode($route);  if (!empty($params) && ($query = http_build_query($params)) !== '') {  $url .= '&' . $query;     } 将urlencode去掉就可以了 3、入口文件内容   入口文件流程如下:


MVC篇:

白俊遥博客

一、控制器详解:

1、修改默认控制器和方法

修改全局控制器:打开vendor/yiisoft/yii2/web/Application.php

eg:

public $defaultRoute = 'student/show';  
修改前台或者后台控制器: eg :打开  frontend/config/main.php 中
'params' => $params, 'defaultRoute' => 'login/show',

2、建立控制器示例:StudentController.php

//命名空间

namespace frontend\controllers;


use Yii;
use yii\web\Controller;   
vendor/yiisoft/yii2/web/Controller.php   
(该控制器继承的是\yii\base\Controller) \web\Controller.php中干了些什么   
1、默认开启了 授权防止csrf攻击 
2、响应Ajax请求的视图渲染 
3、将参数绑定到动作(就是看是不是属于框架自己定义的方法,如果没有定义就走run方法解析) 
4、检测方法(beforeAction)beforeAction() 方法会触发一个 beforeAction 事件,在事件中你可以追加事件处理操作; 
5、重定向路径 以及一些http Response(响应) 的设置

use yii\db\Query; //使用query查询 
use yii\data\Pagination;//分页   
use yii\data\ActiveDataProvider;//活动记录   
use frontend\models\ZsDynasty;//自定义数据模型



class StudentController extends Controller {
  $request = YII::$app->request;//获取请求组件    
  $request->get('id');//获取get方法数据    
  $request->post('id');//获取post方法数据    
  $request->isGet;//判断是不是get请求    
  $request->isPost;//判断是不是post请求    
  $request->userIp;//获取用户IP地址     
  $res = YII::$app->response;//获取响应组件     
  $res->statusCode = '404';//设置状态码     
  $this->redirect('http://baodu.com');//页面跳转     
  $res->sendFile('./b.jpg');//文件下载  
  $session = YII::$app->session;     
  $session->isActive;//判断session是否开启     
  $session->open();//开启session     
  //设置session值     
  $session->set('user','zhangsan');//第一个参数为键,第二个为值     
  $session['user']='zhangsan';     
  //获取session值     
  $session->get('user');     
  $session['user'];    
   //删除session值     
   $session-remove('user');     
   unset($session['user']);  
   $cookies = Yii::$app->response->cookies;//获取cookie对象     
   $cookie_data = array('name'=>'user','value'=>'zhangsan')//新建cookie数据     
   $cookies->add(new Cookie($cookie_data));     
   $cookies->remove('id');//删除cookie     
   $cookies->getValue('user');//获取cookie

//显示视图
return $this->render('add'); 默认.php
return $this->render('upda',["data"=>$data]);


}
}

二、模型层详解

简单模型建立:

<?php  
namespace frontend\models; 
class ListtModel extends \yii\db\ActiveRecord {  
public static function tableName()
     {  
     return 'listt';     
     }  
     public function one(){  
     return $this->find()->asArray()->one();     
     } 
     }


控制器引用
<?php namespace frontend\controllers; use Yii; use yii\web\controller; use frontend\models\ListtModel;  class ListtController extends Controller{  public  function  actionAdd(){  $model=new ListtModel;         $list=$model->one();        $data=$model->find()->asArray()->where("id=1")->all();  print_r($data);     } } ?>



三、视图层详解首先在frontend下建立与控制器名一致的文件(小写)eg:student 在文件下建立文件

eg:index.php
每一个controller对应一个view的文件夹,但是视图文件yii不要求是HTML,而是php,所以每个视图文件php里面都是视图片段:白俊遥博客
白俊遥博客
而views下面会有一个默认的layouts文件夹,里面存放的就是布局文件,什么意思呢?:在控制器中,会有一个layout字段,如果制定他为一个layout视图文件,比如common.php,那么视图就会以他为主视图,其他的view视图片段都会作为显示片段嵌入到layout文件common.php中.而如果不明确重载layout字段,那么默认layout的值是main,意味着layouts的main.php是视图模板。控制器:白俊遥博客
common.php:白俊遥博客

layouts 白俊遥博客
这样就达到了视图复用的作用。
控制器中写入$layout

//$layout="main"  系统默认文件 
//$layout=null  会找父类中默认定义的main public $layout="common"; 
public  function  actionIndex(){  
return  $this->render('index');
 }  
 将以下内容插入 common中
  <?=$content;?> 
   它就是index文件中的内容

当然了,视图与模板之间还有数据传递以及继承覆盖的功能。





YII2框架数据的运用

1、数据库连接

简介

一个项目根据需要会要求连接多个数据库,那么在yii2中如何链接多数据库呢?其实很简单,在配置文件中稍加配置即可完成。

配置

打开数据库配置文件common\config\main-local.php,在原先的db配置项下面添加db2,配置第二个数据库的属性即可

[php] view plain copy

  1. 'db' => [  
        'class' => 'yii\db\Connection',  
        'dsn' => 'mysql:host=localhost;dbname=hyii2',   //数据库hyii2  
        'username' => 'root',  
        'password' => 'pwhyii2',  
        'charset' => 'utf8',  
    ],  
    'db2' => [  
        'class' => 'yii\db\Connection',  
        'dsn' => 'mysql:host=localhost;dbname=hyii',     //数据库hyii  
        'username' => 'root',  
        'password' => 'pwhyii',  
        'charset' => 'utf8',  
    ],

如上配置就可以完成yii2连接多个数据库的功能,但还是需要注意几个点

如果使用的数据库前缀 在建立模型时 这样: eg:这个库叫 haiyong_test return {{%test}}

应用

1.我们在hyii数据库中新建一个测试表test

白俊遥博客

2.通过gii生成模型,这里需要注意的就是数据库链接ID处要改成db2

白俊遥博客

3.查看生成的模型,比正常的model多了红色标记的地方

白俊遥博客

所以各位童鞋,如果使用多数据配置,在建db2的模型的时候,也要加上上图红色的代码。

好了,以上步骤就完成了,yii2的多数据库配置,配置完成之后可以和原因一样使用model或者数据库操作

2、数据操作:


方式一:使用createCommand()函数


获取自增id

$id=Yii::$app->db->getLastInsertID();

[php] view plain copy

  1. Yii::$app->db->createCommand()->insert('user', [    
        'name' => 'test',    
        'age' => 30,    
    ])->execute();

批量插入数据

[php] view plain copy

Yii::$app->db->createCommand()->batchInsert('user', ['name', 'age'], [    
    ['test01', 30],    
    ['test02', 20],    
    ['test03', 25],    
])->execute();  
删除[php] view plain copy
Yii::$app->db->createCommand()->delete('user', 'age = 30')->execute();

修改

[php] view plain copy

Yii::$app->db->createCommand()->update('user', ['age' => 40], 'name = test')->execute();  
查询[php] view plain copy
//createCommand(执行原生的SQL语句)    
$sql= "SELECT u.account,i.* FROM sys_user as u left join user_info as i on u.id=i.user_id";    
$rows=Yii::$app->db->createCommand($sql)->query();    
查询返回多行:      
$command = Yii::$app->db->createCommand('SELECT * FROM post');    
$posts = $command->queryAll();  
返回单行  
$command = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=1');    
$post = $command->queryOne();  
查询多行单值:    
$command = Yii::$app->db->createCommand('SELECT title FROM post');    
$titles = $command->queryColumn();  
查询标量值/计算值:    
$command = Yii::$app->db->createCommand('SELECT COUNT(*) FROM post');    
$postCount = $command->queryScalar();

方式二:模型处理数据(优秀程序媛必备)!!


新增(因为save方法有点low)所以自己在模型层中定义:add和addAll方法

注意:!!!当setAttributes($attributes,fase);时不用设置rules规则,否则则需要设置字段规则;

 //入库一维数组
    public function add($data)
    {
        $this->setAttributes($data);
        $this->isNewRecord = true;
        $this->save();
        return $this->id;
    }
     //入库二维数组
    public function addAll($data){
        $ids=array();
        foreach($data as $attributes)
        {
            $this->isNewRecord = true;
            $this->setAttributes($attributes);
            $this->save()&& array_push($ids,$this->id) && $this->id=0;
        }
        return $ids;
    }

    public function rules()
    {
        return [
            [['title','content'],'required'
       ]];
    }
    控制器:
    $ids=$model->addAll($data);
     var_dump($ids);

删除

使用model::delete()进行删除

[php] view plain copy

  1. $user = User::find()->where(['name'=>'test'])->one();   
    $user->delete();

直接删除:删除年龄为30的所有用户

[php] view plain copy

  1. $result = User::deleteAll(['age'=>'30']);

根据主键删除:删除主键值为1的用户

[php] view plain copy

$result = User::deleteByPk(1);
 /**      
 * @param $files  字段      
 * @param $values  值      
 * @return int  影响行数      
 */  public function del($field,$values){ 
 //        
 $res = $this->find()->where(['in', "$files", $values])->deleteAll();  
 $res=$this->deleteAll(['in', "$field", "$values"]);  
 return $res;     
 }






修改

使用model::save()进行修改

[php] view plain copy
$user = User::find()->where(['name'=>'test'])->one(); //获取name等于test的模型  
$user->age = 40; //修改age属性值  
$user->save();   //保存




直接修改:修改用户test的年龄为40

[php] view plain copy
$result = User::model()->updateAll(['age'=>40],['name'=>'test']);
/**  * @param $data   修改数据  * @param $where  修改条件  * @return int  影响行数  */ public function upda($data,$where){  $result = $this->updateAll($data,$where);  // return $this->id;  return $result; }



基础查询

Customer::find()->one();    
此方法返回一条数据; 
Customer::find()->all();    
此方法返回所有数据;
Customer::find()->count();   
 此方法返回记录的数量; 
 Customer::find()->average();    
 此方法返回指定列的平均值;
  Customer::find()->min();   
   此方法返回指定列的最小值 ;
    Customer::find()->max();    
    此方法返回指定列的最大值 ;
    Customer::find()->scalar();    
    此方法返回值的第一行第一列的查询结果;
     Customer::find()->column();    
     此方法返回查询结果中的第一列的值; 
     Customer::find()->exists();   
      此方法返回一个值指示是否包含查询结果的数据行;
       Customer::find()->batch(10); 
        每次取10条数据  
        Customer::find()->each(10); 
         每次取10条数据,迭代查询 
    //根据sql语句查询:查询name=test的客户 Customer::model()->findAllBySql("select * from customer where name = test");  
    //根据主键查询:查询主键值为1的数据 Customer::model()->findByPk(1);  
   //根据条件查询(该方法是根据条件查询一个集合,可以是多个条件,把条件放到数组里面)  
   Customer::model()->findAllByAttributes(['username'=>'admin']);  
 //子查询 $subQuery = (new Query())->select('COUNT(*)')->from('customer'); 
 // SELECT `id`, (SELECT COUNT(*) FROM `customer`) AS `count` FROM `customer` $query = (new Query())->select(['id', 'count' => $subQuery])->from('customer'); 
 //关联查询:查询客户表(customer)关联订单表(orders),条件是status=1,客户id为1,从查询结果的第5条开始,查询10条数据 $data = (new Query())    
  ->select('*')     
  ->from('customer')     
  ->join('LEFT JOIN','orders','customer.id = orders.customer_id')     
  ->where(['status'=>'1','customer.id'=>'1'])     
  ->offset(5)     ->limit(10)     ->all()



关联查询

[php] view plain copy
/** 
 *客户表Model:CustomerModel  
 *订单表Model:OrdersModel 
 *国家表Model:CountrysModel 
 *首先要建立表与表之间的关系  
 *在CustomerModel中添加与订单的关系 
 */       
Class CustomerModel extends \yii\db\ActiveRecord  
{  
    ...  
    //客户和订单是一对多的关系所以用hasMany  
    //此处OrdersModel在CustomerModel顶部别忘了加对应的命名空间  
    //id对应的是OrdersModel的id字段,order_id对应CustomerModel的order_id字段  
    public function getOrders()  
    {  
        return $this->hasMany(OrdersModel::className(), ['id'=>'order_id']);  
    }  
    //客户和国家是一对一的关系所以用hasOne  
    public function getCountry()  
    {  
        return $this->hasOne(CountrysModel::className(), ['id'=>'Country_id']);  
    }  
    ....  
}  
// 查询客户与他们的订单和国家  
CustomerModel::find()->with('orders', 'country')->all();  
// 查询客户与他们的订单和订单的发货地址(注:orders 与 address都是关联关系)  
CustomerModel::find()->with('orders.address')->all();  
// 查询客户与他们的国家和状态为1的订单  
CustomerModel::find()->with([  
    'orders' => function ($query) {  
        $query->andWhere('status = 1');  
        },  
        'country',  
])->all();





yii2 rbac 详解

翻译 2015年07月30日 10:29:03


yii2 rbac 详解DbManager


1.yii config文件配置(我用的高级模板)(配置在common/config/main-local.php或者main.php)

'authManager' => [ 'class' => 'yii\rbac\DbManager', 'itemTable' => 'auth_item', 'assignmentTable' => 'auth_assignment', 'itemChildTable' => 'auth_item_child', ],

2.当然,在配置里面也可以设置 默认角色,只是我没写。Rbac 支持两种类,PhpManagerDbManager ,这里我使用DbManager

yii migrate(运行这个命令,生成user表)
yii migrate --migrationPath=@yii/rbac/migrations/ 运行此命令生成权限数据表 在下图
3.yii rbac 实际操作的是4张表白俊遥博客

4.操作使用 yii rbac(你的每一个操作,都会在rbac 的那4张表里操作数据,验证是否建立了相关权限,你可以直接进入到这几张表里查看)
注册一个许可:(会在上描的许可表里,生成数据)public function createPermission($item) { $auth = Yii::$app->authManager;
$createPost = $auth->createPermission($item); $createPost->description = '创建了 ' . $item . ' 许可'; $auth->add($createPost); }
创建一个角色:
public function createRole($item) { $auth = Yii::$app->authManager;
$role = $auth->createRole($item); $role->description = '创建了 ' . $item . ' 角色'; $auth->add($role); }
给角色分配许可public function createEmpowerment($items) { $auth = Yii::$app->authManager;
$parent = $auth->createRole($items['name']); $child = $auth->createPermission($items['description']);
$auth->addChild($parent, $child); }
给一个权限增加一条规则:规则是给角色和权限添加额外的约束。一条规则就是一个扩展自yii\rbac\Rule的类,必须实现execute()方法。
在层次结构上,我们先前创建的author角色不能编辑他自己的文章,让我们来修正它。
首先我们需要一条规则来验证这篇用户是文章的作者:
白俊遥博客

注:在上述方法中的excute 中$user来至用户登录后的user_id
白俊遥博客
用户登录后判断用户的权限白俊遥博客

文章借鉴之链接:http://www.360us.net/article/13.html http://www.open-open.com/lib/view/open1424832085843.html


4. 在微信里调支付宝

推荐文章 微信H5支付完整版含PHP回调页面.代码精简2018年2月
支付宝手机支付,本身有提供一个手机网站支付DEMO,是lotusphp版本的,里面有上百个文件,非常复杂.本文介绍的接口,
只需通过一个PHP文件即可实现手机支付宝接口的付款,非常简洁,并兼容微信.
代码在最下面.

注意事项(重要):
一,支付宝接口已经升级了加密方式,现在申请的接口都是公钥加私钥的加密形式.公钥与私钥都需要申请者自己生成,而且是成对的,不能拆开用.并把公钥保存到支付宝平台,该公钥对应的私钥不需要保存在支付宝,只能自己保存,并放在api支付宝接口文件中使用.下面会提到.

APPID 应该填哪个呢? 这个是指开放平台id,格式应该填2018或2016等日期开头的,不要填合作者pid,那个pid新版不需要的.APPID下面还对应一个网关.这个也要对应填写.正式申请通过的网关为https://openapi.alipay.com/gateway.do 如果你是沙箱测试账号,
则填https://openapi.alipaydev.com/gateway.do 注意区别
密钥生成方式为, https://docs.open.alipay.com/291/105971 打开这个地址,下载该相应工具后,解压打开文件夹,运行“RSA签名验签工具.bat”这个文件后.打开效果如下图
如果你的网站是jsp的,密钥格式如下图,点击选择第一个pkcs8的,如果你的网站是php,asp等,则点击pkcs1
密钥长度统一为2048位.
然后点击 生成密钥
然后,再点击打开密钥文件路径按钮.即可看到生成的密钥文件,打开txt文件.即可看到生成的公钥与私钥了.
公钥复制后(注意不要换行),需提供给支付宝账号管理者,并上传到支付宝开放平台。如下图第二
界面示例:
白俊遥博客
白俊遥博客

二,如下,同步回调地址与异步回调地址的区别.
同步地址是指用户付款成功,他自动跳转到这个地址,以get方式返回,你可以设置为跳转回会员中心,也可以转到网站首页或充值日志页面,通过$_GET 的获取支付宝发来的签名,金额等参数.然后进本地数据库验证支付是否正常.
而异步回调地址指支付成功后,支付宝会自动多次的访问你的这个地址,以静默方式进行,用户感受不到地址的跳转.注意,异步回调地址中不能有问号,&等符号,可以放在根目录中.如果你设置为notify_url.php,则你也需要在notify_url.php这个文件中做个判断.比如如果用户付款成功了.则用户的余额则增加多少,充值状态由付款中.修改为付款成功等.

$returnUrl = 'http://域名/user/h5_alipay/return_url.php';     //付款成功后的 同步回调地址,可直接设置为会员中心的地址 $notifyUrl = 'http://域名/notify_url.php';     //付款成功后的异回调地址,如果你的回调地址中包含&符号,最好把回调直接放根目录
  • 1

  • 2

三,orderName 订单名称,注意编码,否则签名可能会失败
向支付宝发起支付请求时,有个orderName 订单名称参数.注意这个参数的编码,如果你的本页面是gb2312编码,$this->charset = ‘UTF-8’这个参数最好还是UTF-8,不需要修改.否则签名时,可能会出现各种问题.,可用下面的方法做个转码.

$orderName=iconv("GB2312//IGNORE","UTF-8",'支付宝充值');
  • 1

四,微信中如何使用支付宝
支付宝有方案,可以进这个页面把ap.js及pay.htm下载后,保存到你的支付文件pay.php文件所在的目录中.
方案解释,会员在微信中打开你网站的页面,登录,并点击充值或购买链接时,他如果选择支付宝付款,则ap.js会自动弹出这个pay.htm页面,提示你在右上角选择用浏览器中打开,打开后,自动跳转到支付宝app中,不需要重新登录原网站的会员即可完成充值,并跳转回去.
注意,在你的客户从微信转到手机浏览器后,并没有让你重新登录你的商城网站,这是本方案的优势所在.
https://docs.open.alipay.com/203/105285/

白俊遥博客

五,如果你申请的支付宝手机支付接口在审核中,则可以先申请一个沙箱测试账号,该账号申请后就可以使用非常方便.同时会提供你一个支付宝商家账号及买家测试账号.登录即可测试付款情况.

白俊遥博客

代码如下(参考)
一.表单付款按钮所在页面代码

<? 
include("../../config/conn.php"); 
include("../../config/function.php"); 
sesCheck_m(); 
?> 
<html> 
<head> 
<meta http-equiv="x-ua-compatible" content="ie=7" /> 
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
 <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
  <title>会员中心 <?=webname?></title> 
  <script language="javascript"> function xz(x){ document.getElementById(x).checked=true;     
  } 
  function tj()
  { 
   t1v=document.f1.t1.value;  
   if(t1v.replace(/\s/,"")=="" || isNaN(t1v))
   {
   layerts("请输入充值金额");return false;
   }  
   r=document.getElementsByName("R1");  
   rv="";  
   for(i=0;i<r.length;i++){if(r[i].checked==true)
   {
   rv=r[i].value;
   }
   }  
   if(rv==""){
   layerts("请选择支付方式");
   return false;
   }  
   if(rv=="alipay" || rv=="")
   {
   fu="h5_alipay/wappay/pay.php";
   }  
   <? 
   if ( strpos($_SERVER['HTTP_USER_AGENT'], 'MicroMessenger') == false ) {
   ?>  
   else if(rv=="wxpay")
   {
   f1.action="wxpay/h5_pay.php";
   }
   //微信中,不可以使用微信h5支付.只可用公众号支付.  
   <? 
   }else{
   ?>  
   else if(rv=="wxpay")
   {
   f1.action="wxpay/wxpay_jspay_pay.php";
   }  
   <? 
   }
   ?>  
   tjwait(); 
    f1.action=fu; 
    } 
    </script> 
    </head> 
    <body>
     <form name="f1" method="post" onSubmit="return tj()"> 
     <input type="hidden" value="pay" name="jvs" />  
     <p class="d1">充值金额</p>
      <input type="text" name="t1" class="inp" style="font-weight:700;color:#ff6600;" value="0.01" /> 
      <br/>
       <input name="R1" id="alipay" type="radio" value="alipay" />
       <img onClick="xz('alipay')" src="../../user/img/pay/alipay.gif" /> 
       <br/>
        <input type="submit" class="tjinput" value="立即充值" /> 
        </form> 
         </p> 
         </body>
          </html>


二,pay.php页面代码(核心代码)

<?php 
header('Content-type:text/html; Charset=GB2312');
 $appid = '2016091200492600';  
 //https://open.alipay.com 账户中心->密钥管理->开放平台密钥,填写添加了电脑网站支付的应用的APPID 
 $notifyUrl = 'http://域名/user/h5_alipay/notify_url.php';     
 //付款成功后的异步回调地址支付宝以post的方式回调 
 $returnUrl = 'http://域名/user/pay_chongzhi.php';     
 //付款成功后,支付宝以 get同步的方式回调给发起支付方
  $sj=date("Y-m-d H:i:s"); 
  $userid=returnuserid($_SESSION["SHOPUSER"]); 
  $ddbh=$bh="h5_ali_".time()."_".$userid;
  //订单编号 
  $uip=$_SERVER["REMOTE_ADDR"];
  //ip地址 $money1=$_POST[t1]; 
  //bz备注,ddzt与alipayzt及ifok表示订单状态, 
  intotable("yjcode_dingdang","bh,ddbh,userid,sj,uip,money1,ddzt,alipayzt,bz,ifok","'".$bh."','".$ddbh."',".$userid.",'".$sj."','".$uip."',".$money1.",'等待买家付款','','支付宝充值',0");
  //订单入库 
  //die(mysql_error());
  //数据库错误 
  //订单名称  
  $orderName=iconv("GB2312//IGNORE","UTF-8",'支付宝充值'); 
  //注意编码 
  $body = $orderName=iconv("GB2312//IGNORE","UTF-8",'支付宝充值');  
  $outTradeNo = $ddbh;     
  //你自己的商品订单号 
  $payAmount = $money1;          
  //付款金额,单位:元 
  $signType = 'RSA2';       
  //签名算法类型,支持RSA2和RSA,推荐使用RSA2 
  $saPrivateKey='这里填2048位的私钥';
  //私钥 
  $aliPay = new AlipayService($appid,$returnUrl,$notifyUrl,$saPrivateKey); 
  $payConfigs = $aliPay->doPay($payAmount,$outTradeNo,$orderName,$returnUrl,$notifyUrl); 
  class AlipayService {    
   protected $appId;     
   protected $returnUrl;     
   protected $notifyUrl;     
   protected $charset;     
   //私钥值     
   protected $rsaPrivateKey;     
   public function __construct($appid, $returnUrl, $notifyUrl,$saPrivateKey)     {         
   $this->appId = $appid;         
   $this->returnUrl = $returnUrl;         
   $this->notifyUrl = $notifyUrl;        
    $this->charset = 'UTF-8';         
    $this->rsaPrivateKey=$saPrivateKey;    
     }    
     /**      
     * 发起订单      
     * @param float $totalFee 收款金额 单位元      
     * @param string $outTradeNo 订单号      
     * @param string $orderName 订单名称      
     * @param string $notifyUrl 支付结果通知url 不要有问号     
      * @param string $timestamp 订单发起时间      
      * @return array      
      */     
      public function doPay($totalFee, $outTradeNo, $orderName, $returnUrl,$notifyUrl)   
        {         
        //请求参数         
        $requestConfigs = array(             'out_trade_no'=>$outTradeNo,             'product_code'=>'QUICK_WAP_WAY',             'total_amount'=>$totalFee, 
        //单位 元             'subject'=>$orderName,  //订单标题         );         
        $commonConfigs = array(             
        //公共参数             
        'app_id' => $this->appId,             'method' => 'alipay.trade.wap.pay', 
        //接口名称             'format' => 'JSON',             
        'return_url' => $returnUrl,             'charset'=>$this->charset,             'sign_type'=>'RSA2',            
         'timestamp'=>date('Y-m-d H:i:s'),             'version'=>'1.0',             'notify_url' => $notifyUrl,            
          'biz_content'=>json_encode($requestConfigs),         );        
           $commonConfigs["sign"] = $this->generateSign($commonConfigs, $commonConfigs['sign_type']);         
           return $commonConfigs;     
           }    
           public function generateSign($params, $signType = "RSA") {         
           return $this->sign($this->getSignContent($params), $signType);     
           }     
           protected function sign($data, $signType = "RSA") {         
           $priKey=$this->rsaPrivateKey;         
           $res = "-----BEGIN RSA PRIVATE KEY-----\n" .             
           wordwrap($priKey, 64, "\n", true) .             
           "\n-----END RSA PRIVATE KEY-----";         
           ($res) or die('您使用的私钥格式错误,请检查RSA私钥配置');         
           if ("RSA2" == $signType) {             
           openssl_sign($data, $sign, $res, version_compare(PHP_VERSION,'5.4.0', '<') ? SHA256 : OPENSSL_ALGO_SHA256); 
           //OPENSSL_ALGO_SHA256是php5.4.8以上版本才支持        
            } else {             
            openssl_sign($data, $sign, $res);        
             }         
             $sign = base64_encode($sign);         
             return $sign;     
             }    
              /**      
              * 校验$value是否非空      
              *  if not set ,return true;      
              *    if is null , return true;     
               **/    
               protected function checkEmpty($value) {         
               if (!isset($value))             
               return true;         
               if ($value === null)             
               return true;        
                if (trim($value) === "")             
                return true;         
                return false;     
                }     
                public function getSignContent($params) {         
                ksort($params);         
                $stringToBeSigned = "";         
                $i = 0;         
                foreach ($params as $k => $v) {             
                if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) {                 
                // 转换成目标字符集                 
                $v = $this->characet($v, $this->charset);                 
                if ($i == 0) {                     
                $stringToBeSigned .= "$k" . "=" . "$v";                 
                } else {                     
                $stringToBeSigned .= "&" . "$k" . "=" . "$v";                 
                }                 
                $i++;             
                }         
                }         
                unset ($k, $v);         
                return $stringToBeSigned;     
                }     
                /**      
                * 转换字符集编码      
                * @param $data      
                * @param $targetCharset      
                * @return string      
                */     
                function characet($data, $targetCharset) {         
                if (!empty($data)) {             
                $fileType = $this->charset;            
                 if (strcasecmp($fileType, $targetCharset) != 0) {                
                  $data = mb_convert_encoding($data, $targetCharset, $fileType);             
                  }         
                  }         
                  return $data;     
                  } } 
                  function isWeixin(){     
                  if ( strpos($_SERVER['HTTP_USER_AGENT'],'MicroMessenger') !== false ) {         
                  return true;    
                   }     
                   return false; 
                   } 
                   $queryStr = http_build_query($payConfigs); if(isWeixin()): 
                   //注意下面的ap.js ,文件的存在目录,如果不确定.可以写绝对地址.否则可能微信中没法弹出提示窗口, 
                   ?> 
                   <script type="text/javascript" src="ap.js"></script> <script>     
                   var gotoUrl = 'https://openapi.alipaydev.com/gateway.do?<?=$queryStr?>';     
                   //注意上面及下面的alipaydev.com,用的是沙箱接口,去掉dev表示正式上线     
                   _AP.pay(gotoUrl); </script> <?php     else:     //print_r(($orderName.$body));
                   exit;         
                   header("Location:https://openapi.alipaydev.com/gateway.do?{$queryStr}");     
                   endif; 
                   ?>

三,回调页面案例一,即notify_url.php文件. post回调,

<?php 
header('Content-type:text/html; Charset=GB2312'); 
//支付宝公钥,账户中心->密钥管理->开放平台密钥,找到添加了支付功能的应用,根据你的加密类型,查看支付宝公钥 
$alipayPublicKey=''; $aliPay = new AlipayService($alipayPublicKey); 
//验证签名,如果签名失败,注意编码问题.特别是有中文时,可以换成英文,再测试
 $result = $aliPay->rsaCheck($_POST,$_POST['sign_type']); 
 if($result===true){     
 //处理你的逻辑,例如获取订单号
 $_POST['out_trade_no'],订单金额$_POST['total_amount']等     
 //程序执行完后必须打印输出“success”(不包含引号)。
 如果商户反馈给支付宝的字符不是success这7个字符,支付宝服务器会不断重发通知,直到超过24小时22分钟。
 一般情况下,25小时以内完成8次通知(通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h);     
 $out_trade_no = $_POST['out_trade_no'];     
 //支付宝交易号     
 $trade_no = $_POST['trade_no'];     
 //交易状态     
 $trade_status = $_POST['trade_status'];   
 switch($trade_status){       
 case "WAIT_BUYER_PAY";       
 $nddzt="等待买家付款";       
 break;       
 case "TRADE_FINISHED":       case "TRADE_SUCCESS";       $nddzt="交易成功";        
 break;       
 }       
 if(empty($trade_no)){echo "success";exit;}       
 //注意,这里的查询不能 强制用户登录,则否支付宝没法进入本页面.没法通知成功 
 $sql="select ifok,jyh from yjcode_dingdang where ifok=1 and jyh='".$trade_no."'";mysql_query("SET NAMES 'GBK'");$res=mysql_query($sql);
 //支付宝生成的流水号 
 if($row=mysql_fetch_array($res)){echo "success";exit;
 }
  $sql="select * from yjcode_dingdang where ddbh='".$out_trade_no."' and ifok=0 and ddzt='等待买家付款'"; 
  mysql_query("SET NAMES 'GBK'"); 
  $res=mysql_query($sql); 
  if($row=mysql_fetch_array($res)){      
  if(1==$row['ifok']){
  echo "success";exit;
  }  
  if($trade_status=="TRADE_SUCCESS" || $trade_status=="TRADE_FINISHED"){      
  if($row['money1']== $_POST['total_fee'] ){          
  $sj=time();$uip=$_SERVER["REMOTE_ADDR"];      
  updatetable("yjcode_dingdang","sj='".$sj."',uip='".$uip."',alipayzt='".$trade_status."',ddzt='".$nddzt."',ifok=1,jyh='".$trade_no."' where id=".$row[id]);  
  $money1=$row["money1"];
  //修改订单状态为成功付款  
  PointIntoM($userid,"支付宝充值".$money1."元",$money1);
  //会员余额增加  echo "success";exit;          
  }  
  } 
  }    
 //——请根据您的业务逻辑来编写程序(以上代码仅作参考)——     
 echo 'success';exit(); 
 } 
 echo 'fail';exit(); 
 class AlipayService {     
 //支付宝公钥     
 protected $alipayPublicKey;     
 protected $charset;     
 public function __construct($alipayPublicKey)     {         
 $this->charset = 'utf8';         
 $this->alipayPublicKey=$alipayPublicKey;     
 }     
 /**      *  验证签名      **/    
  public function rsaCheck($params) {         
  $sign = $params['sign'];         
  $signType = $params['sign_type'];         
  unset($params['sign_type']);         
  unset($params['sign']);         
  return $this->verify($this->getSignContent($params), $sign, $signType);     
  }     
  function verify($data, $sign, $signType = 'RSA') {         
  $pubKey= $this->alipayPublicKey;         
  $res = "-----BEGIN PUBLIC KEY-----\n" .             
  wordwrap($pubKey, 64, "\n", true) .             
  "\n-----END PUBLIC KEY-----";         
  ($res) or die('支付宝RSA公钥错误。请检查公钥文件格式是否正确');         
  //调用openssl内置方法验签,返回bool值         
  if ("RSA2" == $signType) {             
  $result = (bool)openssl_verify($data, base64_decode($sign), $res, version_compare(PHP_VERSION,'5.4.0', '<') ? SHA256 : OPENSSL_ALGO_SHA256);         
  } else {             
  $result = (bool)openssl_verify($data, base64_decode($sign), $res);         
  } //        if(!$this->checkEmpty($this->alipayPublicKey)) { //            
  //释放资源 //            
  openssl_free_key($res); 
  //        }         
  return $result;     
  }     
  /**      * 校验$value是否非空      *  if not set ,return true;      *    if is null , return true;      **/    
   protected function checkEmpty($value) {         
   if (!isset($value))             
   return true;         
   if ($value === null)             
   return true;         
   if (trim($value) === "")             
   return true;        
    return false;     
    }     
    public function getSignContent($params) {        
     ksort($params);         
     $stringToBeSigned = "";         
     $i = 0;         
     foreach ($params as $k => $v) {             
     if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) {                 
     // 转换成目标字符集                 
     $v = $this->characet($v, $this->charset);                 
     if ($i == 0) {                     
     $stringToBeSigned .= "$k" . "=" . "$v";                 
     } else {                     
     $stringToBeSigned .= "&" . "$k" . "=" . "$v";                 
     }                 
     $i++;             
     }        
      }         
      unset ($k, $v);        
       return $stringToBeSigned;     
       }     
       /**      * 转换字符集编码      * @param $data      * @param $targetCharset      * @return string      */    
        function characet($data, $targetCharset) {         
        if (!empty($data)) {            
        $fileType = $this->charset;             
        if (strcasecmp($fileType, $targetCharset) != 0) {                 
        $data = mb_convert_encoding($data, $targetCharset, $fileType);                 
        //$data = iconv($fileType, $targetCharset.'//IGNORE', $data);            
         }        
          }        
           return $data;     
           } 
           }

四.异步回调案例2, 与上面三是重复的,可选择其中一个.本回调可直接放根目录中 如果你服务器不支持mysqli 就替换为mysql 测试回调时, 请先直接访问本页面,进行测试.订单号可以先写一个固定值.

<?php 
define('PHPS_PATH', dirname(__FILE__).DIRECTORY_SEPARATOR);  
//$_POST['trade_no']=1; if($_POST['trade_no']){ $alipayPublicKey='MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyg8BC9UffA4ZoMl12zz';
//RSA公钥,与支付时的私钥对应 
$aliPay = new AlipayService2($alipayPublicKey); 
//验证签名 
$result = $aliPay->rsaCheck($_POST,$_POST['sign_type']);     
//file_put_contents('333.txt',$_POST); 
if($result===true){     
//处理你的逻辑,例如获取订单号
$_POST['out_trade_no'],订单金额$_POST['total_amount']等     
$out_trade_no = $_POST['out_trade_no'];     
//$out_trade_no = '2018022300293843964'; 
//支付宝交易号     
$trade_no = $_POST['trade_no'];     
//交易状态     
$trade_status = $_POST['trade_status'];    
 //$trade_status='';      
 $userinfo = array();          
 if($trade_status=="TRADE_SUCCESS" || $trade_status=="TRADE_FINISHED"){             
 //ini_set('display_errors',1);           
  //错误信息   
  //ini_set('display_startup_errors',1);    
  //php启动错误信息   
  //error_reporting(-1);                    
  //打印出所有的 错误信息         
  $mysql_user=include(PHPS_PATH.'/caches/configs/database.php');        
   $username=$mysql_user['default']['username'];         
   $password=$mysql_user['default']['password'];         
   $tablepre=$mysql_user['default']['tablepre'];         
   $database=$mysql_user['default']['database'];         
   $con = mysqli_connect($mysql_user['default']['hostname'],$username,$password); mysqli_select_db($con,$database);   
   $sql = ' SELECT * FROM '.$tablepre."pay_account where trade_sn='".$out_trade_no."'"; 
   $result2=mysqli_query($con,$sql);
    $orderinfo=mysqli_fetch_array($result2);;
     $uid=$orderinfo['userid']; 
     $sql2 = ' SELECT * FROM '.$tablepre."member where userid=".$uid;     
     $result1=mysqli_query($con,$sql2); $userinfo=mysqli_fetch_array($result1);;         
     if($orderinfo){             
     if($orderinfo['status']=='succ'){             
     //file_put_contents('31.txt',1);                  
     echo 'success';                  
     mysqli_close($con);                  
     exit();            
      }else{                     
      // if($orderinfo['money']== $_POST['total_amount'] ){                                 
      $money = $orderinfo['money'];             
      $amount = $userinfo['amount'] + $money;         
      $sql3 = ' update  '.$tablepre."member set amount= ".$amount."  where userid=".$uid;         
      $result3=mysqli_query($con,$sql3);     
      $sql4 = ' update  '.$tablepre."pay_account set status= 'succ'  where userid=".$uid ." and trade_sn='".$out_trade_no."'";         
      $result4=mysqli_query($con,$sql4);
       //file_put_contents('1.txt',$result4);                  
       echo 'success';                 
        mysqli_close($con);                  
        exit();                  
        //   }             
        }         
        } else {             
         echo 'success';exit();         
         }             
          }     
          echo 'success';exit(); 
          } echo 'fail';exit(); 
          } class AlipayService2 {     
          //支付宝公钥     
          protected $alipayPublicKey;     
          protected $charset;     
          public function __construct($alipayPublicKey)     {         
          $this->charset = 'utf8';         
          $this->alipayPublicKey=$alipayPublicKey;     
          }   
            /**      *  验证签名      **/     
            public function rsaCheck($params) {         
            $sign = $params['sign'];         
            $signType = $params['sign_type'];         
            unset($params['sign_type']);         
            unset($params['sign']);        
             return $this->verify($this->getSignContent($params), $sign, $signType);     
             }    
              function verify($data, $sign, $signType = 'RSA') {         
              $pubKey= $this->alipayPublicKey;         
              $res = "-----BEGIN PUBLIC KEY-----\n" .             
              wordwrap($pubKey, 64, "\n", true) .             
              "\n-----END PUBLIC KEY-----";         
              ($res) or die('支付宝RSA公钥错误。请检查公钥文件格式是否正确');         
              //调用openssl内置方法验签,返回bool值         
              if ("RSA2" == $signType) {             
              $result = (bool)openssl_verify($data, base64_decode($sign), $res, version_compare(PHP_VERSION,'5.4.0', '<') ? SHA256 : OPENSSL_ALGO_SHA256);         
              } else {            
               $result = (bool)openssl_verify($data, base64_decode($sign), $res);         
               } 
               //        if(!$this->checkEmpty($this->alipayPublicKey)) { 
               //            
               //释放资源 //            
               openssl_free_key($res); //        
               }         
               return $result;    
                }    
                 /**      * 校验$value是否非空      *  if not set ,return true;      *    if is null , return true;      **/     
                 protected function checkEmpty($value) {         
                 if (!isset($value))             
                 return true;         
                 if ($value === null)             
                 return true;         
                 if (trim($value) === "")             
                 return true;         
                 return false;     
                 }     
                 public function getSignContent($params) {         
                 ksort($params);         
                 $stringToBeSigned = "";         
                 $i = 0;         
                 foreach ($params as $k => $v) {             
                 if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) {                 
                 // 转换成目标字符集                 
                 $v = $this->characet($v, $this->charset);                 
                 if ($i == 0) {                     
                 $stringToBeSigned .= "$k" . "=" . "$v";                
                  } else {                     
                  $stringToBeSigned .= "&" . "$k" . "=" . "$v";                 
                  }                 
                  $i++;             
                  }         
                  }        
                   unset ($k, $v);         
                   return $stringToBeSigned;     
                   }     /**      * 转换字符集编码      * @param $data      * @param $targetCharset      * @return string      */     
                   function characet($data, $targetCharset) {         
                   if (!empty($data)) {             
                   $fileType = $this->charset;             
                   if (strcasecmp($fileType, $targetCharset) != 0) {                 
                   $data = mb_convert_encoding($data, $targetCharset, $fileType);                 
                   //$data = iconv($fileType, $targetCharset.'//IGNORE', $data);             
                   }         
                   }         
                   return $data;     
                   }
                    }
                     ?>

参考原文
http://blog.csdn.net/jason19905/article/details/78636716
https://github.com/dedemao/alipay


5. 抓包

抓包就是把网络数据包用软件截住或者纪录下来,这样做我们可以分析网络数据包,可以修改它然后发送一个假包给服务器,这种技术多应用于网络游戏外挂的制作方面或者密码截取等等

常用抓包工具

常用的几款抓包工具!
标签: 软件测试软件测试方法软件测试学习
原创来自于我们的微信公众号:软件测试大师


最近很多同学,说面试的时候被问道,有没有用过什么抓包工具,其实抓包工具并没有什么很难的工具,只要你知道你要用抓包是干嘛的,就知道该怎么用了!一般
对于测试而言,并不需要我们去做断点或者是调试代码什么的,只需要用一些抓包工具抓取发送给服务器的请求,观察下它的请求时间还有发送内容等等,有时候,
可能还会用到这个去观察某个页面下载组件消耗时间太长,找出原因,要开发做性能调优。那么下面就给大家推荐几款抓包工具,好好学习下,下次面试也可以拿来
装一下了!


1
Flidder
Fiddler是位于客户端和服务器端的HTTP代理,也是目前最常用的http抓包工具之一 。 它能够记录客户端和服务器之间的所有
HTTP请求,可以针对特定的HTTP请求,分析请求数据、设置断点、调试web应用、修改请求的数据,甚至可以修改服务器返回的数据,功能非常强大,是
web调试的利器。
小编发现了有个兄台写的不错的文章,分享给大家,有兴趣的同学,可以自己去查阅并学习下,反正本小编花了点时间就学会了,原来就这么回事!作为测试学会这点真的是足够用了!
学习链接如下:
http://blog.csdn.net/ohmygirl/article/details/17846199
http://blog.csdn.net/ohmygirl/article/details/17849983
http://blog.csdn.net/ohmygirl/article/details/17855031

2
Httpwatch
火狐浏览器下有著名的httpfox,而HttpWatch则是IE下强大的网页数据分析工具。教程小编也不详述了,找到了一个超级棒的教程!真心很赞!要想学习的同学,可以点击链接去感受下!
http://jingyan.baidu.com/article/5553fa820539ff65a339345d.html


3其他浏览器的内置抓包工具
如果用过Firefox的F12功能键,应该也知道这里也有网络抓包的工具,是内置在浏览器里面的,貌似现在每款浏览器都有这个内置的抓包工具,虽然没有上面两个工具强大,但是对于测试而言,我觉得是足够了!下面是一个非常详细的教程,大家可以去学习下。
http://jingyan.baidu.com/article/3c343ff703fee20d377963e7.html

对于想学习点新知识去面试装逼的同学,小编只能帮你们到这里了,要想学习到新知识,除了动手指去点击这些链接,还需要你们去动脑好好学习下!



6. https / http

超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息,因此,HTTP协议不适合传输一些敏感信息,比如:信用卡号、密码等支付信息。

为了解决HTTP协议的这一缺陷,需要使用另一种协议:安全套接字层超文本传输协议HTTPS,为了数据传输的安全,HTTPS在HTTP的基础上加入了SSL协议,SSL依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。

一、HTTP和HTTPS的基本概念

HTTP:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。

HTTPS:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。

HTTPS协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。

二、HTTP与HTTPS有什么区别?

HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。

简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。

HTTPS和HTTP的区别主要如下:

1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。

2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。

3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

三、HTTPS的工作原理

我们都知道HTTPS能够加密信息,以免敏感信息被第三方获取,所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用HTTPS协议。

1、客户端发起HTTPS请求

这个没什么好说的,就是用户在浏览器里输入一个https网址,然后连接到server的443端口。

2、服务端的配置

采用HTTPS协议的服务器必须要有一套数字证书,可以自己制作,也可以向组织申请,区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面(startssl就是个不错的选择,有1年的免费服务)。

这套证书其实就是一对公钥和私钥,如果对公钥和私钥不太理解,可以想象成一把钥匙和一个锁头,只是全世界只有你一个人有这把钥匙,你可以把锁头给别人,别人可以用这个锁把重要的东西锁起来,然后发给你,因为只有你一个人有这把钥匙,所以只有你才能看到被这把锁锁起来的东西。

3、传送证书

这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间等等。

4、客户端解析证书

这部分工作是有客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。

如果证书没有问题,那么就生成一个随机值,然后用证书对该随机值进行加密,就好像上面说的,把随机值用锁头锁起来,这样除非有钥匙,不然看不到被锁住的内容。

5、传送加密信息

这部分传送的是用证书加密后的随机值,目的就是让服务端得到这个随机值,以后客户端和服务端的通信就可以通过这个随机值来进行加密解密了。

6、服务段解密信息

服务端用私钥解密后,得到了客户端传过来的随机值(私钥),然后把内容通过该值进行对称加密,所谓对称加密就是,将信息和私钥通过某种算法混合在一起,这样除非知道私钥,不然无法获取内容,而正好客户端和服务端都知道这个私钥,所以只要加密算法够彪悍,私钥够复杂,数据就够安全。

7、传输加密后的信息

这部分信息是服务段用私钥加密后的信息,可以在客户端被还原。

8、客户端解密信息

客户端用之前生成的私钥解密服务段传过来的信息,于是获取了解密后的内容,整个过程第三方即使监听到了数据,也束手无策。

六、HTTPS的优点

正是由于HTTPS非常的安全,攻击者无法从中找到下手的地方,从站长的角度来说,HTTPS的优点有以下2点:

1、SEO方面

谷歌曾在2014年8月份调整搜索引擎算法,并称“比起同等HTTP网站,采用HTTPS加密的网站在搜索结果中的排名将会更高”。

2、安全性

尽管HTTPS并非绝对安全,掌握根证书的机构、掌握加密算法的组织同样可以进行中间人形式的攻击,但HTTPS仍是现行架构下最安全的解决方案,主要有以下几个好处:

(1)、使用HTTPS协议可认证用户和服务器,确保数据发送到正确的客户机和服务器;

(2)、HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。

(3)、HTTPS是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本。

七、HTTPS的缺点

虽然说HTTPS有很大的优势,但其相对来说,还是有些不足之处的,具体来说,有以下2点:

1、SEO方面

据ACM CoNEXT数据显示,使用HTTPS协议会使页面的加载时间延长近50%,增加10%到20%的耗电,此外,HTTPS协议还会影响缓存,增加数据开销和功耗,甚至已有安全措施也会受到影响也会因此而受到影响。

而且HTTPS协议的加密范围也比较有限,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用。

最关键的,SSL证书的信用链体系并不安全,特别是在某些国家可以控制CA根证书的情况下,中间人攻击一样可行。

2、经济方面

(1)、SSL证书需要钱,功能越强大的证书费用越高,个人网站、小网站没有必要一般不会用。

(2)、SSL证书通常需要绑定IP,不能在同一IP上绑定多个域名,IPv4资源不可能支撑这个消耗(SSL有扩展可以部分解决这个问题,但是比较麻烦,而且要求浏览器、操作系统支持,Windows XP就不支持这个扩展,考虑到XP的装机量,这个特性几乎没用)。

(3)、HTTPS连接缓存不如HTTP高效,大流量网站如非必要也不会采用,流量成本太高。

(4)、HTTPS连接服务器端资源占用高很多,支持访客稍多的网站需要投入更大的成本,如果全部采用HTTPS,基于大部分计算资源闲置的假设的VPS的平均成本会上去。

(5)、HTTPS协议握手阶段比较费时,对网站的相应速度有负面影响,如非必要,没有理由牺牲用户体验。



7. like 效率

在使用msyql进行模糊查询的时候,很自然的会用到like语句,通常情况下,在数据量小的时候,不容易看出查询的效率,但在数据量达到百万级,千万级的时候,查询的效率就很容易显现出来。这个时候查询的效率就显得很重要!



一般情况下like模糊查询的写法为(field已建立索引):

SELECT `column` FROM `table` WHERE `field` like '%keyword%';

上面的语句用explain解释来看,SQL语句并未用到索引,而且是全表搜索,如果在数据量超大的时候,可想而知最后的效率会是这样

对比下面的写法:

SELECT `column` FROM `table` WHERE `field` like 'keyword%';

这样的写法用explain解释看到,SQL语句使用了索引,搜索的效率大大的提高了!



但是有的时候,我们在做模糊查询的时候,并非要想查询的关键词都在开头,所以如果不是特别的要求,"keywork%"并不合适所有的模糊查询



这个时候,我们可以考虑用其他的方法

1.LOCATE('substr',str,pos)方法
复制代码

SELECT LOCATE('xbar',`foobar`);
###返回0

SELECT LOCATE('bar',`foobarbar`);
###返回4

SELECT LOCATE('bar',`foobarbar`,5);
###返回7

复制代码

备注:返回 substr 在 str 中第一次出现的位置,如果 substr 在 str 中不存在,返回值为 0 。如果pos存在,返回 substr 在 str 第pos个位置后第一次出现的位置,如果 substr 在 str 中不存在,返回值为0。

SELECT `column` FROM `table` WHERE LOCATE('keyword', `field`)>0

备注:keyword是要搜索的内容,field为被匹配的字段,查询出所有存在keyword的数据



2.POSITION('substr' IN `field`)方法

position可以看做是locate的别名,功能跟locate一样

SELECT `column` FROM `table` WHERE POSITION('keyword' IN `filed`)

3.INSTR(`str`,'substr')方法

SELECT `column` FROM `table` WHERE INSTR(`field`, 'keyword' )>0



除了上述的方法外,还有一个函数FIND_IN_SET

FIND_IN_SET(str1,str2):

返回str2中str1所在的位置索引,其中str2必须以","分割开。

SELECT * FROM `person` WHERE FIND_IN_SET('apply',`name`);

8. left、right、join,解决笛卡尔积

sql之union,intersect,except语句

原创 2013年04月30日 15:36:36

A UNION运算符
UNION 运算符通过组合其他两个结果表(例如 TABLE1 TABLE2)并消去表中任何重复行而派生出一个结果表。当 ALL UNION 一起使用时(即 UNION ALL),不消除重复行。两种情况下,派生表的每一行不是来自 TABLE1就是来自 TABLE2
B EXCEPT运算符
EXCEPT 运算符通过包括所有在 TABLE1中但不在 TABLE2中的行并消除所有重复行而派生出一个结果表。当 ALL EXCEPT 一起使用时 (EXCEPT ALL),不消除重复行。
C INTERSECT运算符
INTERSECT 运算符通过只包括 TABLE1 TABLE2 中都有的行并消除所有重复行而派生出一个结果表。当 ALL INTERSECT一起使用时 (INTERSECT ALL),不消除重复行。
  注:使用运算词的几个查询结果行必须是一致的。

改成下面的语句比较好

--对两个结果集进行并集操作,包括重复行,不进行排序;

SELECT *FROM dbo.banji UNION ALL SELECT* FROM dbo.banjinew;

--对两个结果集进行交集操作,不包括重复行,同时进行默认规则的排序;

SELECT *FROM dbo.banji INTERSECT SELECT * FROM dbo.banjinew;

--运算符通过包括所有在TABLE1中但不在TABLE2中的行并消除所有重复行而派生出一个结果表

SELECT * FROM dbo.banji EXCEPT SELECT * FROM dbo.banjinew;
有些DBMS不支持except all和intersect all

9. oop封装、继承、多态

php面向对象之继承、多态、封装简介

作者:PHP中文网2018-03-02 09:49:15

1. 接口
 在php编程语言中接口是一个抽象类型,是抽象方法的集合。接口通常以interface来声明。一个类通过实现接口的方式,从而来实现接口的方法(抽象方法)。

接口定义:

interface InterAnimal{        
public function speak();        
public function name($name);    
}//接口实现 class cat implements InterAnimal{        
public function speak(){            
echo "speak";        
}        
public function name($name){            
echo "My name is ".$name;       
 }   
 }


特别注意:
* 类全部为抽象方法(不需要声明abstract)
* 接口抽象方法是public
* 成员(字段)必须是常量

2. 继承
 继承自另一个类的类被称为该类的子类。这种关系通常用父类和孩子来比喻。子类将继
承父类的特性。这些特性由属性和方法组成。子类可以增加父类之外的新功能,因此子类也
被称为父类的“扩展”。
 在PHP中,类继承通过extends关键字实现。继承自其他类的类成为子类或派生类,子
类所继承的类成为父类或基类。

class Computer {    
private $_name = '联想';    
public function __get($_key) {        
return $this->$_key;    
}    
public function run() {        
echo '父类run方法';    
}}class NoteBookComputer extends Computer {}$notebookcomputer = new NoteBookComputer ();
$notebookcomputer->run ();  
//继承父类中的run()方法echo $notebookcomputer->_name;  
//通过魔法函数__get()获得私有字段


特别注意:
有时候并不需要父类的字段和方法,那么可以通过子类的重写来修改父类的字段和方法。

class Computer {    
public $_name = '联想';    
protected function run() {        
echo '我是父类';    
}}//重写其字段、方法class NoteBookComputer extends Computer {    
public $_name = 'IBM';   
public function run() {        
echo '我是子类';    
}}


通过重写调用父类的方法
有的时候,我们需要通过重写的方法里能够调用父类的方法内容,这个时候就必须使用
语法:父类名::方法()、parent::方法()即可调用。
final关键字可以防止类被继承,有些时候只想做个独立的类,不想被其他类继承使用。

3. 抽象类和方法
抽象类特性:
* 抽象类不能产生实例对象,只能被继承;
* 抽象方法一定在抽象类中,抽象类中不一定有抽象方法;
* 继承一个抽象类时,子类必须重写父类中所有抽象方法;
* 被定义为抽象的方法只是声明其调用方式(参数),并不实现。

abstract class Computer {    
abstract function run();
}
final class NotebookComputer extends Computer {    
public function run() {       
 echo '抽象类的实现';    
}
}


3. 多态
多态是指OOP 能够根据使用类的上下文来重新定义或改变类的性质或行为,或者说接口的多种不同的实现方式即为多态。

interface Computer {    
public function version();    
public function work();
}class NotebookComputer implements Computer {    
public function version() {       
 echo '联想<br>';    
}    
public function work() {        
echo '笔记本正在随时携带运行!';    
}}class desktopComputer implements Computer {    
public function version() {        
echo 'IBM';    
}    
public function work() {        
echo '台式电脑正在工作站运行!';    
}}class Person {   
 public function run($type) {        
$type->version ();        
$type->work ();    
}}
$person = new Person ();
$desktopcomputer = new desktopComputer ();
$notebookcomputer = new NoteBookComputer ();
$person->run ( $notebookcomputer );


相关推荐:

PHP面向对象之标识对象

php面向对象程序设计的开发思路与实例分析

PHP面向对象实用基础知识

以上就是php面向对象之继承、多态、封装简介的详细内容,更多请关注php中文网其它相关文章!

标签: 多态封装PHP

上一篇:PHP实现微信小程序支付代码分享

下一篇:php会话控制session、cookie介绍

为您推荐白俊遥博客PHP面向对象之标识对象

2018-02-1174

白俊遥博客PHP面向对象之标识对象实例详解

2018-02-1074

白俊遥博客PHP实现图片水印类的封装代码分享

2018-02-1059

白俊遥博客详解php封装Mysql操作类

2018-01-0567


10. mvc

MVC模式(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。

MVC模式最早由Trygve Reenskaug在1978年提出[1] ,是施乐帕罗奥多研究中心(Xerox PARC)在20世纪80年代为程序语言Smalltalk发明的一种软件设计模式。MVC模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。除此之外,此模式通过对复杂度的简化,使程序结构更加直观。软件系统通过对自身基本部分分离的同时也赋予了各个基本部分应有的功能。专业人员可以通过自身的专长分组:

  • (控制器Controller)- 负责转发请求,对请求进行处理。

  • (视图View) - 界面设计人员进行图形界面设计。

  • (模型Model) - 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。

白俊遥博客

视图:

在视图中其实没有真正的处理发生,不管这些数据是联机存储的还是一个雇员列表,作为视图来讲,它只是作为一种输出数据并允许用户操纵的方式。
模型
模型表示企业数据和业务规则。在MVC的三个部件中,模型拥有最多的处理任务。例如它可能用象EJBs和ColdFusionComponents这样的构件对象来处理数据库。被模型返回的数据是中立的,就是说模型与数据格式无关,这样一个模型能为多个视图提供数据。由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。
控制器:
控制器接受用户的输入并调用模型和视图去完成用户的需求。所以当单击Web页面中的超链接和发送HTML表单时,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。

MVC的优点
1.低耦合性

  视图层和业务层分离,这样就允许更改视图层代码而不用重新编译模型和控制器代码,同样,一个应用的业务流程或者业务规则的改变只需要改动MVC的模型层即可。因为模型与控制器和视图相分离,所以很容易改变应用程序的数据层和业务规则。
2.高重用性和可适用性
  随着技术的不断进步,现在需要用越来越多的方式来访问应用程序。MVC模式允许你使用各种不同样式的视图来访问同一个服务器端的代码。它包括任何WEB(HTTP)浏览器或者无线浏览器(wap),比如,用户可以通过电脑也可通过手机来订购某样产品,虽然订购的方式不一样,但处理订购产品的方式是一样的。由于模型返回的数据没有进行格式化,所以同样的构件能被不同的界面使用。例如,很多数据可能用HTML来表示,但是也有可能用WAP来表示,而这些表示所需要的命令是改变视图层的实现方式,而控制层和模型层无需做任何改变。
3.较低的生命周期成本
  MVC使开发和维护用户接口的技术含量降低。
4.快速的部署
  使用MVC模式使开发时间得到相当大的缩减,它使程序员(Java开发人员)集中精力于业务逻辑,界面程序员(HTML和JSP开发人员)集中精力于表现形式上。
5.可维护性
  分离视图层和业务逻辑层也使得WEB应用更易于维护和修改。
6.有利于软件工程化管理
  由于不同的层各司其职,每一层不同的应用具有某些相同的特征,有利于通过工程化、工具化管理程序代码。

扩展:

WAP(Wireless Application Protocol)为无线应用协议,是一项全球性的网络通信协议。WAP使移动Internet 有了一个通行的标准,其目标是将Internet的丰富信息及先进的业务引入到移动电话等无线终端之中。WAP定义可通用的平台,把目前Internet网上HTML语言的信息转换成用WML(Wireless Markup Language)描述的信息,显示在移动电话的显示屏上。WAP只要求移动电话和WAP代理服务器的支持,而不要求现有的移动通信网络协议做任何的改动,因而可以广泛地应用于GSM、CDMA、TDMA、3G等多种网络。 随着移动上网成为Internet时代新的宠儿,出现了WAP的各种应用需求。

一些手持设备,如掌上电脑,安装微型浏览器后,借助WAP接入Internet。 微型浏览器文件很小,可较好的解决手持设备内存小和无线网络带宽不宽的限制。 虽然WAP能支持HTHL和XML,但WML才是专门为小屏幕和无键盘手持设备 服务的语言。WAP也支持WMLScript。这种脚本语言类似与JavaScript,但对内存和CPU的要求更低,因为它基本上没有其他脚本语言所包含的无用功能。



11. CDN作用 收费/免费的区别 GO\QR

1、多域名加载资源

一般情况下,浏览器都会对单个域名下的并发请求数(文件加载)进行限制,通常最多有4个,那么第5个加载项将会被阻塞,直到前面的某一个文件加载完毕。

因为CDN文件是存放在不同区域(不同IP)的,所以对浏览器来说是可以同时加载页面所需的所有文件(远不止4个),从而提高页面加载速度。

2、文件可能已经被加载过并保存有缓存

一些通用的js库或者是css样式库,如jQuery,在网络中的使用是非常普遍的。当一个用户在浏览你的某一个网页的时候,很有可能他已经通过你网站使用的CDN访问过了其他的某一个网站,恰巧这个网站同样也使用了jQuery,那么此时用户浏览器已经缓存有该jQuery文件(同IP的同名文件如果有缓存,浏览器会直接使用缓存文件,不会再进行加载),所以就不会再加载一次了,从而间接的提高了网站的访问速度。

3、高效率

你的网站做的再NB也不会NB过百度NBGoogle吧?一个好的CDNs会提供更高的效率,更低的网络延时和更小的丢包率。

4、分布式的数据中心

假如你的站点布置在北京,当一个香港或者更远的用户访问你的站点的时候,他的数据请求势必会很慢很慢。而CDNs则会让用户从离他最近的节点去加载所需的文件,所以加载速度提升就是理所当然的了。

5、内置版本控制

通常,对于CSS文件和JavaScript库来说都是有版本号的,你可以通过特定版本号从CDNs加载所需的文件,也可以使用latest加载最新版本(不推荐)。

6、使用情况分析

一般情况下CDNs提供商(如百度云加速)都会提供数据统计功能,可以了解更多关于用户访问自己网站的情况,可以根据统计数据对自己的站点适时适当的做出些许调整。

7、有效防止网站被攻击

一般情况下CDNs提供商也是会提供网站安全服务的。


动机

为什么不进行数据的直接交付,即让用户直接从源站获取数据呢?
我们常说的互联网实际上由两层组成,一层是以TCP/IP为核心的网络层即Internet(因特网),另一层则是以万维网WWW为代表的应用层。数据从服务器端交付到用户端,至少有4个地方可能会造成网络拥堵。
1. “第一公里”,这是指万维网流量向用户传送的第一个出口,是网站服务器接入互联网的链路。这个出口带宽决定了一个网站能为用户提供的访问速度和并发访问量。当用户请求量超出网站的出口带宽,就会在出口处造成拥塞。
2. “最后一公里”,万维网流量向用户传送的最后一段链路,即用户接入互联网的链路。用户接入的带宽影响用户接收流量的能力。随着电信运营商的大力发展,用户的接入带宽得到了很大改善,“最后一公里”问题基本得到解决。
3. ISP互联,即因特网服务提供商之间的互联,比如中国电信和中国联通两个网络运营商之间的互联互通。当某个网站服务器部署在运营商A的机房,运营商B的用户要访问该网站,那就必须经过A、B之间的互联互通点进行跨网访问。从互联网的架构来看,不同运营商之间的互联互通带宽,对任何一个运营商网络流量来说,占比都非常小。因此,这里也通常是网络传输的拥堵点。
4. 长途骨干传输。首先是长距离传输时延问题,其次是骨干网络的拥塞问题,这些问题都会造成万维网流量传输的拥堵。
从以上对于网络拥堵的情况分析,如果网络上的数据都使用从源站直接交付到用户的方法,那么将极有可能会出现访问拥塞的情况。
如果能有一种技术方案,将数据缓存在离用户最近的地方,使用户以最快的速度获取,那这对于减少网站的出口带宽压力,减少网络传输的拥堵情况,将起到很大的作用。CDN正是这样一种技术方案。

基本过程

用户通过浏览器访问传统的(没有使用CDN)网站的过程如下。
白俊遥博客
1. 用户在浏览器中输入要访问的域名。
2. 浏览器向DNS服务器请求对该域名的解析。
3. DNS服务器返回该域名的IP地址给浏览器。
4. 浏览器使用该IP地址向服务器请求内容。
5. 服务器将用户请求的内容返回给浏览器。

如果使用了CDN,则其过程会变成以下这样。
白俊遥博客
1. 用户在浏览器中输入要访问的域名。
2. 浏览器向DNS服务器请求对域名进行解析。由于CDN对域名解析进行了调整,DNS服务器会最终将域名的解析权交给CNAME指向的CDN专用DNS服务器。
3. CDN的DNS服务器将CDN的负载均衡设备IP地址返回给用户。
4. 用户向CDN的负载均衡设备发起内容URL访问请求。
5. CDN负载均衡设备会为用户选择一台合适的缓存服务器提供服务。
选择的依据包括:根据用户IP地址,判断哪一台服务器距离用户最近;根据用户所请求的URL中携带的内容名称,判断哪一台服务器上有用户所需内容;查询各个服务器的负载情况,判断哪一台服务器的负载较小。
基于以上这些依据的综合分析之后,负载均衡设置会把缓存服务器的IP地址返回给用户。
6. 用户向缓存服务器发出请求。
7. 缓存服务器响应用户请求,将用户所需内容传送到用户。
如果这台缓存服务器上并没有用户想要的内容,而负载均衡设备依然将它分配给了用户,那么这台服务器就要向它的上一级缓存服务器请求内容,直至追溯到网站的源服务器将内容拉取到本地。

总结

在网站和用户之间引入CDN之后,用户不会有任何与原来不同的感觉。
使用CDN服务的网站,只需将其域名的解析权交给CDN的负载均衡设备,CDN负载均衡设备将为用户选择一台合适的缓存服务器,用户通过访问这台缓存服务器来获取自己所需的数据。
由于缓存服务器部署在网络运营商的机房,而这些运营商又是用户的网络服务提供商,因此用户可以以最短的路径,最快的速度对网站进行访问。因此,CDN可以加速用户访问速度,减少源站中心负载压力。



12. nginx 负载均衡

负载均衡策略:

1. 轮循均衡(Round Robin):每一次来自网络的请求轮流分配给内部中的服务器,从1至N然后重新开始。此种均衡算法适合于服务器组中的所有服务器都有相同的软硬件配置并且平均服务请求相对均衡的情况。

2. 权重轮循均衡(Weighted Round Robin):根据服务器的不同处理能力,给每个服务器分配不同的权值,使其能够接受相应权值数的服务请求。例如:服务器A的权值被设计成1,B的权值是3,C的权值是6,则服务器A、B、C将分别接受到10%、30%、60%的服务请求。此种均衡算法能确保高性能的服务器得到更多的使用率,避免低性能的服务器负载过重。

3. 随机均衡(Random):把来自网络的请求随机分配给内部中的多个服务器。

4. 权重随机均衡(Weighted Random):此种均衡算法类似于权重轮循算法,不过在处理请求分担时是个随机选择的过程。

5. 响应速度均衡(Response Time):负载均衡设备对内部各服务器发出一个探测请求(例如Ping),然后根据内部中各服务器对探测请求的最快响应时间来决定哪一台服务器来响应客户端的服务请求。此种均衡算法能较好的反映服务器的当前运行状态,但这最快响应时间仅仅指的是负载均衡设备与服务器间的最快响应时间,而不是客户端与服务器间的最快响应时间。

6. 最少连接数均衡(Least Connection):客户端的每一次请求服务在服务器停留的时间可能会有较大的差异,随着工作时间加长,如果采用简单的轮循或随机均衡算法,每一台服务器上的连接进程可能会产生极大的不同,并没有达到真正的负载均衡。最少连接数均衡算法对内部中需负载的每一台服务器都有一个数据记录,记录当前该服务器正在处理的连接数量,当有新的服务连接请求时,将把当前请求分配给连接数最少的服务器,使均衡更加符合实际情况,负载更加均衡。此种均衡算法适合长时处理的请求服务,如FTP。

7. 处理能力均衡:此种均衡算法将把服务请求分配给内部中处理负荷(根据服务器CPU型号、CPU数量、内存大小及当前连接数等换算而成)最轻的服务器,由于考虑到了内部服务器的处理能力及当前网络运行状况,所以此种均衡算法相对来说更加精确,尤其适合运用到第七层(应用层)负载均衡的情况下。

8. DNS响应均衡(Flash DNS):在Internet上,无论是HTTP、FTP或是其它的服务请求,客户端一般都是通过域名解析来找到服务器确切的IP地址的。在此均衡算法下,分处在不同地理位置的负载均衡设备收到同一个客户端的域名解析请求,并在同一时间内把此域名解析成各自相对应服务器的IP地址(即与此负载均衡设备在同一位地理位置的服务器的IP地址)并返回给客户端,则客户端将以最先收到的域名解析IP地址来继续请求服务,而忽略其它的IP地址响应。在种均衡策略适合应用在全局负载均衡的情况下,对本地负载均衡是没有意义的。

服务故障的检测方式和能力:

1. Ping侦测:通过ping的方式检测服务器及网络系统状况,此种方式简单快速,但只能大致检测出网络及服务器上的操作系统是否正常,对服务器上的应用服务检测就无能为力了。

2. TCP Open侦测:每个服务都会开放某个通过TCP连接,检测服务器上某个TCP端口(如Telnet的23口,HTTP的80口等)是否开放来判断服务是否正常。

3. HTTP URL侦测:比如向HTTP服务器发出一个对main.html文件的访问请求,如果收到错误信息,则认为服务器出现故障。



13. 缓存 (七层分述)

目录

  • 1. 动态内容缓存

  • 2. 浏览器缓存

  • 3. 服务器缓存

  • 4. 反向代理缓存

  • 5. 分布式缓存

  • 6. 总结

本文标题叫“高性能Web缓存浅析”,首先,必须声明,“浅析”并非自己谦虚,而是真的是“浅”析,作为一枚刚毕业应届生,在提笔写这篇文章前一周刚上线了自己作为程序员的第一个正式系统,文中所有内容均来源于阅读和自己的一些思考,与实际生产环境可能会有出入,还请不吝赐教。

首先,简单说下缓存,缓存的思想由来已久,将需要花费大量时间开销的计算结果保存起来,在以后需要的时候直接使用,避免重复计算。在计算机系统中,缓存的应用不胜枚举,比如计算机的三级存储结构、Web 服务中的缓存,本文主要讨论缓存在 Web 环境中的使用,包括浏览器缓存、服务器缓存、反向代理缓存以及分布式缓存等方面。

1. 动态内容缓存

现代 Web 站点更多的提供动态内容,如动态网页、动态图片、Web服务等,它们通常在 Web 服务器端进行计算,生成 HTML 并返回。在生成 HTML 页面的过程中,涉及到大量的 CPU 计算和 I/O 操作,比如数据库服务器的 CPU 计算和磁盘 I/O,以及与数据库服务器通信的网络I/O。这些操作会花费大量时间,然而,大多数情况下,动态网页在多次请求时的生成结果几乎一样,那么,就可以考虑通过缓存去掉这部分时间开销。

1.1 页面缓存

对于动态网页来说,我们将生成的 HTML 缓存起来,称为页面缓存(Page Cache),对于其它动态内容如动态图片、动态 XML 数据,我们也可以相同策略将它们的结果整体进行缓存。

对于页面缓存具体方法,有很多实现方法,如类似 Smarty 的模板引擎或者类似 Zend、Diango 的 MVC 框架,控制器和视图分离,控制器很方便的拥有自己的缓存控制权。

1.1.1 存储方式

通常,我们将动态内容的缓存存储在磁盘上,磁盘提供了廉价的、存储大量文件的方式,不用担心由于空间问题而淘汰缓存,这是一种简单且容易部署的方法。但是,还是可能造成 cache 目录下存在大量缓存文件的可能,从而使 CPU 在遍历目录时花费大量时间,针对这个问题,可以使用缓存目录分级来解决这一问题,从而将每个目录下的子目录或文件数量控制在少量范围内。这样,在存储大量缓存文件的情况下,可以减少 CPU 遍历目录的时间消耗。

当将缓存数据存储在磁盘文件中时,每次缓存加载和过期检查都存在磁盘 I/O 开销,同时也受磁盘负载影响,如果磁盘 I/O 负载大,则缓存文件的 I/O 操作会存在一定的延迟。

另外,可以将缓存放在本机内存中,借助 PHP 的 APC 模块或 PHP 缓存扩展 XCache 可以很方便的实现,这样,加载缓存时就没有任何磁盘 I/O 操作。

最后,还可以将缓存存储在独立的缓存服务器中,利用 memcached 可以很方便的通过 TCP 将缓存存储在其他服务器。使用 memcached,速度会比使用本机内存稍慢,但相比将缓存存放到本机内存,使用 memcached 来实现缓存有两个优势:

  1. Web 服务器内存宝贵,无法提供大量空间做 HTML 缓存。

  2. 使用独立的缓存服务器可以提供良好的可扩展性。

1.1.2 过期检查

既然谈到缓存,就不得不谈过期检查,缓存过期检查主要根据缓存有效期机制来检查,主要两种机制:

  1. 根据缓存创建时间、缓存有效期设置的时间长度以及当前时间,来判断是否过期,也就是说如果当前时间距缓存创建的时间长度超过有效期长度,则认为缓存过期。

  2. 根据缓存的过期时间和当前时间来判断。

对于缓存有效期的设置并不是件容易的事,如果太长,虽然缓存命中率高了,但动态内容更新不及时;如果太短,虽然动态内容更新及时,但缓存命中率降低了。所以,设置合理的有效期十分重要,但更重要的是,我们需要具备能意识到有效期何时需要变换的能力,然后在任何时候找到合适的取值。

除了缓存有效期,缓存还提供了随时强行清除缓存的控制方法。

1.1.3 局部无缓存

在有些情况下,需要页面中某块区域的内容及时更新,如果因为一块区域需要及时更新而重建整个页面缓存的话,会显得不值得。在流行的模板框架中,都提供了局部无缓存的支持,如 Smary。

1.2 静态化内容

前面的方法需动态地控制是否使用缓存,静态化方法将动态内容生成静态内容,然后让用户直接请求静态内容,大幅度提高吞吐率。

同样的,对于静态化内容,同样需要更新,一般有两种方法:

  1. 数据更新时重新生成静态化内容。

  2. 定时重新生成静态化内容。

与前面提到的动态缓存一样,静态页面也可以不更新整个页面,可以通过服务器包含(SSI)技术实现各个局部页面的独立更新,从而大大减少重建整个页面的计算开销和磁盘 I/O 开销,以及分发时的网络 I/O 开销。现在主流的 Web 服务器都支持 SSI 技术,比如 Apache、lighttpd等。

2. 浏览器缓存

串通角度来看,人们习惯将浏览器仅仅看着 PC 上的一个软件,但实际上,浏览器是 Web 站点的重要组成部分。如果将内容缓存在浏览器上,不仅可以减少服务器计算开销,还能避免不必要的传输和带宽浪费。为了在浏览器端存储缓存内容,一般是在用户的文件系统中创建一个目录用来存储缓存文件,并给每个缓存文件打上一些必要标签,如过期时间等。另外,不同浏览器在存储缓存文件时会有细微差别。

2.1 实现

浏览器缓存内容存储在浏览器端,而内容由 Web 服务器生成,要利用浏览器缓存,浏览器和 Web 服务器之间必须沟通,这就是 HTPP 中的“缓存协商”。

2.1.1 缓存协商

当 Web 服务器接收到浏览器请求后,Web 服务器需要告知浏览器哪些内容可以缓存,一旦浏览器知道哪些内容可以缓存后,下次当浏览器需要请求这个内容时,浏览器便不会直接向服务器请求完整内容,二是询问服务器是否可以使用本地缓存,服务器在收到浏览的询问后回应是使用浏览器本地缓存还是将最新内容传回给浏览器。

Last-Modified

Last-Modified 是一种协商方式。通过动态程序为 HTTP 相应添加最后修改时间的标记

header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");

此时,Web 服务器的响应头部会多出一条:

Last-Modified: Fri, 9 Dec 2014 23:23:23 GMT

这代表 Web 服务器对浏览器的暗示,告诉浏览器当前请求内容的最后修改时间。收到 Web 服务器响应后,再次刷新页面,注意到发出的 HTTP 请求头部多了一段标记:

If-Modified-Since: Fri, 9 Dec 2014 23:23:23 GMT

这表示浏览器询问 Web 服务器在该时间后是否有更新过请求的内容,此时,Web 服务器需要检查请求的内容在该时间后是否有过更新并反馈给浏览器,这其实就是缓存过期检查。

如果这段时间里请求的内容没有发生变化,服务器做出回应,此时,Web 服务器响应头部:

HTTP/1.1 304 Not Modified

注意到此时的状态码是304,意味着 Web 服务器告诉浏览器这个内容没有更新,浏览器使用本地缓存。如下图所示:白俊遥博客

ETag

HTTP/1.1 还支持ETag缓存协商方法,与最后过期时间不同的是,ETag不再采用内容的最后修改时间,而是采用一串编码来标记内容,称为ETag,如果一个内容的 ETag 没有变化,那么这个内容就一定没有更新

ETag 由 Web 服务器生成,浏览器在获得内容的 ETag 后,会在下次请求该内容时,在 HTTP 请求头中附加上相应标记来询问服务器该内容是否发生了变化:

If-None-Match: "87665-c-090f0adfadf"

这时,服务器需要重新计算这个内容的 ETag,并与 HTTP 请求中的 ETag 进行对比,如果相同,便返回 304,若不同,则返回最新内容。如下图所示,服务器发现请求的 ETag 与重新计算的 ETag 不同,返回最新内容,状态码为200。白俊遥博客

Last-Modified VS ETag

基于最后修改时间的缓存协商存在一些缺点,如有时候文件需频繁更新,但内容并没有发生变化,这种情况下,每次文件修改时间变化后,无论内容是否发生变化,都会重新获取全部内容。另外,在采用多台 Web 服务器时,用户请求可能在多台服务器间变化,而不同服务器上同一文件最后修改时间很难保证完全一样,便会导致重新获取所有内容。采用 ETag 方法就可以避免这些问题。

2.1.2 性能

首先,原本使用浏览器缓存的动态内容,在使用浏览器缓存后,能否获得大的吞吐率提升,关键在于是否能避免一些额外的计算开销,同事,还取决于 HTTP 响应正文的长度,若 HTTP 响应较长,如较长的视频,则能带来大的吞吐率提到。

但使用浏览器缓存的最大价值并不在此,而在于减少带宽消耗。使用浏览器缓存后,如果 Web 服务器计算后发现可以使用浏览器端缓存,则返回的响应长度将大大减少,从而,大大减少带宽消耗。

2.2 彻底消灭请求

The goal of caching in HTTP/1.1 is to eliminate the need to send requests in many cases.

2.2.1 Expires 标记

在上面两图中,有个Expires标记,告诉浏览器该内容何时过期,在内容过期前不需要再询问服务器,直接使用本地缓存即可

2.2.2 请求页面方式

对于主流浏览器,有三种请求页面方式:

  1. Ctrl + F5:强制刷新,使网页以及所有组件都直接向 Web 浏览器发送请求,并且不适用缓存协商,从而获取所有内容的最新版本。等价于按住 Ctrl 键后点击浏览器刷新按钮。

  2. F5:允许浏览器在请求中附加必要的缓存协商,但不允许直接使用本地缓存,即让Last-Modified生效、Expires无效。等价于单击浏览器刷新按钮。

  3. 单击浏览器地址栏“转到”按钮或通过超链接跳转:浏览器对于所有没过期的内容直接使用本地缓存,Expires只对这种方式生效。等价于在地址栏输入 URL 后回车。

Last-ModifiedExpires
Ctrl + F5无效无效
F5有效无效
转到有效有效

2.2.3 适应过期时间

Expires指定的过期时间来源于 Web 服务器的系统时间,如果与用户本地时间不一致,就会影响到本地缓存的有效期检查。

一般情况下,操作系统都使用基于 GMT 的标准时间,然后通过时区来进行偏移计算,HTTP 中也使用 GMT 时间,所以,一般不会因为时区导致本地与服务器相差数个小时,但没人能保证本地时间与服务器一直,甚至有时服务器时间也是错误的。

针对这个问题,HTTP/1.1 添加了标记 Cache-Control,如上图1所示,max-age 指定缓存过期的相对时间,单位是秒,相对时间指相对浏览器本地时间。目前,当 HTTP 响应中同时含有 Expires 和 Cache-Control 时,浏览器会优先使用 Cache-Control。

2.3 总结

HTTP 是浏览器与 Web 服务器沟通的语言,且是它们唯一的沟通方式,好好学学 HTTP 吧!

3. Web 服务器缓存

前面提到的动态内容缓存和静态化基本都是通过动态程序来实现的,下面讨论 Web 服务器自己实现缓存机制。

Web 服务器接收到 HTTP 请求后,需要解析 URL,然后将 URL 映射到实际内容或资源,这里的“映射”指服务器处理请求并生成响应内容的过程。很多时候,在一段时间内,一个 URL 对应一个唯一的响应内容,比如静态内容或更新不频繁的动态内容,如果将最终内容缓存起来,下次 Web 服务器接收到请求后可以直接将响应内容返回给浏览器,从而节省大量开销。现在,主流 Web 服务器都提供了对这种类型缓存的支持。

3.1 简介

当使用 Web 服务器缓存时,如果直接命中,那么将省略后面的一系列操作,如 CPU 计算、数据库查询等,所以,Web 服务器缓存能带来较大性能提升,但对于普通 HTML 也,带来的性能提升较有限。

那么,缓存内容存储在什么位置呢?一般来说,本机内存和磁盘是主要选择,也可以采用分布式设计,存储到其它服务器的内存或磁盘中,这点跟前面提到的动态内容缓存类似,Apache、lighttpd 和 Nginx 都提供了支持,但配置上略有差别。

提到缓存,就不得不提有效期控制。与浏览器缓存相似,Web 服务器缓存过期检查仍然建立在 HTTP/1.1 协议上,要指定缓存有效期,仍然是在 HTTP 响应头中加入 Expires 标记,如果希望不缓存某个动态内容,那么最简单的办法就是使用:

header("Expires: 0");

这样一来,Web服务器就不会将这个动态内容缓存起来,当然,也有其它方法实现这个功能。

如果动态内容没有输出 Expires 标记,也可以采用 Last-Modified来实现,具体方法不再叙述。

3.2 取代动态内容缓存

那么,是否可以使用 Web 服务器缓存取代动态程序自身的缓存机制呢?当然可以,但有些注意:

  1. 让动态程序依赖特定 Web 服务器,降低应用的可移植性。

  2. Web 服务器缓存机制实质上是以 URL 为键的 key-value 结构缓存,所以,必须保证所有希望缓存的动态内容有唯一的 URL。

  3. 编写面向 HTTP 缓存友好的动态程序是唯一需要考虑的事。

3.3 缓存文件描述符

对静态内容,特别是大量小文件站点, Web 服务器很大一部分开销花在了打开文件上,所以,可以考虑将打开后的文件描述符直接缓存到 Web 服务器的内存中,从而减少开销。但是,缓存文件描述符仅仅适用于静态内容,而且仅适用于小文件,对大文件,处理它们的开销主要在传送数据上,打开文件开销小,缓存文件描述符带来的收益小。

4. 反向代理缓存

4.1 反向代理简介

代理(Proxy),也称网络代理,是一种特殊的网络服务,允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。提供代理服务的电脑系统或其它类型的网络终端称为代理服务器(Proxy Server)。

上面是维基百科对代理的定义,在这种情况下,用户隐藏在代理服务器后,那么,反向代理服务器便刚好与此相反,Web 服务器隐藏在代理服务器后,这种机制即反向代理(Reverse Proxy),同样的,实现这种机制的服务器,便称为反向代理服务器(Reverse Proxy Server)。我们通常称反向代理服务器后的 Web 服务器为后端服务器(Back-end Server),相应的,反向代理服务器便称为前端服务器(Front-end Server),通常,反向代理服务器暴露在互联网中,后端的 Web 服务器通过内部网络与它相连,用户将通过反向代理服务器来间接访问 Web 服务器,这既带来一定的安全性,也可以实现基于缓存的加速。

4.2 反向代理缓存

有很多方式可以实现反向代理,如最常见的 Nginx 服务器就可以作为反向代理服务器。

4.2.1 修改缓存规则

用户浏览器、Web 服务器要想正常工作,都需要经过反向代理服务器,所以,反向代理服务器拥有很大的控制权,可以通过任何手段重写经过它的 HTTP 头信息,也可以通过其他自定义机制来直接干预缓存策略。从前面的内容知道,HTTP 头信息决定内容是否可以被缓存,所以,反向代理服务器本着提高性能的原则可以修改经过它的数据的 HTTP 头信息,决定哪些内容可以被缓存,哪些不能被缓存。

4.2.2 清除缓存

反向代理服务器也提供了清除缓存的功能,但是,与动态内容缓存不同的是,在动态内容缓存中,我们可以通过主动删除缓存的方法实现缓存到期之前的更新,而基于 HTTP 的反向代理缓存机制则不容易做到,后端的动态程序无法做到主动删除某个缓存内容,除非清空反向代理服务器上的缓存区。

5. 分布式缓存

5.1 memcached

现在已经有很多成熟的分布式缓存系统,如 memcached。为了实现高速缓存,我们不会将缓存内容放在磁盘上,基于这个原则,memcached 使用物理内存作为缓存区,使用 key-value 的方式存储数据,这是一种单索引的结构和数据组织形式,我们将每个 key 以及对应 value 合起来称为数据项,所有数据项之间彼此独立,每个数据项以 key 作为唯一索引,用户可以通过 key 来读取或更新这个数据项,memcached 使用基于 key 的hash 算法来设计存储数据结构,使用精心设计的内存分配器,使得数据项查询时间复杂度达到O(1)。

memcached 使用基于 LRU(Lease Recently Used) 算法的淘汰机制淘汰数据,同时,也可以为数据项设置过期时间,同样的,过期时间的设置在前面已经讨论过了。

作为分布式缓存系统,memcached 可以运行在独立服务器上,动态内容通过 TCP Socket 来访问,这种情况下,memcached 本身的网络并发处理模型就显得十分重要。memcached 使用 libevent 函数库来实现网络并发模型,可以在较大并发用户数环境下使用 memcached。

5.2 读写缓存

在使用缓存系统实现读操作时,相当于使用了数据库的“前置读缓存”,可以较大的提高吞吐率。

对于写操作,缓存系统也能带来巨大好处。通常的数据写操作包括插入、更新、删除,这些写操作往往同时伴随着查找和索引更新,往往带来巨大开销。在使用分布式缓存时,我们可以暂时将数据存储在缓存中,然后进行批量写操作。

5.3 缓存监控

memcached 作为一个分布式缓存系统,可以出色的完成任务,同时,memcached 也提供了协议,让我们可以获取它的实时状态,其中有几个重要信息:

  1. 空间使用率:关注缓存空间使用率,可以让我们知道何时需要为缓存系统扩容,以避免由于缓存空间已满造成的数据被动淘汰。

  2. 缓存命中率

  3. I/O 流量:反映了缓存系统的工作量,可以从中得知 memcached 是空闲还是繁忙。

5.4 缓存扩展

并发处理能力、缓存空间容量等都可能到达极限,扩展在所难免。

当存在多台缓存服务器后,我们面临的问题是,如何将缓存数据均衡的分布在多台缓存服务器上?在这种情况下,可以选择一种基于 key 的划分方法,将所有数据项的 key 均衡分布在不同服务器上,比如采取取余的方法。这种情况下,当我们扩展系统后,由于分区算法的改变,会需要将缓存数据从一台缓存服务器迁移到另一台缓存服务器,实际上,根本不需要考虑迁移,因为是缓存数据,重建缓存即可。

6.总结

缓存如何使用还得具体问题具体分析。举例来讲,当年在百度实习时,仅仅是一次搜索的结果,使用动态内容缓存都分了好多层,总之是能用缓存就用缓存,能尽早用缓存就尽早用缓存;而在我现在工作的创业公司有赞,现在也就使用了 redis 做动态内容缓存,随着业务量的增大,正在一步步完善,哦,差点忘了,还有浏览器缓存。


14. 内容分流、cms静态化

一、 为什么要静态化?

  1. 加快页面打开浏览速度,静态页面无需连接数据库打开速度较动态页面有明显提高;

  2. 有利于搜索引擎优化SEO,Baidu、Google都会优先收录静态页面,不仅被收录的快还收录的全;

  3. 减轻服务器负担,浏览网页无需调用系统数据库;

  4. 网站更安全,HTML页面不会受php相关漏洞的影响; 观看一下大一点的网站基本全是静态页面,而且可以减少攻击,防sql注入。

当然有好处,也有不足?

  1. 信息不同步。只有重新生成HTML页面,才能保持信息同步。

  2. 服务器存储问题。数据一直增加,静态HTML页面会不断增加,会占用大量的磁盘。需要考虑这个问题

  3. 静态化算法的精密性。要良好的处理数据与网页模板,及各种文件链接路径的问题,这就要求我们在静态化的算法中考虑到方方面面。稍有细小疏忽,将导致生成的页面中存在这样或那样的错误链接,甚至存在死链。因此,我们必须恰到好处的解决这些问题。既不能增加算法的可能性,又要照顾到方方面面。做到这一点,的确不容易。

二、PHP静态化

参考文章:《分享常见的几种页面静态化的方法》

PHP静态化的简单理解就是使网站生成页面以静态HTML的形式展现在访客面前,PHP静态化分纯静态化伪静态化,两者的区别在于PHP生成静态页面的处理机制不同。

纯静态化:PHP生成HTML文件
伪静态化:把内容存放在nosql内存(memcached),然后访问页面的时候,直接从内存里面读取。

大型动态网站静态化

参考文章:《大型网站的静态化处理》

大型网站(高访问量,高并发量),如果是静态网站,可以通过扩展足够多的 web服务器,然后支持超大规模的并发访问。

如果是一个动态的网站,特别是使用到了数据库的网站是很难做到通过增加web服务器数量的方式来有效的增加网站并发访问能力的。比如淘宝,京东。

静态化方案:

大型静态网站之所以能够快速响应高并发,因为他们尽量把动态网站静态化。

  1. js,css,img等资源,服务端合并在返回

  2. CDN 内容分发网络技术【网络传输的效率跟距离长短有关系的原理,通过算法,计算最近的静态服务器节点】

  3. web服务器动静结合。页面有一部分是一直不变的,比如 header, footer 部分。 那么这一部分是否可以放在缓存。web服务器 apache或ngnix, appache有一个模块叫做ESI,CSI。能够动静拼接。把静态的部分缓存在 web服务器上,然后和服务器返回的动态页面拼接在一起。

  4. 浏览器实现动静结合,前端MVC。

静态页面、动态页面和伪静态页面的区别

1.静态页面

  优点:相对于其他两种页面(动态页面和伪静态页面),速度最快,而且不需要从数据库里面提取数据,速度快的同时,也不会对服务器产生压力。

  缺点:由于数据都是存储在HTML里面,所以导致文件非常大。并且最严重的问题是,更改源代码必须全部更改,而不能改一个地方,全站静态页面就自动更改了。如果是大型网站有较多的数据,那会占用大量的服务器空间,每次添加内容都会生成新的HTML页面。如果不是专业人士维护比较麻烦。

2.动态页面

  优点:空间使用量非常小,一般几万条数据的网站,使用动态页面,可能只有几M的文件大小,而使用静态页面少则十几M,多则几十M甚至更多。因为数据库是从数据库里面调出来的,如果需要修改某些值,直接更改数据库,那么所有的动态网页,就会自动更新了。这一点相比静态页面优点就显而易见了。

  缺点:用户访问速度较慢,为什么会访问动态页面较慢呢?这个问题要从动态页面的访问机制说起了,其实我们的服务器上面有一个解释引擎,当用户访问的时候,这个解释引擎就会把动态页面翻译为静态页面,这样大家就能够在浏览器里面查看源码了。而这个源码就是解释引擎翻译之后的源码。除访问速度较慢以外,动态页面的数据是从数据库里面调用过来的如果访问的人数较多,数据库的压力会非常大。不过现在的动态程序多数都使用了缓存技术。但是总体来讲,动态页面对于服务器的压力比较大一些。同时动态页面的网站一般对于服务器的要求比较高一些,同时访问的人越多也会造成服务器的压力越大。

3.伪静态页面

通过url重写,index.php 变成 index.html

  伪静态页面定义:“假”静态页面,实质上是动态页面。

  优点:相比静态页面而言,并没有速度上的明显提升,因为是“假”静态页面,其实还是一个动态页面,也是同样需要翻译为静态页面的。最大的好处就是让搜索引擎(Search Engine)把自己的网页当做静态页面来处理。

  缺点:顾名思义,“伪静态”就是“假静态”,搜索引擎不会把他当做静态页面来处理,这只是我们靠经验考逻辑去分析的,并不一定准确。或许搜索引擎直接把它认为是动态页面。

简单总结:

  • 静态页面访问最快;维护较为麻烦。

  • 动态页面占用空间小、维护简单;访问速度慢,如果访问的人多,会对数据库造成压力。

  • 使用纯静态和伪静态对于SEO(Search Engine Optimization:搜索引擎优化)没有什么本质的区别。

  • 使用伪静态将占用一定量的CPU占用率,大量使用会导致CPU超负荷。


15. spirit css 越大越好吗?

什么时候用:要从原理说起,当我们请求量特别大,而请求数据少时可以将多个小图片聚合为一个大图片,通过定位来实现;如果图比较大,则需要用切图来实现,减少资源;以此把多个请求合并为一个,用较少网络资源的占用,优化了前端的访问。

这段时间有几次接触到了css sprites的概念,一个就是在用css做滑动门的时候,另外一个就是在用YSlow分析网站性能的时候,于是对css sprites这个概念产生了浓厚的兴趣。在网上查找了很多的资料,但可惜的是大部分都是只言片语,其中很多都是直接翻译国外的资料,也有直接推荐国外的资料网站,无奈英语没有过关,基本上没有理解到什么css sprites,更别谈如何使用了。

最后还是在蓝色理想中的一篇文章受到启发,琢磨了老半天,才算弄清楚里面的内涵,下面将通过本人的理解帮助其他人更加快速的去掌握css sprites。

先简单介绍下概念,关于它的概念,网上那到处都是,这里简单的提下。

什么是css sprites

css sprites直译过来就是CSS精灵,但是这种翻译显然是不够的,其实就是通过将多个图片融合到一副图里面,然后通过CSS的一些技术布局到网页上。这样做的好处也是显而易见的,因为图片多的话,会增加http的请求,无疑促使了网站性能的减低,特别是图片特别多的网站,如果能用css sprites降低图片数量,带来的将是速度的提升。

下面我们来用一个实例来理解css sprites,受蓝色理想中的《制作一副扑克牌系列教程》启发。

我们仅仅只需要制作一个扑克牌,拿黑桃10为例子。

可以直接把蓝色理想中融合好的一幅大图作为背景,这也是css sprites的一个中心思想,就是把多个图片融进一个图里面。

白俊遥博客

这就是融合后的图,相信对PS熟悉的同学应该很容易的理解,通过PS我们就可以组合多个图为一个图。

现在我们书写html的结构:

<p>
   <p>
  <b class="N1 n10"></b>
  <em class="First small_up1"></em>
  <span class="A1 up1"></span>
  <span class="A2 up1"></span>
  <span class="A3 down1"></span>
  <span class="A4 down1"></span>
  <span class="B1 up1"></span>
  <span class="B2 down1"></span>
  <span class="C1 up1"></span>
  <span class="C2 up1"></span>
  <span class="C3 down1"></span>
  <span class="C4 down1"></span>
  <em class="Last small_down1"></em>
  <b class="N2 n10_h"></b> 
   </p>
</p>

在这里我们来分析强调几点:

一:card为这个黑桃十的盒子或者说快,对p了解的同学应该很清楚这点。

二:我用span,b,em三种标签分别代表三种不同类型的图片,span用来表标中间的图片,b用来表示数定,em用来表示数字下面的小图标。

三:class里面的声明有2种,一个用来定位黑桃10中间的图片的位置,一个用来定义方向(朝上,朝下)。

上面是p基本概念,这还不是重点,不过对p不太清楚的同学可以了解。

下面我们重点谈下定义CSS:

span{display:block;width:20px;height:21px; osition:absolute;background:url(images/card.gif) no-repeat;}

这个是对span容器的CSS定义,其他属性就不说了,主要谈下如何从这个里面来理解css sprites。

背景图片,大家看地址就是最开始我们融合的一张大图,那么如何将这个大图中的指定位置显示出来呢?因为我们知道我们做的是黑桃10,这个大图其他的图像我们是不需要的,这就用到了css中的overflow:hidden;

但大家就奇怪了span的CSS定义里面没有overflow:hidden啊,别急,我们已经在定义card的CSS里面设置了(这是CSS里面的继承关系):

.card{width:125px;height:170px; position:absolute;overflow:hidden;border:1px #c0c0c0 solid;}

理解到这点还不够,还要知道width:125px;height:170px; 这个可以理解成是对这个背景图片的准确切割,当然其实并不是切割,而是把多余其他的部分给隐藏了,这就是overflow:hidden的妙用。

通过以上的部分的讲解,你一定可以充分的把握css sprites的运用,通过这个所谓的“切割”,然后再通过CSS的定位技术将这些图准确的分散到这个card里面去!

PS:CSS的定位技术,大家可以参考其他资料,例如相对定位和绝对定位,这里我们只能尝试用绝对定位。

最后我们来补充一点:

为什么要采取<span class="A1 up1"></span>这样的结构?

span这个容器是主要作用就是存放这张大的背景图片,并在里面实现”切割“,span里面切割后的背景是所有内容块里面通用的!

后面class为什么要声明2个属性?

很显然,一个是用来定位内容块的位置,一个是用来定义内容块中的图像的朝上和朝下,方位的!

下面我们附上黑桃10的完整源码:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>制作一幅扑克牌--黑桃10</title>
<style type="text/css"><!--
.card{width:125px;height:170px; position:absolute;overflow:hidden;border:1px #c0c0c0 solid;}
/*中间图片通用设置*/
span{display:block;width:20px;height:21px; position:absolute;background:url(http://www.blueidea.com//articleimg/2009/02/6382/00.gif) no-repeat;}
/*小图片通用设置*/
b{display:block;width:15px;height:10px; position:absolute;font-size:10pt;text-align:center;font-weight:bold;background:url(http://www.blueidea.com//articleimg/2009/02/6382/00.gif) no-repeat; overflow:hidden;}
/*数字通用设置*/
em{display:block;width:15px;height:10px; position:absolute;font-size:10pt;text-align:center;font-weight:bold;background:url(http://www.blueidea.com//articleimg/2009/02/6382/00.gif) no-repeat;overflow:hidden;}
/*各坐标点位置*/
.N1{left:1px;top:8px;}
.First{left:5px;top:20px;}
.A1{left:20px;top:20px;}
.A2{left:20px;top:57px;}
.A3{left:20px;top:94px;}
.A4{left:20px;top:131px;}
.B1{left:54px;top:38px;}
.B2{left:54px;top:117px;}
.C1{left:86px;top:20px;}
.C2{left:86px;top:57px;}
.C3{left:86px;top:94px;}
.C4{left:86px;top:131px;}
.Last{bottom:20px;right:0px;}
.N2{bottom:8px;right:5px;
}
/*大图标黑红梅方四种图片-上方向*/
.up1{background-position:0 1px;}/*黑桃*/
/*大图标黑红梅方四种图片-下方向*/
.down1{background-position:0 -19px;}/*黑桃*/
/*小图标黑红梅方四种图片-上方向*/
.small_up1{background-position:0 -40px;}/*黑桃*/
/*小图标黑红梅方四种图片-下方向*/
.small_down1{background-position:0 -51px;}/*黑桃*/
/*A~k数字图片-左上角*/
.n10{background-position:-191px 0px;left:-4px;width:21px;}
/*A~k数字图片-右下角*/
.n10_h{background-position:-191px -22px;right:3px;width:21px;}
/*A~k数字图片-左上角红色字*/
.n10_red{background-position:-191px 0px;}
/*A~k数字图片-右下角红色字*/
.n10_h_red{background-position:-191px -33px;}
-->
</style>
</head>
<body>
<!--10字符-->
<p>
 <p>
  <b class="N1 n10"></b>
  <em class="First small_up1"></em>
  <span class="A1 up1"></span>
  <span class="A2 up1"></span>
  <span class="A3 down1"></span>
  <span class="A4 down1"></span>
  
  <span class="B1 up1"></span>
  <span class="B2 down1"></span>
  
  <span class="C1 up1"></span>
  <span class="C2 up1"></span>
  <span class="C3 down1"></span>
  <span class="C4 down1"></span>
  <em class="Last small_down1"></em>
  <b class="N2 n10_h"></b> 
 </p>
</p>
</body>
</html>

最后感谢蓝色理想提供的参考资料!



16. 反向代理/正向代理

正向代理与反向代理【总结】

1、前言

  最近工作中用到反向代理,发现网络代理的玩法还真不少,网络背后有很多需要去学习。而在此之前仅仅使用了过代理软件,曾经为了访问google,使用了代理软件,需要在浏览器中配置代理的地址。我只知道有代理这个概念,并不清楚代理还有正向和反向之分,于是赶紧学习一下,补充一下知识。首先弄清楚什么是正向代理,什么是反向代理,然后是二者在实际使用中展示的方式是什么样的,最后总结一下正向代理用来做什么,反向代理可以做什么。

2、正向代理

  正向代理类似一个跳板机,代理访问外部资源。

白俊遥博客

举个例子:

  我是一个用户,我访问不了某网站,但是我能访问一个代理服务器,这个代理服务器呢,他能访问那个我不能访问的网站,于是我先连上代理服务器,告诉他我需要那个无法访问网站的内容,代理服务器去取回来,然后返回给我。从网站的角度,只在代理服务器来取内容的时候有一次记录,有时候并不知道是用户的请求,也隐藏了用户的资料,这取决于代理告不告诉网站。

  客户端必须设置正向代理服务器,当然前提是要知道正向代理服务器的IP地址,还有代理程序的端口。

  例如之前使用过这类软件例如CCproxy,http://www.ccproxy.com/ 需要在浏览器中配置代理的地址。

白俊遥博客

  总结来说:正向代理 是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端必须要进行一些特别的设置才能使用正向代理。

  正向代理的用途:

  (1)访问原来无法访问的资源,如google

(2) 可以做缓存,加速访问资源

  (3)对客户端访问授权,上网进行认证

  (4)代理可以记录用户访问记录(上网行为管理),对外隐藏用户信息

例如CCProxy用途:

白俊遥博客

3、反向代理

  初次接触方向代理的感觉是,客户端是无感知代理的存在的,反向代理对外都是透明的,访问者者并不知道自己访问的是一个代理。因为客户端不需要任何配置就可以访问。

  反向代理(Reverse Proxy)实际运行方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器。

反向代理的作用:

(1)保证内网的安全,可以使用反向代理提供WAF功能,阻止web攻击

大型网站,通常将反向代理作为公网访问地址,Web服务器是内网。

白俊遥博客

(2)负载均衡,通过反向代理服务器来优化网站的负载

白俊遥博客

4、二者区别

借用知乎两张图来表达:https://www.zhihu.com/question/24723688

白俊遥博客

白俊遥博客

5、nginx的反向代理

  nginx支持配置反向代理,通过反向代理实现网站的负载均衡。这部分先写一个nginx的配置,后续需要深入研究nginx的代理模块和负载均衡模块。

nginx通过proxy_pass_http 配置代理站点,upstream实现负载均衡。

白俊遥博客

参考资料:

代理”顾名思义,就是不通过自己,通过第三方去代替自己执行自己要做的事情。可以想象成在本机和目标服务器中又多了一个中间服务器(代理服务器)

正向代理

正向代理是一个位于客户端和原始服务器之间的服务器(代理服务器)。客户端必须先进行一些必要设置(必须知道代理服务器的IP和端口),将每一次请求先发送到代理服务器上,代理服务器转发到真实服务器并取得响应结果后,返回给客户端。

简单说明,就是代理服务器代替客户端去访问目标服务器。(隐藏客户端

作用:

  1. 绕过无法访问的结点,从另一条路由路径进行目标服务器的访问(比如 翻墙)

  2. 加速访问,通过不同的路由路径提高访问速度(现在通过带宽的提高等方式,基本不用此方式提速)

  3. 缓存作用,数据缓存在代理服务器中,若客户端请求的数据在缓存中则不去访问目标主机。

  4. 权限控制,防火墙授权代理服务器访问权限,客户端通过正向代理可以通过防火墙(比如 一些公司采用的ISA SERVER 权限判断)

  5. 隐藏访问者,通过配置,目标服务器只能获得到代理服务器的信息,无法获取真实访客的信息。

反向代理

反向代理正好相反,对于客户端而言它就像是原始服务器,并且客户端不需要进行任何特别的设置。客户端向反向代理发送普通请求,接着反向代理将判断向原始服务器转交请求,并将获得的内容返回给客户端,就像这些内容原本就是它自己的一样。

简单说明,就是代理服务器代替目标服务器去接受并返回客户端的请求。(隐藏目标服务器

作用

  1. 隐藏原始服务器,防止服务器恶意攻击等,让客户端认为代理服务器是原始服务器。

  2. 缓存左右,将原始服务器数据进行缓存,减少原始服务器的访问压力。

透明代理

透明代理的意思是客户端根本不需要知道有代理服务器的存在,它改编你的request fields(报文),并会传送真实IP。注意,加密的透明代理则是属于匿名代理,意思是不用设置使用代理了。

作用

透明代理实践的例子就是时下很多公司使用的行为管理软件。如下图3.1

CDN

CDN的全称是Content Delivery Network,即内容分发网络。其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度。

正向代理与反向代理的区别

用途

正向代理

  1. 典型用途是为在防火墙内的局域网客户端提供访问Internet的途径。

  2. 使用缓冲特性减少网络使用率。

反向代理

  1. 典型用途是将防火墙后面的服务器提供给Internet用户访问。

  2. 为后端的多台服务器提供负载平衡,或为后端较慢的服务器提供缓冲服务。

安全性

正向代理

允许客户端通过它访问任意网站并且隐藏客户端自身,因此你必须采取安全措施以确保仅为经过授权的客户端提供服务。

反向代理

对外都是透明的,访问者并不知道自己访问的是一个代理。

反向代理与CDN的区别

简单来说,CDN是现在一种网络加速的解决方案,反向代理是将用户的请求转发给后端服务器的技术。CDN是用到了反向代理的技术原理,其最关键的核心技术是智能DNS等。

17. mysql 主从、优化

mysql 用主从同步的方法进行读写分离,减轻主服务器的压力的做法现在在业内做的非常普遍。 主从同步基本上能做到实时同步。我从别的网站借用了主从同步的原理图。

白俊遥博客



在配置好了, 主从同步以后, 主服务器会把更新语句写入binlog, 从服务器的IO 线程(这里要注意, 5.6.3 之前的IO线程仅有一个,5.6.3之后的有多线程去读了,速度自然也就加快了)回去读取主服务器的binlog 并且写到从服务器的Relay log 里面,然后从服务器的 的SQL thread 会一个一个执行 relay log 里面的sql , 进行数据恢复。


relay 就是 传递, relay race 就是接力赛的意思


1. 主从同步的延迟的原因

我们知道, 一个服务器开放N个链接给客户端来连接的, 这样有会有大并发的更新操作, 但是从服务器的里面读取binlog 的线程仅有一个, 当某个SQL在从服务器上执行的时间稍长 或者由于某个SQL要进行锁表就会导致,主服务器的SQL大量积压,未被同步到从服务器里。这就导致了主从不一致, 也就是主从延迟。


2. 主从同步延迟的解决办法

实际上主从同步延迟根本没有什么一招制敌的办法, 因为所有的SQL必须都要在从服务器里面执行一遍,但是主服务器如果不断的有更新操作源源不断的写入, 那么一旦有延迟产生, 那么延迟加重的可能性就会原来越大。 当然我们可以做一些缓解的措施。

a. 我们知道因为主服务器要负责更新操作, 他对安全性的要求比从服务器高, 所有有些设置可以修改,比如sync_binlog=1,innodb_flush_log_at_trx_commit = 1 之类的设置,而slave则不需要这么高的数据安全,完全可以讲sync_binlog设置为0或者关闭binlog,innodb_flushlog,innodb_flush_log_at_trx_commit 也 可以设置为0来提高sql的执行效率 这个能很大程度上提高效率。另外就是使用比主库更好的硬件设备作为slave。

b. 就是把,一台从服务器当度作为备份使用, 而不提供查询, 那边他的负载下来了, 执行relay log 里面的SQL效率自然就高了。

c. 增加从服务器喽,这个目的还是分散读的压力, 从而降低服务器负载。


3. 判断主从延迟的方法

MySQL提供了从服务器状态命令,可以通过 show slave status 进行查看, 比如可以看看Seconds_Behind_Master参数的值来判断,是否有发生主从延时。
其值有这么几种:
NULL - 表示io_thread或是sql_thread有任何一个发生故障,也就是该线程的Running状态是No,而非Yes.
0 - 该值为零,是我们极为渴望看到的情况,表示主从复制状态正常


18. web 安全

白俊遥博客


20. memcached和redis区别

> 1. Redis 支持更加丰富的数据存储类型,String、Hash、List、Set 和 Sorted Set。Memcached 仅支持简单的 key-value 结构。
> 2. Memcached key-value存储比 Redis 采用 hash 结构来做 key-value 存储的内存利用率更高。
> 3. Redis 提供了事务的功能,可以保证一系列命令的原子性
> 4. Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中
> 5. Redis 只使用单核,而 Memcached 可以使用多核,所以平均每一个核上 Redis 在存储小数据时比 Memcached 性能更高。

- Redis 如何实现持久化?

> 1. RDB 持久化,将 Redis 在内存中的的状态保存到硬盘中,相当于备份数据库状态。
> 2. AOF 持久化(Append-Only-File),AOF 持久化是通过保存 Redis 服务器锁执行的写状态来记录数据库的。相当于备份数据库接收到的命令,所有被写入 AOF 的命令都是以 Redis 的协议格式来保存的。



21. apache和nginx区别

一、 定义:

1. Apache

Apache HTTP服务器是一个模块化的服务器,可以运行在几乎所有广泛使用的计算机平台上。其属于应用服务器。Apache支持支持模块多,性能稳定,Apache本身是静态解析,适合静态HTML、图片等,但可以通过扩展脚本、模块等支持动态页面等。

Apche可以支持PHPcgiperl,但是要使用Java的话,你需要TomcatApache后台支撑,将Java请求由Apache转发给Tomcat处理。)

缺点:配置相对复杂,自身不支持动态页面。

2. Tomcat:

Tomcat是应用(Java)服务器,它只是一个Servlet(JSP也翻译成Servlet)容器,可以认为是Apache的扩展,但是可以独立于Apache运行。

3. Nginx

Nginx是俄罗斯人编写的十分轻量级的HTTP服务器,Nginx,它的发音为“engine X”,是一个高性能的HTTP和反向代理服务器,同时也是一个IMAP/POP3/SMTP 代理服务器。

二、 比较

1. Apache与Tomcat的比较

相同点:

l 两者都是Apache组织开发的

l 两者都有HTTP服务的功能

l 两者都是免费的

不同点:

l Apache是专门用了提供HTTP服务的,以及相关配置的(例如虚拟主机、URL转发等等),而TomcatApache组织在符合Java EEJSPServlet标准下开发的一个JSP服务器.

l Apache是一个Web服务器环境程序,启用他可以作为Web服务器使用,不过只支持静态网页如(ASP,PHP,CGI,JSP)等动态网页的就不行。如果要在Apache环境下运行JSP的话就需要一个解释器来执行JSP网页,而这个JSP解释器就是Tomcat

l Apache:侧重于HTTPServer Tomcat:侧重于Servlet引擎,如果以Standalone方式运行,功能上与Apache等效,支持JSP,但对静态网页不太理想;

l ApacheWeb服务器,Tomcat是应用(Java)服务器,它只是一个Servlet(JSP也翻译成Servlet)容器,可以认为是Apache的扩展,但是可以独立于Apache运行。

实际使用中ApacheTomcat常常是整合使用:

l 如果客户端请求的是静态页面,则只需要Apache服务器响应请求。

l 如果客户端请求动态页面,则是Tomcat服务器响应请求。

l 因为JSP是服务器端解释代码的,这样整合就可以减少Tomcat的服务开销。

可以理解TomcatApache的一种扩展。

2. Nginx与Apache比较

1) nginx相对于apache的优点

l 轻量级,同样起web 服务,比apache占用更少的内存及资源

l 抗并发,nginx 处理请求是异步非阻塞的,而apache 则是阻塞型的,在高并发下nginx 能保持低资源低消耗高性能

l 高度模块化的设计,编写模块相对简单

l 提供负载均衡

l 社区活跃,各种高性能模块出品迅速

2) apache 相对于nginx 的优点

l apache rewrite nginx 的强大

l 支持动态页面;

l 支持的模块多,基本涵盖所有应用;

l 性能稳定,而nginx相对bug较多。

3) 两者优缺点比较

l Nginx 配置简洁, Apache 复杂

l Nginx 静态处理性能比 Apache 3倍以上

l Apache PHP 支持比较简单,Nginx 需要配合其他后端用;

l Apache 的组件比 Nginx

l apache是同步多进程模型,一个连接对应一个进程;nginx是异步的,多个连接(万级别)可以对应一个进程;

l nginx处理静态文件好,耗费内存少;

l 动态请求由apache去做,nginx只适合静态和反向;

l Nginx适合做前端服务器,负载性能很好;

l Nginx本身就是一个反向代理服务器 ,且支持负载均衡

3. 总结

l Nginx优点:负载均衡、反向代理、处理静态文件优势。nginx处理静态请求的速度高于apache

l Apache优点:相对于Tomcat服务器来说处理静态文件是它的优势,速度快。Apache是静态解析,适合静态HTML、图片等。

l Tomcat:动态解析容器,处理动态请求,是编译JSP\Servlet的容器,Nginx有动态分离机制,静态请求直接就可以通过Nginx处理,动态请求才转发请求到后台交由Tomcat进行处理。

Apache在处理动态有优势,Nginx并发性比较好,CPU内存占用低,如果rewrite频繁,那还是Apache较适合。



22. 端口、进程

1.使用lsof
lsof -i:端口号查看某个端口是否被占用
白俊遥博客
2.使用netstat
使用netstat -anp|grep 80
白俊遥博客

更多内容,可以点击这里:http://www.findme.wang/blog/detail/id/1.html

查看CPU

top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器

可以直接使用top命令后,查看%MEM的内容。可以选择按进程查看或者按用户查看,如想查看oracle用户的进程内存使用情况的话可以使用如下的命令:
$ top -u oracle

Linux 查看进程和删除进程


1. 在 LINUX 命令平台输入 1-2 个字符后按 Tab 键会自动补全后面的部分(前提是要有这个东西,例如在装了 tomcat 的前提下, 输入 tomcat 的 to 按 tab)。
2. ps 命令用于查看当前正在运行的进程。
grep 是搜索
例如: ps -ef | grep java
表示查看所有进程里 CMD 是 java 的进程信息
ps -aux | grep java
-aux 显示所有状态
ps
3. kill 命令用于终止进程
例如: kill -9 [PID]
-9 表示强迫进程立即停止
通常用 ps 查看进程 PID ,用 kill 命令终止进程
网上关于这两块的内容




23. 性能优化

优化手段主要有缓存、集群、异步等。

分布式缓存


网站性能优化第一定律:优先考虑使用缓存。


缓存的基本原理

缓存是指将数据存储在相对较高访问速度的存储介质中。 
(1)访问速度快,减少数据访问时间; 
(2)如果缓存的数据进过计算处理得到的,那么被缓存的数据无需重复计算即可直接使用。 
缓存的本质是一个内存Hash表,以一对Key、Value的形式存储在内存Hash表中,读写时间复杂度为O(1)。

合理使用缓存

不合理使用缓存非但不能提高系统的性能,还会成为系统的累赘,甚至风险。

频繁修改的数据

如果缓存中保存的是频繁修改的数据,就会出现数据写入缓存后,应用还来不及读取缓存,数据就已经失效,徒增系统负担。一般来说,数据的读写比在2:1以上,缓存才有意义。

没有热点的访问

如果应用系统访问数据没有热点,不遵循二八定律,那么缓存就没有意义。

数据不一致与脏读

一般会对缓存的数据设置失效时间,一旦超过失效时间,就要从数据库中重新加载。因此要容忍一定时间的数据不一致,如卖家已经编辑了商品属性,但是需要过一段时间才能被买家看到。还有一种策略是数据更新立即更新缓存,不过这也会带来更多系统开销和事务一致性问题。
  • 1

  • 2

缓存可用性

缓存会承担大部分数据库访问压力,数据库已经习惯了有缓存的日子,所以当缓存服务崩溃时,数据库会因为完全不能承受如此大压力而宕机,导致网站不可用。
这种情况被称作缓存雪崩,发生这种故障,甚至不能简单地重启缓存服务器和数据库服务器来恢复。 
实践中,有的网站通过缓存热备份等手段提高缓存可用性:当某台缓存服务器宕机时,将缓存访问切换到热备服务器上。
但这种设计有违缓存的初衷,**缓存根本就不应该当做一个可靠的数据源来使用**。 
通过分布式缓存服务器集群,将缓存数据分布到集群多台服务器上可在一定程度上改善缓存的可用性。
当一台缓存服务器宕机时,只有部分缓存数据丢失,重新从数据库加载这部分数据不会产生很大的影响。

缓存预热

缓存中的热点数据利用LRU(最近最久未用算法)对不断访问的数据筛选淘汰出来,这个过程需要花费较长的时间。
那么最好在缓存系统启动时就把热点数据加载好,这个缓存预加载手段叫缓存预热。

缓存穿透

    如果因为不恰当的业务、或者恶意攻击持续高并发地请求某个不存在的数据,由于缓存没有保存该数据,所有的请求都会落到数据库上,会对数据库造成压力,甚至崩溃。
     **一个简单的对策是将不存在的数据也缓存起来(其value为null)** 。


分布式缓存架构

分布式缓存指缓存部署在多个服务器组成的集群中,以集群方式提供缓存服务,其架构方式有两种,一种是以JBoss Cache为代表的需要更新同步的分布式缓存,一种是以Memcached为代表的不互相通信的分布式缓存。 JBoss Cache在集群中所有服务器中保存相同的缓存数据,当某台服务器有缓存数据更新,就会通知其他机器更新或清除缓存数据。 它通常将应用程序和缓存部署在同一台服务器上,但受限于单一服务器的内存空间;当集群规模较大的时候,缓存更新需要同步到所有机器,代价惊人。因此这种方案多见于企业应用系统中。 Memcached采用一种集中式的缓存集群管理(互不通信的分布式架构方式)。缓存与应用分离部署,缓存系统部署在一组专门的服务器上,应用程序通过一致性Hash等路由算法选择缓存服务器远程访问数据,缓存服务器之间不通信,集群规模可以很容易地实现扩容,具有良好的伸缩性。 (1)简单的通信协议。Memcached使用TCP协议(UDP也支持)通信; (2)丰富的客户端程序。 (3)高性能的网络通信。Memcached服务端通信模块基于Libevent,一个支持事件触发的网络通信程序库,具有稳定的长连接。 (4)高效的内存管理。 (5)互不通信的服务器集群架构。


异步操作

使用消息队列将调用异步化(生产者--消费者模式),可改善网站的扩展性,还可以改善系统的性能。 
在不使用消息队列的情况下,用户的请求数据直接写入数据库,在高并发的情况下,会对数据库造成巨大压力,使得响应延迟加剧。
在使用消息队列后,用户请求的数据发送给消息队列后立即返回,再由消息队列的消费者进程(通常情况下,该进程独立部署在专门的服务器集群上)从消息队列中获取数据,
  异步写入数据库。由于消息队列服务器处理速度远快于服务器(消息队列服务器也比数据库具有更好的伸缩性)。 
消息队列具有很好的削峰作用--通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。 
需要注意的是,由于数据写入消息队列后立即返回给用户,数据在后续的业务校验、写数据库等操作可能失败,因此在使用消息队列进行业务异步处理后,
  需要适当修改业务流程进行配合,如订单提交后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完后,
  甚至商品出库后,再通过电子邮件或SMS消息通知用户订单成功,以免交易纠纷。



任何可以晚点做的事情都应该晚点再做。


使用集群

在网站高并发访问的场景下,使用负载均衡技术为一个应用构建一个由多台服务器组成的服务器集群,将并发访问请求分发到多台服务器上处理。

代码优化

多线程

从资源利用的角度看,使用多线程的原因主要有两个:IO阻塞与多CPU。
当前线程进行IO处理的时候,会被阻塞释放CPU以等待IO操作完成,由于IO操作(不管是磁盘IO还是网络IO)通常都需要较长的时间,这时CPU可以调度其他的线程进行处理。 
理想的系统Load是既没有进程(线程)等待也没有CPU空闲,利用多线程IO阻塞与执行交替进行,可最大限度利用CPU资源。 
使用多线程的另一个原因是服务器有多个CPU。 
简化启动线程估算公式:
 启动线程数 = [任务执行时间 / (任务执行时间 - IO等待时间)]*CPU内核数 多线程编程一个需要注意的问题是线程安全问题,即多线程并发对某个资源进行修改,导致数据混乱。
所有的资源---对象、内存、文件、数据库,乃至另一个线程都可能被多线程并发访问。 
编程上,解决线程安全的主要手段有:
 (1)将对象设计为无状态对象。
 所谓无状态对象是指对象本身不存储状态信息(对象无成员变量,或者成员变量也是无状态对象),不过从面向对象设计的角度看,无状态对象是一种不良设计。 
 (2)使用局部对象。
 即在方法内部创建对象,这些对象会被每个进入该方法的线程创建,除非程序有意识地将这些对象传递给其他线程,否则不会出现对象被多线程并发访问的情形。 
 (3)并发访问资源时使用锁。
 即多线程访问资源的时候,通过锁的方式使多线程并发操作转化为顺序操作,从而避免资源被并发修改。

资源复用

1系统运行时,要尽量减少那些开销很大的系统资源的创建和销毁,比如数据库连接、网络通信连接、线程、复杂对象等。
2从编程角度,资源复用主要有两种模式:单例(Singleton)和对象池(Object Pool)。 
3单例虽然是GoF经典设计模式中较多被诟病的一个模式,但由于目前Web开发中主要使用贫血模式,从Service到Dao都是些无状态对象,无需重复创建,使用单例模式也就自然而然了。 
4对象池模式通过复用对象实例,减少对象创建和资源消耗。
5对于数据库连接对象,每次创建连接,数据库服务端都需要创建专门的资源以应对,因此频繁创建关闭数据库连接,对数据库服务器是灾难性的,
同时频繁创建关闭连接也需要花费较长的时间。
6因此实践中,应用程序的数据库连接基本都使用连接池(Connection Pool)的方式,数据库连接对象创建好以后,
将连接对象放入对象池容器中,应用程序要连接的时候,就从对象池中获取一个空闲的连接使用,使用完毕再将该对象归还到对象池中即可,不需要创建新的连接。

数据结构

早期关于程序的一个定义是,程序就是数据结构+算法,数据结构对于编程的重要性不言而喻。
在不同场景中合理使用数据结构,灵活组合各种数据结构改善数据读写和计算特性可极大优化程序的性能。

垃圾回收

理解垃圾回收机制有助于程序优化和参数调优,以及编写内存安全的代码。

摘自《大型网站技术架构》–李智慧


24. 支付

手机网站支付接口

产品简介

手机网站支付主要应用于手机、掌上电脑等无线设备的网页上,通过网页跳转或浏览器自带的支付宝快捷支付实现买家付款的功能,资金即时到账。

申请条件

1、您申请前必须拥有企业支付宝账号(不含个体工商户账号),且通过支付宝实名认证审核。
2、如果您有已经建设完成的无线网站(非淘宝、天猫、诚信通网店),网站经营的商品或服务内容明确、完整(古玩、珠宝等奢侈品、投资类行业无法申请本产品)。
3、网站必须已通过ICP备案,备案信息与签约商户信息一致。

配置

假设我们已经成功申请到手机网站支付接口,在进行开发之前,需要使用公司账号登录支付宝开放平台。

查看PID

1、开发者登录开放平台,点击右上角的“账户及密钥管理”。
白俊遥博客

2、选择“合作伙伴密钥”,即可查询到合作伙伴身份(PID),以2088开头的16位纯数字。
白俊遥博客

配置密钥

支付宝提供了DSA、RSA、MD5三种签名方式,本次开发中,我们使用RSA签名和加密,那就只配置RSA密钥就好了。

关于RSA加密的详解,参见《支付宝签名与验签》。

应用环境

本节可以忽略,本节可以忽略,本节可以忽略!因为官方文档并没有提及应用环境配置的问题。

进入管理中心,对应用进行设置。
白俊遥博客
白俊遥博客
上图是我的应用配置选项,公司账号也许会有所不同。具体哪些参数需要配置?请参照接口参数说明,需要什么就配置什么。

交互

Node端发起支付请求

白俊遥博客
我们公司采用的,就是这种方式,步骤3中Node端获取到的支付宝参数,包括sign。其实,sign的计算工作也可以放在Node端,只不过支付宝没有给出Node的demo,实现起来需要耗费多一点时间。

后端发起支付请求

白俊遥博客
这种方式也很好,而且,步骤4中后端获取到支付页面,也可以不传给Node端,自己显示出来。这样,整个流程就更加简单。

return_url和notify_url

return_url,支付完成后的回调url;notify_url,支付完成后通知的url。支付宝发送给两个url的参数是一样的,只不过一个是get,一个是post。

以上两种发起请求的方式中,return_url在Node端,notify_url在后端。我们也可以根据需要,把两个url都放在后端,或者都放在Node端,改变相应业务逻辑即可。

Node端详解

Node端发起支付请求有两种选择,一种是获取到后端给的参数后,通过request模块发起get请求,获取到支付宝返回的支付页面,然后显示到页面上;另一种是获取到后端给的参数后,把参数全部输出到页面中的form表单,然后通过js自动提交表单,获取到支付宝返回的支付页面(同时显示出来)。

request发起请求

// 通过orderId向后端请求获取支付宝支付参数alidata var alipayUrl = 'https://mapi.alipay.com/gateway.do?'+ alidata; request.get({url: alipayUrl},function(error, response, body){     res.send(response.body); });

理论上完全正确的请求,然而,获取到的支付页面,输出到页面上,却是乱码。没错,还是一个错误提示页面。
白俊遥博客

神奇的地方在于,在刷新页面多次后,正常了!!!啊嘞,这是什么鬼?

白俊遥博客

先解决乱码问题,看看报什么错!

request.get({url: alipayUrl},function(error, response, body){     
var str = response2.body;     
str = str.replace(/gb2312/, "utf-8");     
res.setHeader('content-type', 'text/html;charset=utf-8');    
 res.send(str); 
 });

很遗憾,无效!乱码依然是乱码。。。和沈晨帅哥讨论很久,最终决定换一种方案——利用表单提交。

表单提交请求

Node端

// node端 // 通过orderId向后端请求获取支付宝支付参数alidata function getArg(str,arg) {   var reg = new RegExp('(^|&)' + arg + '=([^&]*)(&|$)', 'i');   var r = str.match(reg);   if (r != null) {       return unescape(r[2]);   }   return null; } var alipayParam = {   _input_charset: getArg(alidata,'_input_charset'),   body: getArg(alidata,'body'),   it_b_pay: getArg(alidata, 'it_b_pay'),   notify_url: getArg(alidata, 'notify_url'),   out_trade_no: getArg(alidata, 'out_trade_no'),   partner: getArg(alidata, 'partner'),   payment_type: getArg(alidata, 'payment_type'),   return_url: getArg(alidata, 'return_url'),   seller_id: getArg(alidata, 'seller_id'),   service: getArg(alidata, 'service'),   show_url: getArg(alidata, 'show_url'),   subject: getArg(alidata, 'subject'),   total_fee: getArg(alidata, 'total_fee'),   sign_type: getArg(alidata, 'sign_type'),   sign: getArg(alidata, 'sign'),   app_pay: getArg(alidata, 'app_pay') }; res.render('artist/alipay',{   // 其他参数   alipayParam: alipayParam });

html

<!--alipay.html--> 
<!DOCTYPE html> 
<html lang="en"> 
<head>     
<meta charset="UTF-8">     
<title>支付宝支付</title> </head> <body>     
<form id="ali-form" action="https://mapi.alipay.com/gateway.do" method="get">            
<input type="hidden" name="_input_charset" value="<%= alipayParam._input_charset%>">         
<input type="hidden" name="body" value="<%= alipayParam.body%>">         
<input type="hidden" name="it_b_pay" value="<%= alipayParam.it_b_pay%>">         
<input type="hidden" name="notify_url" value="<%= alipayParam.notify_url%>">         
<input type="hidden" name="out_trade_no" value="<%= alipayParam.out_trade_no%>">         
<input type="hidden" name="partner" value="<%= alipayParam.partner%>">         
<input type="hidden" name="payment_type" value="<%= alipayParam.payment_type%>">         
<input type="hidden" name="return_url" value="<%= alipayParam.return_url%>">         
<input type="hidden" name="seller_id" value="<%= alipayParam.seller_id%>">         
<input type="hidden" name="service" value="<%= alipayParam.service%>">         
<input type="hidden" name="show_url" value="<%= alipayParam.show_url%>">         
<input type="hidden" name="subject" value="<%= alipayParam.subject%>">         
<input type="hidden" name="total_fee" value="<%= alipayParam.total_fee%>">         
<input type="hidden" name="sign_type" value="<%= alipayParam.sign_type%>">         
<input type="hidden" name="sign" value="<%= alipayParam.sign%>">         
<input type="hidden" name="app_pay" value="<%= alipayParam.app_pay%>">     
</form> <% include ../bootstrap.html %> <script type="text/javascript" src="<%= dist %>/js/business-modules/artist/alipay.js?v=2016052401"></script> 
</body> 
</html>

js

// alipay.js seajs.use(['zepto'],function($){     
var index = {        
 init:function(){            
  var self = this;            
   this.bindEvent();        
    },         
    bindEvent:function(){            
     var self = this;            
      $('#ali-form').submit();        
       }    
        }    
         index.init(); 
         });

小结

白俊遥博客
选择支付宝支付后,成功跳转到了支付宝支付页面,nice!看来这种方案很靠谱。

开始时,打算把alidata直接输出到form表单的action中接口的后面,因为这样似乎最简便。但是,提交表单时,后面的参数全部被丢弃了。所以,不得不得把所有参数放在form表单中。Node端拆分了一下参数,组装成了一个alipayParam对象,这个工作也可以交给后端来做。

显然,request模拟表单提交和真实表单提交结果的不同,得出的结论是,request并不能完全模拟表单提交。或者,request可以模拟,而我不会-_-|||。

错误排查

白俊遥博客
值得点赞的是,支付宝给的错误代码很明确,一看就懂。上面这个错误,签名不对。因为我给alipayParam私自加了个app_pay属性,没有经过签名。

微信屏蔽支付宝

问题描述

以上,大功告成?不!还有一个坑要填,因为微信屏蔽了支付宝!
在电脑上,跳转支付宝支付页面正常,很完美!然而,在微信浏览器中测试时,却没有跳转,而是出现如下信息。
白俊遥博客

完美解决办法

微信端支付宝支付,iframe改造
http://www.cnblogs.com/jiqing9006/p/5584268.html

该办法的核心在于:把微信屏蔽的链接,赋值给iframe的src属性。

Node端

res.render('artist/alipay',{   alipayParam: alipayParam,   param: urlencode(alidata)  });

html

<input type="hidden" id="param" value="<%= param%>"> <iframe id="payFrame" name="mainIframe" src="" frameborder="0" scrolling="auto" ></iframe>

js

var iframe = document.getElementById('payFrame'); var param = $('#param').val(); iframe.src='https://mapi.alipay.com/gateway.do?'+param;

报错

然而,在改造时,先是报错ILLEGAL_SIGN,于是利用urlencode处理了字符串。接着,又报错ILLEGAL_EXTERFACE,没有找到解决办法。

暂时放弃,以后如果有了解决办法再补上。

官方解决办法

关于微信公众平台无法使用支付宝收付款的解决方案说明
https://cshall.alipay.com/enterprise/help_detail.htm?help_id=524702

该方法的核心在于:确认支付时,提示用户打开外部系统浏览器,在系统浏览器中支付。

html

<!--alipay.html--> <!DOCTYPE html> <html lang="en"> <head>   <meta charset="UTF-8">   <title>支付宝支付</title> </head> <body>   <form id="ali-form" action="https://mapi.alipay.com/gateway.do" method="get">      <input type="hidden" name="_input_charset" value="<%= alipayParam._input_charset%>">     <input type="hidden" name="body" value="<%= alipayParam.body%>">     <input type="hidden" name="it_b_pay" value="<%= alipayParam.it_b_pay%>">     <input type="hidden" name="notify_url" value="<%= alipayParam.notify_url%>">     <input type="hidden" name="out_trade_no" value="<%= alipayParam.out_trade_no%>">     <input type="hidden" name="partner" value="<%= alipayParam.partner%>">     <input type="hidden" name="payment_type" value="<%= alipayParam.payment_type%>">     <input type="hidden" name="return_url" value="<%= alipayParam.return_url%>">     <input type="hidden" name="seller_id" value="<%= alipayParam.seller_id%>">     <input type="hidden" name="service" value="<%= alipayParam.service%>">     <input type="hidden" name="show_url" value="<%= alipayParam.show_url%>">     <input type="hidden" name="subject" value="<%= alipayParam.subject%>">     <input type="hidden" name="total_fee" value="<%= alipayParam.total_fee%>">     <input type="hidden" name="sign_type" value="<%= alipayParam.sign_type%>">     <input type="hidden" name="sign" value="<%= alipayParam.sign%>">     <p class="wrapper buy-wrapper" style="display: none;">         <a href="javascript:void(0);"            class="J-btn-submit btn mj-submit btn-strong btn-larger btn-block">确认支付</a>     </p>   </form> <input type="hidden" id="param" value="<%= param%>"> <% include ../bootstrap.html %> <script type="text/javascript" src="<%= dist %>/js/business-modules/artist/ap.js"></script> <script>     var btn = document.querySelector(".J-btn-submit");     btn.addEventListener("click", function (e) {         e.preventDefault();         e.stopPropagation();         e.stopImmediatePropagation();         var queryParam = '';         Array.prototype.slice.call(document.querySelectorAll("input[type=hidden]")).forEach(function (ele) {             queryParam += ele.name + "=" + encodeURIComponent(ele.value) + '&';         });         var gotoUrl = document.querySelector("#ali-form").getAttribute('action') + '?' + queryParam;         _AP.pay(gotoUrl);         return false;     }, false);     btn.click(); </script> </body> </html>

该页面会自动跳转到同一文件夹下的pay.htm,该文件官方已经提供,把其中的引入ap.js的路径修改一下即可。最终效果如下:
白俊遥博客

后记

支付宝的支付流程和微信的支付流程,有很多相似之处。沈晨指出一点不同:支付宝支付完成后有return_url和notify_url;微信支付完成后只有notify_url。

研读了一下微信支付的开发文档,确实如此。微信支付完成后的通知也分成两路,一路通知到notify_url,另一路返回给调用支付接口的JS,同时发微信消息提示。也就是说,跳转return_url的工作我们需要自己做。

最后,感谢沈晨帅哥提供的思路和帮助,感谢体超帅哥辛苦改后端。

书签

支付宝开放平台
https://openhome.alipay.com/platform/home.htm

支付宝开放平台-手机网站支付-文档中心
https://doc.open.alipay.com/doc2/detail?treeId=60&articleId=103564&docType=1

支付宝WAP支付接口开发
http://blog.csdn.net/tspangle/article/details/39932963

wap h5手机网站支付接口唤起支付宝钱包付款

商家服务 - 支付宝 知托付!
https://b.alipay.com/order/techService.htm

微信支付-开发文档
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1

微信端支付宝支付,iframe改造
http://www.cnblogs.com/jiqing9006/p/5584268.html

微信如何突破支付宝的封锁
http://blog.csdn.net/lee_sire/article/details/49530875

JavaScript专题(二):深入理解iframe
http://www.cnblogs.com/fangjins/archive/2012/08/19/2645631.html

关于微信公众平台无法使用支付宝收付款的解决方案说明
https://cshall.alipay.com/enterprise/help_detail.htm?help_id=524702


25. 高并发、大流量

对于当今大流量的网站,每天几千万甚至上亿的流量,是如何解决访问量问题的呢?

以下是一些总结的方法: 第一,确认服务器硬件是否足够支持当前的流量。 普通的P4服务器一般最多能支持每天10万独立IP,如果访问量比这个还要大,那么必须首先配置一台更高性能的专用服务器才能解决问题,否则怎么优化都不可能彻底解决性能问题。

第二,优化数据库访问。 服务器的负载过大,一个重要的原因是CPU负荷过大,降低服务器CPU的负荷,才能够有效打破瓶颈。而使用静态页面可以使得CPU的负荷最小化。前台实现完全的静态化 当然最好,可以完全不用访问数据库,不过对于频繁更新的网站,静态化往往不能满足某些功能。 缓存技术 就是另一个解决方案,就是将动态数据存储到缓存文件中,动态网页直接调用这些文件,而不必再访问数据库,WordPress和Z-Blog都大量使用这种缓存技术 。我自己也写过一个Z-Blog的计数器插件,也是基于这样的原理。 如果确实无法避免对数据库的访问,那么可以尝试优化数据库的查询SQL.避免使用Select *from这样的语句,每次查询只返回自己需要的结果,避免短时间内的大量SQL查询。

第三,禁止外部的盗链。 外部网站的图片或者文件盗链往往会带来大量的负载压力,因此应该严格限制外部对于自身的图片或者文件盗链,好在目前可以简单地通过refer来控制盗链,Apache自己就可以通过配置来禁止盗链,IIS也有一些第三方的ISAPI可以实现同样的功能。当然,伪造refer也可以通过代码来实现盗 链,不过目前蓄意伪造refer盗链的还不多,可以先不去考虑,或者使用非技术手段来解决,比如在图片上增加水印。

第四,控制大文件的下载。 大文件的下载会占用很大的流量,并且对于非SCSI硬盘来说,大量文件下载会消耗CPU,使得网站响应能力下降。因此,尽量不要提供超过2M的大 文件下载,如果需要提供,建议将大文件放在另外一台服务器上。目前有不少免费的Web2.0网站提供图片分享和文件分享功能,因此可以尽量将图片和文件上 传到这些分享网站。

第五,使用不同主机分流主要流量 将文件放在不同的主机上,提供不同的镜像供用户下载。比如如果觉得RSS文件占用流量大,那么使用FeedBurner或者FeedSky等服务将RSS输出放在其他主机上,这样别人访问的流量压力就大多集中在FeedBurner的主机上,RSS就不占用太多资源了。

第六,使用流量分析统计软件。 在网站上安装一个流量分析统计软件,可以即时知道哪些地方耗费了大量流量,哪些页面需要再进行优化,因此,解决流量问题还需要进行精确的统计分析 才可以。我推荐使用的流量分析统计软件是GoogleAnalytics(Google分析)。我使用过程中感觉其效果非常不错,稍后我将详细介绍一下 GoogleAnalytics的一些使用常识和技巧。 1.分表 2.读写分离 3.前端优化。Nginx替换Apache(前端做负载均衡) 个人认为主要还是分布式架构是否到位,mysql和缓存的优化都是有限度的优化,而分布式架构做出来了,PV增长后,只需要堆机器就能扩容。

另附一些优化经验,首先学会用explain语句分析select语句,优化索引、表结构,其次,合理运用memcache等缓存,降低mysql的负载,最后,如果可能的话,尽量用facebook的hiphop-php把PHP编译了,提高程序效率。



26. mysql和nosql的区别

NoSQL与关系型数据库设计理念比较  

关系型数据库中的表都是存储一些格式化的数据结构,每个元组字段的组成都一样,即使不是每个元组都需要所有的字段,但数据库会为每个元组分配所有的字段,这样的结构可以便于表与表之间进行连接等操作,但从另一个角度来说它也是关系型数据库性能瓶颈的一个因素。而非关系型数据库以键值对存储,它的结构不固定,每一个元组可以有不一样的字段,每个元组可以根据需要增加一些自己的键值对,这样就不会局限于固定的结构,可以减少一些时间和空间的开销。

特点:
它们可以处理超大量的数据。
它们运行在便宜的PC服务器集群上。
它们击碎了性能瓶颈。
没有过多的操作。
Bootstrap支持

缺点:
但是一些人承认,没有正式的官方支持,万一出了差错会是可怕的,至少很多管理人员是这样看。
此外,nosql并未形成一定标准,各种产品层出不穷,内部混乱,各种项目还需时间来检验

MySQL还是NoSQL:开源盛世下的数据库该如何选择

摘要:MySQL,关系型数据库,有数量庞大的支持者。NoSQL,非关系型数据库,被视为数据库革命者。两者似乎注定要有一场厮杀,可同属开源大家庭的它们却又能携手并进、和睦相处,齐心协力为开发者提供更好的服务。

如何选择:永远正确的经典答案依然是:具体问题具体分析。

MySQL体积小、速度快、成本低、结构稳定、便于查询,可以保证数据的一致性,但缺乏灵活性。NoSQL高性能、高扩展、高可用,不用局限于固定的结构,减少了时间和空间上的开销,却又很难保证数据一致性。两者都有大量的用户和支持者,寻求两者的结合无疑是个很好的解决方案。作者John Engates是Rackspace主机托管部门CTO,也是个开放云支持者,他为我们带来了详细的分析。http://www.csdn.net/article/2014-03-05/2818637-open-source-data-grows-choosing-mysql-nosql

选择其中一个?还是两者都要?

应用程序是否应该与关系型数据库或NoSQL(也许是两者)相一致,当然,这得基于被生成或被检索数据的性质。和大多数科技领域的事物一样,做决定时要折中考虑。

如果规模和性能比24小时的数据一致性更重要,那NoSQL是一个理想的选择 (NoSQL依赖于BASE模型——基本可用、软状态、最终一致性)。

但如果要保证到“始终一致”,尤其是对于机密信息和财务信息,那么MySQL很可能是最优的选择(MySQL依赖于ACID模型——原子性、一致性、独立性和耐久性)。

作为开源数据库,无论是关系型数据库还是非关系型数据库都在不断成熟,我们可以期待还会有一大批基于ACID和BASE模型的新应用产生。

NoSQL还是关系数据库

虽然09年出现了比较激进的文章《关系数据库已死》,但是我们心里都清楚,关系数据库其实还活得好好的,你还不能不用关系数据库。但是也说明了一个事实,关系数据库在处理WEB2.0数据的时候,的确已经出现了瓶颈。

那么我们到底是用NoSQL还是关系数据库呢?我想我们没有必要来进行一个绝对的回答。我们需要根据我们的应用场景来决定我们到底用什么。

如果关系数据库在你的应用场景中,完全能够很好的工作,而你又是非常善于使用和维护关系数据库的,那么我觉得你完全没有必要迁移到NoSQL上面,除非你是个喜欢折腾的人。如果你是在金融,电信等以数据为王的关键领域,目前使用的是Oracle数据库来提供高可靠性的,除非遇到特别大的瓶颈,不然也别贸然尝试NoSQL。

然而,在WEB2.0的网站中,关系数据库大部分都出现了瓶颈。在磁盘IO、数据库可扩展上都花费了开发人员相当多的精力来优化,比如做分表分库(database sharding)、主从复制、异构复制等等,然而,这些工作需要的技术能力越来越高,也越来越具有挑战性。如果你正在经历这些场合,那么我觉得你应该尝试一下NoSQL了。


27. 后台结构服务化

概述

  相对于结构化数据(即行数据,存储在数据库里,可以用二维表结构来逻辑表达实现的数据)而言,不方便用数据库二维逻辑表来表现的数据即称为非结构化数据,包括所有格式的办公文档、文本、图片、XML、HTML、各类报表、图像和音频/视频信息等等。

  非结构化数据库是指其字段长度可变,并且每个字段的记录又可以由可重复或不可重复的子字段构成的数据库,用它不仅可以处理结构化数据(如数字、符号等信息)而且更适合处理非结构化数据(全文文本、图象、声音、影视、超媒体等信息)。

  非结构化WEB数据库主要是针对非结构化数据而产生的,与以往流行的关系数据库相比,其最大区别在于它突破了关系数据库结构定义不易改变和数据定长的限制,支持重复字段、子字段以及变长字段并实现了对变长数据和重复字段进行处理和数据项的变长存储管理,在处理连续信息(包括全文信息)和非结构化信息(包括各种多媒体信息)中有着传统关系型数据库所无法比拟的优势。

  结构化数据(即行数据,存储在数据库里,可以用二维表结构来逻辑表达实现的数据)

  非结构化数据,包括所有格式的办公文档、文本、图片、XML、HTML、各类报表、图像和音频/视频信息等等

  所谓半结构化数据,就是介于完全结构化数据(如关系型数据库、面向对象数据库中的数据)和完全无结构的数据(如声音、图像文件等)之间的数据,HTML文档就属于半结构化数据。它一般是自描述的,数据的结构和内容混在一起,没有明显的区分。

  数据模型:

  结构化数据:二维表(关系型)
  半结构化数据:树、图
  非结构化数据:无

  RMDBS的数据模型有:如网状数据模型、层次数据模型、关系型

  其他:

  结构化数据:先有结构、再有数据
  半结构化数据:先有数据,再有结构

  随着网络技术的发展,特别是Internet和Intranet技术的飞快发展,使得非结构化数据的数量日趋增大。这时,主要用于管理结构化数据的关系数据库的局限性暴露地越来越明显。因而,数据库技术相应地进入了“后关系数据库时代”,发展进入基于网络应用的非结构化数据库时代。

  我国非结构化数据库以北京国信贝斯(iBase)软件有限公司的IBase数据库为代表。IBase数据库是一种面向最终用户的非结构化数据库,在处理非结构化信息、全文信息、多媒体信息和海量信息等领域以及Internet/Intranet应用上处于国际先进水平,在非结构化数据的管理和全文检索方面获得突破。它主要有以下几个优点:

  (1)Internet应用中,存在大量的复杂数据类型,iBase通过其外部文件数据类型,可以管理各种文档信息、多媒体信息,并且对于各种具有检索意义的文档信息资源,如HTML、DOC、RTF、TXT等还提供了强大的全文检索能力。

  (2)它采用子字段、多值字段以及变长字段的机制,允许创建许多不同类型的非结构化的或任意格式的字段,从而突破了关系数据库非常严格的表结构,使得非结构化数据得以存储和管理。

  (3)iBase将非结构化和结构化数据都定义为资源,使得非结构数据库的基本元素就是资源本身,而数据库中的资源可以同时包含结构化和非结构化的信息。所以,非结构化数据库能够存储和管理各种各样的非结构化数据,实现了数据库系统数据管理到内容管理的转化。

  (4)iBase采用了面向对象的基石,将企业业务数据和商业逻辑紧密结合在一起,特别适合于表达复杂的数据对象和多媒体对象。

  (5)iBase是适应Internet发展的需要而产生的数据库,它基于Web是一个广域网的海量数据库的思想,提供一个网上资源管理系统iBase Web,将网络服务器(WebServer)和数据库服务器(Database Server)直接集成为一个整体,使数据库系统和数据库技术成为Web的一个重要有机组成部分,突破了数据库仅充当Web体系后台角色的局限,实现数据库和Web的有机无缝组合,从而为在Internet/Intranet上进行信息管理乃至开展电子商务应用开辟了更为广阔的领域。

  (6)iBase全面兼容各种大中小型的数据库,对传统关系数据库,如Oracle、Sybase、SQLServer、DB2、Informix等提供导入和链接的支持能力。

  通过从上面的分析后我们可以预言,随着网络技术和网络应用技术的飞快发展,完全基于Internet应用的非结构化数据库将成为继层次数据库、网状数据库和关系数据库之后的又一重点、热点技术。

 数据分类

  半结构化数据(semi-structured data)

  在做一个信息系统设计时肯定会涉及到数据的存储,一般我们都会将系统信息保存在某个指定的关系数据库中。我们会将数据按业务分类,并设计相应的表,然后将对应的信息保存到相应的表中。比如我们做一个业务系统,要保存员工基本信息:工号、姓名、性别、出生日期等等;我们就会建立一个对应的staff表。

  但不是系统中所有信息都可以这样简单的用一个表中的字段就能对应的。

  结构化数据

  就像上面举的例子。这种类别的数据最好处理,只要简单的建立一个对应的表就可以了。

  非结构化数据

  像图片、声音、视频等等。这类信息我们通常无法直接知道他的内容,数据库也只能将它保存在一个BLOB字段中,对以后检索非常麻烦。一般的做法是,建立一个包含三个字段的表(编号 number、内容描述 varchar(1024)、内容 blob)。引用通过编号,检索通过内容描述。现在还有很多非结构化数据的处理工具,市面上常见的内容管理器就是其中的一种。

  半结构化数据

  这样的数据和上面两种类别都不一样,它是结构化的数据,但是结构变化很大。因为我们要了解数据的细节所以不能将数据简单的组织成一个文件按照非结构化数据处理,由于结构变化很大也不能够简单的建立一个表和他对应。本文主要讨论针对半结构化数据存储常用的两种方式。

  先举一个半结构化的数据的例子,比如存储员工的简历。不像员工基本信息那样一致每个员工的简历大不相同。有的员工的简历很简单,比如只包括教育情况;有的员工的简历却很复杂,比如包括工作情况、婚姻情况、出入境情况、户口迁移情况、党籍情况、技术技能等等。还有可能有一些我们没有预料的信息。通常我们要完整的保存这些信息并不是很容易的,因为我们不会希望系统中的表的结构在系统的运行期间进行变更。

 储存方式

  化解为结构化数据

  这种方法通常是对现有的简历中的信息进行粗略的统计整理,总结出简历中信息所有的类别同时考虑系统真正关心的信息。对每一类别建立一个子表,比如上例中我们可以建立教育情况子表、工作情况子表、党籍情况子表等等,并在主表中加入一个备注字段,将其它系统不关心的信息和已开始没有考虑到的信息保存在备注中。

  优点:查询统计比较方便。

  缺点:不能适应数据的扩展,不能对扩展的信息进行检索,对项目设计阶段没有考虑到的同时又是系统关心的信息的存储不能很好的处理。

  用XML格式来组织并保存到CLOB字段中

  XML可能是最适合存储半结构化的数据了。将不同类别的信息保存在XML的不同的节点中就可以了。

  优点:能够灵活的进行扩展,信息进行扩展式只要更改对应的DTD或者XSD就可以了。

  缺点:查询效率比较低,要借助XPATH来完成查询统计,随着数据库对XML的支持的提升性能问题有望能够很好的解决。


28. 单链表翻转

单链表

1、链接存储方法
 链接方式存储的线性表简称为链表(Linked List)。
 链表的具体存储表示为:
  ① 用一组任意的存储单元来存放线性表的结点(这组存储单元既可以是连续的,也可以是不连续的)
  ② 链表中结点的逻辑次序和物理次序不一定相同。为了能正确表示结点间的逻辑关系,在存储每个结点值的同时,还必须存储指示其后继结点的地址(或位置)信息(称为指针(pointer)或链(link))
注意:
  
链式存储是最常用的存储方式之一,它不仅可用来表示线性表,而且可用来表示各种非线性的数据结构。

2、链表的结点结构
┌──┬──┐
│data│next│
└──┴──┘
 data域--存放结点值的数据域
 next域--存放结点的直接后继的地址(位置)的指针域(链域)
注意:
  ①链表通过每个结点的链域将线性表的n个结点按其逻辑顺序链接在一起的。
 ②每个结点只有一个链域的链表称为单链表(Single Linked List)。
【例】线性表(bat,cat,eat,fat,hat,jat,lat,mat)的单链表示如示意图

单链表反转总结篇

单链表的反转是常见的面试题目。本文总结了2种方法。

1 定义

单链表node的数据结构定义如下:

白俊遥博客

class ListNode {     
int val;     
ListNode next;     
ListNode(int x) {         
val = x;        
 next = null;     
 } 
 }

白俊遥博客

2 方法1:就地反转法

2.1 思路

把当前链表的下一个节点pCur插入到头结点dummy的下一个节点中,就地反转。

dummy->1->2->3->4->5的就地反转过程:

dummy->2->1->3->4->5
dummy->3->2->1->4->5
dummy->4>-3->2->1->5
dummy->5->4->3->2->1

2.2 解释

1初始状态

白俊遥博客

2 过程

pCur是需要反转的节点。

  1. prev连接下一次需要反转的节点

  2. 反转节点pCur

  3. 纠正头结点dummy的指向

  4. pCur指向下一次要反转的节点

伪代码

1 prev.next = pCur.next; 
2 pCur.next = dummy.next; 
3 dummy.next = pCur; 
4 pCur = prev.next;

白俊遥博客

3 循环条件

pCur is not null

2.3 代码

白俊遥博客

 1     // 1.就地反转法  
 2     public ListNode reverseList1(ListNode head) {  
 3         if (head == null)  
 4             return head;  
 5         ListNode dummy = new ListNode(-1);  
 6         dummy.next = head;  
 7         ListNode prev = dummy.next;  
 8         ListNode pCur = prev.next;  
 9         while (pCur != null) { 
 10             prev.next = pCur.next; 
 11             pCur.next = dummy.next; 
 12             dummy.next = pCur; 
 13             pCur = prev.next; 
 14         } 
 15         return dummy.next; 
 16     }

白俊遥博客

2.4 总结

  • 1个头结点,2个指针,4行代码

  • 注意初始状态和结束状态,体会中间的图解过程。

3 方法2:新建链表,头节点插入法

3.1 思路

新建一个头结点,遍历原链表,把每个节点用头结点插入到新建链表中。最后,新建的链表就是反转后的链表。

3.2 解释

1 初始状态

白俊遥博客

2 过程

pCur是要插入到新链表的节点。

pNex是临时保存的pCur的next。

  1. pNex保存下一次要插入的节点

  2. 把pCur插入到dummy中

  3. 纠正头结点dummy的指向

  4. pCur指向下一次要插入的节点

伪代码

1 pNex = pCur.next 
2 pCur.next = dummy.next 
3 dummy.next = pCur 
4 pCur = pNex

白俊遥博客

3 循环条件

pCur is not null

3.3 代码

白俊遥博客

 1     // 2.新建链表,头节点插入法 
 2     public ListNode reverseList2(ListNode head) {  
 3         ListNode dummy = new ListNode(-1);  
 4         ListNode pCur = head;  
 5         while (pCur != null) {  
 6             ListNode pNex = pCur.next;  
 7             pCur.next = dummy.next;  
 8             dummy.next = pCur;  
 9             pCur = pNex; 
 10         } 
 11         return dummy.next; 
 12     }

白俊遥博客

29. 最擅长的php


30. 索引

MySQL索引优化

  MySQL官方对索引的定义:索引是帮助MySQL高效获取数据的数据结构。索引是在存储引擎中实现的,所以每种存储引擎中的索引都不一样。如MYISAM和InnoDB存储引擎只支持BTree索引;MEMORY和HEAP储存引擎可以支持HASH和BTREE索引。

  这里仅针对常用的InnoDB存储引擎所支持的BTree索引进行介绍:

一、索引类型

先创建一个新表,用于演示索引类型

白俊遥博客

CREATE TABLE index_table (    
 id BIGINT NOT NULL auto_increment COMMENT '主键',     
 NAME VARCHAR (10) COMMENT '姓名',     
 age INT COMMENT '年龄',     
 phoneNum CHAR (11) COMMENT '手机号',     
 PRIMARY KEY (id) ) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;

白俊遥博客

下图是Col2为索引列,记录与B树结构的对应图,仅供参考:

白俊遥博客

1、普通索引

这是最基本的索引,没有任何限制。

------直接创建索引 create index index_name on index_table(name);

2、唯一索引

索引列的值必须唯一,可以有空值

---------直接创建唯一索引 create UNIQUE index index_phoneNum on index_table(phoneNum);

3、主键

是一种特殊的唯一索引,必须指定为 PRIMARY KEY,如我们常用的AUTO_INCREMENT自增主键

4、多列索引

也称为组合索引,就是在多个字段上联合建立一个索引

-------直接创建组合索引 create index index_union on index_table(name,age,phoneNum);

这里一个组合索引,相当于在有如下三个索引:

name;

name,age;

name,age,phoneNum;

这里或许有这样一个疑惑:为什么age或者age,phoneNum字段上没有索引。这是由于BTree索引因要遵守最左前缀原则,这个原则在后面详细展开。

二、索引优化

1、选择索引列

创建索引简单,但是在哪些列上创建索引则需要好好思考。可以考虑在where字句中出现列或者join字句中出现的列上建索引

白俊遥博客

SELECT     age----不使用索引 FROM     index_union WHERE     NAME = 'xiaoming'---考虑使用索引 AND phoneNum = '18668247687';---考虑使用索引

白俊遥博客

2、最左前缀原则

  联合索引(name,age,phoneNum) ,B+树是按照从左到右的顺序来建立搜索树的。如('张三',18,'18668247652')来检索数据的时候,B+树会优先匹配name来确定搜索方向,name匹配成功再依次匹配age、phoneNum,最后检索到最终的数据。也就是说这种情况下是有三级索引,当name相同,查找age,age也相同时,去比较phoneNum;但是如果拿 (18,'18668247652')来检索时,B+树没有拿到一级索引,根本就无法确定下一步的搜索方向。('张三','18668247652')这种场景也是一样,当name匹配成功后,没有age这个二级索引,只能在name相同的情况下,去遍历所有的phoneNum。

  B+树的数据结构决定了在使用索引的时候必须遵守最左前缀原则,在创建联合索引的时候,尽量将经常参与查询的字段放在联合索引的最左边。

3、like的使用

一般情况下不建议使用like操作,如果非使用不可的话,需要注意:like '%abd%'不会使用索引,而like ‘aaa%’可以使用索引。这也是前面的最左前缀原则的一个使用场景。

4、不能使用索引说明

mysql会按照联合索引从左往右进行匹配,直到遇到范围查询,如:>,<,between,like等就停止匹配,a = 1 and b =2 and c > 3 and d = 4,如果建立(a,b,c,d)顺序的索引,d是不会使用索引的。但如果联合索引是(a,b,d,c)的话,则a b d c都可以使用到索引,只是最终c是一个范围值。

5、order by

order by排序有两种排序方式:using filesort使用算法在内存中排序以及使用mysql的索引进行排序;我们在部分不情况下希望的是使用索引。

1select test_index where id = 3 order by id desc;

如果ID是单列索引,则order by会使用索引

1select test_index where id = 3 order by name desc;

如果ID是单列索引,name不是索引或者name也是单列索引,则order by不会使用索引。因为Mysql的一次查询只会从众多索引中选择一个索引,而这次查询中使用的是ID列索引,而不是name列索引。在这种场景下,如果想让order by也使用索引的话,就建立联合索引(id,name),这里需要注意最左前缀原则,不要建立这样的联合索引(name,id)。

最后需要注意mysql对排序记录的大小有限制:max_length_for_sort_data 默认为1024;也就意味着如果需要排序的数据量大于1024,则order by不会使用索引,而是使用using filesort。



31. 商城架构

1 电商案例原因

分布式大型网站,目前看主要有几类1.大型门户,比如网易,新浪等;2.SNS网站,比如校内,开心网等;3.电商网站:比如阿里巴巴,京东商城,国美在线,汽车之家等。大型门户一般是新闻类信息,可以使用CDN,静态化等方式优化开心网等交互性比较多,可能会引入更多的NOSQL,分布式缓存,使用高性能的通信框架等。电商网站具备以上两类的特点,比如产品详情可以采用CDN,静态化,交互性高的需要采用NOSQL等技术。因此,我们采用电商网站作为案例,进行分析。

2 电商网站需求

白俊遥博客

客户需求:

  • 建立一个全品类的电子商务网站(B2C),用户可以在线购买商品,可以在线支付,也可以货到付款;

  • 用户购买时可以在线与客服沟通;

  • 用户收到商品后,可以给商品打分,评价;

  • 目前有成熟的进销存系统;需要与网站对接;

  • 希望能够支持3~5年,业务的发展;

  • 预计3~5年用户数达到1000万;

  • 定期举办双11,双12,三八男人节等活动;

  • 其他的功能参考京东或国美在线等网站。

客户就是客户,不会告诉你具体要什么,只会告诉你他想要什么,我们很多时候要引导,挖掘客户的需求。好在提供了明确的参考网站。因此,下一步要进行大量的分析,结合行业,以及参考网站,给客户提供方案。

需求管理传统的做法,会使用用例图或模块图(需求列表)进行需求的描述。这样做常常忽视掉一个很重要的需求(非功能需求),因此推荐大家使用需求功能矩阵,进行需求描述

本电商网站的需求矩阵如下:

白俊遥博客

白俊遥博客

以上是对电商网站需求的简单举例,目的是说明(1)需求分析的时候,要全面,大型分布式系统重点考虑非功能需求;(2)描述一个简单的电商需求场景,使大家对下一步的分析设计有个依据。

3 网站初级架构

一般网站,刚开始的做法,是三台服务器,一台部署应用,一台部署数据库,一台部署NFS文件系统。这是前几年比较传统的做法,之前见到一个网站10万多会员,垂直服装设计门户,N多图片。使用了一台服务器部署了应用,数据库以及图片存储。出现了很多性能问题。如下图:

白俊遥博客

但是,目前主流的网站架构已经发生了翻天覆地的变化。一般都会采用集群的方式,进行高可用设计。至少是下面这个样子。

白俊遥博客

  • (1)使用集群对应用服务器进行冗余,实现高可用;(负载均衡设备可与应用一块部署)

  • (2)使用数据库主备模式,实现数据备份和高可用;

4 系统容量预估

  1. 预估步骤:

  • 注册用户数-日均UV量-每日的PV量-每天的并发量;

  • 峰值预估:平常量的2~3倍;

  • 根据并发量(并发,事务数),存储容量计算系统容量;

客户需求:3~5年用户数达到1000万注册用户;

  1. 每秒并发数预估:

  • 每天的UV为200万(二八原则);

  • 每日每天点击浏览30次;

  • PV量:200*30=6000万;

  • 集中访问量:240.2=4.8小时会有6000万0.8=4800万(二八原则);

  • 每分并发量:4.8*60=288分钟,每分钟访问4800/288=16.7万(约等于);

  • 每秒并发量:16.7万/60=2780(约等于);

  • 假设:高峰期为平常值的三倍,则每秒的并发数可以达到8340次。

  • 1毫秒=1.3次访问;

服务器预估:(以tomcat服务器举例)

  • 按一台web服务器,支持每秒300个并发计算。平常需要10台服务器(约等于);[tomcat默认配置是150]

  • 高峰期:需要30台服务器;

容量预估:70/90原则

  • 系统CPU一般维持在70%左右的水平,高峰期达到90%的水平,是不浪费资源,并比较稳定的。内存,IO类似。

以上预估仅供参考,因为服务器配置,业务逻辑复杂度等都有影响。在此CPU,硬盘,网络等不再进行评估。

5 网站架构分析

根据以上预估,有几个问题:

  • 需要部署大量的服务器,高峰期计算,可能要部署30台Web服务器。并且这三十台服务器,只有秒杀,活动时才会用到,存在大量的浪费。

  • 所有的应用部署在同一台服务器,应用之间耦合严重。需要进行垂直切分和水平切分。

  • 大量应用存在冗余代码。

  • 服务器SESSION同步耗费大量内存和网络带宽。

  • 数据需要频繁访问数据库,数据库访问压力巨大。

大型网站一般需要做以下架构优化(优化是架构设计时,就要考虑的,一般从架构/代码级别解决,调优主要是简单参数的调整,比如JVM调优;如果调优涉及大量代码改造,就不是调优了,属于重构):

  • 业务拆分

  • 应用集群部署(分布式部署,集群部署和负载均衡)

  • 多级缓存

  • 单点登录(分布式Session)

  • 数据库集群(读写分离,分库分表)

  • 服务化

  • 消息队列

  • 其他技术

6 网站架构优化

6.1 业务拆分

根据业务属性进行垂直切分,划分为产品子系统,购物子系统,支付子系统,评论子系统,客服子系统,接口子系统(对接如进销存,短信等外部系统)。

根据业务子系统进行等级定义,可分为核心系统和非核心系统。核心系统:产品子系统,购物子系统,支付子系统;非核心:评论子系统,客服子系统,接口子系统。

业务拆分作用:提升为子系统可由专门的团队和部门负责,专业的人做专业的事,解决模块之间耦合以及扩展性问题;每个子系统单独部署,避免集中部署导致一个应用挂了,全部应用不可用的问题。

等级定义作用:用于流量突发时,对关键应用进行保护,实现优雅降级;保护关键应用不受到影响。

拆分后的架构图:

白俊遥博客

参考部署方案2:

白俊遥博客

(1)如上图每个应用单独部署;

(2)核心系统和非核心系统组合部署;

6.2 应用集群部署(分布式,集群,负载均衡)

分布式部署:将业务拆分后的应用单独部署,应用直接通过RPC进行远程通信;

集群部署:电商网站的高可用要求,每个应用至少部署两台服务器进行集群部署;

负载均衡:是高可用系统必须的,一般应用通过负载均衡实现高可用,分布式服务通过内置的负载均衡实现高可用,关系型数据库通过主备方式实现高可用。

集群部署后架构图:

白俊遥博客

6.3 多级缓存

缓存按照存放的位置一般可分为两类本地缓存和分布式缓存。本案例采用二级缓存的方式,进行缓存的设计。一级缓存为本地缓存,二级缓存为分布式缓存。(还有页面缓存,片段缓存等,那是更细粒度的划分)

一级缓存,缓存数据字典,和常用热点数据等基本不可变/有规则变化的信息,二级缓存缓存需要的所有缓存。当一级缓存过期或不可用时,访问二级缓存的数据。如果二级缓存也没有,则访问数据库。

缓存的比例,一般1:4,即可考虑使用缓存。(理论上是1:2即可)。

白俊遥博客

根据业务特性可使用以下缓存过期策略:

(1)缓存自动过期;

(2)缓存触发过期;

6.4 单点登录(分布式Session)

系统分割为多个子系统,独立部署后,不可避免的会遇到会话管理的问题。一般可采用Session同步,Cookies,分布式Session方式。电商网站一般采用分布式Session实现。

再进一步可以根据分布式Session,建立完善的单点登录或账户管理系统。

白俊遥博客

流程说明:

(1)用户第一次登录时,将会话信息(用户Id和用户信息),比如以用户Id为Key,写入分布式Session;

(2)用户再次登录时,获取分布式Session,是否有会话信息,如果没有则调到登录页;

(3)一般采用Cache中间件实现,建议使用Redis,因此它有持久化功能,方便分布式Session宕机后,可以从持久化存储中加载会话信息;

(4)存入会话时,可以设置会话保持的时间,比如15分钟,超过后自动超时;

结合Cache中间件,实现的分布式Session,可以很好的模拟Session会话。

6.5 数据库集群(读写分离,分库分表)

大型网站需要存储海量的数据,为达到海量数据存储,高可用,高性能一般采用冗余的方式进行系统设计一般有两种方式读写分离和分库分表

读写分离:一般解决读比例远大于写比例的场景,可采用一主一备,一主多备或多主多备方式。

本案例在业务拆分的基础上,结合分库分表和读写分离。如下图:

白俊遥博客

(1)业务拆分后:每个子系统需要单独的库;

(2)如果单独的库太大,可以根据业务特性,进行再次分库,比如商品分类库,产品库;

(3)分库后,如果表中有数据量很大的,则进行分表,一般可以按照Id,时间等进行分表;(高级的用法是一致性Hash)

(4)在分库,分表的基础上,进行读写分离;

相关中间件可参考Cobar(阿里,目前已不在维护),TDDL(阿里),Atlas(奇虎360),MyCat(在Cobar基础上,国内很多牛人,号称国内第一开源项目)。

分库分表后序列的问题,JOIN,事务的问题,会在分库分表主题分享中,介绍。

6.6 服务化

将多个子系统公用的功能/模块,进行抽取,作为公用服务使用。比如本案例的会员子系统就可以抽取为公用的服务。

白俊遥博客

6.7 消息队列

消息队列可以解决子系统/模块之间的耦合,实现异步,高可用,高性能的系统。是分布式系统的标准配置。本案例中,消息队列主要应用在购物,配送环节。

(1)用户下单后,写入消息队列,后直接返回客户端;

(2)库存子系统:读取消息队列信息,完成减库存;

(3)配送子系统:读取消息队列信息,进行配送;

白俊遥博客

目前使用较多的MQ有Active MQ,Rabbit MQ,Zero MQ,MS MQ等,需要根据具体的业务场景进行选择。建议可以研究下Rabbit MQ

6.8 其他架构(技术)

除了以上介绍的业务拆分,应用集群,多级缓存,单点登录,数据库集群,服务化,消息队列外。还有CDN,反向代理,分布式文件系统,大数据处理等系统。

此处不详细介绍,大家可以问度娘/Google,有机会的话也可以分享给大家。

7 架构总结

白俊遥博客


32. app

RESTful是"分布式超媒体应用"的架构风格
1.采用URI标识资源;

2.使用“链接”关联相关的资源;

3.使用统一的接口;

4.使用标准的HTTP方法;

5.支持多种资源表示方式;

6.无状态性;



34. AB

windows

前沿

最近要熟悉一下网站优化,包括前端优化,后端优化,涉及到的细节Opcode,Xdebuge等,都会浅浅的了解一下。

像类似于,刷刷CSDN博客的人气啦,完全是得心应手啊。

我测试了博客园,使用ab并不能刷访问量,为什么CSDN可以,因为两者统计的方式不同。

--PV(访问量):Page View, 即页面浏览量或点击量,用户每次刷新即被计算一次。 --UV(独立访客):Unique Visitor,访问您网站的一台电脑客户端为一个访客。00:00-24:00内相同的客户端只会被计算一次。 --IP(独立IP):指独立IP数。00:00-24:00内相同IP地址之被计算一次。
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

PV与来访者的数量成正比,但是PV并不直接决定页面的真实来访者数量。比如一个网站就你一个人进来,通过不断的刷新页面,也可以制造出非常高的PV。这也就是ab可以刷csdn访问的原因了。


UV是指不同的、通过互联网访问、浏览一个网页的自然人。类似于注册用户,保存session的形式


IP就不用说啦。类似于博客园,使用的统计方式就必须是IP啦

安装

ab是Apache的自带的工具,如果是window安装的,找到Apache的bin目录,在系统全局变量中添加Path,然后就可以使用ab了

ab的基本参数

    -c  并发的请求数     -n  要执行的请求总数     -k  启用keep-alive功能(开启的话,请求会快一些)     -H  一个使用冒号分隔的head报文头的附加信息     -t  执行这次测试所用的时间
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

ab基本语法

ab -c 5 -n 60 -H "Referer: http://baidu.com" -H "Connection: close" http://blog.csdn.net /XXXXXX/article/details/XXXXXXX
  • 1

  • 2

  • 3

ab -c 100 -n 100 -H "User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:3 8.0) Gecko/20100101 Firefox/38.0" -e "E:\ab.csv" http://blog.csdn.net/uxxxxx/artic le/details/xxxx
  • 1

  • 2

  • 3

ab执行结果分析

白俊遥博客

总结

本文介绍ab测试,并没有恶意使用它。途中的博客地址,也只是测试过程中借用了一下,没有别的恶意。

linux 下ab压力测试

原创 2015年10月19日 18:24:31


装ab工具

ubuntu安装ab

apt-get install apache2-utils

centos安装ab

yum install httpd-tools

ab 测试命令

ab -kc 1000-n 1000 http://localhost/ab.html(是服务器下的页面)


白俊遥博客白俊遥博客

35. 慢查询

ySQL中的日志包括:错误日志、二进制日志、通用查询日志、慢查询日志等等。这里主要介绍下比较常用的两个功能:通用查询日志和慢查询日志。

1)通用查询日志:记录建立的客户端连接和执行的语句。

2)慢查询日志:记录所有执行时间超过long_query_time秒的所有查询或者不使用索引的查询

(1)通用查询日志

在学习通用日志查询时,需要知道两个数据库中的常用命令:

1) showvariables like ‘%version%’;

效果图如下:

白俊遥博客

上述命令,显示当前数据库中与版本号相关的东西。

1) showvariables like ‘%general%’;

白俊遥博客

可以查看,当前的通用日志查询是否开启,如果general_log的值为ON则为开启,为OFF则为关闭(默认情况下是关闭的)。

1) showvariables like ‘%log_output%’;

白俊遥博客

查看当前慢查询日志输出的格式,可以是FILE(存储在数数据库的数据文件中的hostname.log),也可以是TABLE(存储在数据库中的mysql.general_log)

问题:如何开启MySQL通用查询日志,以及如何设置要输出的通用日志输出格式呢?

开启通用日志查询: set global general_log=on;

关闭通用日志查询: set globalgeneral_log=off;

设置通用日志输出为表方式: set globallog_output=’TABLE’;

设置通用日志输出为文件方式: set globallog_output=’FILE’;

设置通用日志输出为表和文件方式:set global log_output=’FILE,TABLE’;

注意:上述命令只对当前生效,当MySQL重启失效,如果要永久生效,需要配置my.cnf

日志输出的效果图如下:

记录到mysql.general_log表中的数据如下:

白俊遥博客

记录到本地中的.log中的格式如下:

白俊遥博客

my.cnf文件的配置如下:

general_log=1 #为1表示开启通用日志查询,值为0表示关闭通用日志查询

log_output=FILE,TABLE#设置通用日志的输出格式为文件和表

(2)慢查询日志

MySQL的慢查询日志是MySQL提供的一种日志记录,用来记录在MySQL中响应时间超过阈值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中(日志可以写入文件或者数据库表,如果对性能要求高的话,建议写文件)。默认情况下,MySQL数据库是不开启慢查询日志的,long_query_time的默认值为10(即10秒,通常设置为1秒),即运行10秒以上的语句是慢查询语句。

一般来说,慢查询发生在大表(比如:一个表的数据量有几百万),且查询条件的字段没有建立索引,此时,要匹配查询条件的字段会进行全表扫描,耗时查过long_query_time,

则为慢查询语句。

问题:如何查看当前慢查询日志的开启情况?

在MySQL中输入命令:

showvariables like ‘%quer%’;

白俊遥博客

主要掌握以下的几个参数:

(1)slow_query_log的值为ON为开启慢查询日志,OFF则为关闭慢查询日志。

(2)slow_query_log_file 的值是记录的慢查询日志到文件中(注意:默认名为主机名.log,慢查询日志是否写入指定文件中,需要指定慢查询的输出日志格式为文件,相关命令为:show variables like ‘%log_output%’;去查看输出的格式)。

(3)long_query_time 指定了慢查询的阈值,即如果执行语句的时间超过该阈值则为慢查询语句,默认值为10秒。

(4)log_queries_not_using_indexes 如果值设置为ON,则会记录所有没有利用索引的查询(注意:如果只是将log_queries_not_using_indexes设置为ON,而将slow_query_log设置为OFF,此时该设置也不会生效,即该设置生效的前提是slow_query_log的值设置为ON),一般在性能调优的时候会暂时开启。

问题:设置MySQL慢查询的输出日志格式为文件还是表,或者两者都有?

通过命令:show variables like ‘%log_output%’;

白俊遥博客

通过log_output的值可以查看到输出的格式,上面的值为TABLE。当然,我们也可以设置输出的格式为文本,或者同时记录文本和数据库表中,设置的命令如下:

#慢查询日志输出到表中(即mysql.slow_log)

set globallog_output=’TABLE’;

#慢查询日志仅输出到文本中(即:slow_query_log_file指定的文件)

setglobal log_output=’FILE’;

#慢查询日志同时输出到文本和表中

setglobal log_output=’FILE,TABLE’;

关于慢查询日志的表中的数据个文本中的数据格式分析:

慢查询的日志记录myql.slow_log表中,格式如下:

白俊遥博客

慢查询的日志记录到hostname.log文件中,格式如下:

白俊遥博客

可以看到,不管是表还是文件,都具体记录了:是那条语句导致慢查询(sql_text),该慢查询语句的查询时间(query_time),锁表时间(Lock_time),以及扫描过的行数(rows_examined)等信息。

问题:如何查询当前慢查询的语句的个数?

在MySQL中有一个变量专门记录当前慢查询语句的个数:

输入命令:show global status like ‘%slow%’;

白俊遥博客

注意:上述所有命令,如果都是通过MySQL的shell将参数设置进去,如果重启MySQL,所有设置好的参数将失效,如果想要永久的生效,需要将配置参数写入my.cnf文件中)。

补充知识点:如何利用MySQL自带的慢查询日志分析工具mysqldumpslow分析日志?

perlmysqldumpslow –s c –t 10 slow-query.log

具体参数设置如下:

-s 表示按何种方式排序,c、t、l、r分别是按照记录次数、时间、查询时间、返回的记录数来排序,ac、at、al、ar,表示相应的倒叙;

-t 表示top的意思,后面跟着的数据表示返回前面多少条;

-g 后面可以写正则表达式匹配,大小写不敏感。

白俊遥博客

上述中的参数含义如下:

Count:414 语句出现了414次;

Time=3.51s(1454) 执行最长时间为3.51s,累计总耗费时间1454s;

Lock=0.0s(0) 等待锁最长时间为0s,累计等待锁耗费时间为0s;

Rows=2194.9(9097604) 发送给客户端最多的行数为2194.9,累计发送给客户端的函数为90976404

http://blog.csdn.net/a600423444/article/details/6854289

注意:mysqldumpslow脚本是用perl语言写的,具体mysqldumpslow的用法后期再讲)

问题:实际在学习过程中,如何得知设置的慢查询是有效的?

很简单,我们可以手动产生一条慢查询语句,比如,如果我们的慢查询log_query_time的值设置为1,则我们可以执行如下语句:

selectsleep(1);

该条语句即是慢查询语句,之后,便可以在相应的日志输出文件或表中去查看是否有该条语句。


36. 框架

开源框架(TP,CI,Laravel,Yii)

转载 2017年07月10日 13:36:47


  1. ThinkPHP简称TP,TP借鉴了Java思想,基于PHP5,充分利用了PHP5的特性,部署简单只需要一个入口文件,一起搞定,简单高效。中文文档齐全,入门超级简单。自带模板引擎,具有独特的数据验证和自动填充功能,框架更新速度比较速度。

    优点:这个框架易使用 易学 安全 对bae sae支持很好提供的工具也很强大 可以支持比较大的项目开发 易扩展 全中文文档 总的来说这款框架适合非常适合国人使用 性能 上比CI还要强一些

    缺点:配置对有些人来说有些复杂(其实是因为没有认真的读过其框架源码)文档有些滞后 有些组件未有文档说明。

  2. CodeIgniter简称CI 简单配置,上手很快,全部的配置使用PHP脚本来配置,没有使用很多太复杂的设计模式,(MVC设计模式)执行性能和代码可读性上都不错。执行效率较高,具有基本的MVC功能,快速简洁,代码量少,框架容易上手,自带了很多简单好用的library。 框架适合中小型项目,大型项目也可以,只是扩展能力差。优点:这个框架的入门槛很底 极易学 极易用 框架很小 静态化非常容易 框架易扩展 文档比较详尽

    缺点:在极易用的极小下隐藏的缺点即是不安全 功能不是太全 缺少非常多的东西 比如你想使用MongoDB你就得自己实现接口… 对数据的操作亦不是太安全 比如对update和delete操作等不够安全 暂不支持sae bae等(毕竟是欧洲)对大型项目的支持不行 小型项目会非常好。

    CI和TP的对比(http://www.jcodecraeer.com/a/phpjiaocheng/2012/0711/309.html)

  3. Laravel的设计思想是很先进的,非常适合应用各种开发模式TDD, DDD和BDD(http://blog.csdn.net/bennes/article/details/47973129 TDD DDD BDD解释 ),作为一个框架,它为你准备好了一切,composer是个php的未来,没有composer,PHP肯定要走向没落。laravel最大的特点和处优秀之就是集合了php比较新的特性,以及各种各样的设计模式,Ioc容器,依赖注入等。因此laravel是一个适合学习的框架,他和其他的框架思想有着极大的不同,这也要求你非常熟练php,基础扎实。

    优点:http://www.codeceo.com/article/why-laravel-best-php-framework.html

  4. Yii是一个基于组件的高性能的PHP的框架,用于开发大规模Web应用。Yii采用严格的OOP编写,并有着完善的库引用以及全面的教程。从MVC,DAO/ActiveRecord,widgets,caching,等级式RBAC,Web服务,到主体化,I18N和L10N,Yii提供了今日Web 2.0应用开发所需要的几乎一切功能。而且这个框架的价格也并不太高。事实上,Yii是最有效率的PHP框架之一。


37. 命名空间

PHP命名空间 namespace 及导入 use 的用法

原创 2016年11月09日 17:58:50


在PHP中,出现同名函数或是同名类是不被允许的。为防止编程人员在项目中定义的类名或函数名出现重复冲突,在PHP5.3中引入了命名空间这一概念。

1.命名空间,即将代码划分成不同空间,不同空间的类名相互独立,互不冲突。一个php文件中可以存在多个命名空间,第一个命名空间前不能有任何代码。内容空间声明后的代码便属于这个命名空间,例如:

<?php     
echo 111;       
//由于namespace前有代码而报错   
  namespace Teacher;    
   class Person{        
    function __construct(){            
     echo 'Please study!';        
      }     
      }

2.调用不同空间内类或方法需写明命名空间。例如:

<?php     
namespace Teacher;     
class Person{        
 function __construct(){             
 echo 'Please study!<br/>';        
  }     
  }     
  function Person(){        
   return 'You must stay here!';    
    };     
    namespace Student;     
    class Person{         
    function __construct(){            
     echo 'I want to play!<br/>';        
      }     
      }     
      new Person();                    
      //本空间(Student空间)     
      new \Teacher\Person();           
      //Teacher空间     
      new \Student\Person();           
      //Student空间     
      echo \Teacher\Person();          
      //Teacher空间下Person函数    
       //输出:     I want to play!     Please study!     I want to play!     You must stay here!

3.在命名空间内引入其他文件不会属于本命名空间,而属于公共空间或是文件中本身定义的命名空间。例:

首先定义一个1.php和2.php文件:

<?php     
//1.php class Person{     
function __construct(){             
echo 'I am one!<br/>';        
 } 
 }
<?php 
namespace Newer;
 require_once './1.php';
  new Person();      
 //报错,找不到Person; 
 new \Person();     
 //输出 I am tow!;
<?php
     //2.php namespace Two class Person{     
     function __construct(){            
      echo 'I am tow!<br/>';        
       } 
       }
<?php
 namespace New; 
 require_once './2.php';
  new Person();      
  //报错,(当前空间)找不到
  Person; new \Person();     
  //报错,(公共空间)找不到
  Person; new \Two\Person();  
  //输出 I am tow!;

4.下面我们来看use的使用方法:(use以后引用可简写)

    namespace School\Parents;     
    class Man{       
      function __construct(){            
       echo 'Listen to teachers!<br/>';         
       }     
       }     
       namespace School\Teacher;     
       class Person{         
       function __construct(){             
       echo 'Please study!<br/>';        
        }     
        }     
        namespace School\Student;     
        class Person{    
             function __construct(){             
             echo 'I want to play!<br/>';         
             }    
              }    
               new Person();                  
                //输出I want to play!     
                new \School\Teacher\Person();   
                //输出Please study!     
                new Teacher\Person();          
                 //报错     ----------     
                 use School\Teacher;       
                 new Teacher\Person();           
                 //输出Please study!         ----------    
                  use School\Teacher as Tc;      
                   new Tc\Person();          
                    //输出Please study!       ----------     
                    use \School\Teacher\Person;      
                    new Person();           
                    //报错     ----------    
                     use \School\Parent\Man;      
                     new Man();           
                     //报错

相关推荐:

php的面试总结

2017php经典面试题

以上就是php面试的总结的详细内容,更多请关注php中文网其它相关文章!

php中文网最新课程二维码

声明:本文原创发布php中文网,转载请注明出处,感谢您的尊重!如有疑问,请联系admin@php.cn处理

  • 相关标签:php 总结 面试
  • 相关文章

    相关视频


    网友评论

    文明上网理性发言,请遵守 新闻评论服务协议

    我要评论
  • 专题推荐

    作者信息
    推荐视频教程
  • javascript初级视频教程javascript初级视频教程
  • jquery 基础视频教程jquery 基础视频教程
  • 视频教程分类