霜天部落 | 关注LAMP高性能、高并发架构的设计与研究

Innodb中的事务隔离级别和锁的关系

前言:

我们都知道事务的几种性质,数据库为了维护这些性质,尤其是一致性和隔离性,一般使用加锁这种方式。同时数据库又是个高并发的应用,同一时间会有大量的并发访问,如果加锁过度,会极大的降低并发处理能力。所以对于加锁的处理,可以说就是数据库对于事务处理的精髓所在。这里通过分析MySQL中InnoDB引擎的加锁机制,来抛砖引玉,让读者更好的理解,在事务处理中数据库到底做了什么。

#一次封锁or两段锁?
因为有大量的并发访问,为了预防死锁,一般应用中推荐使用一次封锁法,就是在方法的开始阶段,已经预先知道会用到哪些数据,然后全部锁住,在方法运行之后,再全部解锁。这种方式可以有效的避免循环死锁,但在数据库中却不适用,因为在事务开始阶段,数据库并不知道会用到哪些数据。
数据库遵循的是两段锁协议,将事务分成两个阶段,加锁阶段和解锁阶段(所以叫两段锁)

  • 加锁阶段:在该阶段可以进行加锁操作。在对任何数据进行读操作之前要申请并获得S锁(共享锁,其它事务可以继续加共享锁,但不能加排它锁),在进行写操作之前要申请并获得X锁(排它锁,其它事务不能再获得任何锁)。加锁不成功,则事务进入等待状态,直到加锁成功才继续执行。
  • 解锁阶段:当事务释放了一个封锁以后,事务进入解锁阶段,在该阶段只能进行解锁操作不能再进行加锁操作。
事务 加锁/解锁处理
begin;  
insert into test ….. 加insert对应的锁
update test set… 加update对应的锁
delete from test …. 加delete对应的锁
commit; 事务提交时,同时释放insert、update、delete对应的锁

这种方式虽然无法避免死锁,但是两段锁协议可以保证事务的并发调度是串行化(串行化很重要,尤其是在数据恢复和备份的时候)的。 Read more ›

Facade模式

一 意图

  为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

二 动机

  将一个系统划分成为若干个子系统有利于降低系统的复杂性。一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小。

达到该目标的途径之一是就是引入一个外观(Facade)对象,它为子系统中较一般的设施提供了一个单一而简单的界面。

将各个子系统整合起来作为Facade,提供给客户端使用。

  看下面这样一个系统:

Facade模式 

 

  转变成:

Facade模式    

三 适用性及其结构

  1. 当你要为一个复杂子系统提供一个简单接口时。
  2. 客户程序与抽象类的实现部分之间存在着很大的依赖性。
  3. 当你需要构建一个层次结构的子系统时,使用Facade模式定义子系统中每层的入口点。仅通过facade进行通讯。

结构:

Facade模式      

Facede

  知道哪些子系统类负责处理请求。

  将客户的请求代理给适当的子系统对象。

Subsystem classes

  实现子系统的功能。

  处理由Facade对象指派的任务。

  没有facade的任何相关信息;即没有指向facade的指针。

 

  客户程序通过发送请求给Facade的方式与子系统通讯, Facade将这些消息转发给适当的子系统对象。

尽管是子系统中的有关对象在做实际工作,但Facade模式本身也必须将它的接口转换成子系统的接口。

  Facade模式有助于建立层次结构系统,也有助于对对象之间的依赖关系分层

  Facade模式可以消除复杂的循环依赖关系降低客户-子系统之间的耦合度

  使用Facade的客户程序不需要直接访问子系统对象。

谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词

这是一篇程序员写给程序员的趣味读物。所谓趣味是指可以比较轻松地了解一些原来不清楚的概念,增进知识,类似于打RPG游戏的升级。整理这篇文章的动机是两个问题:

问题一:

使用Windows记事本的“另存为”,可以在GBK、Unicode、Unicode big endian和UTF-8这几种编码方式间相互转换。同样是txt文件,Windows是怎样识别编码方式的呢?

我很早前就发现Unicode、Unicode big endian和UTF-8编码的txt文件的开头会多出几个字节,分别是FF、FE(Unicode),FE、FF(Unicode big endian),EF、BB、BF(UTF-8)。但这些标记是基于什么标准呢?

