杂谈---第一次真正的高并发编程体验
发布时间:2013-11-18 01:23:00作者:左潇龙阅读(10433 )评论(84)
引言
由于最近LZ负责的业务系统频繁宕机,导致LZ疲于本命,上一个星期(因为现在是周一了,0.0)连续加了五天班,其中还包括周末,就是为了出一套应急方案。宕机的根本原因,现在已经渐渐真正的明晰了,最早的一次是因为消息发送与数据库操作的顺序错误导致的数据库宕机,目前已经通过顺序的调换解决了数据库的压力。然而数据库的问题解决之后,则开始是应用服务器出问题。最近频繁宕机的原因是连接数经常爆满,完全无法应付nginx的疯狂攻击。
经过与公司领导与业务人员的交流,最终才知道,由于业务模式的变化,导致LZ负责的业务系统用户剧增。因为高并发的原因,LZ的系统现在已经频繁的不堪重负,数次被压垮,提升服务器的响应速度已经迫在眉睫。最终定制的解决方案是比较主流的解决方案,也就是将前端采用集群部署,而后台与其它业务系统的交互,则会采用独立的服务器处理。不过这个方案并不能一解燃眉之急,集群的部署和前端与后台的分割都需要不短的时间进行部署和测试,因此只能安排到这周一(其实就是今天,0.0)才开始推行。
为了避免在系统向集群转换的过程中再出现问题,LZ与各位领导商量之后就制定了一个简单的应急方案,而LZ则担任了编写应急方案的职责。最近的几天,LZ就是在忙于编写这个应急方案(除此之外,还要边应付业务人员和其它同事对系统的不满,0.0),怎一个焦头烂额可以形容。
并发的意识
应急方案采用的方式是,增加用户数限制以及优先级的功能。用户数限制的功能类似于火车站之前的抢登陆,简单的说,就是这个系统只能若干个(比如1000个)用户登陆,后面来的用户将被拒绝登陆。优先级的功能,则是指在人数达到一定数目时,需要将某些角色的用户踢掉,让另外一部分角色的人优先使用系统(这是因为那些被踢的角色查询的数据量较大且对系统的依赖性不高,容易给服务器造成不必要的压力)。这个功能LZ一开始觉得挺简单的,但是当LZ真写起来的时候,才发现真的没有想象中的那么简单。
最开始的难度在于用户数的精确统计,因为用户数与session数量并没有直接关系,要想统计精确的用户数,必须按照用户名去重。这一点在找到适合的监听器(LZ使用的监听器是存在于web服务器中的HttpSessionAttributeListener,但这不一定适用于所有项目)之后被解决,方案就是记录每个用户的session数量,为0则代表该用户已退出,否则代表正在使用系统(也就是会算作1个用户)。
然而最难的地方却不是用户数的精确统计,而是并发所导致的难度。因为用户数是在高并发的情况下统计的,因此必须考虑并发的情况,在代码中添加适当的同步,即要保证统计数据的正确性,又得保证足够的性能。如果因为这个数量统计而影响性能,那就与这个功能的意义背道而驰了。
这还是LZ第一次在高并发的情况下编程(以前其实也有,只是由于功能并不核心,所以从未考虑过性能,只是无脑的在方法上使用synchronized),每写一句代码,都要考虑如果有成百上千个线程同时运行会如何。这每一句代码似乎都成了美女,每一个都可能有成百上千个大爷翻牌,而且还是同时,因此到底如何同时伺候N(N>1)个大爷,自然就成了一个问题。这也算是LZ第一次在编程的过程中,真真切切的产生了并发的意识。
书中自有黄金屋
或许是老天开眼,也或许是LZ运气尚且说的过去,在这之前的几个星期,LZ刚买了一本关于并发编程的书籍,一直都没有看。这下可好了,刚好派上用场,因此拿起这本书不到两天,LZ就一口气读了将近一半。最终也算是临时抱佛脚,将这个应急方案给应付过去了。
由于LZ只是为了应付当前的情况,所以并没有细读,尽管读的过程中都读懂了,也与作者有深深的共鸣,但过后其实印象并不深。不过这足以让LZ度过当前的难关了,在编写这段并发代码时,LZ主要采取了以下几种小技巧(基于书中的思想)。
1、将原本存在于监听器和过滤器的属性全部提出,使得两者不再需要考虑线程安全的问题。
2、提出的属性放在单独的两个类(session有效列表和session无效列表,无效列表其实就是可能要被踢的session)当中,这两个类都是单例,并确保这两个类是线程安全的。
3、由于需要遍历一个装满session的集合实现踢人的功能,因此采用备份的方式。如此一来,在遍历session并使得session失效时,并不会锁住失效session的列表,这样可以极大的提高性能(前提是限制的用户数并不高)。
4、在监听器与过滤器使用这两个单例类时,坚决杜绝各种竞态条件或者复合操作。
5、重构现有的代码,让需要同步的地方集中在一起,减少性能的损耗。
这算是LZ从书中紧急领略的几个方式,今天LZ的应急方案已经正式上线,是否能顶住高并发还有待考量。不过LZ是领略了书中的一部分真谛才下的手,而非一时臆测,因此还是有一定把握的。(希望明早一上班不会被业务部门炮轰,0.0)
黄金屋的诱惑
由于这次事件,LZ已经彻底爱上了这本并发编程(前几天还说爱上了深入理解那本书,有点花心啊)。这本书并不厚,才200多页,LZ用了一天半不到的时间看了80页,准备直接趁热打铁,等看完这本书之后,再继续深入理解和设计模式这两本书。因此最近的计算机系统原理系列可能要延迟一下了。
本文写的十分匆忙(今晚其实刚加过班,0.0),因此难免漏洞百出,各位猿友如果遇到过类似高并发的问题,可以畅述己见,让LZ等一届屌丝得以一窥天机。时间已经不早了,各位猿友明早见分晓吧。
版权声明:本文版权归作者(左潇龙)所有,欢迎转载。但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
|
|
|
|
|
|
|
|
分类: 编程之路
施主理解错误了,这里2049个线程并不是为了并行的完成某项任务。如果真是这样设计的话,岂不是要来一台2049个CPU的机子吗。。。。怎么可能呢。。。
这里的2049个线程,只是web服务器(web服务器会启动线程响应请求)响应不过来用户的请求,导致响应速度变慢,恶性循环之后,线程数就超了。这不是刻意设计的。施主理解错了,0.0。
我觉得是你理解错误了,问题的症结就在这里,你不理解I/O模型,不能区分I/O线程和Worker线程,不知道在读取/写入 I/O 时使用的线程是非常少的,而大量创建的线程被你的应用程序逻辑代码占据.最明显的例子是收到请求后以同步的方式访问数据库或其它I/O设备.这就造成调用线程不得不等待一个I/O操作完成,而没法立即被复用.正确的做法是仍然以异步I/O的模式访问数据库或其它I/O设置,调用线程就可以理解返回用于处理其它请求.
如果你收到请求后执行的是CPU密集型任务,那么你更应该减少创建的线程数,而通过任务队列的形式,使用尽可能少的线程来执行队列中的任务.甚至你可以向另一台物理机器发起一个异步I/O请求,将计算密集型任务投递给它,它在完成任务后会唤醒调用机器的线程来告知处理结果.
总的原则就是,尽可能让创建的线程都在工作,而不是创建大量的线程后,让它们都在等待.
施主还是理解失误了,施主刚开始说并行。但是这里很明显不是并行,而是并发。
现在又说并发,施主到底是想讨论并行还是并发。。。。而且这里和I/O模型,I/O瓶颈,数据库等并没有多大关系,因为数据库并没有达到瓶颈,数据库一直表现很稳定,主要还是并发量大,导致系统响应不急时的问题。其实根本原因是tomcat是开源的非商业的web服务器,本身支持的并发量就不高,所以用户数一剧增就扛不住了。
“如果你真需要如此多的线程来完成你的任务,那你就需要重新设计你的应用程序架构.因为实际上并行运行的线程数不会改变,创建更多的线程,意味着更多的资源竞争.”
而且施主这里说的,并行的线程数,其实是不对的。这里是并发的线程数,希望施主搞清楚并行和并发的区别。
@L'nemo
贫僧再次强调一下,那次2049个线程是因为有线程等待资源导致的,是不正常的状态。贫僧说这个的意思,只是想表达一下,JVM的线程数并不是只有200多个,针对上面那位施主(laosong)的问题。(再次PS:这个结论是上面的施主提出的,贫僧可没这么说过,这里本身也与JVM的线程数没什么关系)
贫僧的tomcat设置到底有没有问题不是贫僧说了算的,因为贫僧其实只是项目中的一个程序员而已,连高级都不是。而且就算是有问题,现在也不重要了,因为说到底tomcat已经扛不住了,这就像你让一个国际上的拳击选手去打一个小朋友,小朋友的任何招数都是无用的,因为在绝对的实力面前,一切挣扎都是徒劳的。其实现在看来,这些参数设置高设置低都没什么意义了(这项目贫僧来之前就已经运行三年多了,换句话说,这些参数已经用了三年多),应急方案已经有了,今天也安然度过,接下来只需要等着集群的部署就OK了。
贫僧就解释到这里吧。
贫僧已无语。。。。好吧,施主就当贫僧不懂并发吧。
贫僧只想说,懂点信号量,事件,锁机制真的没什么意义,解决实际问题才是正道,而且对于I/O,已经说了,不是I/O瓶颈,施主一再强调I/O有什么意思呢。
况且,扛不住高并发不一定就是业务逻辑,同步的问题,刚才都已经说了。比如给你个单核256M内存的机子,你能抗住100个并发吗?再次强调,现在的主要问题是硬件和软件已经跟不上用户带来的压力了,提升硬件,或者做集群才是正道,只靠点花拳绣腿,累死累活改代码,是解决不了根本问题的。施主何必在这一直秀自己的基本功呢。。。哎。
贫僧很好奇施主是做Java的吗?难道施主的Java项目当中充满了
“critical section,event,lock,semaphore等,那么你应该修改你的业务逻辑,减少同步原语,或者抛弃抢先式计划模型,改用协作式计划模型(比如 Concurrency Runtime).”
这些东西吗?
况且,一个维护了三年多的项目,老代码一大堆,请问施主有这个兴趣去改吗?
施主,你再看下回复。另外,施主是做Java的吗?再另外,不能创建本地线程的原因施主能否说一下?
施主的意思是说,“uable to create new native thread”是因为I/O引起的?
我可不是这么说的,从字面看,直接原因是已创建且存活的线程数量达到了系统或JVM的上限。
这篇博文中也有些描述:http://xiaohuabiao.blog.163.com/blog/static/138482182011102633657724/
而我指出的问题是,如果你已经有 2049 个线程了,而你还要再创建新线程来执行请求,这就说明你的程序结构有问题,或者内存泄露了。
而通常这种 web 应用,线程数高的主要原因是当前的线程都在等待I/O完成,或者等待同步对象释放。如果是前者,我说了,可以通过异步I/O机制来释放调用线程,这样你就不需要新建线程来响应请求。
多核时代,并发编程,需要你自己计划线程的调度,你可以看下OpenMP,AMP等,这里已经屏蔽了线程,只有任务,运行时会通过适配硬件的线程数量来执行这些任务。并发,但是还要协调。
怪不得交流起来有点累呢。。总感觉贫僧看不懂施主的意思,施主也看不懂贫僧的意思。原来施主不是做Java的,抽象层次不一样,讨论起来其实意义不大。
至于施主发的这篇文章,贫僧还是更相信书中的内容,就不看了吧。多谢施主的推荐了。
不管怎么说,非常感谢施主的热心回复。
施主真会断章取义,贫僧只能呵呵了。园子里也有这种无脑喷。。。施主自便吧。
线程数太高是因为设置的比较高,就贫僧平时的观察来看,正常的线程数大约在400左右,最低的时候200多。那次2000多个线程是因为资源等待引起的,或者是锁之间的等待引起的。不过当时比较匆忙,这只是猜测,还没仔细观察就给restart了,之后也没再出现,面对如山的代码,接连不断的需求,没有时间去一一排查代码,因此这个问题就只能暂且放下了。
不过这次做集群的时候要做压力测试,希望能找出一点蛛丝马迹,只是项目时间已经比较久了,诞生于最早技术部成立的时候,公司也正在着手建立ESB总线,一旦建立好,这个项目也就到死期了,最大的可能就是被兼并到其它的综合系统当中了。
其实贫僧就是在照顾着一个即将死去的老人。
晚上再仔细看看施主的回复,才发现我们讨论的根本不是一个问题。白天工作比较忙,都是瞄一眼就匆匆忙忙回复了,没时间认真的回复,这点抱歉。(贫僧的确有点夜猫子,0.0)
施主至始至终大部分都是一直在强调那次2049个线程的问题,其实这一点只是为了告诉之前那位施主,JVM的线程数根本不是200多,是为了澄清那位施主的误解。
之后施主就一直揪着这个问题不放了,简单来说,施主就是一直在分析这2049个线程是因为什么引起的,比如等待I/O资源等等。说的倒也算是头头是道吧,但也都是一些博文当中就能搜索到的概念或者是原理解释,但是说真的,没有什么实际的作用。
问题的关键点在于,2049这个问题早就过去了(再次强调,现在系统的问题并不是线程数超额,而是连接数,或者更准确的说是TCP连接,提到这个2049只是为了回答那位施主的疑问),公司不会给你时间去大量翻代码找一个若即若离的问题(再次强调,这个项目已经三年多,贫僧进入公司一年整,大部分的代码都是之前产生的,换句话说,贫僧是在维护一个老项目),因为总会有人打断你,也会有需求缠着你,还有一些运维的事找你,业务人员也找你,还有其它系统的人找你,也会有新人问你问题等等。
因此施主一直想表达这样的意思,你去代码里找问题啊,你去好好看看你的代码啊,你去好好设计你的项目啊。贫僧真的很无语,0.0。
第一是,Java的代码(专指企业级使用现有J2EE框架的开发)当中很少有主动的线程创建,或者是主动的同步操作(主动的是指程序猿写的),所以代码里不小心多建了线程或者是同步写错了这种情况是不太可能出现的,也正因如此,贫僧才难得写一次需要同步的程序。当然了,不知道施主的项目中是不是都是自己创建的,这点贫僧就不清楚了,贫僧也不知道施主做什么语言,不了解没发言权,0.0。第二,这些代码大部分其实并不是贫僧写的,而且代码量确实很大。第三,贫僧也想看,无数次想重构一下代码,但真的没时间,所以施主给贫僧的感觉是有点站着说话不腰疼,0.0。第四,贫僧倒是想设计我们的项目,可惜那是四年前设计的,当时贫僧还没进公司,0.0。第五,这个项目已经快被抛弃了,这个公司已经明确表示了,将来会有新的综合系统(综合就是指将多个系统集成到总线上)替代,其实简单点说,这个项目已经快功成身退了,到时贫僧也会转到新项目当中,没有人会花费这么大的精力去完善一个即将被抛弃的项目。
这就是工作,平时你可以有自己的兴趣,你可以去死钻研那些代码(不过贫僧的选择一般是看优秀框架或者JDK的源码或者看书,不会是看那些老代码,0.0),但是工作讲究的是效率,讲究的是解决问题。只要能解决问题,都是好办法,而不是花费大量的时间去做一件不一定能有收成的事,比如死找代码。
另外,贫僧为了保护一定的公司机密,所以没敢透漏任何具体的项目内容,难道施主就这么自信,就凭2049个线程就一下断定了问题?贫僧项目组包括领导上下也有几十个人参与过这件事,难道不敌施主一个基于2049的猜测或者推理?事实情况是,其实都能大概猜出来是因为什么(无非就是数据库有锁,也就是施主说的I/O,或者是线程之间有锁呗),可是真的找出来岂是那么好找的?
如果施主真的想帮贫僧,贫僧自然感激不尽,不过施主只是一直在争论一个与本文关系不大的问题。
如果施主真有意帮忙的话,可以讨论下本文的正题,比如最大的正题--连接数爆满,nginx不关闭连接。或者是Java中如何合理的编写并发程序(就像贫僧列出的那五条一样)。又或者在集群方面有什么好的建议?又或者对于nginx有何高见?
其实听到施主不是做Java的,就觉得讨论这些没什么意思,因为贫僧曾经就想当然的YY过其它语言的代码,结果发现完全不是这么回事。所以贫僧也希望施主莫要YY非自己熟悉的领域的代码结构,其实有时候是有很大差别的。
话就至此吧,大半夜了,看会书休息了。
鄙人也好久没搞过java了,但一些基本概念还是有的,并发本质上线程多少没有任何关系,只有有多线程,只要有共享资源,写代码的时候脑子中就得蹦一根弦。博主的系统并发其实完全依赖tomcat这种app engine,自己的代码也基本跟线程和共享资源没啥关系,请求来了,tomcat发现线程池中没有可用线程时创建一个新线程来满足请求,但由于博主的系统一个请求处理时间很长,所以线程池经常不得空,tomcat老是得创建新线程,所以才会到2000的上限(然后就悲剧了),正常情况tomcat的200个线程完全可以满足大负荷请求,创建更多的线程虽然可以让请求得到处理,但请求处理效率反而下降。
其实我也明白博主的意思,但说实话博主此篇文章技术含量不大,关于sessionlist这种共享资源,谁都知道要尽快使用,不要锁太长时间。其实如果对数据精度要求不高,完全可以把在使用这个list之前把需要的特定信息复制过来(新list),然后可以慢慢使用(一些无锁的并发算法就是这么干的)
做过Java还是不一样,知道线程是由web服务器控制的。
此外施主说的线程数太高这个问题,确实引起了贫僧的好奇心,因此贫僧今天还真专门问了问项目经理,到底为什么设这么高。
项目经理的意思是,之前其实也设的比较低,约莫3、4百左右。但是后来达到accpetCount时(当时accept具体是多少,这个没提),就会给用户显示connection refused。因为这边可用的线程满了,挂起来的数目(也就是accpetCount)也达到上限了,就直接拒绝服务了。
后来业务人员就暴动了,他们可不管你什么参数优化,配置优化,他们只觉得,我要用系统,你凭什么拒绝我连接?
所以后面项目经理就给调高了,也就再没出现过问题。以上这些事是发生在贫僧来之前的。
所以说,有些东西,其实得看需求,不能太死板,不一定就得按照文档上的建议去做就是对的。施主说的也没错,找一个合适的线程数,会让响应稍微快一点,但是同时可以接受的请求数变少了,用户会被拒绝,这就会导致业务那边(其实也就是用户)体验不好。
总的来说,现在是拿时间换了点人数。就是说,设置的低点,可以同时处理200个,但是速度比较快。设置的高点,同时处理的数目增加,但是速度比较慢了。(但是要记住,速度慢点,业务人员是没感觉的,但是你要是拒绝服务,你试试?)
比如就按照设置成200和300来说,假设一下来了300个请求,开300个线程去处理,可能总体时间确实没200个快,但是用户的请求都被接受了,他们只是感觉慢点,还是可以接受的,甚至不怎么能感觉出来。但是你要是只接受200个,哪怕你再快,这100个也得等一会,假设acceptCount设置为50的话,那会有50个用户就直接显示connection refused了,这样他们可就暴走了。
于是当时应该就是秉承着宁可慢点,也不能让业务人员被拒绝这种思想,就给调高了,为了彻底消除connection refused。因为慢点他们不投诉,但是拒绝他们可就投诉了,你说你怎么设置吧?
至于施主说本文技术含量不高,这个贫僧没什么好说的,只是贫僧有说本文技术含量很高吗?好像没有吧。。。。或者施主要是有何高招,给个技术含量高的方案也行。贫僧拜读。
应急方案只是代码上的更改,因此不需要重新搭建测试环境,只需要贫僧一个人花一天多写完代码,测试一下就可以上线了。总共也就用了两天,就搞定了。
而负载均衡的方案不只是负载均衡,还有前后端的分离。负载均衡和前后端的分离是需要重新搭建一套测试环境的,再加上搭建完以后测试的时间,说不定有什么问题还需要改一下(比如session和缓存的问题以及一些意外的问题),而且最主要的是,部门间的协作一定会延长这个时间,因为需要测试部配合,大公司流程多(如果施主进过的话,你懂的),难免耽误不少时间。
不过施主说的也没错,要说简单是很简单,改下配置,多启动几个应用,完事。但是这需要严格的测试,公司有公司的规定,必须按流程走。
什么叫应急方案?应急的意思就是说实施起来会很快。施主貌似不是很明白应急的意义所在,这和难度没关系,就是时间上快而已。
不知道施主的公司是不是做集群和前后端分离的时候,就是直接改下配置,启动俩应用做负载均衡,再启动一个应用做后端服务,然后就扔上去了,一天搞定。。。呵呵。
呵呵,施主想多了。贫僧一直很淡定。。
至于施主说自己做过什么全球前一千的网站,还是03年。个人还是觉得年数与水平关系不大,尤其在天朝。至于施主是否真的很牛X其实与贫僧没多大关系。
其实施主说贫僧项目的架构烂,贫僧是很欢迎的,因为这说明贫僧表现的时候到了。但是关键就是施主既然说了烂,那最好就要给个可行的方式,如何能有效地改善,并且确实可行。只是施主只是一味的说烂,确没任何建议。唯一的建议也是文中已经提过的集群,这也就算了,施主又开始说你们太慢,我分分钟搞定。而且贫僧多次强调还有前后端的分离,施主却只字不提。
其实施主说的分分钟搞定,就是指你可以分分钟把nginx设置好,然后启动俩应用,说实话这没什么可炫耀的,这本身就没什么难度。问题就是你弄好以后,这个项目能否正常工作。所以施主就加了个前提,项目好的前提下可以分分钟搞定。言外之意就是说,项目好,我设置完就肯定没错,所以分分钟搞定。换句话说,施主的分分钟依赖于项目,与施主关系不大。
这就像现在要去玩,有三十个人,但是只有一个轿车。轿车一次拉不了三十个,所以只能多拉几次,有点慢。此时,施主就开始发话了,你们真慢,给我个公交车我分分钟就把你们一次性拉过去。但问题这不是没有公交车吗,有的话谁都能做到。要真厉害,施主说个办法,哪怕是小轿车,施主也能一次性拉走,这样的话,说别人慢才有意义吧?
不知道施主能看懂这个比喻不?如果看懂了,施主自己觉得有意思吗?
施主错了,大错特错啊。阿弥陀佛。
“似乎也并不打算优化这一点”这句显然错了,真实的情况是贫僧非常想优化(只要是个程序猿,只要他在维护一个比较古老的项目,试问有人不想把自己维护的项目优化的维护性和扩展性更强一些?)。
“认为这一点做不到了”这句就更错了,贫僧当然可以做到,前面都说了,只是集群(也就是施主所谓的水平扩展性)需要测试一下,大约需要一个多星期的时间。
只是那位施主一直在强调自己可以分分钟搞定,因此贫僧才说“搞不定”。但是这里的“搞不定”,只是在说,对于贫僧个人的项目以及公司制度来说,无法“分分钟搞定”而已,而不是“搞不定”。
而对于那位施主的分分钟,贫僧只能表示呵呵,详见74楼,不再重复了。
跟你同感,之前看过LZ的博客的一些文章,有些简直在胡扯,不过佩服LZ的分享精神。
站内搜索
用户中心
用户名: | 登录 | 注册 | |
密 码: | ||
用户名支持字母,数字,下划线和中文 |
最新评论
随机推荐
服务框架调查,快来sho...
左潇龙2018-07-19
(二)代理模式详解(包...
左潇龙2013-08-16
到底是否应该重复造轮...
左潇龙2015-04-15
(二十二)访问者模式详...
左潇龙2013-08-17
用脑子写程序,鄙人就...
左潇龙2013-09-13