NoSQL对REST的影响,无状态,扩展性

infoq文章 http://www.infoq.com/news/2011/10/nosql-rest 谈到noSql可能对REST产生的影响。

REST要求状态要么被放入资源状态中,要么保存在客户端上。而这个资源就可以使用NoSql来存储,像Redis。

无状态通信最直接的理由就是可伸缩性—— 如果服务器需要保持客户端状态,那么大量的客户端交互会严重影响服务器的内存可用空间(footprint)。

 

参见:

http://www.infoq.com/cn/articles/rest-introduction  深入浅出REST

http://a-kuei.iteye.com/blog/706836  对REST中无状态(stateless)的理解

Continue reading NoSQL对REST的影响,无状态,扩展性

REST 将替代 SOAP?

infoq上看到这篇文章http://www.infoq.com/articles/rest-soap,其统计数据表明REST(在SOA中)越来越占主导优势。并在文章结尾为mule打了个广告,原来作者是MuleSoft的创始人。文章评论很多,打头的把emule和mule混淆,认为eMule(应该是mule)仍然解决不了根本的集成问题,后面的拿这个开涮!

写这篇文章时我对REST早闻其名,最近也用过,但是自己还觉得没怎么透彻的理解。不过由于早年深受webservice欺骗和xml的虐待,对soap一直深恶痛绝。使用rest基于json格式的服务后,感觉如此之美。

大执概括一下这篇文章:

Web API越来越火从2005的105个到2011的5000以上。

REST风格从2008年开始急剧上身(60%->74%),而SOAP上升缓慢(25%->15%)。

REST的上升要得益于客户端访问的便捷,json格式要比xml格式易懂,易用,简洁。目前大部分api都提供或只提供json格式。

后面就开始说各Api集成问题,然后提出使用mule解决。

Continue reading REST 将替代 SOAP?

冒烟测试 smoke test

没听过这个词的认为说它的人很牛X,了解了后会认为是装X,原来就是这么easy。

冒烟测试的对象是每一个新编译的需要正式测试的软件版本,目的是确认软件基本功能正常,可以进行后续的正式测试工作。冒烟测试的执行者是版本编译人员。

上面一句话很经典,冒烟测试:

  • 费时很短,吸根烟的功夫。
  • 是正式测试前的检验步骤,这个都没通过那就不能进行测试流程。
  • 关注改动,由编译人员或是修改者执行。

Continue reading 冒烟测试 smoke test

三级封锁协议两段锁以及隔离级别

并发控制的主要方法是封锁(Locking)。就是要用正确的方式调度并发操作,使一个用户的事务在执行过程中不受其他事务的干扰,从而避免造成数据的不一致性。
封锁是使事务对它要操作的数据有一定的控制能力。封锁通常具有3个环节:第一个环节是申请加锁,即事务在操作前要对它将使用的数据提出加锁申请;第二个环节是获得锁,即当条件成熟时,系统允许事务对数据进行加锁,从而事务获得数据的控制权;第三个环节是释放锁,即完成操作后事务放弃数据的控制权。
基本的封锁类型有以下两种: 锁是事务实现并发控制隔离级别的实现方法
1.排它锁(Exclusive Locks,简称X锁)
排它锁也称为独占锁或写锁。一旦事务T对数据对象A加上排它锁(X锁),则只允许T读取和修改A,其他任何事务既不能读取和修改A,也不能再对A加任何类型的锁,直到T释放A上的锁为止。
2.共享锁(Share Locks,简称S锁)
共享锁又称读锁。如果事务T对数据对象A加上共享锁(S锁),其他事务只能再对A加S锁,不能加X锁,直到事务T释放A上的S锁为止。
在对数据进行加锁时,另外需要约定并执行一些规则和协议,其中包括何时申请锁,保持锁的时间以及何时释放等,这些规则就称为封锁协议(Locking Protocol){谁定义的?-确实找不出来,也许就是理论基础},其总共分为以下三级:
(1)一级封锁协议。一级封锁协议是事务T在修改数据之前必须先对其加X锁,直到事务结束才释放。 防止丢失更新。
(2)二级封锁协议。二级封锁协议是事务T对要修改数据必须先加X锁,直到事务结束才释放X锁;对要读取的数据必须先加S锁,读完后即可释放S锁。 防止丢失更新和脏读

 T1T2 
 Slock A  
 读A=20
unlcok A 事务还没完成,但是读完了马上释放
  
  Xlock A 
  A = 60 
  Unclock A
事务提交
 
 SlockA  
 A=60
不可重复读
  
 T1事务造成了不可重复读  

(3)三级封锁协议。三级封锁协议是事务T在读取数据之前必须先对其加S锁,在要修改数据之前必须先对其加X锁,直到事务结束后才释放所有锁。 防止丢失更新,脏读,不可重复读

相对于二级锁,Slock的范围加长了,开销自然大了。

执行了封锁协议之后,就可以克服数据库操作中的数据不一致所引起的问题。

参看图4。

Snap2

从图4的情况我们看到事务T1在执行过程中独自占用并加X锁,直到处理完之后再释放锁,T2虽然也需要使用,但是在封锁协议的约束之下,T2所要求的X 锁就被拒绝,因此必须处于等待状态,直到T1释放之后,T2才获得使用的权利,这样就不会发生使用冲突,避免了数据的丢失。这里我们看到,此处实际上是执行了一级封锁协议。

下面我们看图5。

Snap3