问题二:
最近在网上看到一个ConvertUTF.c,实现了UTF-32、UTF-16和UTF-8这三种编码方式的相互转换。对于Unicode(UCS2)、GBK、UTF-8这些编码方式,我原来就了解。但这个程序让我有些糊涂,想不起来UTF-16和UCS2有什么关系。

查了查相关资料,总算将这些问题弄清楚了,顺带也了解了一些Unicode的细节。写成一篇文章,送给有过类似疑问的朋友。本文在写作时尽量做到通俗易懂,但要求读者知道什么是字节,什么是十六进制。 Read more ›

InnoDB事务和锁

InnoDB支持事务,MyISAM不支持事务.

一.事务的基本特性

ACID特性

1.原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。

2.一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。

3. 隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。

4.持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。

 

二.并发事务带来的问题

1.更新丢失

假设table中的price在更新前为0

如A用户开始一个事务:

BEGIN;

SELECT price FROM table WHERE id=1;

#开始更新

UPDATE table SET  price = price + 1  WHERE id=1;

COMMIT;

 

B用户在A用户未提交事务时,同样更新:

BEGIN;

SELECT price FROM table WHERE id=1; #此处的price应该是1,但是A用户未提交事务,所以还是0

#开始更新

UPDATE table SET  price = price+2  WHERE id=1;

COMMIT;

 

最终price=2,但实际上应该是3,这就是更新丢失

 

2.脏读

一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做”脏读”.上面B用户在读取price时,A用户未提交事务,B用户读到就是脏数据.

 

3.不可重复读

一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。

 

4.幻读

一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。

 

三.事务的隔离级别

在上面讲到的并发事务处理带来的问题中,“更新丢失”通常是应该完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。

 

“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。数据库实现事务隔离的方式,基本上可分为以下两种。

 

1.一种是在读取数据前,对其加锁,阻止其他事务对数据进行修改。

2.另一种是不用加任何锁,通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取。从用户的角度来看,好象是数据库可以提供同一数据的多个版本,因此,这种技术叫做数据多版本并发控制(MultiVersion Concurrency Control,简称MVCC或MCC),也经常称为多版本数据库。

 

3.mysql的四种隔离级别

 自动草稿

 

Ps:

a.Repeatable read是默认隔离级别.

b. 查看当前会话隔离级别:select @@tx_isolation;

c.查看系统隔离级别: select @@global.tx_isolation;

 

 

四.InnoDB锁争用:

mysql> show global status like ‘innodb_row_lock%’;

 

Innodb_row_lock_current_waits 0

Innodb_row_lock_time 530

Innodb_row_lock_time_avg 106

Innodb_row_lock_time_max 327

Innodb_row_lock_waits 5

 

如果发现锁争用比较严重,如InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比较高,还可以通过设置InnoDB Monitors来进一步观察发生锁冲突的表、数据行等,并分析锁争用的原因。

Ps:InnoDB锁超时时间由变量innodb_lock_wait_timeout控制,默认是50s

 

五.InnoDB的行锁模式及加锁方法

(一).行锁模式

1.共享锁(S锁):

对同一行数据都可以共享一把锁,但是没有获得锁的事务只可以读,不可以修改

2.排它锁(X锁)

对同一行数据,获得该锁的事务可读可写,未获得锁的事务不可读也不可写.

 

另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。

3.意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。

4.意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

自动草稿

 

 

如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。

 

意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁。

 

(二).加锁方法:

Ps:

  1. select语句默认不会加任何锁类型
  2. update,delete,insert都会自动给涉及到的数据加上排他锁

 

 1.      共享锁

SELECT … LOCK IN SHARE MODE

 

主要用在需要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作. 但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT… FOR UPDATE方式获得排他锁。

对于加了共享锁的数据行,其他事务可以加共享锁或不加锁,但无法加排它锁.

自动草稿

 

 2.      排它锁

SELECT  …  FOR UPDATE

 自动草稿

 

 

六.InnoDB的行锁实现方式

InnoDB行锁是通过给索引上的索引项加锁来实现的, InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!

 

