网站高并发,老出问题,真烦,我来研究研究

  很久以前我是学J2EE的,后来做了J2ME,又后来做了一点Android,再后来就学了PHP,励志要用PHP在Web开发上有所发展。我上家公司的老板告诉我说:“你的方向错了,现在是移动互联网的时代,去搞PHP木有前途”。这话想来不错,可是我觉得,再是移动互联网的时代,它也得联网不是,要联网就得用PHP or RoR or Python or J2EE or ASP.NET等等写联网,所以,我学习PHP,是为了有个好基础,有平台,其他才能做得起来。至于,选择什么样的技术实现,各有所爱,我觉得在我们这个小地方,用Java不合时宜,都是大公司用啊,.NET我不喜欢,毕竟是从Java阵营里出来的,这两货老死不相往来,RoR用的人好少,在我们这个小地方就更少了,不好就业,Python用的人也不多,也不好找工作,我对Python还是很有好感的,列在学习计划之列,前段时间学了一点,但是老是被中断,工作上的事情一打扰,就不能够有持续性,这样学习的效果也不好,而且,我深刻体会到,要学以致用,才能记得牢,才能深刻理解内涵,我学了不用,而且断断续续,所以,学习的效果并不理想,看来,还得找个项目做做,才能基本掌握。话说,我学PHP,可是看书自学了半年,又在一家做网站的公司,也就是我的上家,用半生不熟的PHP写了一个又一个具有实验性的东西,才完全入门了。终于,跳槽到现在这家公司,从事真正的PHP开发工作了。

  废话真多,书归正传。

  我们公司是做网贷平台的,到目前为止,我觉得我最大的贡献,是解决了平台高并发请求时引发的一些问题。虽然我也做了很多其他的并不轻松的工作,但这对这个问题的解决,我还是很自豪的。因为,在我之前,这个问题没有人真正解决掉,直到有一天,我研究投标模块代码的时候,想到了解决方法。

  我们每天上午10点开始投标,虽然用户不是很多,但瞬间服务器的压力还是有点的。这类似于秒杀,在那一时刻,会出现高并发的情况。每个标的都有一个总额,所有人的投标金额达到标的总额的时候,即是满标。但由于高并发,会导致在满标的一刹那,出现投标总金额超出标的总额的情况,然后会引发一系列其他问题。投标的流程是判断是否满标,如果没满,则该用户可以投标;如果满了,则中断,并提示现在已满标。问题的根本原因在于,在最后快满标的那一秒,有多个用户同时投标,当然不是绝对的同时,之间相差个几毫秒,但你可以想象成同时。然后,每个请求同时判断是否满标,这时发现还没有满,则同时投标,投标数据进数据库,并扣除用户账户资金、发放奖励、续投奖励等一系列操作(所有用户奖励都会多发)。但实际上,标的不需要这么多资金了,这些同时投上的资金已然成为有效投资,最终导致超标的情况发生。出现这种情况后,我们就需要做一系列善后事宜,挨个给客户打电话,说抱歉啦,金额超了,要扣除多发的奖励啦。然后,技术再哼哧哼哧的改数据,烦死了,当然不是我悲催的改数据,是另外一位程序媛MM或我们技术部总监亲自上阵。

  之前我们研究过解决方案,想是不是可以用队列机制来解决。可是,PHP的运行机制,不太好搞队列(当然有方法,但我们都对于此方法没有经验,不能贸然尝试)。后来,我研究出可以用文件锁的方式,类似于Java的同步锁,可以写同步代码块,这样给判断满标的时候开始加锁,到最后扣除账户金额、发完奖励再释放,就应该可以解决问题。可是,因为以前没有用过这种方式,最后领导没有采纳,让程序媛MM写了一套代码另外处理了,最后证明无效。关于文件锁的使用,请自行google,以后我再写一篇关于PHP文件锁的技术文吧。

  后来我想,我们都是在投标之前去判断是否满标,这样如果多个请求同时进来,那么就会同时判断,必定会出现超标的情况。何不在投标数据进数据库之后,我们再去判断呢?于是,一道曙光照进了我的心田。

  搞PHP的,数据库大多都用Mysql,上次我还特意研究了一下Mysql的锁机制。很好的一点是,Mysql是自动加锁的,当有一个insert or update请求进来的时候,会先锁表,然后去执行insert or update操作,再释放锁,这样就会避免多个请求同时修改表,造成数据混乱了。如果一个select和一个insert or update请求同时进来,则会先执行insert or update,再执行select。有了这个,我们就放心了。在一个请求的投标记录进入数据库之后,回返回该条记录的id,我们立刻去根据此id查询这条刚刚插进数据库之前的记录,获得到之前的投标数据总金额,看看是否超标了,如果没超,ok,程序继续往下走,本条投标是有效的,只不过还得再判断用户的投标金额+之前的投标总额是否会超出标的总额,再做一下微处理;如果超了,那么,本条投标完全无效,程序立刻终止,提示用户现在已满标了。ok,问题解决。

  核心思想就在于,投标数据进数据库之后,我们就可以把数据库作为基础,围绕着它做文章,把数据库中的数据作为唯一的参照标准,去分析在库的数据。因为,Mysql有很好的锁机制,我们可以很放心在库的数据,从而能得到正确的分析结果。这样,即使很多很多的请求同来到达,没有问题,实际上每个请求只分析自己这条入库的数据之前的记录,然后只处理自己的记录,从而不会影响别的请求,不会篡改别的请求写入的数据,保证了正确的结果。

  代码实现上,就很简单了,加上花括号也不超过10行,思想是最重要的。

  现在,修复后的程序,跑了快两个月了吧,天天投标,还没有出现过超标的情况,并且,我很自信的说,今后也不会出现。当然像手机版等地方我还没打补丁,如果用户在那些地方投标,就可能超标,回头还是要给补上。

  以前确实是没怎么整过web,更别提这个高并发情况的处理,现在在这家公司,真的是积累了一些宝贵的经验,还是很有收获的。

----------------------------------------2014-04-11 补充----------------------------------------

  感谢园友的支持和评论,好多园友提出了不少宝贵的意见,其中,我看到有两次提到“乐观锁”,于是我查了一下,明白了其中的奥妙,很不错的,但是却跟我们的业务不符,不能解决我们的问题。我画了一张图,不会用专业工具,手绘一张,见笑了,描述了投标流程:

投资表 -> 用户投一次标,就往投资表中insert一次数据(应该还有个外键:借款id)

借款表 -> 主要字段:借款金额

其实流程中最关键的地方,是判断向投资表中insert进去的投标数据是否有效,而园友提议用乐观锁,只能用于update的情况,而阻止不了insert,所以我觉得这种乐观锁的方式,不适用我们业务需求。

当然,还是很感谢园友提出的宝贵建议,让我和其他人也能知道的更多。

----------------------------------------2014-04-12 补充----------------------------------------

有园友留言说,这么简单的问题都没考虑周到,程序弱爆了。还有人说用锁和事务还有存储过程可以解决问题。可能好多人多没真正理解我要表达的问题,正好,58楼留言的同学,是唯一一个理解我的人,评论的很中肯。当然,理解不全面也是可以理解的,毕竟大家只是听我叙述,没有看实际的业务代码,理解就有偏差了,没关系,能给别人以参考帮助,就足以,也是记录一下我的这个技术历程。

 

 

 

posted @ 2014-04-10 22:44  bananaplan  阅读(7376)  评论(65编辑  收藏  举报