通过图5,能够清楚的看到,由于施行了封锁协议,使事务T1使用了共享锁占用A,B两块数据,这样T2需要加上的X锁就无法实现,(如果是S锁,虽然可以加上,但也不能够随便修改数据,只是读取一下数据。)当T1释放锁之后,T2就可以得到并使用锁了,这样读取的数据B仍然还是100,不影响A+B的结果,这就是可重复读取。因此我们看到,其实这里用的就是三级封锁协议

 

参看图6,事务T1在对数据C修改之前,先加上了X锁,修改后写回数据库,这时T2请求在C上添加S锁,因为T1加了X锁,T2只好等待,当T1因为某种原因撤销了修改的数据后,C就恢复了原来的数据100,等T1释放 X锁后T2获得C上的S锁,读到的还是C=100,因此避免了读出“脏”数据。这里使用的其实就是二级封锁协议。

Snap1

 

两阶段封锁协议

对锁机制,保证事务可串行性的最常用协议是两阶段封锁协议。该协议要求每个事务分两个阶段提出加锁和解锁申请:

(1)增长阶段。事务可以获得锁,但不能释放锁。

(2)缩减阶段。事务可以释放锁,但不能获得锁。

一开始,事务处于增长阶段,事务根据需要获得锁。一旦该事务释放了锁,它就进入缩减阶段,不能再发出加锁请求。

两阶段封锁协议实现了事务集的串行化调度,但同时,一个事务的失败可能会引起一连串事务的回滚。为避免这种情况的发生,我们需要进一步加强对两阶段封锁协议的控制,这就是:严格两阶段封锁协议和强两阶段封锁协议。

严格两阶段封锁协议除了要求封锁是两阶段之外,还要求事务持有的所有排它锁必须在事务提交之后方可释放。这个要求保证未提交事务所写的任何数据,在该事务提交之前均以排它锁封锁,防止其他事务读取这些数据。

强两阶段封锁协议,要求事务提交之前不得释放任何锁。使用锁机制的数据库系统,要么使用严格两阶段封锁协议,要么使用强两阶段封锁协议。

两阶段封锁协议并不保证不会发生死锁,数据库系统必须采取其他的措施,预防和解决死锁问题。

 

数据库并发操作存在的异常情况:

1. 更新丢失(Lost update):事务 T1 读取数据 A,然后对 A 进行运算修改,最后写回数据库。如果在 T1 读取和写回数据库之间,有其他事务修改了 A 值,就造成了丢失更新,因为 T1 是在旧的数据上进行的运算。

第一类丢失更新(lost update): 在完全未隔离事务的情况下,两个事物更新同一条数据资源,某一事物异常终止,回滚造成第一个完成的更新也同时丢失。

第二类丢失更新(second lost updates):是不可重复读的特殊情况,如果两个事务都读取同一行,然后两个都进行写操作,并提交,第一个事务所做的改变就会丢失。

说他是第二类更新,又说他其实是不可重复读的特例,实际上在隔离级别上又和不可重复读相同,大部分分类根本不提这个第二类丢失更新。

2. 脏读取(Dirty Reads):一个事务读取了另一个未提交的并行事务写的数据。事务 T1 修改了数据 A,然后事务 T2 读取了数据 A,然后事务 T1 回滚了事务。则T2读的是错误的数据。

3. 不可重复读取(Non-repeatable Reads):一个事务对同一行数据重复读取两次但是却得到了不同结果。例如在两次读取中途有另外一个事务对该行数据进行了修改并提交

4. 幻读(Phantom Reads):也称为幻像(幻影,虚读)。事务在操作过程中进行两次查询,第二次查询结果包含了第一次查询中未出现的数据(这里并不要求两次查询SQL语句相同)这是因为在两次查询过程中有另外一个事务插入数据造成的。

幻读和不可重复读可认为是同类的,但是在控制上有区别。要控制不可重复读只需要控制记录的修改,而要控制幻读则要控制记录的添加和删除。所以,隔离级别可重复读取不能禁止幻读,而串行则可以。

 

事务隔离级别:

为了避免上面出现几种情况在标准SQL规范中定义了4个事务隔离级别,不同隔离级别对事务处理不同 。

1. 未授权读取(Read Uncommitted):也称未提交读。防止更新丢失(这不对应一级锁吗),如果一个事务已经开始写数据则另外一个数据则不允许同时进行写操作但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。事务隔离的最低级别,仅可保证不读取物理损坏的数据。与READ COMMITTED 隔离级相反,它允许读取已经被其它用户修改但尚未提交确定的数据。

2. 授权读取(Read Committed):也称提交读。1之上防止脏读取(这不对应二级锁吗)。这可以通过“瞬间共享读锁”和“排他写锁”实现,读取数据的事务允许其他事务继续访问该行数据,但是未提交写事务将会禁止其他事务访问该行。SQL Server 默认的级别。在此隔离级下,SELECT 命令不会返回尚未提交(Committed) 的数据,也不能返回脏数据。

3. 可重复读取(Repeatable Read):2之上防止不可重复读取(这不对应三级锁吗)。但是有时可能出现幻影数据,这可以通过“共享读锁”和“排他写锁”实现,读取数据事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。在此隔离级下,用SELECT 命令读取的数据在整个命令执行过程中不会被更改。此选项会影响系统的效能,非必要情况最好不用此隔离级。

三级封锁协议并不能阻止幻读,修改的不能再被读取,但是新增(删除)的记录数可以统计。

