FX2N PLC AND,ANI指令的使用编程举例

FX2N PLC AND,ANI指令的使用编程举例

助记符与功能:

符号、名称 功能 可用软元件 程序步

AND 与 a触点串联连接 X,Y,M,S,T,C 1

ANI 与非 b触点串联连接 X,Y,M,S,T,C 1

当使用M1536-M3071时,程序步加1。

指令说明:

用AND,ANI指令可进行1个触点的串联连接。串联触点的数量不受限制,该指令可多次使用。

OUT指令后,通过触点对其他线圈使用OUT指令,称之为纵接输出,(下图的OUT M101 与OUT Y004)

这种纵接输出,如果顺序不错,可多次重复。

串联触点数和纵接输出次数不受限制,但使用图形编程设备和打印机则有限制。

建议尽量做到1行不超过10个触点和1个级圈,总共不要超过24行。

编程:

0 LD X002

1 AND X000

2 OUT Y003

3 LD Y003

4 ANI X003

5 OUT M101

6 AND T1

7 OUT Y004

如上图所示,紧接着OUT M101以后通过触点T1可以驱动OUT

FX-4AD模拟量输入模块

FX-4AD模拟量输入模块



FX2N系列中有关模拟量的特殊功能模块有:FX2N-2AD(2路模拟量输入)、FX2N-4AD(4路模拟量输入)、FX2N-8AD(8路模拟量输入)、FX2N-4AD-PT(4路热电阻直接输入)、FX2N-4AD-TC(4路热电偶直接输入)、FX2N-2DA(2路模拟量输出)、FX2N-4DA(4路模拟量输出)和FX2N-2LC(2路温度PID控制模块)等。



下面主要介绍常用的模拟量输入模块FX2N-4AD。



1. FX-4AD概述



FX-4AD模拟量输入模块是FX系列专用的模拟量输入模块。该模块有4个输入通道(CH),通过输入端子变换,可以任意选择电压或电流输入状态。电压输入时,输入信号范围为DC–10~+



10V,输入阻抗为200kΩ,分辨率为5mV;电流输入时,输入信号范围为DC -20~+20mA,输入阻抗为250Ω,分辨率为20μA。



FX-4AD将接收的模拟信号转换成12位二进制的数字量,并以补码的形式存于16位数据寄存器中,数值范围是-2048~+



2047。它的传输速率为15ms/K,综合精度为量程的1%。



FX-4AD的工作电源为DC24V,模拟量与数字量之间采用光电隔离技术,但各通道之间没有隔离。FX-4AD消耗PLC主单元或有源扩展单元5V电源槽30mA的电流。FX-4AD占用基本单元的8个映像表,即在软件上占8个I/O点数,在计算PLC的I/O时可以将这8个点作为PLC的输入点来计算。



2. FX-4AD的接线



FX-4AD的接线如图6-29所示,图中模拟输人信号采用双绞屏蔽电缆与FX-4AD连接,电缆应远离电源线或其它可能产生电气干扰的导线。如果输入有电压波动,或在外部接线中有电气干扰,可以接一个0.1μF~0.47μF(25V)的电容。如果是电流输入,应将端子V+和I+连接。FX2N-4AD接地端与PLC主单元接地端连接,如果存在过多的电气干扰,再将外壳地端FG和FX-4AD接地端连接。



图6-29 FX-4AD的接线图



3. FX-4AD缓冲寄存器(BFM)的分配



FX-4AD模拟量模块内部有一个数据缓冲寄存器区,它由32个16位的寄存器组成,编号为BFM#0-#31,其内容与作用如表6-1所示。数据缓冲寄存器区内容,可以通过PLC的



FROM和 TO指令来读、写。



表6-1 FX-4AD缓冲寄存器(BFM)的分配



BFM编号



内容



备注



#0(*)



通道初始化,用4位十六位数字H××××表示,4位数字从右至左分别控制1、2、3、4四个通道



每位数字取值范围为0~3,其含义如下:



0表示输入范围为-10V~+10V



l表示输入范围为+4mA~+20mA



2表示输入范围为-20mA~+20mA



3表示该通道关闭



缺省值为H0000



#1(*)



通道1



采样次数设置



采样次数是用于得到平均值,其设置范围为1~4096,缺省值为8



#2(*)



通道2



#3(*)



通道3



#4(*)



通道4



#5



通道1



平均值存放单元



根据#1~#4缓冲寄存器的采样次数,分别得出的每个通道的平均值



#6



通道2



#7



通道3



#8



通道4



#9



通道1



当前值存放单元



每个输入通道读入的当前值



#10



通道2



#11



通道3



#12



通道4



#13~#14



保留



#15(*)



A/D转换速度设置



设为0时:正常速度,15ms/通道(缺省值)



设为1时:高速度,6ms/通道



#16~#19



保留



#20(*)



复位到缺省值和预设值



缺省值为0;设为1时,所有设置将复位缺省值



#21(*)



禁止调整偏置和增益值



b1、b0位设为1、0时,禁止;



b1、b0位设为0、1时,允许(缺省值)



#22(*)



偏置、增益调整通道设置



b7与b6、b5与b4、b3与b2、b1与b0分别表示调整通道4、3、2、1的增益与偏置值



#23(*)



偏置值设置



缺省值为0000,单位为mV或μA



#24(*)



增益值设置



缺省值为5000,单位为mV或μA



#25~#28



保留



#29



错误信息



表示本模块的出错类型



#30



识别码(K2010)



固定为K2010,可用FROM读出识别码来确认此模块



#31



禁用

FX-2DA模拟量输出模块

FX-2DA模拟量输出模块



1. FX-2DA概述



FX-2DA模拟量输出模块也是FX系列专用的模拟量输出模块。该模块将12位的数字值转换成相应的模拟量输出。FX-2DA有2路输出通道,通过输出端子变换,也可任意选择电压或电流输出状态。电压输出时,输出信号范围为DC



-10~+10V,可接负载阻抗为1kΩ~1MΩ,分辨率为5mV,综合精度 0.1V;电流输出时,输出信号范围为DC



+4~+20mA,可接负载阻抗不大于250Ω,分辨率为20μA,综合精度0.2mA。



FX-2DA模拟量模块的工作电源为DC24V,模拟量与数字量之间采用光电隔离技术。FX-2AD模拟量模块的2个输出通道,要占用基本单元的8个映像表,即在软件上占8个I/O点数,在计算PLC的I/O时可以将这8个点作为PLC的输出点来计算。



2. FX-2DA的接线



FX-2DA的接线如图6-30所示,图中模拟输出信号采用双绞屏蔽电缆与外部执行机构连接,电缆应远离电源线或其它可能产生电气干扰的导线。当电压输出有波动或存在大量噪声干扰时,可以接一个0.1μF~0.47μF(25V)的电容。对于是电压输出,应将端子I+和VI-连接。FX2N-2DA接地端与PLC主单元接地端连接。



图6-30 FX-2DA的接线图



1.FX-2DA的缓冲寄存器(BFM)分配



FX-2DA模拟量模块内部有一个数据缓冲寄存器区,它由32个16位的寄存器组成,编号为BFM#0~#31,其内容与作用如表6-2所示。数据缓冲寄存器区内容可以通过PLC的



FROM和 TO指令来读、写。



表6-2 FX-2DA缓冲寄存器(BFM)的分配



BFM编号



内容



备注



#0



通道初始化,用2位十六位数字H××表示,2位数字从右至左分别控制CH1、CH2两个通道



每位数字取值范围为0、1,其含义如下:



0表示输出范围为-10V~+10V



l表示输入范围为+4mA~+20mA



#1



通道1



存放输出数据



#2



通道2



#3~#4



保留



#5



输出保持与复位