在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。

 

1.在不通过索引条件查询的时候,InnoDB使用的是表锁,而不是行锁

 自动草稿

 

在name列上加锁后,上述情况将不存在

 

2. MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁等待的

 自动草稿

 

3. 当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。

 自动草稿

 

4.即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。

 

如下示例(在mysql5.1版本上,5.5以上版本不存在下列问题):

name列有索引,name字段类型为varchar.

EXPLAIN SELECT * FROM  `test` WHERE  `name` =1;#执行全表扫描

 自动草稿

 

EXPLAIN SELECT * FROM `test` WHERE `name`=’1′;#执行索引扫描

 自动草稿

 

七.间隙锁

当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。

 

举例来说,假如emp表中只有101条记录,其empid的值分别是 1,2,…,100,101,下面的SQL:

 

Select * from  emp where empid > 100 for update;

 

是一个范围条件的检索,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。

 

InnoDB使用间隙锁的目的,一方面是为了防止幻读,以满足相关隔离级别的要求,对于上面的例子,要是不使用间隙锁,如果其他事务插入了empid大于100的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;另外一方面,是为了满足其恢复和复制的需要。有关其恢复和复制对锁机制的影响,以及不同隔离级别下InnoDB使用间隙锁的情况,在后续的会做进一步介绍。

 

很显然,在使用范围条件检索并锁定记录时,InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待。因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。

 

还要特别说明的是,InnoDB除了通过范围条件加锁时使用间隙锁外,如果使用相等条件请求给一个不存在的记录加锁,InnoDB也会使用间隙锁!

 自动草稿

 PS:

在多列条件查找时,

如SELECT * FROM table WHERE col1=2 AND col2>1000 FOR UPDATE;在col1和col2上均有索引

这个时候

INSERT INTO table(col1,col2) VALUES(3,8);可以执行

INSERT INTO table(col1,col2) VALUES(2,8);不可以执行

InnoDB根据索引只是锁定了需要锁定的间隙锁.

 

尽量少用不确定的SQL语句如

insert  into target_tab select * from source_tab where …

create  table new_tab …select … From  source_tab where …(CTAS)

通过使用“select * from source_tab … Into outfile”和“load data infile …”语句组合来间接实现,采用这种方式MySQL不会给source_tab加锁

 

七.InnoDB的表锁

注意点:

1.LOCK TABLES tb_name WRITE;//当前会话对表tb_name可读可写,其余会话对表tb_name不可读不可写
2.LOCK TABLES tb_name READ;//当前会话对表tb_name可读不可写,其余会话对表tb_name可读不可写
3.你需要一次锁定更新的表
LOCK TABLES tb1_name WRITE;
在锁定过程中,你可以读tbl2_name,当你需要更新tbl2_name,你将得到一个表无法锁定的错误
4.innodb的表锁,开始事务时会自动释放表锁,所以begin;或set autocommit=0;等命令应该在lock tables的前面.

在InnoDB下,使用表锁要注意以下两点。

 

1.使用LOCK TABLES虽然可以给InnoDB加表级锁,但必须说明的是,表锁不是由InnoDB存储引擎层管理的,而是由其上一层──MySQL Server负责的,仅当autocommit=0、innodb_table_locks=1(默认设置)时,InnoDB层才能知道MySQL加的表锁,MySQL Server也才能感知InnoDB加的行锁,这种情况下,InnoDB才能自动识别涉及表级锁的死锁;否则,InnoDB将无法自动检测并处理这种死锁。有关死锁,下一小节还会继续讨论。

 

2.在用LOCK TABLES对InnoDB表加锁时要注意,要将AUTOCOMMIT设为0,否则MySQL不会给表加锁;事务结束前,不要用UNLOCK TABLES释放表锁,因为UNLOCK TABLES会隐含地提交事务;COMMIT或ROLLBACK并不能释放用LOCK TABLES加的表级锁,必须用UNLOCK TABLES释放表锁。正确的方式见如下语句:

 

例如,如果需要写表t1并从表t读,可以按如下做:

 

SET AUTOCOMMIT=0;

 

LOCK TABLES t1 WRITE, t2 READ, …;

 