4. 串行(Serializable):也称可串行读(这不对应两段锁吗)。提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过 “行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作事务访问到。事务隔离的最高级别,事务之间完全隔离。如果事务在可串行读隔离级别上运行,则可以保证任何并发重叠事务均是串行的。

 

 LU丢失更新DR脏读NRR非重复读SLU二类丢失更新PR幻像读
未提交读 RUYYYYY
提交读 RCNNYYY
可重复读 RRNNNNY
串行读 SNNNNN

ORACLE的默认事务级别:READ COMMITTED

ORACLE支持的事务隔离级别:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SET TRANSACTION ISOLATION LEVEL READ ONLY;

少数数据库默认的隔离级别为Repeatable Read, 如MySQL InnoDB存储引擎
即使是最低的级别,也不会出现 第一类 丢失 更新问题 .

查看InnoDB系统级别的事务隔离级别:

mysql> SELECT @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ       |
+-----------------------+
1 row in set (0.00 sec)

查看InnoDB会话级别的事务隔离级别:

mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.00 sec)

修改事务隔离级别:

mysql> set global transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)

InnoDB的可重复读隔离级别和其他数据库的可重复读是有区别的,不会造成幻象读(phantom read)。

 

Oracle对隔离级别的支持:

Oracle 明确地支持READ COMMITTED(读已提交)和SERIALIZABLE(可串行化)隔离级别,因为标准中定义了这两种隔离级别。不过,这还不是全部。SQL标准试图 建立多种隔离级别,从而允许在各个级别上完成的查询有不同程度的一致性。REPEATABLE READ(可重复读)也是SQL标准定义的一个隔离级别,可以保证由查询得到读一致的(read-consistent)结果。在SQL标准的定义 中,READ COMMITTED不能提供一致的结果,而READ UNCOMMITTED(读未提交)级别用来得到非阻塞读(non-blocking read)。

不 过,在Oracle中,READ COMMITTED则有得到读一致查询所需的所有属性。在其他数据库中,READ COMMITTED查询可能(而且将会)返回数据库中根本不存在的答案(即实际上任何时间点上都没有这样的结果)。另外,Oracle还秉承了READ UNCOMMITTED的“精神”。(有些数据库)提供脏读的目的是为了支持非阻塞读,也就是说,查询不会被同一个数据的更新所阻塞,也不会因为查询而阻 塞同一数据的更新。不过,Oracle不需要脏读来达到这个目的,而且也不支持脏读。但在其他数据库中必须实现脏读来提供非阻塞读。

        除 了4个已定义的SQL隔离级别外,Oracle还提供了另外一个级别,称为READ ONLY(只读)。READ ONLY事务相对于无法在SQL中完成任何修改的REPEATABLE READ或SERIALIZABLE事务。如果事务使用READ ONLY隔离级别,只能看到事务开始那一刻提交的修改,但是插入、更新和删除不允许采用这种模式(其他会话可以更新数据,但是READ ONLY事务不行)。如果使用这种模式,可以得到REPEATABLE READ和SERIALIZABLE级别的隔离性。
        所以,Oracle对隔离级别的支持如下:
1.SERIALIZABLE:支持
2.READ ONLY:Oracle特有的级别,利用它来实现对REPEATABLE READ的支持
3.READ COMMITTED:支持
4.READ UNCOMMITTED:不明确且不完全地支持

 

参见:

http://seaizon.iteye.com/blog/571139

http://wenku.baidu.com/view/2f89710879563c1ec5da7130.html

http://yjhexy.iteye.com/blog/658706

http://www.cnblogs.com/ityfei/articles/1502153.html

http://heysql.com/mysql/%E5%9F%BA%E7%A1%80%E7%9A%84%EF%BC%9A%E5%B0%81%E9%94%81%E5%8D%8F%E8%AE%AE%EF%BC%8C%E9%94%81%E7%B1%BB%E5%9E%8B%EF%BC%8C%E8%84%8F%E8%AF%BB%E3%80%81%E4%B8%8D%E5%8F%AF%E9%87%8D%E5%A4%8D%E8%AF%BB%E5%92%8C/

Continue reading 三级封锁协议两段锁以及隔离级别

Notepad++ 使用技巧

早就听说过Notepad++大名,最近用了下,觉得挺不错。

可以使用主题配色,看文本舒服多了。我最喜欢Rubyblue。

配合插件NppFTP: notepad++ ftp插件,查看服务端日志方便多了。修改文本文件自然不在话下。我想用这个修改服务器上的php文件岂不很好。

之前格式化个xml还要用专门的xml工具,使用nodepad++的xml tools插件,使用它的pretty print(line break)功能就能很好的格式化。

要格式化json或者javascript,可以使用jsmin插件中的jsformat功能。

常用快捷键:

Ctrl+L 删除行

Ctrl+D复制行

Ctrl+W关闭当前编辑页

Ctrl+Alt+鼠标 纵列选择

Ctrl+M批量修改文件

Continue reading Notepad++ 使用技巧

BCNF 示例

BCNF定义:

定义一:若关系模式R是第一范式,且每个属性都不传递依赖于R的候选键。这种关系模式就是BCNF模式。

定义二:若关系模式R∈1NF,且对于每一个非平凡的函数依赖X→Y,都有X 包含码,则R∈BCNF。

平凡函数依赖

当关系中属性集合Y是属性集合X的子集时(Y?X),存在函数依赖X→Y,即一组属性函数决定它的所有子集,这种函数依赖称为平凡函数依赖。

非平凡函数依赖

当关系中属性集合Y不是属性集合X的子集时,存在函数依赖X→Y,则称这种函数依赖为非平凡函数依赖。

