现在稍微有一点规模的公司基本都上微服务了,后端工程师在大小公司打杂的话都会碰到因为是微服务,所以在做开发的时候:
- 依赖太多,没有稳定的环境,服务跑不起来
- 服务要走网络,稳定性问题难以解决
- 上下游要解耦,每次上游做修改下游都会有故障
各种各样奇形怪状的问题,每一个痛点都会涉及到不少相关的解决方案,比如环境问题,之前我分享过 https://tilt.dev/;稳定性问题,我们直接去看 Google 三步曲 https://sre.google/books/;上下游用队列解耦之后,上游的不稳定业务事件导致下游故障,有 data validation 平台和 schema registry 来缓解。
我们这里还只是举了几个简单的例子,每一个问题都需要额外的努力来规避,对于那些正在迁移到微服务的公司来说,这些不过是一大堆问题里的九牛一毛。
对于想要使用微服务的公司来说,需要了解微服务税的概念:
It is the cost you pay to reap the substantial benefits of distributed application architecture.
K8s,可观测性基础设施,监控和基础的框架,都是在研发成本以外,需要额外付出的成本,好在现在有 CNCF,有一些微服务税我们不用交了,直接使用现成的基础设施即可(尽管这个所谓的 free lunch,也并没有多好吃。
在一大堆问题里最重要的还是框架问题,对于 Go 语言的生态来说,目前依然没有最像 java 生态的 spring cloud 那样大家都认可的解法。
框架在微服务技术架构中是承上启下的作用:
需要负责对接公司内的基础设施,尽量将这些基础设施的差异性对业务方屏蔽,业务研发只要写逻辑就可以了,其它问题一律不用关心。
另一方面,框架本身也应该协助业务降低重复代码的编写次数,比如根据协议 IDL 来生成协议/控制层代码,根据用户预定义好的 SQL 生成相关的访问函数代码,都是大家熟知的例子。
看起来好像没什么技术含量,但是为什么现在市面上的框架都是灾难一样的使用体验呢?哪怕那些大公司开源的框架,里面一样充斥的垃圾代码和 bug?
这个问题我和同事们认真地分析过,主要有下面一些原因:
- 大公司里的框架研发,基本都是不写业务的
- 公司内的晋升、考核,与框架的用户体验没有直接关系
- 框架的研发者有意地隐瞒了那些对他们不利的信息和数据
第一点比较好理解,大多数写框架的人可能一行业务代码都没写过,没有动过手,自然很难去体会到业务到底苦在哪,没有共情,做出来的东西也很难对路。如果连用户调研都不愿意做,那结果就是自 high 了。
第二点,中大型公司的考核体系,对于框架组很多时候是考核规模和性能数据指标,比如:框架覆盖度,框架和其它业界同类方案的优势,以及看起来比较先进的技术点(看你怎么吹),不会考核框架本身的用户体验。
你可以关注一下你们公司里会不会针对公司内的框架收集 nps 调查问卷,如果没有,那大概率就是不会考核。之前某个国内的巨头公司,因为框架研发组的老板级别很高,大家不敢在内网吐槽,还要跑到脉脉去讲。
第三点,也是因为第二点衍生出来的,公司考核的就是这些性能指标,如果性能指标出问题了,对于框架的研发人员的绩效和考核来说是非常不利的,所以有些数据在内部测完之后不会放出来,也就是刻意对用户进行了隐瞒。这个其实也不难理解,比如有些 hack 的性能优化,是要结合业务场景严谨地分析才能知道是否合适的,研发人员在公司内为了不出问题,一般也会这么做,但在对外宣传的时候,他们会把这些优化的限制刻意隐瞒掉,导致公司外部的人被相应的宣传所误导,后续走弯路。
所以这也是我建议在使用那些大公司开源的技术方案时,应该自己多做一些性能测试的原因。
好像扯了一些有的没的,我们还是要讲一点干货的。
最近被之前蚂蚁的质量同事教育,了解了做域内自动化测试时,对于服务的外部依赖的 mock 需求:
如上图,自动化测试脚本对被测服务发起测试,希望看被测服务是否能够处理各种外部依赖的正常/异常流程,这时候希望能够在不侵入被测代码的前提下,能够改变被测服务依赖的外部服务(即这里的 serv_a,serv_b,serv_c)的返回结果,在 java 中有 jvm-sandbox 这种能够比较方便地对测试进行隔离,并且动态修改 class 实现的神器,但是在 Go 里,这个需求不太好实现。
正好目前公司内在大规模落地 go-zero,所以我们和 go-zero 社区的同学合作,想了一些办法来解决这个问题,下面是社区同学提出的方案:
因为想要在不改被测服务的前提下来修改外部服务的行为,这让我们想到了日常工作中用到的一些代理服务,所以我们在中间加一个 proxy。
这个 proxy 可以和自动化测试脚本交互,由脚本来设定相应的服务、请求和响应匹配规则,可以做到 case by case 的请求响应匹配/隔离,也就是可以动态地对 serv_a,serv_b,serv_c 这样的服务做 mock。
因为公司内默认使用的是 grpc,目前市面上所有 mock 都需要用户提供 pb 才能进行,并且没有办法动态设置。而对 grpc 比较熟悉的同学则知道在 grpcurl 这个工具中,给我们提供了 describe 功能,我们可以使用 grpc desc 来查看服务的定义,当然,前提是服务开启了 reflection。
通过 reflection 能够拿到服务定义的话,我们无需再去要求用户提供原始的 pb 定义。