wangpin34 / blog

个人博客, 博文写在 Issues 里
5 stars 0 forks source link

周记 2019/09/06:尴尬的微服务,web app 的性能瓶颈 #45

Open wangpin34 opened 5 years ago

wangpin34 commented 5 years ago

我似乎又回到了一年之前开发前一个 app 时所处的困境,不同的是,此时我既要负责前端还要负责后端。我想要做的更好一点,但现实狠狠地给了我一巴掌。

尴尬的微服务

微服务的卖点确实很亮眼:把一个庞大的系统纵向切分成不同的更小的系统,每个小系统就是一个微服务(micro service)。微服务带来很多好处:

  1. 单个服务 crash 不会再影响整个系统的稳定性
  2. 给任务最繁重的服务配置更多的资源使其更加稳定可靠

微服务带来的好处和软件工程中频频提及的模块化,组件化,高内聚低耦合,有异曲同工之妙。所以,我有理由相信,微服务最初的想法来源于软件工程固有的最佳实践。而软件工程的这些最佳实践也无疑参考了传统企业的做法。比如,我可以把微服务的核心思想抽象一下:将内部太过于复杂的整体分离成可以逻辑自治的小单位,使得每个单位只需要负责自己的内部事务,对外只需要提供约定的职能。你会发现将前面这段套用在任何地方都无不妥,比如,如果你负责一个大车间,几百个工人,那按照每个人负责的工种不同,将工人们组成一个个专业的团体,对外提供专业单一的职能。你可以称这样的团队「微小组」。

相应的,组成「微小组」之后,作为上层管理者,你得明白从这一刻起你不再是管理一个个活生生的人了,而是一个个新鲜的集体,方式方法都得与时俱进。比如,以前你会找张三去做电焊,现在你需要将电焊的工作安排给电焊二组,由二组负责人再将具体的工作安排给张三李四甚至王五。我得承认,经历过这样的一层消息传递,肯定不比你直接找张三快速直接,但是好处也是显而易见的:

  1. 如果张三不能提供服务(请假,任务中,等等),二组负责人会安排其他人干活;
  2. 如果张三做的有问题,二组负责人会纠正甚至返工;
  3. 如果张三离职了,二组负责人会招聘新人;

作为上级,你获得的好处是更加可靠的服务,同时,你的管理压力也通过这样的方式转嫁到小组负责人肩上。不足之处在于,各个小组只能负责只能相对单一的工作,如果要跨小组协作,依然离不开你的指挥。

我们首先遇到的问题,就是跨小组协作。

协作艰难

注:我司真实案例 员工个人信息(profile),组织关系(relation),组织详细信息(department),分别由三个微服务管理,如何查询完整的员工信息(profile + relation + department)呢?

作为一个极其业余的微服务开发者,我的第一版程序就是简单的先查 profile API,再 relation API,再 department API,然后组合 profile + relation + department,最后返回。API 响应时间相当于三个 API 叠加,这还只是最简单的情况。有时候,一个员工可能隶属于多个 department,于是需要查询多次 department API,最终累计下来的时间消耗达到 2 ~ 3 秒。当时,我们在好几个会议上讨论这个问题,最终在架构师的建议下开始做并行查询,也就是多起几个线程一起查。这样的做法从表面上看放佛解决了问题,但很快引起了其他问题如线程太多引起内存极速消耗,服务频繁崩溃。而且这种做法很快达到了性能瓶颈,毕竟,并行查询只是查询发起的一方牺牲部分内存达到的虚假快速,最终得到数据还要依赖后台相关服务的响应,而大量的查询请求又对这些服务,以及后台的数据库造成了巨大的压力。

查询的问题仅仅只是速度的问题,而其他操作还要涉及到数据完整性准确性,所以更加复杂。比如,一个用户操作需要更新 a,b,c 三块数据,那我必须保证三者更新完整。换言之,更新 a 成功,但其他失败,则必须回滚 a 到更新操作之前的状态。这就是事务安全。截止到我写这篇文章为止,我们系统中很多的事务安全是依靠调用 api 来回滚的,而非依赖数据库的事务支持。使用 api 的坏处是有随时都可能失败,失败的后果就是数据状态不正确。