第二个不好记,也不好理解,理解第一个就是了,两个是等同的。

举例 ①:

假设仓库管理关系表为StorehouseManage(仓库ID, 存储物品ID, 管理员ID, 数量),且有一个管理员只在一个仓库工作;一个仓库可以存储多种物品。这个数据库表中存在如下决定关系:

(仓库ID, 存储物品ID) →(管理员ID, 数量)

(管理员ID, 存储物品ID) → (仓库ID, 数量)

所以,(仓库ID, 存储物品ID)和(管理员ID, 存储物品ID)都是StorehouseManage的候选关键字,表中的唯一非关键字段为数量,它是符合第三范式的。但是,由于存在如下决定关系:

(仓库ID) → (管理员ID)

(管理员ID) → (仓库ID)

即存在关键字段决定关键字段的情况,所以其不符合BCNF范式。也就是存在循环传递依赖(仓库ID) → (管理员ID)  → (仓库ID)

 

它会出现如下异常情况:

(1) 删除异常:

当仓库被清空后,所有"存储物品ID"和"数量"信息被删除的同时,"仓库ID"和"管理员ID"信息也被删除了。

(2) 插入异常:

当仓库没有存储任何物品时,无法给仓库分配管理员。

(3) 更新异常:

如果仓库换了管理员,则表中所有行的管理员ID都要修改。

把仓库管理关系表分解为二个关系表:

仓库管理:StorehouseManage(仓库ID, 管理员ID);

仓库:Storehouse(仓库ID, 存储物品ID, 数量)。

这样的数据库表是符合BCNF范式的,消除了删除异常、插入异常和更新异常。

举例②:

CSZ(CITY 城市,ST 街道,ZIP 邮编),其属性组上的函数依赖集是 F={( CITY,ST) →ZIP,ZIP→CITY}。

存在两个候选码:

(CITY, ST)->ZIP

(ST, ZIP)->CITY

(CITY, ST)和(ST, ZIP)是两个候选码,没有非主属性,自然CSZE∈3NF。

但存在传递依赖(CITY, ST)->ZIP->CITY, 所以CSZ∉BCNF。
关系模式CSZ 也存在种删除,插入,更新异常:

若将CSZ 分解为两个关系模式ZC(ZIP, CITY)和SZ(ST,ZIP),就不再有
非平凡的函数依赖的决定因素中不包含码的情况,都是BCNF 的关系模式了。

 

可以看到上面两个例子都是存在循环依赖造成的主属性传递依赖于码,不知道这是必然还是有别的例子属于3NF不属于BCNF但是也不存在循环依赖的情况?

Continue reading BCNF 示例

IO 接口,设备

IO这个词出现太多了太多了,这里整理一下思路。

计算机由控制单元,运算单元,存储单元,输入设备输出设备组成。其中IO指的就是输入输出。

关于IO又有接口,设备,操作的概念: 参见http://book.51cto.com/art/200704/45417.htm

IO接口

IO接口是实现外部设备与主机之间的连接和信息交换的设备,也可称I/O适配器(Adapter)或适配卡。目前有:

① 总线系统

② 直接内存访问(DMA)

③ 通道

④ SCSI(Small Computer System Interface)

⑤ 并行口

⑥ RS-232C接口

⑦ USB(Universal Serial Bus,通用串行总线)接口

⑧ IEEE 1394接口

IO接口的控制方式

(1)程序查询方式
这种方式下,CPU通过I/O指令询问指定外设当前的状态,如果外设准备就绪,则进行数据的输入或输出,否则CPU等待,循环查询。
这种方式的优点是结构简单,只需要少量的硬件电路即可,缺点是由于CPU的速度远远高于外设,因此通常处于等待状态,工作效率很低

(2)中断处理方式
在这种方式下,CPU不再被动等待,而是可以执行其他程序,一旦外设为数据交换准备就绪,可以向CPU提出服务请求,CPU如果响应该请求,便暂时停止当前程序的执行,转去执行与该请求对应的服务程序,完成后,再继续执行原来被中断的程序。
中断处理方式的优点是显而易见的,它不但为CPU省去了查询外设状态和等待外设就绪所花费的时间,提高了CPU的工作效率,还满足了外设的实时要求。但需要为每个I/O设备分配一个中断请求号和相应的中断服务程序,此外还需要一个中断控制器(I/O接口芯片)管理I/O设备提出的中断请求,例如设置中断屏蔽、中断请求优先级等。
此外,中断处理方式的缺点是每传送一个字符都要进行中断,启动中断控制器,还要保留和恢复现场以便能继续原程序的执行,花费的工作量很大,这样如果需要大量数据交换,系统的性能会很低。

(3)DMA(直接存储器存取)传送方式
DMA最明显的一个特点是它不是用软件而是采用一个专门的控制器来控制内存与外设之间的数据交流,无须CPU介入,大大提高CPU的工作效率。
在进行DMA数据传送之前,DMA控制器会向CPU申请总线控制 权,CPU如果允许,则将控制权交出,因此,在数据交换时,总线控制权由DMA控制器掌握,在传输结束后,DMA控制器将总线控制权交还给CPU。

(4) 通道方式

(5) 外围处理机(输入输出处理机)方式

有专门的处理机负责IO,一般用于大型系统

IO设备

键盘,鼠标,硬盘,打印机,扫描仪,网络设备等……

 

说实话,我还是没搞清楚!有时候把接口当成设备,有时又要把它当成控制方式,下面显然是控制方式:

常见I/O接口方式的分类方式有:

①按数据传送格式分类:串行接口和并行接口

②按时序控制方式分类:同步接口和异步接口

③按传送控制方式分类:直接程序传送接口,中断接口,DMA接口.

网络IO

还有网络IO这个说法,对于web方面的来说,性能与网络IO操作有很大关系,参见http://blog.csdn.net/historyasamirror/article/details/5778378

对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。

当一个read操作发生时,它会经历两个阶段:
1 等待数据准备 (Waiting for the data to be ready)
2 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)记住这两点很重要,因为这些IO Model的区别就是在两个阶段上各有不同的情况。

阻塞IO:特点就是在IO执行的两个阶段都被block了,调用返回时数据一定是准备好了的。

非阻塞IO:调用马上返回,但是数据可能还没准备好,需要不断的主动询问kernel数据好了没有

多路IO(IO multiplexing):先选择,后获取。选择操作会阻塞知道其中一个有数据。

异步IO:调用马上返回,有数据后会发信号。(和ajax取数据有点像哈)

此文将多路分为同步IO,

这篇文章有另一种分法:http://blog.chinaunix.net/space.php?uid=20357359&do=blog&id=1963658

090830172605

将多路归类为异步阻塞,各有各道理,知道原理就好。不钻牛角尖!

显然非阻塞异步IO性能应该是最好的,Nodejs中的事件驱动非阻塞IO就是AIO。

Continue reading IO 接口,设备

【转】ThreadPoolExecutor使用和思考(上)-线程池大小设置与BlockingQueue的三种实现区别

 
转自:http://dongxuan.iteye.com/blog/901689

工作中多处接触到了ThreadPoolExecutor。趁着现在还算空,学习总结一下。

前记:

  1. jdk官方文档(javadoc)是学习的最好,最权威的参考。
  2. 文章分上中下。上篇中主要介绍ThreadPoolExecutor接受任务相关的两方面入参的意义和区别,池大小参数corePoolSize和maximumPoolSize,BlockingQueue选型(SynchronousQueue,LinkedBlockingQueue,ArrayBlockingQueue);中篇中主要聊聊与keepAliveTime这个参数相关的话题;下片中介绍一下一些比较少用的该类的API,及他的近亲:ScheduledThreadPoolExecutor
  3. 如果理解错误,请直接指出。

查看JDK帮助文档,可以发现该类比较简单,继承自AbstractExecutorService,而AbstractExecutorService实现了ExecutorService接口。

ThreadPoolExecutor的完整构造方法的签名是:

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

先记着,后面慢慢解释。

===============================神奇分割线==================================

其实对于ThreadPoolExecutor的构造函数网上有N多的解释的,大多讲得都很好,不过我想先换个方式,从Executors这个类入手。因为他的几个构造工厂构造方法名字取得令人很容易了解有什么特点。但是其实Executors类的底层实现便是ThreadPoolExecutor!

ThreadPoolExecutor是Executors类的底层实现。

在JDK帮助文档中,有如此一段话:

“强烈建议程序员使用较为方便的 Executors 工厂方法 Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)和 Executors.newSingleThreadExecutor()(单个后台线程),它们均为大多数使用场景预定义了设置。”

可以推断出ThreadPoolExecutor与Executors类必然关系密切。

===============================神奇分割线==================================

OK,那就来看看源码吧,从newFixedThreadPool开始。

ExecutorService newFixedThreadPool(int nThreads):固定大小线程池。

可以看到,corePoolSize和maximumPoolSize的大小是一样的(实际上,后面会介绍,如果使用无界queue的话maximumPoolSize参数是没有意义的),keepAliveTime和unit的设值表名什么?-就是该实现不想keep alive!最后的BlockingQueue选择了LinkedBlockingQueue,该queue有一个特点,他是无界的。

Java代码 收藏代码

  1. public static ExecutorService newFixedThreadPool(int nThreads) { 
  2. return new ThreadPoolExecutor(nThreads, nThreads, 
  3.                                       0L, TimeUnit.MILLISECONDS, 
  4. new LinkedBlockingQueue<Runnable>()); 
  5.     } 

ExecutorService newSingleThreadExecutor():单线程。

可以看到,与fixedThreadPool很像,只不过fixedThreadPool中的入参直接退化为1

Java代码 收藏代码

  1. public static ExecutorService newSingleThreadExecutor() { 
  2. return new FinalizableDelegatedExecutorService 
  3.             (new ThreadPoolExecutor(1, 1, 
  4.                                     0L, TimeUnit.MILLISECONDS, 
  5. new LinkedBlockingQueue<Runnable>())); 
  6.     } 

ExecutorService newCachedThreadPool():无界线程池,可以进行自动线程回收。

这个实现就有意思了。首先是无界的线程池,所以我们可以发现maximumPoolSize为big big。其次BlockingQueue的选择上使用SynchronousQueue。可能对于该BlockingQueue有些陌生,简单说:该QUEUE中,每个插入操作必须等待另一个

线程的对应移除操作。比如,我先添加一个元素,接下来如果继续想尝试添加则会阻塞,直到另一个线程取走一个元素,反之亦然。(想到什么?就是缓冲区为1的生产者消费者模式^_^)

注意到介绍中的自动回收线程的特性吗,为什么呢?先不说,但注意到该实现中corePoolSize和maximumPoolSize的大小不同。

Java代码 收藏代码

  1. public static ExecutorService newCachedThreadPool() { 
  2. return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 
  3.                                       60L, TimeUnit.SECONDS, 
  4. new SynchronousQueue<Runnable>()); 
  5.     } 