缺省值为H00



H00表示CH2保持、CH1保持



H01表示CH2保持、CH1复位



H10表示CH2复位、CH1保持



Hll表示CH2复位、CH1复位



#6~#15



保留



#16



输出数据的当前值



8位数据存于b7~b0



#17



转换通道设置



将b0由1变成0,CH2的D/A转换开始



将b1由1变成0,CH1的D/A转换开始



将b2由1变成0,D/A转换的低8位数据保持



#18~#19



保留



#20



复位到缺省值和预设值



缺省值为0;设为1时,所有设置将复位缺省值



#21



禁止调整偏置和增益值



b1、b0位设为1、0时,禁止;



b1、b0位设为0、1时,允许(缺省值)



#22



偏置、增益调整通道设置



b3与b2、b1与b0分别表示调整CH2、CH1的增益与偏置值



#23



偏置值设置



缺省值为0000,单位为mV或μA



#24



增益值设置



缺省值为5000,单位为mV或μA



#25~#28



保留



#29



错误信息



表示本模块的出错类型



#30



识别码(K3010)



固定为K3010,可用FROM读出识别码来确认此模块



#31



禁用



4. FX-2DA偏置与增益的调整



FX-2DA出厂时偏置值和增益值已经设置成:数字值为0到4000,电压输出为0到10V。当



FX-2DA用作电流输出时,必须重新调整偏置值和增益值。偏置值和增益值的调节是对数字值设置实际的输出模拟值,可通过FX-2DA的容量调节器,并使用电压和电流表来完成。



增益值可设置为0~4000的任意数字值。但是,为了得到12位的最大分辨率,电压输出时,对于10V的模拟输出值,数字值调整到



4000;电流输出时,对于20mA的模拟输出值,数字值调整到4000。



偏置值也可根据需要任意进行调整。但一般情况下,电压输入时,偏置值设为OV;电流输入时,偏置值设为4mA。



调整偏置与增益时应该注意以下几个问题:



l)对通道1和通道2分别进行偏置调整和增益调整。



2)反复交替调整偏置值和增益值,直到获得稳定的数值。

FX PLC切纸机控制

FX PLC切纸机控制



1引言



切纸机械是印刷和包装行业最常用的设备之一。切纸机完成的最基本动作是把待裁切的材料送到指定位置,然后进行裁切。其控制的核心是一个单轴定位控制。我公司引进欧洲一家公司的两台切纸设备,其推进定位系统的实现是利用单片机控制,当接收编码器的脉冲信号达到设定值后,单片机系统输出信号,断开进给电机的接触器,同时电磁离合制动器的离合分离,刹车制动推进系统的惯性,从而实现精确定位。由于设备的单片机控制系统老化,造成定位不准,切纸动作紊乱,不能正常生产。但此控制系统是早期产品,没有合适配件可替换,只能采取改造这一途径。目前国内进行切纸设备进给定位系统改造主要有两种方式,一是利用单片机结合变频器实现,一是利用单片机结合伺服系统实现,不过此两种改造方案成本都在两万元以上。并且单片机系统是由专业开发公司设计,技术保守,一旦出现故障只能交还原公司维修或更换,维修周期长且成本高,不利于改造后设备的维护和使用。我们结合自己设备的特点提出了新的改造方案,就是用plc的高速计数器功能结合变频器的多段速功能实现定位控制,并利用hmi(人机界面humanmachineinterface)进行裁切参数设定和完成手动操控。



2 改造的可行性分析



现在的大多plc都具有高速计数器功能,不需增加特殊功能单元就可以处理频率高达几十或上百khz的脉冲信号。切纸机对进给系统的精度和响应速度要求不是很高,可以通过对切纸机进给系统相关参数的计算,合理的选用编码器,让脉冲频率即能在plc处理的范围内又可以满足进给的精度要求。在进给过程中,plc对所接收的脉冲数与设定数值进行比较,根据比较结果驱动相应的输出点对变频器进行输出频率的控制,实现接近设定值时进给速度变慢,从而减小系统惯性,达到精确定位的目的。另外当今变频器技术取得了长足的发展,使电机在低速时的转矩大幅度提升,从而也保证了进给定位时低速推进的可行性。



3 主要控制部件的选取



3.1plc的选取



设备需要的输入输出信号如表1所示。



表1plc输入输出分配表



针对这些必需的输入点数,选用了fx1s-30mr的plc,因为选用了人机界面,其它一些手动动作,如前进、后退、换刀等都通过人机界面实现,不需占用plc输入点,从而为选用低价位的fx1s系列plc成为可能,因为fx1s系列plc输入点最多只有16点。另外此系列plc的高速计数器具有处理频率高达60千赫的脉冲的能力,足可以满足切纸机对精度的要求。



3.2编码器的选取



编码器的选取要符合两个方面,一是plc接收的最高脉冲频率,二是进给的精度。我们选用的是编码器分辨率是500p/r(每转每相输出500个脉冲)的。通过验正可以知道此分辨率可以满足上面两个条件。验证所需的参数:电机最高转速是1500转/分(25转/秒)、进给丝杆的导程是10mm/转。验证如下:



本系统脉冲最高频率=25转/秒×500个/转×2(a/b两相)=25khz



理论进给分辨率=10mm/500=0.02mm



同时由上面的数据知道进给系统每走1mm编码器发出50(此数据很重要,在plc程序的数据处理中要用到)个脉冲信号。由于此工程中对编码器的a/b相脉冲进行了分别计数,使用了两个高速计数器,且在程序中应用了高速定位指令,则此plc可处理的最高脉冲频率为30千赫,因此满足了第一个条件;我们的切纸机的载切精度要求是0.2mm,可知理论精度完全满足此要求。



3.3变频器和hmi的选取



这两个部件我们都选用了三菱公司的产品,分别是fr-e540-0.75k-ch和f920got-bbd-k-c。f920got是带按键型的hmi,它的使用和编程非常简单方便。它具有以下特点:(1)可以方便的实现和plc的数据交换;(2)通过本身自带的6个功能按键开关,可以控制plc内部的软继电器,从而可以减少plc输入点的使用;(3)具有两个通讯口,一个rs232c(用于和个人电脑通讯)和一个rs422(用于和plc通讯),利用电脑和f920got相连后不仅可以对hmi进行程序的读取和上传,还可以直接对plc的程序进行上传下载、调整和监控。



4 plc和hmi程序的设计



此工程中程序的难点主要在于数据的处理上。在切纸机工作过程中除手动让进给定位机构前进后退外,还要实现等分裁切功能和指定具体位置定位功能,并且hmi上还要即时显示定位机构的当前位置。我们为了简化程序中的计算,采用了两个高速计数器c235和c236。c236通过计算前进后退的脉冲数,再进行换算后用于显示进给机构的当前位置;c235用于进行精确定位。定位过程是这样的,每次进给机构需要定位工作时,通过计算把需要的脉冲数送到c235,不论进给机构前进还是后退c235进行减计数,同时对c235中的数值进行比较,根据比较结果驱动相应的输出点对变频器进行输出频率的控制,实现接近设定值时进给速度变慢,从而达到精确定位。因为任何系统都有惯性和时间上的迟滞,所以变频器停止输出的时间并不是c235中的计数值减小到0时,而是让c235和一个数据寄存器d130比较,当c235中的值减小到d130中的设定值时plc控制变频器停止输出。d130的值可通过人机界面进行修改和设定,在调试时通过修改这个值,以达到定位准确的目的。



1)显示定位机构当前位置的程序



2)实现定位控制的程序段