[do something with tables t1 and t2 here];

 

COMMIT;

 

UNLOCK TABLES;

 

八.在应用中避免死锁的方法

 

发生死锁后,InnoDB一般都能自动检测到,并使一个事务释放锁并回退,另一个事务获得锁,继续完成事务。但在涉及外部锁,或涉及表锁的情况下,InnoDB并不能完全自动检测到死锁,这需要通过设置锁等待超时参数innodb_lock_wait_timeout来解决。需要说明的是,这个参数并不是只用来解决死锁问题,在并发访问比较高的情况下,如果大量事务因无法立即获得所需的锁而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖跨数据库。我们通过设置合适的锁等待超时阈值,可以避免这种情况发生。

 

1. 在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会。

 自动草稿

 

 

2. 在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低出现死锁的可能。

 

3. 在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁,更新时再申请排他锁,因为当用户申请排他锁时,其他事务可能又已经获得了相同记录的共享锁,从而造成锁冲突,甚至死锁。

 

4. 前面讲过,在REPEATABLE-READ隔离级别下,如果两个线程同时对相同条件记录用SELECT…FOR UPDATE加排他锁,在没有符合该条件记录情况下,两个线程都会加锁成功。程序发现记录尚不存在,就试图插入一条新记录,如果两个线程都这么做,就会出现死锁。这种情况下,将隔离级别改成READ COMMITTED,就可避免问题

5. 当隔离级别为READ COMMITTED时,如果两个线程都先执行SELECT…FOR UPDATE,判断是否存在符合条件的记录,如果没有,就插入记录。此时,只有一个线程能插入成功,另一个线程会出现锁等待,当第1个线程提交后,第2个线程会因主键重复出错,但虽然这个线程出错了,却会获得一个排他锁!这时如果有第3个线程又来申请排他锁,也会出现死锁。对于这种情况,可以直接做插入操作,然后再捕获主键重复异常,或者在遇到主键重错误时,总是执行ROLLBACK释放获得的排他锁

压测工具http_load与apache ab

http_load 简介

http_load以并行复用的方式运行,用以测试Web服务器的吞吐量与负载。但是它不同于大多数压力测试工具,其可以以一个单一的进程运行,这样就不会把客户机搞死,还可以测试HTTPS类的网站请求。关于http_load的详细信息参见这里,点击这里下载。

安装

# tar xzvf http_load-09Mar2016.tar.gz
# cd http_load-09Mar2016
# make && make install

测试

http_load的命令参数比较简单,直接在命令行下执行http_load就能看到,具体如下:

-parallel简写-p:并发的用户进程数。
-fetches简写-f:总计的访问次数
-rate简写-r:每秒的访问频率
-seconds简写-s:总计的访问时间

http_load的命令格式也比较简单,各参数可以自由组合,常用格式如下:

$ http_load -p 并发访问进程数 -f 访问总数 需要访问的URL文件
$ http_load -r 每秒访问频率 -s 访问时间 需要访问的URL文件

通常:参数pf一起使用,参数rs一起使用。

这里以某网页测试为例说明之:

$ vi test.url
http://www.xxxxx.com

当然上面存放请求URL的文件中,可以存放多个URL,每行一个。

$ http_load -p 5 -s 300 test.url
11694 fetches, 5 max parallel, 5.6872e+08 bytes, in 300 seconds
48633.5 mean bytes/connection
38.98 fetches/sec, 1.89573e+06 bytes/sec
msecs/connect: 10.476 mean, 9008.81 max, 5.002 min
msecs/first-response: 36.5928 mean, 5383.94 max, 16.614 min
7 timeouts
10428 bad byte counts
HTTP response codes:
code 200 -- 7552

上面命令表示:5个并发进程,持续300秒请求某网页。

#http_load -r 5 -s 300 test.url
1499 fetches, 5 max parallel, 9.49031e+07 bytes, in 300.017 seconds
63310.9 mean bytes/connection
4.99638 fetches/sec, 316326 bytes/sec
msecs/connect: 21.5961 mean, 3020.17 max, 16.782 min
msecs/first-response: 36.6554 mean, 61.907 max, 17.919 min
1261 bad byte counts
HTTP response codes:
code 200 -- 1261