===============================神奇分割线==================================

到此如果有很多疑问,那是必然了(除非你也很了解了)

先从BlockingQueue<Runnable> workQueue这个入参开始说起。在JDK中,其实已经说得很清楚了,一共有三种类型的queue。以下为引用:(我会稍微修改一下,并用红色突出显示)

所有 BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:
  • 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。(什么意思?如果当前运行的线程小于corePoolSize,则任务根本不会存放,添加到queue中,而是直接抄家伙(thread)开始运行)
  • 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程
  • 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

先不着急举例子,因为首先需要知道queue上的三种类型。

排队有三种通用策略:

  1. 直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  2. 无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  3. 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。  

===============================神奇分割线==================================

到这里,该了解的理论已经够多了,可以调节的就是corePoolSize和maximumPoolSizes 这对参数还有就是BlockingQueue的选择。

例子一:使用直接提交策略,也即SynchronousQueue。

首先SynchronousQueue是无界的,也就是说他存数任务的能力是没有限制的,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加。在这里不是核心线程便是新创建的线程,但是我们试想一样下,下面的场景。

我们使用一下参数构造ThreadPoolExecutor:

Java代码 收藏代码

  1. new ThreadPoolExecutor( 
  2. 2, 3, 30, TimeUnit.SECONDS,  
  3. new <span style="white-space: normal;">SynchronousQueue</span><Runnable>(),  
  4. new RecorderThreadFactory("CookieRecorderPool"),  
  5. new ThreadPoolExecutor.CallerRunsPolicy()); 

当核心线程已经有2个正在运行.

  1. 此时继续来了一个任务(A),根据前面介绍的“如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。”,所以A被添加到queue中。
  2. 又来了一个任务(B),且核心2个线程还没有忙完,OK,接下来首先尝试1中描述,但是由于使用的SynchronousQueue,所以一定无法加入进去。
  3. 此时便满足了上面提到的“如果无法将请求加入队列,则创建新的线程,除非创建此线程超出maximumPoolSize,在这种情况下,任务将被拒绝。”,所以必然会新建一个线程来运行这个任务。
  4. 暂时还可以,但是如果这三个任务都还没完成,连续来了两个任务,第一个添加入queue中,后一个呢?queue中无法插入,而线程数达到了maximumPoolSize,所以只好执行异常策略了。

所以在使用SynchronousQueue通常要求maximumPoolSize是无界的,这样就可以避免上述情况发生(如果希望限制就直接使用有界队列)。对于使用SynchronousQueue的作用jdk中写的很清楚:此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。

什么意思?如果你的任务A1,A2有内部关联,A1需要先运行,那么先提交A1,再提交A2,当使用SynchronousQueue我们可以保证,A1必定先被执行,在A1么有被执行前,A2不可能添加入queue中

例子二:使用无界队列策略,即LinkedBlockingQueue

这个就拿newFixedThreadPool来说,根据前文提到的规则:

写道

如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。

那么当任务继续增加,会发生什么呢?

写道

如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。

OK,此时任务变加入队列之中了,那什么时候才会添加新线程呢?

写道

如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

这里就很有意思了,可能会出现无法加入队列吗?不像SynchronousQueue那样有其自身的特点,对于无界队列来说,总是可以加入的(资源耗尽,当然另当别论)。换句说,永远也不会触发产生新的线程!corePoolSize大小的线程数会一直运行,忙完当前的,就从队列中拿任务开始运行。所以要防止任务疯长,比如任务运行的实行比较长,而添加任务的速度远远超过处理任务的时间,而且还不断增加,如果任务内存大一些,不一会儿就爆了,呵呵。

可以仔细想想哈。

例子三:有界队列,使用ArrayBlockingQueue。

这个是最为复杂的使用,所以JDK不推荐使用也有些道理。与上面的相比,最大的特点便是可以防止资源耗尽的情况发生。

举例来说,请看如下构造方法:

Java代码 收藏代码

  1. new ThreadPoolExecutor( 
  2. 2, 4, 30, TimeUnit.SECONDS,  
  3. new ArrayBlockingQueue<Runnable>(2),  
  4. new RecorderThreadFactory("CookieRecorderPool"),  
  5. new ThreadPoolExecutor.CallerRunsPolicy()); 

假设,所有的任务都永远无法执行完。

对于首先来的A,B来说直接运行,接下来,如果来了C,D,他们会被放到queu中,如果接下来再来E,F,则增加线程运行E,F。但是如果再来任务,队列无法再接受了,线程数也到达最大的限制了,所以就会使用拒绝策略来处理。

总结:

  1. ThreadPoolExecutor的使用还是很有技巧的。
  2. 使用无界queue可能会耗尽系统资源。
  3. 使用有界queue可能不能很好的满足性能,需要调节线程数和queue大小
  4. 线程数自然也有开销,所以需要根据不同应用进行调节。

通常来说对于静态任务可以归为:

  1. 数量大,但是执行时间很短
  2. 数量小,但是执行时间较长
  3. 数量又大执行时间又长
  4. 除了以上特点外,任务间还有些内在关系

看完这篇问文章后,希望能够可以选择合适的类型了

Continue reading 【转】ThreadPoolExecutor使用和思考(上)-线程池大小设置与BlockingQueue的三种实现区别

DSSA(Domain Specific Software Architecture) 特定领域软件架构

与软件体系结构风格的区别