3)参数设定时的小数点位问题。实际工作中在设定位置时要精确到0.1mm。这个问题在一些单片机系统中常会遇到,常见的处理办法是加大一个数量级,就是设定数据时,在人机界面上用1代替0.1mm,10代替1mm。不过我们在处理此问题时通过hmi中对数据的设置和plc的程序编写达到了所见即所得的效果。hmi中主要是对数值的格式要设定好。hmi中的设置画面如图1所示。例如等分裁切10.5mm的纸,就可以在hmi上设定为10.5,而不是像公司的类似其它设备上要设为105,但plc的寄存器d128的内容是105而不是10.5,这样在计算需要的脉冲数时就要用下面一条命令:muld128k5d10(此命令中尽管编程时d11不出现但实际上寄存器d11被占用,不能再应用于其它地方,否则会出现问题。)



而不是用:muld128k50d10。



4)编程中其它应注意的问题



●双线圈问题。本工程中利用条件跳转和步进指令避免了双线圈问题。



●误差信号问题。编码器是一种比较精密的光电产品,受振动时不可避免的会出现误差信号,而切纸机在执行裁切动作时会造成很大振动,如果忽视这个现象,定位精度和执行机构当前位置的显示都会不准确。本工程中处理方法参见上面例子程序图1,只有y3、y4接通,即只有进给机构前进和后退时才让c236进行计数,这样就屏蔽了裁切时震动造成的误信号。



5 变频器的参数设置



设定的变频器的主要参数见表2。在调试过程中为了达到定位速度和精度的完美结合,应对三段速设定值,加减速时间和hmi中d130、d200和d202的数值进行相应调整。



表2变频器主要参数设置一览表



6 结束语

FX 2的状态元件

FX 2的状态元件



状态元件是构成状态转移图的基本元素,是可编程控制器的软元件之一。 FX 2 共有 1000个状态元件,其分类、编号、数量及用途如表1所示。



表1 FX 2 的状态元件



类别



元件编号



个数



用途及特点



初始状态



S0~S9



10



用作 SFC的初始状态



返回状态



S10~S19



10



多运行模式控制当中,用作返回原点的状态



一般状态



S20~S499



480



用作 SFC的中间状态



掉电保持状态



S500~S899



400



具有停电保持功能,停电恢复后需继续执行的场合,可用这些状态元件



信号报警状态



S900~S999



100



用作报警元件使用



注: 1状态的编号必须在指定范围选择。



2各状态元件的触点,在PLC内部可自由使用,次数不限。



3在不用步进顺控指令时,状态元件可作为辅助继电器在程序中使用。

FX2N PLC AND, FX2N PLC AND,ANI指令的使用编程举例 助记符与功能: 符号、名称 功能 可用软元件 程序步 AND 与 a触点串联连接 X,Y,M,S,T,C 1 ANI 与非 b触点串联连接 X,Y,M,S,T,C 1 当使用M1536-M3071时,程序步加1。 指令说明: 用AND,ANI指令可进行1个触点的串联连接。串联触点的数量不受限制,该指令可多次使用。 OUT指令后,通过触点对其他线圈使用OUT指令,称之为纵接输出,(下图的OUT M101 与OUT Y004) 这种纵接输出,如果顺序不错,可多次重复。 串联触点数和纵接输出次数不受限制,但使用图形编程设备和打印机则有限制。 建议尽量做到1行不超过10个触点和1个级圈,总共不要超过24行。 编程: 0 LD X002 1 AND X000 2 OUT Y003 3 LD Y003 4 ANI X003 5 OUT M101 6 AND T1 7 OUT Y004 如上图所示,紧接着OUT M101以后通过触点T1可以驱动OUT ANI指令的使用编程举例

PHP递归算法(一)

在PHP开发过程中,递归算法通常用于无限极分类。那么所谓递归就是一种函数调用自身的机制。简单来说就是在函数体内直接或间接自己调用自己,但需要设置自调用的条件,若满足条件,则调用函数本身,若不满足则终止本函数的自调用。

并且递归算法的实现方法是有多种的,如通过“静态变量”、“全局变量”、“引用传参”的方式。

下面我们就结合具体的代码示例,给大家介绍其中一种方法即利用静态变量的方法!

代码如下:

<?php

function call(){

static $i = 0;

echo $i . '';

$i++;

if($i<10){

    call();

}

}

call();

输出:

0 1 2 3 4 5 6 7 8 9

这种方法我们主要利用static定义静态变量来实现递归排序。如上我们定义了一个call方法和静态变量$i,如果我们不给$i变量添加判断,而是直接运行,就会出现死循环。所以我们这里添加了一个if条件判断语句。最后循环调用自身方法。

本篇文章就是关于利用静态变量实现PHP递归算法的介绍,在后续的文章中,我们会继续为大家介绍PHP递归算法的相关实现方法。

