【编者按】本文系国内 ITOM 管理平台 OneAPM 翻译自 Steven Haines 的文章。Steven Haines 是 Pisksel 技术架构师,目前在奥兰多迪士尼乐园工作。他是在线教育网站 geekcap.com 的创始人,著有上百篇 Java 相关的文章以及三本 Java 著作:《Java 2 From Scratch》《Java 2 Primer Plus》以及《Pro Java EE Performance Management and Optimization》 实现有效 APM 策略所面临的挑战: - 代码依赖
- 过度或不必要的日志
- 同步与锁
- 潜在数据库问题
- 潜在的基础架构问题
1、代码依赖开发程序是一项具有挑战性的工作。你不仅要为了满足商业需求而建立程序逻辑,还要选择最合适的代码库和工具来帮助你。你能想象自己创建所有的日志管理代码,XML 和 JSON 解析逻辑,或所有的序列化库么?你当然可以编写代码来完成这些事,但是诸多开源开发者团队已经做好了这些事情,你又何必亲力亲为呢?此外,如果你正在与第三方系统集成,你会自己读完专有的通信协议规范,还是购买供应商提供的库帮你完成呢? 我相信你会同意:如果有人已经解决了你的问题,使用他的解决办法会比自己想办法解决效率更高。如果这是一个已经被许多公司采用的开源项目,那么很可能它已经经过完备的测试,文档充足,而且你应该找得到许多使用教程。 然而,使用依赖库是有危险的。你需要回答以下问题: - 这个库真的写得很好并且已经充分测试了吗?
- 你是否用与众多公司一样的方式使用这个库?
- 你的使用方式是否正确?
请确保在选择外部库之前进行一些调查,如果你对某个库的性能有什么疑问,那就进行一些性能测试。开源项目很好的地方在于你可以访问它们的全部源代码以及测试套件和构建流程。下载它们的源代码,执行编译过程,并查看测试结果。如果你看到很高的测试覆盖率,那么就可以比没有测试案例时信心百倍! 最后,确保正确地使用依赖库。如果正确使用,ORM 工具的确能够大大提高性能。ORM 工具的问题在于,如果你不花时间去学习如何正确地使用它,你就会轻易的砸自己脚,破坏自己的应用性能。关键就在于如果不花时间学习这些工具,本应帮助你的工具反而会伤害你。
2、过度或不必要的日志日志记录是调试工具库里的强大武器,可以帮助你识别应用执行过程中在特定时间内可能发生的异常。当错误发生时,捕捉错误信息并收集尽可能多的上下文信息是非常重要的。然而,简洁地捕捉错误条件和过度记录之间是有差别的。 最普遍的两个问题就是: 异常日志能帮助你了解应用程序中发生的问题,因而非常重要。但一个常见的问题是,应用程序所有层级的异常都进行记录。例如,你的某个数据访问对象捕获到一个数据库异常,并将该异常传达到服务层。服务层可能会捕捉该异常,并将其传达到网络层。如果我们在数据层、服务层和网络层上都记录该异常,那么我们对此相同的错误条件就有三条堆栈记录。这会导致写入日志文件的额外负担,还会使日志文件充满冗余信息。但这个问题非常普遍,我敢断言,如果你检查自己的日志文件,你很可能会发现多个这样的例子。 生产应用中常见的另一个大的日志问题与日志级别有关。.NET 日志记录器定义了以下日志记录级别(.NET TraceLevel 与 log4net 中的命名会有所不同,但绝对相似): - Off
- Fatal
- Error
- Warning
- Info
- Verbose / Debug
在生产应用程序中,你应该只记录 error 或 fetal 级别的日志语句,在更宽松的环境中,捕捉 warning 甚至 info 级别的日志信息也完全可以,但是一旦应用投入生产环境,用户负载将迅速填满日志并使应用程序陷入瘫痪。如果你不经意地将生产环境下的应用日志级别设为 debug,应用的响应时间比正常情况下高两或三倍都不奇怪!
3、同步与锁有时候,你想确保应用代码中每次只有一个线程执行一段代码子集。
例如,读取单线程规则执行组件之类的共享软件资源,以及文件句柄或网络连接之类的共享基础架构资源。.NET 框架提供了许多不同类型的同步策略,包括锁/监视器、进程间互斥,和读/写锁这类的专用锁。 不管你为什么要同步代码或者选择什么机制实现代码同步,都会导致一个问题:那就是有部分代码一次只能由一个线程执行。
设想去超市,只有一个收银员在工作:许多人进入商店,浏览商品,将商品放进购物车里,但某一时候,他们不得不排队以进行支付。在这个例子中,购物是多线程的,每个人都代表一个线程。然而结账是单线程的,这意味着每个人都要花费排队付款的时间。这个过程如图1所示。
图1:线程同步 我们有七个线程,都需要访问一段同步代码块,所以它们依次获得权限访问该代码块,执行其功能,然后继续。 在图2中总结了线程同步的过程。
图2 线程同步过程 首先,为特定的对象(System.Object 派生)创建锁,意味着当一个线程试图进入同步代码块时必须获取该同步对象的锁。如果该锁可用,则该线程被授予执行同步代码的权限。在图2中的例子中,当第二个线程到达时,第一个线程已经占有了该锁,所以第二个线程被强制等待,直到第一个线程执行完毕。当第一个线程执行结束时,会释放该锁,然后第二个线程被授予访问权限。 正如你可能猜测到的,线程同步将给 .NET 应用带来一个极大的挑战。我们设计应用程序时,希望其能支持数十个甚至数百个同步请求,但线程同步会把所有处理这些请求的线程串行化,导致性能瓶颈! 解决的办法有两种: - 仔细检查同步的代码,以确定是否存在其他可行办法
- 限制同步代码块的范围
|