DSSA与软件体系结构风格是从不同角度出发研究问题的两种结果,前者从问题域出发,后者从解决域出发。

DSSA只对某个领域进行专家知识的提取、存储、组织,但可以同时使用多种软件体系结构风格;而在某个软件体系结构风格中进行专家知识组织时,可以将提取的公共结构和设计方法扩展到多个应用领域。

DSSA通常选用一个或多个适合所研究的领域的体系结构风格,并设计一个该领域专用的体系结构分析设计工具。但该方法提取的专家知识只能应用于一个较小的范围--所在领域。一个领域的DSSA及其工具在另一个领域中是不适应的,或不可以重用的。

体系结构风格避免设计到特定的应用领域或背景,所提取的知识比DSSA提取的知识应用范围要广。但在一个特定领域中,正是由于体系结构风格对领域知识、领域背景的忽略,使其在一个具体领域的开发中的作用并不比DSSA要大。

DSSA与体系结构风格是互为补充的两种技术。

http://iknow.seforge.org/sewiki/DSSA_e6_96_b9_e6_b3_95

作为DARPA的DSSA计划的一部分,Will Tracz在DSSA-ADAGE项目中提出了DSSA领域工程方法,与基于构架的系统开发过程相配合,应用于航空电子设备自动导航领域。

在DSSA方法中,进行领域工程的主要方式是领域工程师与领域专家的会谈,其中领域专家要就领域工程师提出的一系列问题进行报告,领域工程师对这些报告进行综合和整理,然后与领域专家一起对结果进行复审。

DSSA的领域工程过程是并发的 (concurrent)、递归的(recursive)和迭代的(iterative)。或者可以说,它是螺旋型的(spiral)。完成这个过程可能需要对每个阶段都经历几遍,每次增加更多的细节。对每个阶段的描述中包括一组需要回答的问题,一组需要的输入,一组将产生的输出和验证准则。

该方法分为五个主要阶段,在此之前还需要进行一些准备工作。其中前三个阶段集中于领域分析,即显式地把握领域特定的知识,这些知识时常被领域专家看作普通的知识。后两个阶段处理DSSA的设计、分析以及实现。

准备工作

在与领域专家会谈之前,领域工程师应尽可能熟悉该领域。理想情况下,领域工程师应对该领域具有一定经验。领域工程师应对DSSA方法指南中的问题回答有一定的想法,以便在会谈过程中诱导领域专家的答案,或者由领域专家对这些想法进行确认。领域工程师要确定DSSA方法的指南文档中哪些问题是与本次领域工程相关的,并对指南中的领域工程过程进行相应的裁剪。领域工程师还要理解本次领域工程所要达到的目标和实施这项工作的原因。

第一个阶段――定义领域分析的范围

本阶段集中于在感兴趣的领域之内有哪些事物。本步骤的输出包括感兴趣的领域与其它领域间的关系图、领域中应用的用户的需求列表、作为信息源的人的列表、作为信息源的项目的列表。

第二个阶段――定义特定于领域的元素并限制范围

本阶段的目标是编辑特定于领域的术语字典和同义词词典。在高级别块图的基础上,增加更多的细节,其中重点是在领域中的应用之间识别共同性,分离差异性。应特别重视对领域中的基本元素进行标准化和分类。

第三个阶段――定义和精化特定于领域的设计和实现约束

DSSA方法的一个特色是区别“需求”和“约束”。在DSSA方法中,“需求”描述一组在问题空间中的特性。“约束”描述一组在解空间中的特性。本阶段的目标是描述解空间中的特征。不但要识别约束,而且要记录它们对设计和实现决定的影响,以及对处理它们时产生的问题的讨论。

第四个阶段――开发领域构架/模型 领域设计

本阶段的目标是提出一般的构架,并说明构成构架的模块或构件的语法和语义。为满足前面识别的需求和约束,在一个应用领域中可能需要设计几个DSSA。

第五个阶段――产生或搜集复用产品 领域实现

本阶段的目标是为DSSA充实构件使得它可以被用来产生问题域中的新应用。参与这个阶段的领域专家是开发过这个领域中应用的软件工程师。他们最适合识别现有的可复用构件,或可以作为产生可复用构件的基础的构件。

Snap1

参见:

http://read.chaoxing.com/ebook/read_11401987.html P93

Continue reading DSSA(Domain Specific Software Architecture) 特定领域软件架构

正交软件体系结构

正交软件体系结构由组织层和线索的构件构成。层是由一组具有相同抽象级别的构件构成。线索是子系统的特例,它是由完成不同层次功能的构件组成(通过相互调用来关联),每一条线索完成整个系统中相对独立的一部分功能。每一条线索的实现与其他线索的实现无关或关联很少,在同一层中的构件之间是不存在相互调用的。

如果线索是相互独立的,即不同线索中的构件之间没有相互调用,那么这个结构就是完全正交的。从以上定义,我们可以看出,正交软件体系结构是一种以垂直线索构件族为基础的层次化结构,其基本思想是把应用系统的结构按功能的正交相关性,垂直分割为若干个线索(子系统),线索又分为几个层次,每个线索由多个具有不同层次功能和不同抽象级别的构件构成。各线索的相同层次的构件具有相同的抽象级别。因此,我们可以归纳正交软件体系结构的主要特征如下:

(1)正交软件体系结构由完成不同功能的n(n > 1)个线索(子系统)组成;

(2)系统具有m(m > 1)个不同抽象级别的层;

(3)线索之间是相互独立的(正交的);

