1.前面写过,相信很多小伙伴在自己的公司都在使用SpringCloud框架搭建微服务架构。毕竟这是现在非常流行的技术。如果只是传统的IT系统,用户量不大,使用SpringCloud可能暴露不出任何问题。如果是互联网公司的系统,用户量大,每秒几万个并发请求的峰值,那么在使用SpringCloud技术时,有一些问题需要注意。2.场景介绍,刚出现问题的时候,先讲一个真实的例子,不讲原理和理论。这是一个真实的案例,发生在我的一个刚起步的互联网公司的朋友身上。朋友A的公司是一家互联网公司,组建了一个小的研发团队。它使用SpringCloud技术栈构建微服务架构体系。经过一段时间日夜加班,核心业务系统终于搭建完成。正常的QA测试没有发现什么大问题。感觉表现还不错,一切都很完美。然后系统就这样上线了。一开始,用户规模不大,注册用户几十万,日活跃用户几千。每天都有新数据进入数据库表,并且随着时间的推移不断累积。没想到单表的数据量逐渐增加到几百万。这个时候好像没什么大问题,但是有用户反映系统有些操作会卡顿几秒,页面显示不出来。为什么?核心原因是单表数据量较大,达到几百万。对于某些服务,运行的SQL比较复杂。有很多多表关联没有设计索引,或者设计了索引,但是有的小弟写了几百行的大SQL。SQL太复杂了,一条SQL跑几秒肯定是正常的。对微服务框架稍有了解的应该知道,比如Feign+Ribbon组成的服务调用框架是有接口调用超时的,有一些参数可以设置接口调用的超时时间。如果你调用一个接口,有几秒不能刷新,人家就会返回超时异常,用户就不能刷新页面了。3.挤汤不沸,饮鸩止渴这种事一般都会遇到,一大堆SQL像屎一样,写SQL的人一个月就看不懂了,80%的工程师不愿意这样做。花时间重写和优化。一是改装人工成本太高,二是谁敢承担这个责任?系统运行良好,只是有点慢。如果只是随意改动、重构、挂掉系统的核心业务流程怎么办?所以,那些兄弟的第一反应就是:增加超时时间!界面可以慢点,但不要超时没反应!让我们让接口执行几秒,返回结果,用户就可以刷出页面了!无需重构系统!简单+有趣!如何增加呢?很简单,看下面几个参数就可以了:看过前面几篇文章的应该知道,SpringCloud一般使用hystrix线程池来执行接口调用请求。所以设置超时一般设置两个地方,feign和ribbon的超时,hystrix的超时。后一个块的超时时间一般必须大于前者。玩SpringCloud的好兄弟,别笑这些配置,因为我确实见过很多哥们用SpringCloud没那么溜,真的是这么干的。好吧,日子还在继续。..优化参数后,看起来不错。虽然用户觉得有些页面很慢,但至少几秒就能刷新。这时候日活跃用户数以千计,根本就没有并发。高峰期每秒最多有10、20个并发请求。我们先看下图,感受一下现场气氛:4.问题爆发,公司业务随着时间的推移飞速发展……兄弟的公司,在系统打磨成熟后,和几万用户的试点还可以,老板立马拿到了一轮几千万的融资。全公司士气高昂!下一步就是组建运营团队,把团队推到本地,在全国范围内大规模推广。总之就是三个字:推!推!推!这一推无所谓!研发人员在后台系统中发现,他们的用户数量呈线性增长。注册用户增长了数十倍,突破了数千万,日活跃用户也增长了数十倍。在活动等高峰期,日活达数百万!快乐的烦恼。..你为什么这么说?因为在用户量增加之后,悲剧的事情发生了。高峰期每秒并发请求数居然达到近万。研发组的兄弟们不敢怠慢!在这个过程中,首先是各种激烈的扩容业务,一变二,二变四。那么数据库主从架构就挂了,读写分离是必须的,不然单一的数据库服务器怎么承载这么大的请求!多建几个从库,处理大量的读请求,这样就基本可以搞定了。正当我准备坐下,喝口茶,长舒一口气时,更悲惨的事情发生了。在这个过程中,兄弟们经常会发现,在高峰期,系统的某个功能页面突然完全挂掉,就是无法响应任何请求!所有刷新此页面的用户均无响应!为什么是这样?原因很简单!在服务A的一个实例中,线程池中可能有几十个线程专门调用服务B,每个调用服务B的线程都会卡5秒。那么,如果每秒有数百个请求此服务实例怎么办?一时之间,线程池中的所有线程都挂死了,再也无法响应任何请求。我们先看看下图,再直观感受一下这个无奈的过程吧!这个时候怎么办?兄弟们只能使出程序员的老法宝,重启机器!如果无法刷新页面,只能重启机器,相当于简单初始化了机器中的资源。然后继续跑一会,又卡了,又重启!太令人沮丧了!用户体验极差,老板怒了!画外音:其实这个问题本身并不是什么大问题,但是如果你没有真正使用过SpringCloud高并发场景的经验,你可能确实会像这些兄弟一样出现一些莫名其妙的问题。比如这家公司,明明应该优化服务接口的性能,却偏偏增加了超时时间。结果并发量高,调用那个服务直接挂了,系统核心页面无法刷新,影响用户体验。谁是罪魁祸首?5.追根溯源,治标不治本,兄弟们只能找人帮忙。以下是笔者指导他们完成系统优化的全过程。第一步的重点是优化图中核心服务B的性能。互联网公司,核心业务逻辑,C端用户高并发请求,不使用百行多表关联的大SQL。如果单表有百万行数据,一次执行需要好几秒。其实最好的方式就是对数据库进行简单的单表查询和更新,然后在java系统中执行所有复杂的业务逻辑,比如一些关联或者计算。这一步做完之后,核心服务B的响应速度已经优化到几十毫秒了。你快乐吗?从几秒到几十毫秒!第二步的超时时间就是上面ribbon和hystrix的超时时间设置。奉劝同学们不要因为系统界面性能不好就偷懒,做个几秒甚至几十秒的超时。一般定义超时时间在1秒以内,比较普遍合理。你为什么这么说?因为有接口,理论上最佳的响应速度应该在200ms以内,慢一点的接口也就几百毫秒。如果一个接口的响应时间达到1秒+,建议考虑缓存、索引、NoSQL等你能想到的技术手段来优化性能。不然你随意把超时时间设置成几秒,甚至几十秒,万一下游服务不小心遇到问题,响应时间稍长一点怎么办?那么你线程池中的所有线程都会立即卡住!这两步解决后,其实系统性能正常,核心服务B响应很快,超时时间在1秒以内,所以hystrix线程池不会频繁卡死。第三步还没有结束,如果你真的觉得两步都做完了,那说明你还是没经验。如果设置超时时间为1秒,万一是因为偶尔的网络抖动导致接口在1.5秒内被调用了怎么办?这种情况经常发生,因为网络问题,接口调用偶尔会超时。所以此时结合超时时间,一般会设置一个合理的重试,如下图:设置这个重试后,SpringCloud中Feign+Ribbon的组合,在进行服务调用的时候,如果发现有机器的话超时请求失败,会自动重试本机,如果仍然失败,会再尝试另一台机器。这样偶尔网络请求导致的超时也可以通过自动重试来避免?第四步其实还没有结束。如果你配置了重试参数,最后你放手了,那你还是不对别人负责!在你的系统架构中,只要涉及到重试,就必须实现接口的幂等性保证机制。不然试想一下,重试一个接口几次,人家重复插入多条数据怎么办?其实幂等性保证本身并不复杂。根据业务,常见的解决方案:可以在数据库中建立唯一索引。插入数据时,如果唯一索引冲突,则不会插入重复的数据,也不会把唯一的放在redis中。id值,然后每次要插入数据,通过redis判断。如果该值已经存在,则不要插入重复数据。还有其他类似的程序。总之,需要保证在多次调用一个接口时,不能插入重复的数据。6.总结全文,回头看图看真相!像往常一样,我会在最后给你看一张图片。最终优化后的系统性能将如下所示。
