Author : 岑文初(淘宝花名:放翁)
Email: fangweng@taobao.com
Blog: http://blog.csdn.net/cenwenchu79
这部分内容是我前个礼拜作内部分享的一部分,是挑了大家在日常中经常使用的生产者消费者模式作了一个细节问题的分析来讲述关于系统设计中的一些问题,其实在我前面的救火经验分享里面也有部分的介绍,不过那些比较抽象一点,需要有实际的工作经历的同学才会体会的到,而这里就具体的针对某一个特定场景作了分析.
图 1 生产者消费者模式
生产者和消费者模式在我们日常生产的代码里面应该出现的很频繁,但是有一些细节足以导致这种模式漏洞百出,同时也会使得系统不稳定。在早期Java来实现这种生产者和消费者模式,通常就采用线程池,资源池加上消息通知(wait,notify)的方式来实现,现在的Jdk有原生线程池(ExecutorService)和阻塞式队列,那我就主要说说后者这种实现需要注意的一些问题。
消费者的依赖带来的系统不稳定性
在我们现有系统中消费者往往会依赖于外部系统(文件系统,DB等等),或者内部处理会有比较长时间的消耗,那么对于整个模式来说就会出现在特定情况下队列暴涨(消费者被Hold住,同时控制了总消费者的数量),此时对于队列的压力直接会导致应用系统的不稳定,这时候通常就两种解决方式,一种就是加大消费者线程数(治标不治本),另一种就是将业务处理再细分,同时考虑优化。
业务处理再细分,其实就是考虑对于消费者的角色是否应该还有分层。参看nio等设计思想,其实可以看到对于工作者角色和消息监听者角色的分割,可以提高对于消息的处理,增加吞吐量。简单来说就是消费者作的事情更少,功能更加单薄,目的就是将消费这个动作加速,而将具体的业务操作分配给后端的工作线程来做,同时考虑在分配的过程中作合并和其他的优化处理,批量处理消息提高效率。
这么做的优点就在于避免“单点”资源池或者队列的不稳定性,任务在低并发下按常规即时处理,在高并发下批量优化处理。
图2 生产者消费者模式
三个维度解决消费慢于生产的情况
当消费无论如何优化都慢于生产的情况,那么需要考虑在三个维度上去防止异常情况发生。
1. 控制生产者生产频度。
2. 对列或者资源池空间大小限制,同时制定满载的消息处理策略(出错丢弃,磁盘固化等等)
3. 工作者处理时长控制,超时丢弃任务。
资源是宝贵的
这个在以前反复说过,但是实际操作过程中往往被很多同学忽视。
1.使用线程池一定要设置边界,不然连接池不断膨胀会立刻导致内存溢出。
2.队列需要设置大小,特别是在选择时用阻塞队列的时候需要仔细考虑,同时存储在队列的内容尽量是对象的标示,在性能允许的范畴下,由工作线程去获得具体的庞大的处理数据集。
3.超时时间无论如何需要设置,不然依赖的不稳定性随时可以击垮系统。
并行和串行相辅相成
很怕很多同学动不动就起一个线程池,说是多线程效率高。其实是否选择多线程首先就是要考虑这个任务并行执行是否好于串行执行。
1. 是不是关键路径。有时候优化了半天其实到后续还会堵塞在流程的某一阶段,那么多线程的意义就不大了。
2. 会不会有资源竞争。有资源竞争问题不大,但是发现竞争带来的性能损失要远多于多线程带来的性能节省,那么就绝对不选择多线程。并行化计算的最大问题就是一个共享资源访问控制问题,解决这个问题就两种方式:a.共享资源,锁机制保证数据一致性。b.不共享资源,操作结果可合并。(Share nothing,也是MapReduce, Erlang等分布式计算的核心设计理念)
3. 简单的工作串行,复杂的工作多线程并行执行。这个其实回到上面将消费者在分成消息监听者和任务执行者两个角色。(消息监听如果处理得够快,那么采用单线程串行处理也可以接受,只要保证任何异常不会中止监听工作)
多线程,并行处理不是包治百病的良药,串行并行结合起来根据实际场景来合理使用,才会设计出简单高效的系统架构。(设计作复杂容易,作简单难,因此不要在意简单的设计图拿不出手,因为用户只在乎如何得到稳定,高效的服务)
容错策略的抉择
上面有提到如果队列满了应该做一定的策略去保证业务的正常流转。但是对于容错策略的选择上,其实要考虑自己系统地特性。原则如下:
1. 业务需求优先。(任务是否可以丢,任务执行顺序是否有要求,任务的及时性)
2. 架构简单。
3. 不引入新的性能瓶颈和系统不稳定因素。
根据上面的几点,首先业务是否可以丢弃,如果可以丢弃,那么很简单,直接丢弃过载的任务请求(考虑异步记录一些日志备作查询和告警)。如果不可以丢弃,那么就考虑执行的顺序是否有要求,执行的即时性是否有要求,这将直接决定你数据恢复处理的策略。此时就会结合2,3两点来考量方案,有可能会引入持久化操作,同时还有恢复处理的顺序等等。但是一定要仔细判断是否因此会带来其他的性能瓶颈。总结起来一个结论,在业务容许的范围内,结构越简单越好。
后话:
我记得我刚工作那阵子也和现在一些刚毕业不久的同学一样,如果觉得自己的设计很简单,发现出去讲讲都很没面子,一定要想一个很完备的方案,面面俱到,但其实就像我前面所说的,客户在乎的不是你如何实现,而是是否能够满足他的需求(业务上,稳定性,容错性)。
会做出很复杂的设计但是从来不关心客户的想法的程序员仅仅只能算是一个学生。
会考虑如何满足客户需求但是不会走在客户前面多为将来考虑的程序员是一个新手。
会考虑如何满足客户,同时会为客户更进一步思考的程序员是一个合格的程序员。
工作3年内还是一个新手不可怕,可怕的是工作了几年还是处于一个学生状态,如何把设计从简单做复杂,然后再从复杂作到简单,其实才考验一个人实际的工作能力,在合时的环境采用合适的方法,得出最简化方案是程序员应该追求的。引用我小时候读书老师常常灌输我的一句话:“书是先要读厚来,然后再读薄的。“
分享到:
相关推荐
### .NET 23种常用设计模式 #### 概述 设计模式是在软件工程领域内被广泛采用的一种软件设计思路,旨在解决特定类型的问题。在.NET框架中,这些设计模式同样适用并能帮助开发者构建更加灵活、可扩展及易于维护的...
工厂模式是最常用的设计模式之一,主要用于封装对象的创建过程。工厂模式的主要优点是解耦了创建过程和使用过程,系统可扩展性增强,稳定性增强。 例如,在面向对象编程中,一般一个 Class 都会继承一个接口,设定 ...
《J2EE设计模式》是软件开发中针对Java企业级应用的一种重要理论,它基于面向对象编程,旨在提高系统的可扩展性和稳定性。设计模式并非孤立存在,而是与架构设计、框架紧密相连,构成了J2EE多层系统的基石。衡量一个...
总结,模式识别和分类器设计是数据分析的关键环节,而k-means和KLT变换是其中的常用工具。在C语言中实现这些算法,既考验编程能力,也要求对统计和机器学习原理有深入理解。实际项目中的应用能帮助我们更好地理解和...
它不仅定义了一个完整的框架,还提出了许多最佳实践和设计模式来指导开发者构建稳定、可扩展且易于维护的应用系统。本文将重点介绍几种重要的设计模式及其在J2EE各层的应用。 #### 二、J2EE架构分层 J2EE架构被...
工厂模式通过抽象产品创建过程,使得客户端无需知道具体对象的创建细节,从而提高系统的可扩展性和稳定性。例如,创建一个实现了特定接口的对象,可以使用工厂类的静态方法create()来完成,而不是直接通过new关键字...
设计模式是软件开发中用来解决特定问题的一套经验总结,被广泛应用于软件设计中,以增强软件的可复用性、可维护性和可扩展性。本知识点将详细介绍设计模式的分类、设计模式的六大原则,以及Java中23种设计模式的具体...
这样,即使底层实现发生变化,也不影响高层模块的稳定性。 #### 总结 以上介绍的设计模式是面向对象设计中非常重要的一部分。遵循这些原则不仅可以帮助开发者写出更加优雅、高效、易于维护的代码,还可以提高团队...
设计模式是在软件设计过程中解决常见问题的一套可重复使用的解决方案。这些模式可以帮助开发者编写出更易于理解、维护和扩展的代码。本文主要针对依赖与关联、合成与聚合的区别以及设计模式的目的进行详细探讨。 ##...
模板方法设计模式是一种在面向对象设计中常用的行为型设计模式,它定义了操作中的算法骨架,而将一些步骤延迟到子类中。这种模式允许子类在不改变算法整体结构的情况下,重定义某些特定步骤。 在软件开发中,模板...
通过阅读和实践,你可以掌握如何在实际项目中应用这些模式,解决并发问题,提高代码的可读性和可维护性。 总之,Java多线程设计模式是每个Java开发者必备的技能之一。深入学习并熟练运用这些模式,将有助于你编写出...
设计模式是软件工程领域的一个重要概念,指的是在特定情况下解决软件设计问题的一种最佳实践。这些模式是开发者们通过多年的经验积累提炼出来的,旨在提高代码的可读性、可维护性和可扩展性。根据它们的目的和使用...
3. 设计模式和框架选择:在设计阶段,开发者可能会参考设计模式来解决常见问题,如工厂模式、单例模式等。同时,会选择适合项目需求的开发框架,如Spring、AngularJS等,这些决策应在文档中明确说明。 4. 类和接口...
在面向对象编程语言中,C++因其高性能和灵活性成为实现设计模式的常用语言之一。本文将介绍和解析C++中实现Factory模式的细节,它是创建型模式的一种,主要解决创建对象的问题,使对象的创建延迟到子类中实现。 ...
在Java编程领域,多线程设计模式是一种至关重要的技术,特别是在构建高效、可扩展和并发的应用程序时。...在实际项目中,根据具体需求选择合适的模式,并结合Java并发API,能够有效地提高程序的并发性能和稳定性。
- **应用场景**:在设计软件模块时,考虑未来可能的变化,保持模块的稳定性。 2. **里氏替换原则**(Liskov Substitution Principle) - **定义**:子类类型必须能够替换掉它们的基类类型。 - **应用场景**:...
**第六章 设计模式的概念与常用的J2EE设计模式** 设计模式是解决特定问题的可重用解决方案。工厂模式和单例模式是J2EE中常见的设计模式。工厂模式用于创建对象,降低了对象实例化的耦合度;单例模式保证了类只有一...
抽象工厂模式常用于跨平台或者多层架构的场景,因为它可以确保在不同的环境下创建出的对象具有相同的接口,从而保持系统的稳定性和一致性。 工厂模式和抽象工厂模式之间的区别在于,工厂模式关注于单一产品的创建,...
4. **数据库设计**:这部分涵盖了数据库模式、关系模型、ER图(实体关系图)和数据字典,确保数据存储的有效性和高效性。数据库设计对于支持软件功能和性能至关重要。 5. **详细设计文档**:比概要设计更深入,包含...