Architecture

为什么要做模块分拆?

为什么大类要拆成小类?为什么要把系统拆成多个模块? 以下列举出的原因都是老生常谈,对很多人来说都是直觉的东西; 直觉的坏处是:如果它成直觉了,你反而说不出它有什么好处了,当争论来临时,你就无法说服你的同事了。 1. 大变小,能使部分功能可重用。如果所有功能的揉在一起,那一般就没有专们针对某小块功能的接口,外界无法直调这块功能,也就无法重用。 2. 模块拆分,实际上强制实施了封装性,使得模块之间只能通过有限的接口互相访问,从而降低耦合。耦合的意思是:一样东西变时,另一样东西不必变;拆分后,一个模块的变动如果不影响接口,其它的模块就不用变; 如果未拆分,一个功能可能全身都是接口,每处都有可能被其他功能依赖,自己任意一个小变都会导致其他模块跟着变。 3. Separation of Concern, 即分离关注点,每个团队、每个人只需要关注、维护系统的一部分模块或一个子系统。这在大中型组织中非常重要,对开发效率的影响是全方位的。它可以降低开发人员的系统知识门槛,并使得每个模块都有相应的精熟者,对开发效率的影响不言而喻。 4. 最后,模块拆分对开发环境、配置管理、运行维护也很重要。如果一个代码库过大,svn checkout一下,编译一下,可能半小时就过去了; 如果系统未做拆分,一个哥们提交了一行错误代码导致编译错误,会使得整个团队都无法继续开发; 如果整个应用作为单个进程运行,那么某块功能一挂,整个应用都挂,比如某个垂直频道压力过大,导致进程异常退出,那么压力不大的其他频道也没法访问了。

长连接交互中的连接恢复与异常处理

1. 客户端应该有自动重连机制,或者,在需要发送业务数据前如果发现连接已断开,则重连。 2. 有一种极端的情况是,服务端出问题关闭连接,客户端立即重连,由于服务端问题还没修复,客户端重连上后马上又断开。。。如果并发很高的话,服务端会不堪重负。 解决办法是客户端应逐渐重连间隔,第二次重连在第一次5秒后,第三次在10秒后,第N次在1分种后。。。 3. 服务端出现异常如何处理?如果确定当前处于request/reply语境下,则应该回送当前操作相应的错误报文(就像web服务器的http 500); 否则,应该关闭连接。为什么这时要关闭连接?    a. 如果你写一个错误报文给客户端,那么客户端可能并不知道这个错误针对的是哪个请求,因为客户端可能并没有主动请求,是服务端主动发下行消息时才出的错。    b. 如果你不关闭连接,什么也不干,客户端可能会陷入无限等待,因为当前可能正处于request/reply语境下,服务端却不知道。 比如,netty handler中执行到exceptionCaught()方法时,并不知道自己处于哪种语境下。

异步程序的难搞之处

1. 任务完成的顺序与代码被执行的顺序未必相同。 先调用asyncA(), 再调用asyncB(),但最后可能是B任务率先完成。 2. 有时候不知道代码走到当前此处的根本原因,给调试带来麻烦。 同步程序中,总是可以在调用栈的底部找到触发当前操作的原因,而异步程序中,走到这里可能是因为另一个线程做了什么操作,修改了公有变量,导致本线程走到这里; 那么是哪个线程做什么操作,需要对代码非常熟悉才知道。 3. Callback中出了异常,主线程往往不知道,写在主线程代码里的异常处理机制没被触发。 待续 。。。

服务端开发中的两种“异步”