(4)系统有一个公共驱动层(一般为最高层,下图的第一层)和公共数据结构(一般为最低层,下图第五层)。

对于大型的和复杂的软件系统,其子线索(一级子线索)还可以划分为更低一级的子线索(二级子线索),形成多级正交结构。正交软件体系结构的框架如图1所示。

xjj

图1是一个三级线索、五层结构的正交软件体系结构框架图,在该图中,ABDFK组成了一条线索,ACEJK也是一条线索。因为B、C处于同一层次中,所以不允许进行互相调用;H、J处于同一层次中,也不允许进行互相调用。一般来讲,第五层是一个物理数据库连接构件或设备构件,供整个系统公用。

在软件进化过程中,系统需求会不断发生变化。在正交软件体系结构中,因线索的正交性,每一个需求变动仅影响某一条线索,而不会涉及到其他线索。这样,就把软件需求的变动局部化了,产生的影响也被限制在一定范围内,因此实现容易。

正交软件体系结构具有以下优点:

(1)结构清晰,易于理解。正交软件体系结构的形式有利于理解。由于线索功能相互独立,不进行互相调用,结构简单、清晰,构件在结构图中的位置已经说明它所实现的是哪一级抽象,担负的是什么功能。

(2)易修改,可维护性强。由于线索之间是相互独立的,所以对一个线索的修改不会影响到其他线索。因此,当软件需求发生变化时,可以将新需求分解为独立的子需求,然后以线索和其中的构件为主要对象分别对各个子需求进行处理,这样软件修改就很容易实现。系统功能的增加或减少,只需相应的增删线索构件族,而不影响整个正交体系结构,因此能方便地实现结构调整。

(3)可移植性强,重用粒度大。因为正交结构可以为一个领域内的所有应用程序所共享,这些软件有着相同或类似的层次和线索,可以实现体系结构级的重用。

 

正交软件体系结构设计过程
正交软件体系结构设计过程分为2 个阶段:原型阶段和演化阶段。开发原型的主要目的是为了确认需求,设计体系结构基线。软件开发小组需要建立一系列原型,与用户一起讨论和评审。原型有助于需求的清晰化,同时也有助于进一步分析系统的可行性,更清楚地认识系统,完善对系统的理解。
原型开发的第1 个迭代周期的目标并不明确具体。为了提高开发效率,缩短开发周期,可将开发人员分为2 组,一组负责界面原型;一组负责业务模型。界面原型是界面层次的水平模型, 没有真正地实现系统功能。业务模型可用UML(统一建模语言)图表述,最终生成系统的SRS(软件需求说明书)。原型开发的第2 个迭代周期的任务是设计和建立正交软件体系结构。本次迭代大致分为6 个阶段[5]:
(1)标识构件:为系统生成逻辑结构,比如生成类图、包、构件等。

(2)提出软件体系结构模型:选择合适的软件体系结构风格是必要的,在此基础上,开发人员通过软件体系结构模型,熟悉了软件体系结构属性等方面的要求,虽然这个模型可能存在错误,但它为整个系统的演化确立了目标。
(3)把已标识的构件映射到软件体系结构中:把第(1)阶段标识的构件映射到体系结构中。
(4)分析构件,建立连接件:为了把已标识的构件集成到体系结构中,必须分析所有构件的关系,可结合UML 图表述,比如活动图、时序图等。
(5)生成软件体系结构。关键构件决定软件体系结构,主要是在第(4)阶段的基础上做精化。
(6)正交化。通过以上几个阶段产生的软件体系结构不一定满足正交性(同一层次的构件之间可能存在相互调)。通过从左至右、自顶向下地增加、删除、拆分合并构件,把不满足正交性的线索和构件正交化。

正交软件体系结构的演化控制

正交软件体系结构的重用粒度大。在演化控制和新系统开发过程中,逐渐建立线索库和构件库,如果有新需求,可以重用库中的线索和构件,以提高开发效率。因此,对新需求可以通过在原软件体系结构基础之上新
增、修改、删除线索来完成。对线索中的构件进行修改和添加时按照从左到右、自顶向下的原则,先修改高级构件,然后根据需要修改被其调用的构件。基于正交软件体系结构开发的软件,必须按照正交的演化步骤修改。其实演化过程也是正交软件体系结构的验证和完善过程。

(1)变动需求归类。将变动对应到相应的线索和构件上,对于新需求需要建立的新的线索和构件,都先作好标记。
(2)制定演进计划。在对原有模型改动前,需要制定一个周密的演进计划,作为后续开发工作的指南。

(3)增加、删除和修改构件。在第(1)步的基础上,开发人员依据演进计划对线索和构件进行增删改。
(4)更新构件关系。参照正交化简易算法对构件关系做必要调整,使线索和构件满足正交性。
(5)产生演化后的新软件体系结构。在原有系统上的修改必须映射到原有体系结构上,作为后续开发的基础。
(6)对以上修改作阶段性技术评审。
(7)迭代演进。迭代地进行第(3)步~第(6)步,直到新体系结构足够详细。

 

参考:

http://se.csai.cn/NewTech/No046.htm 张友生

http://read.chaoxing.com/ebook/read_11401987.html 张友生 p72

http://ishare.iask.sina.com.cn/f/8438626.html 基于正交软件体系结构的CRM 系统 汪保杰,王如龙 (湖南大学软件学院,长沙 410082)

Continue reading 正交软件体系结构

Pagination


Total views.

© 2013 - 2019. All rights reserved.

Powered by Hydejack v6.6.1