为什么会需要消息队列(MQ)?

  为什么会需要消息队列(MQ)?

  ##########################################################################################

  主要原因是由于在高并发环境下,由于来不及同步处理,请求往往会发生堵塞,比如说,大量的insert,update之类的请求同时到达MySQL,直接导致无数的行锁表锁,甚至最后请求会堆积过多,从而触发too many connections错误。通过使用消息队列,我们可以异步处理请求,从而缓解系统的压力。

  ##########################################################################################

  美国计算机科学家,LaTex的作者Leslie Lamport说:“分布式系统就是这样一个系统,系统中一个你甚至都不知道的计算机出了故障,却可能导致你自己的计算机不可用。”一语道破了开发分布式系统的玄机,那就是它的复杂与不可控。所以Martin Fowler强调:分布式调用的第一原则就是不要分布式。这句话看似颇具哲理,然而就企业应用系统而言,只要整个系统在不停地演化,并有多个子系统共同存在时,这条原则就会被迫打破。盖因为在当今的企业应用系统中,很难寻找到完全不需要分布式调用的场景。Martin Fowler提出的这条原则,一方面是希望设计者能够审慎地对待分布式调用,另一方面却也是分布式系统自身存在的缺陷所致。无论是CORBA,还是EJB 2;无论是RPC平台,还是Web Service,都因为驻留在不同进程空间的分布式组件,而引入额外的复杂度,并可能对系统的效率、可靠性、可预测性等诸多方面带来负面的影响。

  然而,不可否认的是在企业应用系统领域,我们总是会面对不同系统之间的通信、集成与整合,尤其当面临异构系统时,这种分布式的调用与通信变得越重要,它在架构设计中就更加凸显其价值。并且,从业务分析与架构质量的角度来讲,我们也希望在系统架构中尽可能地形成对服务的重用,通过独立运行在进程中服务的形式,彻底解除客户端与服务端的耦合。这常常是架构演化的必然道路。在我的同事陈金洲发表在InfoQ上的文章《架构腐化之谜》中,就认为可以通过“将独立的模块放入独立的进程”来解决架构因为代码规模变大而腐化的问题。

  随着网络基础设施的逐步成熟,从RPC进化到Web Service,并在业界开始普遍推行SOA,再到后来的RESTful平台以及云计算中的PaaS与SaaS概念的推广,分布式架构在企业应用中开始呈现出不同的风貌,然而殊途同归,这些分布式架构的目标仍然是希望回到建造巴别塔的时代,系统之间的交流不再为不同语言与平台的隔阂而产生障碍。正如Martin Fowler在《企业集成模式》一书的序中写道:“集成之所以重要是因为相互独立的应用是没有生命力的。我们需要一种技术能将在设计时并未考虑互操作的应用集成起来,打破它们之间的隔阂,获得比单个应用更多的效益”。这或许是分布式架构存在的主要意义。

  1、集成模式中的消息模式

  归根结底,企业应用系统就是对数据的处理,而对于一个拥有多个子系统的企业应用系统而言,它的基础支撑无疑就是对消息的处理。与对象不同,消息本质上是一种数据结构(当然,对象也可以看做是一种特殊的消息),它包含消费者与服务双方都能识别的数据,这些数据需要在不同的进程(机器)之间进行传递,并可能会被多个完全不同的客户端消费。在众多分布式技术中,消息传递相较文件传递与远程过程调用(RPC)而言,似乎更胜一筹,因为它具有更好的平台无关性,并能够很好地支持并发与异步调用。对于Web Service与RESTful而言,则可以看做是消息传递技术的一种衍生或封装。在《面向模式的软件架构(卷四)》一书中,将关于消息传递的模式划归为分布式基础设施的范畴,这是因为诸多消息中间件产品的出现,使得原来需要开发人员自己实现的功能,已经可以直接重用。这极大地降低了包括设计成本、实现成本在内的开发成本。因此,对于架构师的要求也就从原来的设计实现,转变为对业务场景和功能需求的判断,从而能够正确地进行架构决策、技术选型与模式运用。

  常用的消息模式

  在我参与过的所有企业应用系统中,无一例外地都采用(或在某些子系统与模块中部分采用)了基于消息的分布式架构。但是不同之处在于,让我们做出架构决策的证据却迥然而异,这也直接影响我们所要应用的消息模式。

  消息通道(Message Channel)模式

  我们常常运用的消息模式是Message Channel(消息通道)模式,如图1所示。

  图1 Message Channel模式(图片来自eaipatterns )

  消息通道作为在客户端(消费者,Consumer)与服务(生产者,Producer)之间引入的间接层,可以有效地解除二者之间的耦合。只要实现规定双方需要通信的消息格式,以及处理消息的机制与时机,就可以做到消费者对生产者的“无知”。事实上,该模式可以支持多个生产者与消费者。例如,我们可以让多个生产者向消息通道发送消息,因为消费者对生产者的无知性,它不必考虑究竟是哪个生产者发来的消息。

  虽然消息通道解除了生产者与消费者之间的耦合,使得我们可以任意地对生产者与消费者进行扩展,但它又同时引入了各自对消息通道的依赖,因为它们必须知道通道资源的位置。要解除这种对通道的依赖,可以考虑引入Lookup服务来查找该通道资源。例如,在JMS中就可以通过JNDI来获取消息通道Queue。若要做到充分的灵活性,可以将与通道相关的信息存储到配置文件中,Lookup服务首先通过读取配置文件来获得通道。

  消息通道通常以队列的形式存在,这种先进先出的数据结构无疑最为适合这种处理消息的场景。微软的MSMQ、IBM MQ、JBoss MQ以及开源的RabbitMQ、Apache ActiveMQ都通过队列实现了Message Channel模式。因此,在选择运用Message Channel模式时,更多地是要从质量属性的层面对各种实现了该模式的产品进行全方位的分析与权衡。例如,消息通道对并发的支持以及在性能上的表现;消息通道是否充分地考虑了错误处理;对消息安全的支持;以及关于消息持久化、灾备(fail over)与集群等方面的支持。因为通道传递的消息往往是一些重要的业务数据,一旦通道成为故障点或安全性的突破点,对系统就会造成灾难性的影响。在本文的第二部分,我将给出一个实际案例来阐释在进行架构决策时应该考虑的架构因素,并由此做出正确地决策。

  发布者-订阅者(Publisher-Subscriber)模式

  一旦消息通道需要支持多个消费者时,就可能面临两种模型的选择:拉模型与推模型。拉模型是由消息的消费者发起的,主动权把握在消费者手中,它会根据自己的情况对生产者发起调用。如图2所示:

  图2 拉模型

  拉模型的另一种体现则由生产者在状态发生变更时,通知消费者其状态发生了改变。但得到通知的消费者却会以回调方式,通过调用传递过来的消费者对象获取更多细节消息。

  在基于消息的分布式系统中,拉模型的消费者通常以Batch Job的形式,根据事先设定的时间间隔,定期侦听通道的情况。一旦发现有消息传递进来,就会转而将消息传递给真正的处理器(也可以看做是消费者)处理消息,执行相关的业务。在本文第二部分介绍的医疗卫生系统,正是通过引入Quartz.NET实现了Batch Job,完成对消息通道中消息的处理。

  推模型的主动权常常掌握在生产者手中,消费者被动地等待生产者发出的通知,这就要求生产者必须了解消费者的相关信息。如图3所示:

  图3 推模型

  对于推模型而言,消费者无需了解生产者。在生产者通知消费者时,传递的往往是消息(或事件),而非生产者自身。同时,生产者还可以根据不同的情况,注册不同的消费者,又或者在封装的通知逻辑中,根据不同的状态变化,通知不同的消费者。

  两种模型各有优势。拉模型的好处在于可以进一步解除消费者对通道的依赖,通过后台任务去定期访问消息通道。坏处是需要引入一个单独的服务进程,以Schedule形式执行。而对于推模型而言,消息通道事实上会作为消费者观察的主体,一旦发现消息进入,就会通知消费者执行对消息的处理。无论推模型,拉模型,对于消息对象而言,都可能采用类似Observer模式的机制,实现消费者对生产者的订阅,因此这种机制通常又被称为Publisher-Subscriber模式,如图4所示:

  图4 Publisher-Subscriber模式(图片来自eaipatterns )

  通常情况下,发布者和订阅者都会被注册到用于传播变更的基础设施(即消息通道)上。发布者会主动地了解消息通道,使其能够将消息发送到通道中;消息通道一旦接收到消息,会主动地调用注册在通道中的订阅者,进而完成对消息内容的消费。

  对于订阅者而言,有两种处理消息的方式。一种是广播机制,这时消息通道中的消息在出列的同时,还需要复制消息对象,将消息传递给多个订阅者。例如,有多个子系统都需要获取从CRM系统传来的客户信息,并根据传递过来的客户信息,进行相应的处理。此时的消息通道又被称为Propagation通道。另一种方式则属于抢占机制,它遵循同步方式,在同一时间只能有一个订阅者能够处理该消息。实现Publisher-Subscriber模式的消息通道会选择当前空闲的唯一订阅者,并将消息出列,并传递给订阅者的消息处理方法。

  目前,有许多消息中间件都能够很好地支持Publisher-Subscriber模式,例如JMS接口规约中对于Topic对象提供的MessagePublisher与MessageSubscriber接口。RabbitMQ也提供了自己对该模式的实现。微软的MSMQ虽然引入了事件机制,可以在队列收到消息时触发事件,通知订阅者。但它并非严格意义上的Publisher-Subscriber模式实现。由微软MVP Udi Dahan作为主要贡献者的NServiceBus,则对MSMQ以及WCF做了进一层包装,并能够很好地实现这一模式。

  消息路由(Message Router)模式

  无论是Message Channel模式,还是Publisher-Subscriber模式,队列在其中都扮演了举足轻重的角色。然而,在企业应用系统中,当系统变得越来越复杂时,对性能的要求也会越来越高,此时对于系统而言,可能就需要支持同时部署多个队列,并可能要求分布式部署不同的队列。这些队列可以根据定义接收不同的消息,例如订单处理的消息,日志信息,查询任务消息等。这时,对于消息的生产者和消费者而言,并不适宜承担决定消息传递路径的职责。事实上,根据S单一职责原则,这种职责分配也是不合理的,它既不利于业务逻辑的重用,也会造成生产者、消费者与消息队列之间的耦合,从而影响系统的扩展。

  既然这三种对象(组件)都不宜承担这样的职责,就有必要引入一个新的对象专门负责传递路径选择的功能,这就是所谓的Message Router模式,如图5所示:

  图5 Message Router模式(图片来自eaipatterns )

  通过消息路由,我们可以配置路由规则指定消息传递的路径,以及指定具体的消费者消费对应的生产者。例如指定路由的关键字,并由它来绑定具体的队列与指定的生产者(或消费者)。路由的支持提供了消息传递与处理的灵活性,也有利于提高整个系统的消息处理能力。同时,路由对象有效地封装了寻找与匹配消息路径的逻辑,就好似一个调停者(Meditator),负责协调消息、队列与路径寻址之间关系。

  除了以上的模式之外,Messaging模式提供了一个通信基础架构,使得我们可以将独立开发的服务整合到一个完整的系统中。 Message Translator模式则完成对消息的解析,使得不同的消息通道能够接收和识别不同格式的消息。而且通过引入这样的对象,也能够很好地避免出现盘根错节,彼此依赖的多个服务。Message Bus模式可以为企业提供一个面向服务的体系架构。它可以完成对消息的传递,对服务的适配与协调管理,并要求这些服务以统一的方式完成协作。

  2、消息模式的应用场景

  基于消息的分布式架构总是围绕着消息来做文章。例如可以将消息封装为对象,或者指定消息的规范例如SOAP,或者对实体对象的序列化与反序列化。这些方式的目的只有一个,就是将消息设计为生产者和消费者都能够明白的格式,并能通过消息通道进行传递。

  场景一:基于消息的统一服务架构

  在制造工业的CIMS系统中,我们尝试将各种业务以服务的形式公开给客户端的调用者,例如定义这样的接口:

  public interface IService {

  IMessage Execute(IMessage aMessage);

  void SendRequest(IMessage aMessage);

  }

  之所以能够设计这样的服务,原因在于我们对业务信息进行了高度的抽象,以消息的形式在服务之间传递。此时的消息其实是生产者与消费者之间的契约或接口,只要遵循这样的契约,按照规定的格式对消息进行转换与抽取,就能很好地支持系统的分布式处理。

  在这个CIMS系统中,我们将消息划分为ID,Name和Body,通过定义如下的接口方法,可以获得消息主体的相关属性:

  public interface IMessage:ICloneable

  {

  string MessageID { get; set; }

  string MessageName() { get; set; }

  IMessageItemSequence CreateMessageBody();

  IMessageItemSequence GetMessageBody();

  }

  消息主体类Message实现了IMessage接口。在该类中,消息体Body为IMessageItemSequence类型。这个类型用于获取和设置消息的内容:Value和Item:

  public interface IItemValueSetting {

  string getSubValue(string name);

  void setSubValue(string name, string value);

  }

  public interface IMessageItemSequence:IItemValueSetting, ICloneable

  {

  IMessageItem GetMessageItem(string aName);

  IMessageItem CreateMessageItem(string aName);

  }

  Value为字符串类型,它利用了HashTable存储Key和Value的键值对。Item则为IMessageItem类型,在IMessageItemSequence的实现类中,同样利用了HashTable存储Key和Item的键值对。

  IMessageItem支持消息体的嵌套。它包含了两部分:SubValue和SubItem。实现的方式和IMessageItemSequence相似。通过定义这样的嵌套结构,使得消息的扩展成为可能。一般的消息结构如下所示:

  IMessage——Name

  ——ID

  ——Body(IMessageItemSequence)

  ——Value

  ——Item(IMessageItem)

  ——SubValue

  ——SubItem(IMessageItem)

  ——……

  各个消息对象之间的关系如图6所示:

  图6 消息对象之间的关系

  在实现服务进程通信之前,我们必须定义好各个服务或各个业务的消息格式。通过消息体的方法在服务的一端设置消息的值,然后发送,并在服务的另一端获得这些值。例如发送消息端定义如下的消息体:

  IMessageFactory factory = new MessageFactory();

  IMessage message = factory.CreateMessage();

  message.SetMessageName(“service1”);

  IMessageItemSequence body = message.CreateMessageBody();

  body.SetSubValue(“subname1″,”subvalue1”);

  body.SetSubValue(“subname2″,”subvalue2”);

  IMessageItem item1 = body.CreateMessageItem(”item1”);

  item1.SetSubValue(“subsubname11″,”subsubvalue11”);

  item1.SetSubValue(“subsubname12″,”subsubvalue12”);

  //Send Request Message

  MyServiceClient service = new MyServiceClient(“Client”);

  service.SendRequest(message);

  我们在客户端引入了一个ServiceLocator对象,它通过MessageQueueListener对消息队列进行侦听,一旦接收到消息,就获取该消息中的name去定位它所对应的服务,然后调用服务的Execute(aMessage)方法,执行相关的业务。

  ServiceLocator承担的定位职责其实是对存储在ServiceContainer容器中的服务进行查询。ServiceContainer容器可以读取配置文件,在启动服务的时候初始化所有的分布式服务(注意,这些服务都是无状态的),并对这些服务进行管理。它封装了服务的基本信息,诸如服务所在的位置,服务的部署方式等,从而避免服务的调用者直接依赖于服务的细节,既减轻了调用者的负担,还能够较好地实现服务的扩展与迁移。

  在这个系统中,我们主要引入了Messaging模式,通过定义的IMessage接口,使得我们更好地对服务进行抽象,并以一种扁平的格式存储数据信息,从而解除服务之间的耦合。只要各个服务就共用的消息格式达成一致,请求者就可以不依赖于接收者的具体接口。通过引入的Message对象,我们就可以建立一种在行业中通用的消息模型与分布式服务模型。事实上,基于这样的一个框架与平台,在对制造行业的业务进行开发时,开发人员最主要的活动是与领域专家就各种业务的消息格式进行讨论,这样一种面向领域的消息语言,很好地扫清了技术人员与业务人员的沟通障碍;同时在各个子系统之间,我们也只需要维护服务间相互传递的消息接口表。每个服务的实现都是完全隔离的,有效地做到了对业务知识与基础设施的合理封装与隔离。

  对于消息的格式和内容,我们考虑引入了Message Translator模式,负责对前面定义的消息结构进行翻译和解析。为了进一步减轻开发人员的负担,我们还可以基于该平台搭建一个消息-对象-关系的映射框架,引入实体引擎(Entity Engine)将消息转换为领域实体,使得服务的开发者能够以完全面向对象的思想开发各个服务组件,并通过调用持久层实现消息数据的持久化。同时,利用消息总线(此时的消息总线可以看做是各个服务组件的连接器)连接不同的服务,并允许异步地传递消息,对消息进行编码。这样一个基于消息的分布式架构如图7所示:

  图7 基于Message Bus的CIMS分布式架构

  场景二:消息中间件的架构决策

  在一个医疗卫生系统中,我们面临了客户对系统性能/可用性的非功能需求。在我们最初启动该项目时,客户就表达了对性能与可用性的特别关注。客户希望最终用户在进行复杂的替换删除操作时,能够具有很好的用户体验,简言之,就是希望能够快速地得到操作的响应。问题在于这样的替换删除操作需要处理比较复杂的业务逻辑,同时牵涉到的关联数据量非常大,整个操作若需完成,最坏情况下可能需要几分钟的时间。我们可以通过引入缓存、索引、分页等多种方式对数据库操作进行性能调优,但整个操作的耗时始终无法达到客户的要求。由于该系统是在一个遗留系统的基础上开发,如果要引入Map-Reduce来处理这些操作,以满足质量需求,则对架构的影响太大,且不能很好地重用之前系统的某些组件。显然,付出的成本与收益并不成正比。

  通过对需求进行分析,我们注意到最终客户并不需要实时获得结果,只要能够保证最终结果的一致性和完整性即可。关键在于就用户体验而言,他们不希望经历漫长的等待,然后再通知他们操作究竟是成功还是失败。这是一个典型需要通过后台任务进行异步处理的场景。

  在企业应用系统中,我们常常会遭遇这样的场景。我们曾经在一个金融系统中尝试通过自己编写任务的方式来控制后台线程的并发访问,并完成对任务的调度。事实证明,这样的设计并非行之有效。对于这种典型的异步处理来说,基于消息传递的架构模式才是解决这一问题的最佳办法。

  因为消息中间件的逐步成熟,对于这一问题的架构设计,已经由原来对设计实现的关注转为如何进行产品选型和技术决策。例如,在.NET平台下,架构师需要重点考虑的是应该选择哪种消息中间件来处理此等问题?这就需要我们必须结合具体的业务场景,来识别这种异步处理方式的风险,然后再根据这些风险去比较各种技术,以求寻找到最适合的方案。

  通过分析业务场景以及客户性质,我们发现该业务场景具有如下特征:

  在一些特定情形下,可能会集中发生批量的替换删除操作,使得操作的并发量达到高峰;例如FDA要求召回一些违规药品时,就需要删除药品库中该药品的信息;

  操作结果不要求实时性,但需要保证操作的可靠性,不能因为异常失败而导致某些操作无法进行;

  自动操作的过程是不可逆转的,因此需要记录操作历史;

  基于性能考虑,大多数操作需要调用数据库的存储过程;

  操作的数据需要具备一定的安全性,避免被非法用户对数据造成破坏;

  与操作相关的功能以组件形式封装,保证组件的可重用性、可扩展性与可测试性;

  数据量可能随着最终用户的增多而逐渐增大;

  针对如上的业务需求,我们决定从以下几个方面对各种技术方案进行横向的比较与考量。

  并发:选择的消息队列一定要很好地支持用户访问的并发性;

  安全:消息队列是否提供了足够的安全机制;

  性能伸缩:不能让消息队列成为整个系统的单一性能瓶颈;

  部署:尽可能让消息队列的部署更为容易;

  灾备:不能因为意外的错误、故障或其他因素导致处理数据的丢失;

  API易用性:处理消息的API必须足够简单、并能够很好地支持测试与扩展;

  我们先后考察了MSMQ、Resque、ActiveMQ和RabbitMQ,通过查询相关资料,以及编写Spike代码验证相关质量,我们最终选择了RabbitMQ。

  我们选择放弃MSMQ,是因为它严重依赖Windows操作系统;它虽然提供了易用的GUI方便管理人员对其进行安装和部署,但若要编写自动化部署脚本,却非常困难。同时,MSMQ的队列容量不能查过4M字节,这也是我们无法接收的。Resque的问题是目前仅支持Ruby的客户端调用,不能很好地与.NET平台集成。此外,Resque对消息持久化的处理方式是写入到Redis中,因而需要在已有RDBMS的前提下,引入新的Storage。我们比较倾心于ActiveMQ与RabbitMQ,但通过编写测试代码,采用循环发送大数据消息以验证消息中间件的性能与稳定性时,我们发现ActiveMQ的表现并不太让人满意。至少,在我们的询证调研过程中,ActiveMQ会因为频繁发送大数据消息而偶尔出现崩溃的情况。相对而言,RabbitMQ在各个方面都比较适合我们的架构要求。

  例如在灾备与稳定性方面,RabbitMQ提供了可持久化的队列,能够在队列服务崩溃的时候,将未处理的消息持久化到磁盘上。为了避免因为发送消息到写入消息之间的延迟导致信息丢失,RabbitMQ引入了Publisher Confirm机制以确保消息被真正地写入到磁盘中。它对Cluster的支持提供了Active/Passive与Active/Active两种模式。例如,在Active/Passive模式下,一旦一个节点失败,Passive节点就会马上被激活,并迅速替代失败的Active节点,承担起消息传递的职责。如图8所示:

  图8 Active/Passive Cluster(图片来自RabbitMQ官方网站)

  在并发处理方面,RabbitMQ本身是基于erlang编写的消息中间件,作为一门面向并发处理的编程语言,erlang对并发处理的天生优势使得我们对RabbitMQ的并发特性抱有信心。RabbitMQ可以非常容易地部署到Windows、Linux等操作系统下,同时,它也可以很好地部署到服务器集群中。它的队列容量是没有限制的(取决于安装RabbitMQ的磁盘容量),发送与接收信息的性能表现也非常好。RabbitMQ提供了Java、.NET、Erlang以及C语言的客户端API,调用非常简单,并且不会给整个系统引入太多第三方库的依赖。 例如.NET客户端只需要依赖一个程序集。

  即使我们选择了RabbitMQ,但仍有必要对系统与具体的消息中间件进行解耦,这就要求我们对消息的生产者与消费者进行抽象,例如定义如下的接口:

  public interface IQueueSubscriber

  {

  void ListenTo(string queueName, Action action);

  void ListenTo(string queueName, Predicate messageProcessedSuccessfully);

  void ListenTo(string queueName, Predicate messageProcessedSuccessfully, bool requeueFailedMessages);

  }

  public interface IQueueProvider

  {

  T Pop(string queueName);

  T PopAndAwaitAcknowledgement(string queueName, Predicate messageProcessedSuccessfully);

  T PopAndAwaitAcknowledgement(string queueName, Predicate messageProcessedSuccessfully, bool requeueFailedMessages);

  void Push(FunctionalArea functionalArea, string routingKey, object payload);

  }

  在这两个接口的实现类中,我们封装了RabbitMQ的调用类,例如:

  public class RabbitMQSubscriber : IQueueSubscriber

  {

  public void ListenTo(string queueName, Action action)

  {

  using (IConnection connection = _factory.OpenConnection())

  using (IModel channel = connection.CreateModel())

  {

  var consumer = new QueueingBasicConsumer(channel);

  string consumerTag = channel.BasicConsume(queueName, AcknowledgeImmediately, consumer);

  var response = (BasicDeliverEventArgs) consumer.Queue.Dequeue();

  var serializer = new JavaScriptSerializer();

  string json = Encoding.UTF8.GetString(response.Body);

  var message = serializer.Deserialize(json);

  action(message);

  }

  }

  }

  public class RabbitMQProvider : IQueueProvider

  {

  public T Pop(string queueName)

  {

  var returnVal = default(T);

  const bool acknowledgeImmediately = true;

  using (var connection = _factory.OpenConnection())

  using (var channel = connection.CreateModel())

  {

  var response = channel.BasicGet(queueName, acknowledgeImmediately);

  if (response != null)

  {

  var serializer = new JavaScriptSerializer();

  var json = Encoding.UTF8.GetString(response.Body);

  returnVal = serializer.Deserialize(json);

  }

  }

  return returnVal;

  }

  }

  我们用Quartz.Net来实现Batch Job。通过定义一个实现了IStatefulJob接口的Job类,在Execute()方法中完成对队列的侦听。Job中RabbitMQSubscriber类的ListenTo()方法会调用Queue的Dequeue()方法,当接收的消息到达队列时,Job会侦听到消息达到的事件,然后以同步的方式使得消息弹出队列,并将消息作为参数传递给Action委托。因此,在Batch Job的Execute()方法中,可以定义消息处理的方法,并调用RabbitMQSubscriber类的ListenTo()方法,如下所示(注意,这里传递的消息事实上是Job的Id):

  public void Execute(JobExecutionContext context)

  {

  string queueName = queueConfigurer.GetQueueProviders().Queue.Name;

  try

  {

  queueSubscriber.ListenTo(

  queueName,

  job => request.MakeRequest(job.Id.ToString()));

  }

  catch(Exception err)

  {

  Log.WarnFormat(“Unexpected exception while processing queue ‘{0}’, Details: {1}”, queueName, err);

  }

  }

  队列的相关信息例如队列名都存储在配置文件中。Execute()方法调用了request对象的MakeRequest()方法,并将获得的消息(即JobId)传递给该方法。它会根据JobId到数据库中查询该Job对应的信息,并执行真正的业务处理。

  在对基于消息处理的架构进行决策时,除了前面提到的考虑因素外,还需要就许多设计细节进行多方位的判断与权衡。例如针对Job的执行以及队列的管理,就需要考虑如下因素:

  对Queue中Job状态的监控与查询;

  对Job优先级的管理;

  能否取消或终止执行时间过长的Job;

  是否能够设定Job的执行时间;

  是否能够设定Poll的间隔时间;

  能否跨机器分布式的放入Job;

  对失败Job的处理;

  能否支持多个队列,命名队列;

  能否允许执行Job的工作进程对应特定的队列;

  对Dead Message的支持。

  3、选择的时机

  究竟在什么时候,我们应该选择基于消息处理的分布式架构?根据我参与的多个企业应用系统的经验,窃以为需要满足如下几个条件:

  对操作的实时性要求不高,而需要执行的任务极为耗时;

  存在企业内部的异构系统间的整合;

  服务器资源需要合理分配与利用;

  对于第一种情况,我们常常会选择消息队列来处理执行时间较长的任务。此时引入的消息队列就成了消息处理的缓冲区。消息队列引入的异步通信机制,使得发送方和接收方都不用等待对方返回成功消息,就可以继续执行下面的代码,从而提高了数据处理的能力。尤其是当访问量和数据流量较大的情况下,就可以结合消息队列与后台任务,通过避开高峰期对大数据进行处理,就可以有效降低数据库处理数据的负荷。前面提到的医疗卫生系统正是这样一种适用场景。

  对于不同系统乃至于异构系统的整合,恰恰是消息模式善于处理的场景。只要规定了消息的格式与传递方式,就可以有效地实现不同系统之间的通信。在为某汽车制造商开发一个大型系统时,分销商作为.NET客户端,需要将数据传递到管理中心。这些数据将被Oracle的EBS(E-Business Suite)使用。分销商管理系统(Dealer Management System,DMS)采用了C/S结构,数据库为SQL Server,汽车制造商管理中心的EBS数据库为Oracle 10g。我们需要解决两种不同数据库间数据的传递。解决方案就是利用MSMQ,将数据转换为与数据库无关的消息数据,并在两端部署MSMQ服务器,建立消息队列以便于存储消息数据。实现架构如图9所示。

  图10 利用MSMQ实现的分布式处理架构

  首先,分销商的数据通过MSMQ传递到MSMQ Server,再将数据插入到SQL Server数据库的同时,利用FTP将数据传送到专门的文件服务器上。EBS App Server会将文件服务器中的文件,基于接口规范写入到Oracle数据库,从而实现.NET系统与Oracle系统之间的整合。

  分布式系统通常能够缓解单个服务器的压力,通过将不同的业务操作与数据处理以不同的服务形式部署并运行在不同的服务器上,就可以有效地分配与利用服务器资源。在这种情况下,部署在不同服务器上的服务,既可能作为服务端,用以处理客户端调用的请求,也可能作为客户端,在处理完自己的业务后,将其余业务请求委派给其他服务。在早期的CORBA系统中,通过建立统一的Naming Service,用以管理和分派服务,并通过Event Service实现事件的分发与处理。但CORBA系统采用的是RPC的方式,需要将服务设计和部署为远程对象,并建立代理。如果通过消息通道的方式,则既可以解除这种对远程对象的依赖,又可以很好地支持异步调用模型。在前面提到的CIMS系统,就是通过消息总线提供消息传递的基础设施,并建立统一的消息处理服务模型,解除服务见的依赖,使得各个服务能够独立地部署到不同服务器上。

  4、面临的困难

  由于消息模式自身的特殊性,我们在运用消息模式建立基于消息的分布式架构时,常常会面临许多困难。

  首先是系统集成的问题。由于系统之间的通信靠消息进行传递,就必须保证消息的一致性,同时,还需要维护系统之间(主要是服务之间)接口的稳定性。一旦接口发生变化,就可能影响到该接口的所有调用者。即使服务通过接口进行了抽象,由于消息持有双方服务规定的业务数据,在一定程度上违背了封装的要义。换言之,生产与消费消息的双方都紧耦合于消息。消息的变化会直接影响到各个服务接口的实现类。然而,为了尽可能保证接口的抽象性,我们所要处理的消息都不是强类型的,这就使得我们在编译期间很难发现因为消息内容发生变更产生的错误。在我之前提到的汽车零售商管理系统就存在这样的问题。当时我负责的CRM模块需要同时与多个子系统进行通信,而每个子系统又是由不同的团队进行开发。团队之间因为沟通原因,常常未能及时地同步接口表。虽然各个子系统的单元测试和功能测试都已通过,但直到对CRM进行集成测试,才发现存在大量消息不匹配的集成问题,这些问题的起因都是因为消息的变更。

  解决的方案是引入充分的集成测试,甚至是回归测试,并需要及时运行这些测试,以快速地获得反馈。我们可以将集成测试作为提交代码的验证们,要求每次提交代码都必须运行集成测试与指定的回归测试 。这正是持续集成的体现。通过在本地构建与远程构建运行集成测试与回归测试,有效地保证本地版本与集成后的版本不会因为消息的改变使得功能遭受破坏。一旦遭受破坏,也能够及时获得反馈,发现问题,即刻解决这些问题,而不是等到项目后期集中进行集成测试。

  另一个问题是后台任务的非实时性带来的测试困难。由于后台任务是定期对消息队列中的消息进行处理,因而触发的时机是不可预测的 。对于这种情况,我们通常会同时运用两种方案,双管其下地解决问题。首先,我们会为系统引入一个同步实现功能的版本,并通过在配置文件中引入toggle的开关机制,随时可以在同步功能与异步功能之间进行切换。如果我们能够保证消息队列处理与后台任务执行的正确性,就可以设置为同步功能,这样就能快速而准确地对该任务所代表的功能进行测试,并及时收获反馈。同时,我们可以在持续集成服务器上建立一个专门的管道(pipeline),用以运行基于消息处理的异步版本。这个管道对应的任务可以通过手动执行,也可以对管道设置定时器,在指定时间执行(例如在凌晨两点执行一次,这样在第二天开始工作之前可以获得反馈)。我们需要为该管道准备特定的执行环境,并将后台任务的侦听与执行时间修改为可以接受的值。这样既能够及时了解功能是否正确,又能保证基于消息的系统是工作正常的。

  当然,分布式系统还存在解析消息、网络传递的性能损耗。对于这些问题,需要架构师审慎地分析业务场景,正确地选择架构方案与架构模式。相比较本地系统而言,分布式系统的维护难度可能成倍递增。这既需要我们在进行架构决策与设计时,充分考虑系统架构的稳定性,同时还需要引入系统日志处理。更好的做法是为日志处理增加错误通知的功能,只要发生消息处理的错误信息,就通过邮件、短信等方式通知系统管理员,及时地处理错误。因为只有在发生错误的当时查询错误日志,才能够更好对问题进行定位。同时,还可以为系统引入Error Message Queue以及Dead Message Queue,以便于处理错误和异常情况。

  对于分布式系统而言,还需要考虑服务执行结果的一致性,尤其是当某个业务需要多个服务参与到一个会话中时,一旦某个服务发生故障,就可能导致应用出现状态不一致的情况,因为只有所有参与者都成功执行了任务,才能视为完全成功。这就牵涉到分布式事务的问题,此时任务的执行就变成了事务型的:即任务必须是原子的,结果状态必须保持一致。在任务处理过程中,状态修改是彼此隔离的,成功的状态修改在整个事务执行过程中是持久的。这就是事务的ACID(Atomic,Consistent,Isolated与Durable)属性。

  一种方案是引入分布式事务协调器,即DTC(Distributed Transaction Coordinator),将事务分为两段式甚至三段式提交,要求整个事务的所有参与者以投票形式决定事务是完全成功还是失败。另一种方案是降低对结果一致性的要求。根据eBay的最佳实践,考虑到分布式事务的成本,获得分布式资源即时的一致性是不必要的,也是不现实的。在Randy Shoup的文章《可伸缩性最佳实践:来自eBay的经验》中提到了Eric Brewer的CAP公理:分布式系统的三项重要指标——一致性(Consistency)、可用性(Availability)和 分区耐受性(Partition-tolerance)——在任意时刻,只有两项能同时成立。我们应该根据不同的应用场景,权衡这三个要素。在不必要保证即时的一致性前提下,我们可以考虑合理地划分服务,尽量将可能作用在同一个事务范围的业务操作部署在同一个进程中,以避免分布式部署。如果确实需要多个分布式服务之间保持执行结果的一致,可以考虑引入数据核对,异步恢复事件或集中决算等手段。