上面命令表示:每秒频率为5条请求,持续300秒请求某网页。
结果分析如下:
(1)1499 fetches, 5 max parallel, 9.49031e+07 bytes, in 300.017 seconds
说明在上面的测试中运行了1499个请求,最大的并发进程数是5,总计传输的数据是9.49031e+07 bytes,运行的时间是300.017秒
(2)63310.9 mean bytes/connection
说明每个连接平均传输的数据量63310.9/1499=42.24
(3)4.99638 fetches/sec, 316326 bytes/sec
说明每秒的响应请求为4.99638,每秒传递的数据为316326 bytes/sec
(4)msecs/connect: 21.5961 mean, 3020.17 max, 16.782 min
说明每个连接的平均响应时间是21.5961 msecs,最大的响应时间3020.17 msecs,最小的响应时间16.782 msecs
(5)HTTP response codes: code 200 — 1261
说明打开响应页面的类型,如果403的类型过多,那可能要注意是否系统遇到了瓶颈
特殊说明:测试结果中主要的指标是fetches/sec和msecs/connect,即服务器每秒能够响应的请求次数和每个连接的平均响应时间。当然仅有这两个指标并不能完成对性能的分析,这两个指标主要反映的是QPS和RT。此外,还需要对服务器的CPU(idle和load)、Mem进行分析,才能得出结论。
需要注意的是:http_load请求方式默认为GET方式,不支持POST方式。

apache ab 简介

ab即ApacheBench,是apache自带的一款功能强大的测试工具,安装了apache一般就自带了,即httpd(在bin目录下)。ab可以同时模拟多个并发请求,专门用于HTTP Server的benchmark testing。

