前景回顾
上一期我说到项目组的规模不断扩大,许多问题接踵而至。我个人最大的负担是code review。起初三四人小团队,领导让我code review,我还是力所能及的。但是随着规模增到十几人,code review已鞭长莫及。隔壁组里的leader也开始向我抱怨Jenkins推送。
这里插一个前提概要。IVF(嗯,就是我们的母系统)部署出奇得慢,跑一趟部署可能要两个多小时,而且失败率高于成功率。我们serverless的CI/CD一般都能在十分钟内完成,所以我的部署很频繁。因此相对于IVF的推送来说,我们的消息实在是太多了。
因此隔壁组leader开始喷我“噪音污染”了。尤其是Success推送,他更显得轻蔑;他认为部署成功如呼吸空气一般理所当然,不该作为推送污染channel。不过对我而言Success推送还是有意义的。因为我们一个repo的MR很多,上一次部署成功意味着我马上可以accept下一个MR了,继而开始下一轮部署。这样我就不必为了等build结果而盯着Jenkins。
也许这就是旧时代和新时代的冲突吧。除此之外,诸如以下一些分歧也开始慢慢显现出来。这些问题涉及到管理范畴,我只是底层小职员显然不懂管理,自然也不配谈论这类话题,就一笔带过了。
- 单体应用还是微服务
- 代码质量管理问题
- 团队治理,小组分治还是大组集权
p.s. 特别想听领导们讲述管理经验,比如《人月神话》里的外科手术队伍,我当时看得真是津津有味。
旧架构的问题
还是回到技术范畴。随着业务的扩展,我们的serverless应用也逐渐演变成了一个大的单体应用。这时候一系列单体应用的弊端开始显现。是否开启微服务提上了日程。
我个人对大单体的想法就是:拆!当然,这时一定会有另一种声音:“微服务需要考虑服务发布、服务发现、服务追踪等等问问,一定要谋定而后动。”审慎的态度自然没有问题,但是并不是说只有拥有了一套阿里腾讯级别的方案才能开启微服务。有些话题是在服务到达一定复杂度之后才适合讨论的。微服务开始之初应该考虑的是解耦,只要它的生产效率高于单体应用就可以尝试微服务治理。
我曾经开发过一段时间的IVF。作为一个喷子,我一点也不吝啬对IVF的鄙视:不仅仅是框架、库和应用,我对它的代码也不敢恭维。尤其是作为一个复杂的单体应用更放大了一些顽疾。下面只列举一些琐碎的问题,对于我们底层工作人员来说,这类细微的问题对效率的影响是十分严重的。
-
层级
IVF就是这样一个有趣的单体应用,业务功能很简单。后端几乎只有增删改查,但是层级却是操作系统级别的——有四五层之多。尤其是service layer互相调用经常遇到循环依赖。
-
依赖
这个是IVF框架本身的问题了,大一统全包全揽。不仅build缓慢,最后连打开个IDE也似便秘。理想的状况是,各个微服务相互隔离,自己寻找需要的依赖。
-
文件
我看过神作《Clean Code》,它就提到阅读复杂代码对开发效率的影响。我当时就很反感IVF,代码文件过多、命名极长,连搜索效率也十分低下。我们底层员工每天都要阅读相似的代码几十遍甚至是上百遍,寻找文件就消耗了许多心力,这种无形的伤害是没有意义的。
-
无谓的Bug
另一个潜在的问题就是容易制造一些无谓的bug,比如使用IDE重命名可能牵连不相干的代码块,无意间修改了别人的函数等等。这是协同开发中不可避免的问题,但是是否有方法减少这类影响呢?
-
Git
几十人的git repo,冲突如何解决?CI/CD是否可靠?Code review如何保证?Google用的是mono-repo,但是一般公司有google的管理水平吗?
-
其他因素
上一期提到,E-pub之后又一批新的应用被加到serverless里,最大的两个单体应用是M-pub和I-pub。(不好意思我只能起这种名字)很快我发现了一些不协调的端倪:
- 模块冗余:M-pub和I-pub各自实现了一整套相同的Company api。
- 应用耦合:E-pub用户注册竟然在M-pub的后台,它俩本是单独的应用。
- 冷启动:过大的单体应用导致lambda的冷启动时间变长,有些组甚至曝出几十秒的启动时间,当时我们愈发觉得这种方式不可持续。
最后领导们达成了一些共识:
- 开启微服务治理
- 服务以multi-repo形式独立开发、独立测试、独立部署
- 3人左右小组负责单个服务
解决方案
-
拆分冗余
我把company、user、schedule等几个相互独立的模块从M-pub中分离出去。服务间通过restful api调用。(本来想用gRpc的,可惜lambda不支持。)
-
配置中心
网上有很多著名的开源配置中心如Apollo,近乎DB的功能。不过现阶段,我们并没有使用这类应用。我找到的是AWS自带的一个叫SSM parameter strore的服务,是一个Key-value的环境变量存储中心。线上统一管理环境参数,避免了application.properties这种大坑。
-
服务发现
Serverless服务没有集群,所以服务注册和服务发现相对来说要简单得多。我们的方案是:服务部署阶段将Provider映射的endpoint写入配置中心。Consumer通过动态读取配置就可以找到对应的restful Provider了。(是的,又很乞丐)
-
反向代理
上期提到IVF是通过轮询调用serverless api,为了避免暴露过多的endpoint,我找到了另一个乞丐版的应用——cloudfront。CloudFront功能有限,不过现阶段我们api读写不大,做load balance还没有这个必要;能做到简单的api分发也大体满足了需求。Cloudfront简单实惠,aws能提供这个应用我还是挺满意的。
-
避免多层调用
说实在,服务追踪还玩不起,我的做法是将调用链控制在两层以内。利用Graphql各个field可以异步或同步调用api的特点,将Graphql服务单独分割作为一个中心化的收集平台。功能服务(或叫做Provider)之间互相隔离,数据只通过graphql聚合。然后,前端通过
/graphql
查询后端数据。
经过了一个多月的重构,我们将原有架构大体改造了一遍。
后话
Serverless应用的复杂度又到了新的高度,Blog也快赶上开发进度了。很欣慰能一步步地走到今天,架构预计在一段时间内是不会有太大变动了,因为我们很多软实力还没跟上进度,比如文档、测试、监控、部署……
下次我想再探讨一些微服务治理的心得。现在,越来越多的同事加入了Serverless项目,分工越来越细,有些模块我也开始不熟悉了,要好好补补课了。