不可不知的10款开源IT安全工具

  1. Nessus

  Nessus

  从许多方面来看,搞好安全先要了解情形。对于几代IT安全专业人士来说,了解网络漏洞始于Tenable的Nessus。据sectools.org声称,Nessus是最受欢迎的漏洞扫描器,也是目前使用的第三大流行安全程序。

  Nessus有免费版和商业版。目前的版本Nessus 7.1.0是一款商业程序,不过也有供个人家庭使用的免费版。版本2仍然是开源免费的。

  Tenable维护版本2;不过按照开源软件的最佳传统,它有了分叉,并往几个不同的方向发展。Nessus专长仍是一项宝贵的职业技能。

  链接:http://repository.slacky.eu/slackware-12.1/network/nessus/2.2.11/

  2. SNORT

  SNORT

  正如成千上万的IT安全专业人员最初从Nessus了解漏洞扫描一样,Snort也是好几代安全专业人员最初了解入侵检测系统(IDS)的起点。

  Snort的部分价值在于,它可以配置成三种不同的模式:网络嗅探器、数据包记录器或标准的IDS。正因为如此,它可能是自动化安全系统的核心,也可能是与一系列商业产品搭配使用的组件。

  现在Snort归思科拥有,继续发展,并由活跃的社区开发。社区开发的IDS规则可以使用,采用商业许可证的规则也可以使用。谈论开源安全软件,免不了提到Snort,它是这个行业和市场的重要成员。

  链接:https://www.snort.org/downloads#snort-downloads

  3. Nagios

  Nagios

  Nagios监控网络:基础设施、流量和连接的服务器都属于其基本功能或扩展功能的监控对象。与其他许多开源软件包一样,Nagios也有免费版和商业版。

  Nagios Core是开源项目的核心,基于免费的开源版本。它可以由插件来监控单个产品,执行单项任务;Nagios开发了大约50个“官方”插件,社区贡献了3000多个插件。

  Nagios的用户界面可以通过桌面、Web或移动平台的前端进行改动,可通过其中一款可用的配置工具来管理配置。

  链接:https://www.nagios.org/projects/nagios-core/

  4. Ettercap

  Ettercap

  如果你需要测试企业网络抵御中间人攻击(MITM)的本领,那么Ettercap是适合你的工具。自2001年首次发布以来,该软件一直专心做一件事:发动MITM攻击。

  Ettercap有四种基本的攻击模式:基于IP的策略、基于MAC的策略和两种基于ARP的策略。你可以决定探查哪种类型的漏洞,看看环境如何响应每种漏洞。

  在扫描测试攻击的过程中,Ettercap可以提供关于网络及其设备的大量信息。作为整体安全工具包的一部分,Ettercap为MITM攻击提供了强大的功能,有力地补充了分析和可见性功能。

  链接:https://www.ettercap-project.org/

  5. Infection Monkey

  Infection Monkey

  Infection Monkey是一个相当全面的测试工具,旨在向你表明一旦攻击者成功突破边界,网络内可能发生的情况。Infection Monkey由GuardiCore开发和支持,它是免费的,且功能齐全。

  用户界面是Infection Monkey的显著特点之一。虽然一些开源安全项目提供精简的UI或依赖插件或皮肤来提供GUI,但Infection Monkey拥有与许多商业软件工具媲美的GUI。

  Infection Monkey的源代码放在GitHub上,活跃的开发者社区支持该项目。其他工具有助于检查你的防御体系有无漏洞;Infection Monkey可以向你表明为何应加强整个基础设施。

  链接:http://www.guardicore.com/infectionmonkey/

  6. Delta

  Delta

  有许多工具用来测试传统网络上的安全。然而,测试可能给软件定义网络(SDN)带来的安全问题是个新兴领域,这就是Delta很重要的原因。

  Delta是开放网络基金会(ONF)的一个项目,寻找SDN中的潜在问题,然后探测问题,帮助确定它们的安全隐患有多大。借助内置的模糊测试功能,Delta旨在探测是否存在已知和未知的网络漏洞。

  立足于ONF之前的项目Florence和Poseidon的基础上,Delta的代码和可执行文件放在GitHub上,仍在迅速开发之中。

  链接:https://www.opennetworking.org/incubator-projects/delta/

  7. Cuckoo

  Cuckoo

  有很多方法可以查明某文件是不是恶意文件,但许多方法存在一定的风险。Cuckoo Sandbox是一种开源框架,用于安全地测试文件以查明其性质,以及如果它在你的环境中启动,实际上会执行怎样的操作。

  源代码放在GitHub上,Cuckoo Sandbox可以在各种不同的操作系统下分析文件和网站。它对API和网络流量进行分析,并进行全面的内存转储,以便详细分析被测试的软件在哪里运行、是否在企图越界运行。

  链接:https://cuckoosandbox.org/

  8. Sleuth Kit

  Sleuth Kit

  弄清楚攻击中发生了什么是防止未来入侵的关键步骤。 Sleuth Kit是一套基于CLI的工具和库,让调查人员可以深入钻研以各种格式和条件格式化的硬盘里面的内容。

  Sleuth Kit是Autopsy的基础,后者是一个GUI前端,以便大多数用户更快速更轻松地分析。两者都在积极开发之中,拥有庞大而活跃的用户群体,为新的功能特性做贡献。

  链接:https://www.sleuthkit.org/

  9. Lynis

  Lynis

  Lynis是一款制作列表的工具,列出它在基于Unix的系统上找到的应用程序和实用工具,列出那些系统的版本,以及列出它在每个系统的代码或配置中找到的安全漏洞。

  源代码放在GitHub上,Lynis有一个活跃的开发社区,主要支持来自开发者Cisofy。 Lynis的特殊功能之一是,由于基于Unix,它能够对流行的物联网开发板(包括Raspberry Pi)进行扫描和评估。

  链接:https://cisofy.com/lynis/

  10. Cerbot

  Cerbot

  加密对许多安全标准而言很重要,包括近下的新宠儿:GDPR。实施加密可能很复杂、成本高,但电子前沿基金会(EFF)试图借助像Certbot这样的工具来降低难度和成本,Certbot是一款开源自动客户软件,可以为你的Web服务器提取和部署SSL/TLS证书。

  Certbot一开始是Let’s Encrypt的前端,但已成为支持ACME协议的任何证书管理机构(CA)的客户软件。

  Certbot项目是EFF旨在“加密互联网”的一部分,这个目标已得到了许多隐私倡导者和政府监管机构的积极响应。确保员工、合作伙伴和客户安全既是有价值的目标,又是法律责任。文中讨论的这些开源工具有助于往这个方向迈出可喜的一步。