ab命令格式如下:
ab [options] [http[s]://]hostname[:port]/path
ab的参数比较多,直接在命令行下执行ab就能看到,常用参数如下:
-n在测试会话中所执行的请求个数。默认时,仅执行一个请求。
-c一次产生的请求个数。默认是一次一个。
-t测试所进行的最大秒数。其内部隐含值是-n 50000,它可以使对服务器的测试限制在一个固定的总时间以内。默认时,没有时间限制。
-p包含了需要POST的数据的文件。
-P对一个中转代理提供BASIC认证信任。用户名和密码由一个:隔开,并以base64编码形式发送。无论服务器是否需要(即, 是否发送了401认证需求代码),此字符串都会被发送。
-T POST数据所使用的Content-type头信息。
-v设置显示信息的详细程度-4或更大值会显示头信息,3或更大值可以显示响应代码(404,200等),2或更大值可以显示警告和其他信息。
-V显示版本号并退出。
-w以HTML表的格式输出结果。默认时,它是白色背景的两列宽度的一张表。
-i执行HEAD请求,而不是GET。
-x设置<table>属性的字符串。
-X对请求使用代理服务器。
-y设置<tr>属性的字符串。
-z设置<td>属性的字符串。
-C对请求附加一个Cookie:行。其典型形式是name=value的一个参数对,此参数可以重复。
-H对请求附加额外的头信息。此参数的典型形式是一个有效的头信息行,其中包含了以冒号分隔的字段和值的对(如,”Accept-Encoding:zip/zop;8bit”)。
-A对服务器提供BASIC认证信任。用户名和密码由一个:隔开,并以base64编码形式发送。无论服务器是否需要(即,是否发送了401认证需求代码),此字符串都会被发送。
-h显示使用方法。
-d不显示”percentage served within XX [ms] table”的消息(为以前的版本提供支持)。
-e产生一个以逗号分隔的(CSV)文件,其中包含了处理每个相应百分比的请求所需要(从1%到100%)的相应百分比的(以微妙为单位)时间。由于这种格式已经“二进制化”,所以比’gnuplot’格式更有用。
-g把所有测试结果写入一个’gnuplot’或者TSV(以Tab分隔的)文件。此文件可以方便地导入到Gnuplot,IDL,Mathematica,Igor甚至Excel中。其中的第一行为标题。
-i执行HEAD请求,而不是GET。
-k启用HTTP KeepAlive功能,即在一个HTTP会话中执行多个请求。默认时,不启用KeepAlive功能。
-q如果处理的请求数大于150,ab每处理大约10%或者100个请求时,会在stderr输出一个进度计数。此-q标记可以抑制这些信息。
需要注意的是:ab每次只能测试一个URL,适合做重复压力测试,支持POST方式。

测试

(1)如果要测试POST请求,可以使用如下命令:
$ ab -n 1000000 -c 100 -p data.txt -T 'application/x-protobuf'  'http://www.xxxxx.com/'

上面命令表示模拟100个并发用户,发送1000000个POST请求到http://www.xxxxx.com/,POST数据从文件data.txt读取,Content-type为application/x-protobuf。

$ ab -t 300 -c 100 -p data.txt -T 'application/x-protobuf' 'http://www.xxxxx.com/'

上面命令表示模拟100个并发用户,持续300秒发送POST请求到http://www.xxxxx.com/,POST数据从文件data.txt读取,Content-type为application/x-protobuf。

需要注意的是:如果在发送请求完后,出现如下错误:
apr_poll: The timeout specified has expired (70007)

使用-k参数指定发送keep-alive指令到服务器端,可以解决上面问题:

#ab -t 300 -c 100 -k -p data.txt -T 'application/x-protobuf' 'http://www.xxxxx.com/'

(2)如果要测试GET请求,可以使用如下命令(这里以搜狗首页测试为例说明之):

$ ab -t 300 -c 5 -k http://www.sogou.com

执行上面命令会出现如下错误:

ab: invalid URL
Usage: ab [options] [http[s]://]hostname[:port]/path

上面错误的意思是URL后面要加/path,从提示也能看出来,可以改成如下即可:

$ ab -t 300 -c 5 -k http://www.sogou.com/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking www.sogou.com (be patient)
Completed 5000 requests
Finished 5211 requests

Server Software:        nginx                   #被测试服务名称
Server Hostname:        www.sogou.com           #请求地址
Server Port:            80                      #请求端口号

Document Path:          /                       #请求页面
Document Length:        15785 bytes             #页面长度

Concurrency Level:      5                       #并发数
Time taken for tests:   300.011 seconds         #整个测试持续的时间
Complete requests:      5211                    #完成的请求数
Failed requests:        0                       #失败的请求数
Write errors:           0
Keep-Alive requests:    0
Total transferred:      86136965 bytes          #总共传输字节数,包含http的头信息等
HTML transferred:       82258493 bytes          #html字节数,实际的页面传递字节数
Requests per second:    17.37 [#/sec] (mean)    #每秒请求数(这是一个非常重要的指标,即服务器的吞吐量)
Time per request:       287.863 [ms] (mean)     #用户平均请求等待时间(这也是一个非常重要的指标,即请求延迟)
Time per request:       57.573 [ms] (mean, across all concurrent requests)    #服务器平均处理时间,即服务器吞吐量的倒数
Transfer rate:          280.38 [Kbytes/sec] received    #每秒网络上的流量(可以帮助排除是否存在网络流量过大导致响应时间延长的问题)

Connection Times (ms)
min     mean[+/-sd]  median     max
Connect:           28       42 215.8     34      9034
Processing:        86      233 2009.8   108     93465
Waiting:           29       71 547.5     37     21034
Total:            114      275 2031.9   143     93493

Percentage of the requests served within a certain time (ms)
50%    143        #50%的请求在143ms内返回
66%    148        #60%的请求在148ms内返回
75%    154
80%    160
90%    179
95%    195
98%    469
99%   3158
100%  93493 (longest request)    #最大响应时间小于93493ms

特殊说明:测试结果中主要的指标是Requests per second和Time per request(第一个),即每秒请求数和用户平均请求等待时间,这两个指标主要反映的是QPS和RT。

PHP 命名空间 解惑

1. PHP中的命名空间是什么?

官方解释在此: 命名空间概述

命名空间用一句话说,就是:把 类、函数、变量 等放到逻辑子文件夹中去,以避免命名冲突。

注:命名空间跟实际代码文件在文件系统中的路径没有任何关系,如果你感到迷茫,可以看看本文最后一节。 Read more ›