应用架构设计“防火”经验分享
Author : 岑文初(淘宝花名:放翁)
Email: fangweng@taobao.com
Blog: http://blog.csdn.net/cenwenchu79
Date: 2009-08-26
刚从阿软到淘宝不久,现在主要负责TOP平台的技术框架设计,同时要肩负“救火”和“防火”的工作,也需要培养团队的同学能够有“防火”意识,减少“救火”次数,因此今天下午花了一点时间,也没于写任何的PPT,就直接将自己想的起来的一些自己认为应用架构设计“防火”知识做了一下事例分享,这里也想记录下来给更多的同学分享一下,当然很多都是老生常谈的常识,但是有时候不经意就会忘记一些血的教训。
资源是有限的
着火点:
系统设计的时候总是估摸不到会有大数据量从远端传输过来(例如处理Http请求时,对于大附件内容的处理,全部装载到内存,结果资源耗尽。从搜索引擎或者DB或者缓存里面拉数据,没有分页,结果内存被吃尽。Socket无限建立连接,结果linux的文件句柄被耗尽。)
防火点:
对业务场景中资源的分配与申请需要做到上限控制,以及达到上限以后的逻辑处理(排队,丢弃,告警)。可以采取一些滑动窗口设计来将不需要过多处理的内容分段直接送入下一个处理管道中。
依赖是未知的
着火点:
事务中嵌入对于第三方系统的请求(例如在数据库操作时去发送邮件或者缓存获取内容,结果连接池资源被Hold,导致系统不可用)。默认依赖系统会给出结果,如果出现异常就反复重试,结果对方被压垮,自己也牺牲。
防火点:
对于第三方系统的依赖能够异步的就采用异步方式,能够从主流程中剥离的就剥离。同时设计好容错的机制,采用本地时效性缓存减少对对方的压力和依赖。最重要的就是注意系统间的死锁,申请了一套资源处理业务逻辑,结果由于远端系统的不可用,导致本地资源的无法释放,最后击垮自己系统。
线程安全与性能
着火点:
对于线程不安全的对象处理一定要小心,否则业务出现异常的地方其实已经离设计出现问题的地方十万八千里,问题时常成为灵异问题,解决只有靠经验。
防火点:
首先对于自己设计的类和方法需要注释是否是线程安全的。同时明确类的使用场景,对线程安全及高性能作判断,因为采用线程安全的对象一定会有性能损耗。最近给同学写的一个Http消息的Lazy获取参数,就是线程不安全的类,但是这个类只会保存在ThreadLocal中,因此不存在问题。很直观的一点判断是否线程安全,就看看你设计的类里面的成员变量在多线程操作时候是否会有并发问题,例如一个普通的Map,多个线程操作就会导致结果的不可估量性。
资源释放
着火点:
正常逻辑都会将IO流关闭,Socket关闭,但是异常抛出时候,没有走到资源释放的流程中,产生了资源泄露问题。另外,资源中可能会有内嵌资源,当内部资源被外部的对象引用,则释放将不成功,内部资源依然会泄露。一些需要显式回收的资源(例如ThreadLocal),如果不回收,那么下次线程被操作系统重用,则会出现莫名其妙的问题(Java的线程创建和使用依赖于操作系统的实现)。
防火点:
Finally的处理。需要释放的资源要做深度检查。需要显式回收的资源要确保使用完毕以后被回收(异常情况也需要考虑)。
创建与复用
着火点:
在以前设计Cache客户端的时候,有同学给我建议说我对于字节数组利用可以采取复用的方式,这样可以减少对象的申请。但是做了一下测试,这样的重复利用其实效果不像想象的那么好,甚至还不如直接创建。
防火点:
Java的垃圾收集器已经在性能上有了很大的提高,同时对于对象的复用需要考虑对象复用前的初始化或者是内容重置,这些得成本及复杂度可能远远要高于复用带来的优势,因此需要根据具体的业务场景选择复用和创建。当然对于稀缺资源采用池的方式是最好的。
字符串处理,日志级别的选择
着火点:
这两个是小问题,但是会带来大麻烦。首先字符串的累加是老生常谈的问题,但是很多新手不以为然,当你是一个高速运转的系统时,你就会发现1ms的延时在上千万次调用下回被无限放大,10byte的申请,在上千万次的请求下会带来GC多次的操作(带来的短暂处理停滞直接影响系统的可用性)。日志级别的随意性会导致线上环境日志迅速膨胀,出错难以查找,影响系统的效率。(log4j优化的再好也是要写文件的,虽然是异步刷页)
防火点:
谨慎处理字符串拼接,选择线程安全或者不安全的两个StringBuilder和StringBuffer。日志尽量区分清楚,debug和Info,前者纯粹调试,可以有海量信息,Info一般用于系统或者模块的状况报告。Warn通常不建议使用了。Error就把你需要的关键信息都打出来。附带这里说一下对于日期对象的处理,在传输和保存的过程中,建议都还是采用long型,可以很好的提高性能及满足国际化的需求。
原子操作与并发控制
着火点:
对于本地的对象操作通常情况下通过锁机制保证并发的一致性,当在设计一个对于资源访问控制的策略时,例如集群应用处理某人每天发送短信1000条,这时候计数器保存在远端的集中式缓存中,采用get和put方式就会有并发问题,因为在应用获得到999这个计数器值的时候,也许正有10000个请求也获得了这个值,这样原有的控制就失效了。
防火点:
其实就是一个原子操作的支持,本地数据可以通过锁来达到原子操作,远程依赖就需要对方系统提供原子操作接口来实现高并发下的业务处理,例如Memcached Cache提供的incr 和decr。结合黑名单策略,计数器可以发挥很多用途,包括及时监控告警等。
接口实现与松耦合
着火点:
没有接口提供,团队间合作困难,无法Mock,相互之间进度影响很大。同时业务实现的修改直接影响业务调用方,使得双方耦合性很强,系统不稳定性被放大。
防火点:
对外提供的服务,或者模块间交互的服务都需要接口化。框架性代码需要在模块载入时考虑是否需要接口化定义,以便在不同环境可以切换不同实现提供对特殊场景的支持,同时也可以将具体实现延后交给使用者实现,使得框架更加灵活。Jdk对于xml的解析就是最好的范例。
灵活性和性能和可维护性的折中
着火点:
最近看了一些同学的代码,看到大量的使用了反射,拦截器等。但是在线上环境运行过程中就发现对于一些拦截器的配置疏漏导致系统性能大幅度降低。对于几十个spring文件,有谁能够很清楚和直观的了解到这些看似灵活和无侵入性的设计。
防火点:
对于业务逻辑不复杂,同时场景不多变的流程采用简单的实现,不要追求花哨的灵活性,带来的只会是可读性,可维护性,可用性的降低。
要有分布式和并发的观念,但是不要本末倒置
着火点:
有些同学在做设计的时候考虑的很清晰,但是就是没有考虑集群部署的情况,结果系统上线以后出现了无法集群部署的问题。并发情况的设计也一样,仅仅在满足业务需求以后,对于多用户并发操作的考虑缺失,导致系统流程错误。
防火点:
设计的时候需要适度考虑这些问题,但是是在满足现有业务逻辑的前提下,不要为了追求分布式而分布式。
便利性的函数与性能的冲突
着火点:
首先申明的是这点适用范围有限(高速运转的模块)。对于String,Date等对象的便利性函数,例如正则匹配,分割,Format等等其实都会有不少的性能损耗。例如你只是需要判断文件名最后的后缀是否满足需求,采用了正则匹配,结果发现性能在高速运转的情况下大大下降。
防火点:
高速运转的模块尽量采用原始方式或者半原始方式。例如上面说到的文件后缀,就用string的endwith来判断。对于一些字符串的替换,能够用字符串拼接就拼接。对于一些字节流的处理也可以自己根据需求来订制的写。总的一句话,能够满足的就用最低成本的方法。
防止系统设计的完备性成为攻击或者压力的瓶颈
着火点:
在很多设计的时候,对于一些系统设计讲究比较完美。例如对于对象的查询会分本地缓存,集中缓存,DB三个阶段。当对方攻击采用不存在的资源名称时候,这种分阶段的设计反而会增加系统负荷。
防火点:
简化流程的分支和层次,对于消耗性资源的访问尽量减少或者没有(采用黑名单本地缓存或者集中缓存的方式),同时改Pull为Push方式,通过控制数据变更点来通知相关系统,而非轮询获取更新状态。
多级缓存和异步缓解异构系统的瓶颈
着火点:
有时候设计系统时,服务提供方向我们许诺说对方系统如何高效和健壮,但是当频繁访问产生网络风暴的时候,我们发现原来带宽和网络IO本身都会成为瓶颈。
防火点:
对于第三方系统的依赖,要做到松耦合就要从流程的异步化来实现。同时通过缓存的使用来达到,系统的高效性和降低对于第三方系统的依赖程度。这样可以大大降低系统的瓶颈。
运行期白盒化,模块可重置
着火点:
系统运行起来以后就无法在知道内部的状态,也无法对问题组件进行单独处理,造成线上环境的不可知性和无法部分修复。不得不停机重起和看日志。
防火点:
模块设计过程中考虑运行期可观测和可重置,提高系统的模块化程度及健壮性。
站在用户角度设计接口,提升系统可用性
着火点:
总是从自身业务体系和架构去考虑如何设计对外接口,但是发现最后用户使用的很别扭,同时由于需求不能直接被满足,会多次反复调用接口,导致自身系统的压力增大。例如对于一个状态的检查接口,是否提供一个状态变更通知接口就会极大降低轮询的压力
防火点:
需要从客户角度考虑问题,设计接口,防止需求和实现脱节,导致系统压力增加。
懒惰有时候是件好事
着火点:
业务流程中很多耗时的操作在流程编排方面没有考虑清楚,当耗时工作做完以后,发现不符合最基本的交验,这样就会导致系统无谓的增加了开销。对于需要申请的资源,考虑处理流程的阶段,阶段性申请要优于一次申请(不过需要注意死锁)。
防火点:
流程编排需要合理性,尽量将耗时的工作放到合理的位置,同时做好基础的防攻击轻量前端屏障逻辑,提高系统的健壮性。
主流程和副流程隔离
着火点:
SIP早先的日志分析模块中有分析日志,备份,发邮件,更新系统缓存,操作数据库等多种操作,但是一股脑儿都被放到一个流程中,结果当邮件没有发成功导致整个流程的失败。
防火点:
把真正的主流程梳理出来,同时对于一些副流程可以考虑采用后台异步方式完成,提高系统稳定性。
模块间接口交互,控制资源直接操作入口
着火点:
对于数据库中的资源任何模块不区分范围都可以访问,最后导致数据结构变更困难,业务对象管理混乱,模块无法剥离独立。
防火点:
模块化设计的基本思想,模块间通过接口交互获得其他模块管辖的数据,接口方式屏蔽了对于后端实现及业务对象的依赖。
学习份外的事情,配置决定成败
着火点:
没有SA就高不定环境,也无法了解操作系统的配置与Web容器的配置对于应用的影响。没有DBA就无法确定如何写SQL避免一些简单的耗时查询。没有测试同学就无法作压力测试,无法了解当前系统性能。
防火点:
多学多问,多了解一些其他岗位的内容,才能够更加全面的掌握好架构设计。
不要迷信
着火点:
总是看到新技术如何有优点,但是看不到它的成熟度。总是听到很多经验之谈,但是从来没有真的比较过。结果就是别人说什么就是什么,系统地稳定性和可用性基于Google出来的结果。
防火点:
需要听取各种意见和经验,同时用测试结果说明问题的结果,看代码说明结果背后的问题。这样才会走得更加踏实,学的更加实际。其实技术发展来说,真正的基础性内容还是有限的,而且各种技术都是触类旁通。分布式,不论是文件系统,DB,缓存都会遇到分布式的共性问题(负载均摊,容错,数据复制,动态扩容等等),在结合一些文件系统,DB,缓存的自身特质。因此扎扎实实学好基础,了解Http协议,了解七层通信协议,了解文件系统设计,了解MapReduce思路,了解结构化,半结构化(bigmap),非结构化存储的要点,就会不会让自己迷失在技术宣传中。
明天晚上去北京参加系统架构师会议,到时候会和大家分享一下TOP的一些商业和技术上的心得,准备得很仓促,但是个人觉得分享就在于自己将自己知道的说出来,时间不长,45分钟,能讲的也不多,但是如果对于淘宝开放平台有兴趣的同学可以来听一下。这里也算是做个广告,不过不要期望过高,免得失望也大^_^。五年没有来北京了,首都应该也变化不小了。