服务端开发时经常会用到“异步”这个概念。要注意你说的是哪种异步,否则可能会导致沟通的误会,甚至设计的失误。 1. 客户端和服务端之间的异步交互。 客户端发出一个业务请求,服务端在这个业务请求完成之前就发出响应,然后再在后台进行处理。 2. 服务端处理请求时,在内部使用异步模式。 比如收到请求时将它丢给一个线程池来处理。 请求处理中可以同时使用这两种异步,也可以只用一种。 对于http来说,一般是“交互要同步”,但“服务端内部处理要异步”。I/O线程收到请求后,立即丢给worker线程池处理;但在worker线程处理完请求、回送响应之前,客户端要处于阻塞状态,等待响应;服务端内部使用异步处理,对客户端并没有什么好处,但它可以提高服务端自身的吞吐率。 一个常见的失误就是,一味想着“异步”,结果把一些本该同步的c/s交互也变成了异步。 最近我在处理websocket握手逻辑时就遇到这样一个问题。我们基于Netty在握手时进行了用户验证。Netty的握手API采用异步编程模型,于是我们就把验证的逻辑放到了握手API的callback中;结果 验证逻辑还没执行完,客户端就收到“握手成功”的通知,然后发出正常的业务请求。解决办法是先验证,再执行握手;如果验证逻辑采用了异步编程模型,则可以把握手API放到验证逻辑的callback中。

中间件开发:不要阻塞I/O线程

当某块逻辑出现问题,导致程序阻塞时,可能会导致以下两种情况之一: 1. 如果这块逻辑由中间件的I/O线程负责处理,那么会导致I/O线程处理 2. 如果这块逻辑不是由I/O线程负责处理,则bla bla 一定要避免第一种情况。虽然对这块逻辑来说,两种选项都是程序被阻塞,业务未被处理;但在第二种选项中,指向其他健康逻辑的请求还是可以被中间件所接受请工作。 在第一种选项中,极端情况下所有I/O线程都被会被阻塞,导致指向其他健康逻辑的请求都进不来,整个系统彻底不可用。 所以,I/O线程一般不要用来处理业务逻辑。 业务逻辑应该交由专门的线程池来处理。

关于pipeline架构的几点说明

最近要将系统改造成pipeline架构,所以研究了一下pipeline相关的理论,并看了看servlet filter, struts2 interceptor, spring mvc interceptor, webx pipeline和netty pipeline的一些源码。 这里总结一些小点: 结构: 1. Pipeline的类模型由Pipeline, Valve 和 Context 组成。 Pipeline代表一个执行流,Valve代表执行流中的一个节点,Context是执行时的上下文信息,它一般由两部分组成: request/response + 当前流的执行状态。  2. 有的系统只能有一个Pipeline, 有的则允许配多个。 在struts2中,一种interceptor的组合,就代表了一个Pipeline;多种组合,意味着多个Pipeline. 用法: 1. 有的pipeline流是在同一层次上,每个valve处理的request/response对象基本上是同质的。比如servlet filter和各种mvc框架,所处理的事情都是web层的,所处理的context对象就是http request或框架自定义的javabean. 2. 有的pipeline流则是纵向的,从上层流到下层,或从下层流到上层,这时每个valve所处理的request/response对象一般是异构的。协议栈就是个典型的例子:下层valve处理字节流,上层Valve处理字符流。 不过,为了适应pipeline架构,这些异构的context对象必须有共同的基类,并且把这个基类作为各个valve的输入参数。 3. 从请求处理的角度来说, Pipeline可以代表整个执行流,也可以只用作请求被最终处理前的Interceptor.  Webx, netty中的Pipeline会将最后一个Valve作为请求处理者(一般称为Request Handler),而servlet filter和struts2 interceptor只作请求拦截、加工,真正的请求处理者则是servlet和action. 程序流: 1. 一个完整的Pipeline执行流一般是个环路: Valve1 => Valve2 => Valve3 => Valve2 => Valve1. 从拦截的角度说,valve既是前置拦截(下一个valve执行前),又是后置拉截(下一个 …

关于pipeline架构的几点说明 Read More »

OAuth的核心作用

OAuth的核心作用: 用户不必在第三方网站上输入OPEN ID及其密码,否则第三方网站可以把你的用户名、密码偷偷地保存起来。