所以我们是如何面对协作问题的:依靠操作的发起方自己来保证数据的正确。也就是说,如果你是工程部需要技术部提供电焊和水管工作,技术部的负责人不会帮你安排这两个小组,需要你自己去找人。也就是说,技术部老大划分好小组就可以睡大觉了。

事实上我司的架构师们就是这么做的,写好 ppt,开几个 meeting 通知一下技术部门,然后就去做别的了。

业务逻辑分散

前面所说我负责的微服务,就是我们公司所谓的前台服务,也就是直接为 app 提供数据支持的服务。前台服务不直接操作数据库,访问后台服务 API 来获取/更新数据。后台服务是一个个职责非常单一的微服务,简单到只有基本的增删改查。比如前文提到的 profile,它只负责用户个人信息的增删改查,至于这个用户是哪个部门的员工,领导是谁,它一无所知。

正是因为后台服务太过于简单,所以很多核心业务逻辑转移到了前台服务。为什么我觉得前台服务不适合处理核心逻辑。原因有三:

  1. 核心逻辑对数据准确性十分敏感,最好就近管理数据库,以免数据损失; 2.核心逻辑可能大量消耗计算资源(比如我司关于足底压力的计算),最好由专用的服务器负责计算和持久化; 3.核心逻辑只应该有一个活跃版本,避免数据不统一(比如我们现在有很多核心逻辑散落在几个前台服务,造成最终呈现在 app 上的数据差异);

我们现在遇到的问题几乎都是由于核心逻辑的分散造成的,同样的原始数据,仅仅是因为在不同的前台服务中处理过,最终产生显著的差异。同时,前台服务天生不具备数据库访问权限,很多实时计算的结果不能很好的存储起来,导致计算资源浪费。

缓存缺失

第三个严重的问题和微服务本身无关,只是微服务架构让这个问题更加突出而已。我们的系统并没有一个统一的缓存平台。后台服务不需要缓存,前台服务按照自己的需要,或用 redis,或用 mongodb,缓存些仅仅和自己业务相关的数据。作为一个整体,我们应该有统一的缓存平台,只要是对 profile 的缓存,不管是 A 服务缓存的,还是 B 服务缓存的,大家可以彼此信任,互惠互利,而不是像现在各自为政,不理不睬。这变相的加剧了协作的问题,毕竟,如果大量的请求打到缓存上,后台服务的压力不会太大,处理起并行查询也会从容更多把。

总结

我们的微服务架构太简陋,只考虑切分系统而不考虑如何协作,导致前台承载过多业务逻辑以至于规模庞大,效率低下。同时,缺乏统一的缓存系统又变相加剧了这个问题。

web app 的性能瓶颈

我们的 web app 挺可怜,一方面,后台 api 效率感人,所以 app 的开发需要考虑添加很多额外的逻辑来减弱 api 的负面影响,比如,模拟后台服务的数据更新,当发出 api 后,直接更新 app 状态而不是等待后台确认。如果 api 的响应足够快,这样的逻辑其实是可有可无的。

另一方面,相当数量的业务逻辑,还有所谓的为了兼容老版本数据的代码,已经越来越难以维护了。这些代码产生的原因大部分是因为前端的妥协,理由是后台服务更新后,一部分客户端还没有更新到最新版本,所以在添加新代码支持新版服务的同时,保留维护老代码。问题是,客户端总有一天会全部升级到新版本,这部分老代码也就失去意义了。所以我们为什么没有一个章程来终止对老版本的支持呢?

所以 web app 的性能瓶颈就这么来了,而且让我无法反抗。因为从本质上来讲,app 本身的优化空间虽然还有(上 ssr,pwa ),但根本问题不解决( api 慢,老代码多),总归还是死路一条。遗憾的是,掌握话语权的人并不这样想。所以我们的 app 总是很慢,慢的不可思议。