微服务经历总结
微服务网上已经有很多人定义过了,这里不再纠结或者往这方面细节上去做过多的描述。本篇文章我将结合项目经历,谈谈开发过程以及遇到的坑。
九曲十八弯的过程
项目一开始并没有追求微服务化,而是本着够用就行的原则(实际上Boss也不会给你过多的时间去规划,因为“互联网就是要快”),项目构成很简单:
- 统一认证服务。面向用户的授权鉴权,2个服务
- 交易中台,承接交易业务,十来个服务
- 接口服务,承接业务需求,四十几个服务
- 管理后台,7个服务
- App
项目一些基本的技术栈:
- 后端全部C#
- 对外对内接口都使用gRPC格式
- App直接集成gRPC接口,使用
gRPC JSON转码为Web提供RESTful接口 - 广播类通知类的数据使用RabbitMQ
- 全部容器化、微服务化、云化
- Web使用Vue
- CS客户端使用Flutter
如果项目发展就此打住也没什么大问题,但项目是创业项目,发展了好几年,其间产品经理都经历了5手,那模块需求更是数不胜数,光管理后台一级菜单就占满了屏幕。问题出现在服务设计没有跟上变化,具体就是上面接口服务。我负责后端部分服务的统筹后,项目已经上线了一年,此时反复看接口服务已经一百多的表,必须改变单体的思路,于是我提议微服务化并得到了执行,但一直难以调整。主要还是时间成本不愿意被付出,一直是打一枪算一枪。于是,微服务化在项目里面并没有得到很好的设计。具体表现是服务确实拆了,也确实单独部署了服务,但也仅限于此。由此产生了包括下面在内的大问题, 下面同时提到的改变,直到空降一个技术头头才陆续被推动调整
架构问题
这是最大而且是根本问题。前期基于几台阿里云ECS,集群只使用了简单的架构:nginx做网关和负载均衡,往后以docker swarm的模式部署微服务;打包镜像/更新服务全靠手工,只有很有限的服务有健康检查,总之整个服务流程并没有形成一套高效稳定的、有流程有规范的微服务,期间也有因为不规范造成的事故。不过从23年底开始有了转机,新任技术头头来了三把火,直接要求上K8S + AWS。于是漫长的架构改造和云服务迁移被提上日程,整个过程耗时一年,可以说解决了很多之前的弊病,还是管理和规范拿捏问题。所以云服务还是要有一个强势的架构Leader,规定一个服务能用什么,怎么用,下面人的服务有问题不能想怎么样就怎么样,得经过详细商议,靠谱的管理能让大家的工作和项目架构都清晰起来。总结这些管理和规范有:
- 规定CI/CD使用Jenkins,包括构建镜像/打镜像标签/更新服务/切换蓝绿集群(终于不用连接N台服务器更新N个服务了)
- 规定后端服务使用K8S跑(终于不是杂乱的docker swarm/docker-compose/docker-stack了)
- 规定不是长时间运行而是只在某些时段运行的定时任务使用k8s的cronjob规划运行(节省计算资源)
- 规定后端的服务使用的资源标准(限制请求的CPU/内存,能发现异常的内存,比如迁移期间就监控发现了一个因方法死循环而内存暴涨的问题)
- 规定前端接口只使用一个子域名,不同服务使用不同二级路由做指向(终于不是各个中台/领域服务一个子域名了,前端之前不同领域的服务接不同的子域名,乱的很)
- 规定整体服务切分为四大块服务:App业务服务/核心1服务/核心2服务/前端相关服务(服务端渲染、CDN等)
- 规定K8S文件处理使用S3,并且只用两个Bucket:公开的和私有的
- 规定公开访问走CDN网络(AWS CloudFront)
接口暴露问题
项目里面统一认证由Identity Server提供面向用户的服务,对于暴露公网的服务是通过nginx网关配置的,服务之间没有统一的鉴权处理,实际上服务之间不走公网的话不一定需要鉴权。但问题在有一两个服务的暴露是连带着内部接口一起的,公网接口会指定统一认证鉴权,但内部接口路径混在一起了,只能额外为他们增加另外一种鉴权,然后调用客户端加对应的授权,麻烦。实际操作比较好弄的,是将接口分为两种:外部接口和内部接口,直接在gRPC的接口定义Proto文件里面就区分好:
- Proto脚本里面新建External文件夹,全部外部接口proto都放这里,包命名也带上,因为涉及接口路径
- Proto脚本里面新建Internal文件夹,全部内部接口proto都放这里,包命名也带上,因为涉及接口路径
- 外部接口由统一认证做鉴权,网关那路径指定到External命名空间
- 内部接口应保证不会被网关暴露到公网
管理端
项目的管理端本应该只做“管理”的职责,因为初期融入了几块业务,让它也有了“业务”的职责。随着堆积的需求越来越多,整个管理端扩展成了百来张表,7个服务。直到我接手后才向着微服务处理的方式,原则上不再增加新业务,相关的、成块的业务那就用新的、独立的服务区支撑。后面团队扩招和调整,让这种方向相当难以把握,主要是:1、处理惯性,很多业务需求依然在管理端实现;2、团队管理,管理端是跨团队的,不了解业务的情况直接要求用独立的服务去实现需求并不容易。至今臃肿的管理端仍然是我在找机会解决的技术债务之一。所以伙伴们,如果一开始就确定微服务化,一定不要让任何业务处理代码出现在管理端,管理端只需要处理权限、接入各个服务接口就行了。
多语言需要专门的服务
这是个国际项目。依然是初期没有一个很好的规划,导致现在多语言处理困难。首先多语言或本地化是前后端都要参与,客户端有自己的一套就不细说了;主要后端的话,目前最大的问题是不易扩展。主要体现在要增加一种语言,要处理的地方非常多:N个地方的的表和resx文件要增加。resx还好,增加或编辑对应的key就行,不过依然没办法动态处理;最不好扩展的是多语言文案放在了字段上,一种语言一个字段,想要增加语言还得加字段,疯了的做法。因此这里建议:
- 如果合适弄一个专用的
多语言服务,相当于一个词典中心,用于响应多语言词条/文案 - 多语言尽量存在数据库中而不是resx中。数据库的话可以用MySQL的json类型做动态扩展,也可以lang字段不同语言做不同行记录
配置中心
我司早期用着Apollo配置,但因为并没有统一推行而且服务笨重,后续逐渐剥离不用。目前并没有使用一个成型的配置服务,最多使用了AWS的Sysmtem Manager - Parameter Store去存储部分和环境相关的参数。如果有建议的配置中心使用方法,欢迎评论
日志处理
早期服务我尝试使用过Exceptionless收集日志和处理异常,但因为存储过大问题被上司停掉了(😮💨)。后面并没有一个比较好的日志监察和处理的方法,最多只是迭代之后观察一阵。换K8S架构后,新任技术头头既要及时监察日志,又要排查问题,于是要求部分关键服务上了......错误日志到邮件,后面邮箱都爆了两次...怨气满满(😮💨)。而服务的日志一般性处理则由日志组件接入AWS的CloudWatch完成,想吐槽的是这样就和云厂商相关了。不过24年年底完成迁移之后,接下来日志计划是要让日志输出到容器控制台,然后由某个自托管的相关服务统一收集,方便各个开发通过KubeSphere查看日志,不用先登录AWS账户了
CI/CD与迭代服务部署
可能无法想象,团队的微服务还停留在docker swarm的时候,当测试一声令下说可以迭代发版,包括我在内的2~3个项目Maintainer就会齐刷刷本地发布文件,打包镜像,再一个个服务核对更新事项(配置变更/SQL执行)后更新服务,没错就是人肉执行(🤣),每次更新服务中断时间2小时算少的。期间我实践过用Jenkins推动CI/CD化,后来由大佬检测到购买的云服务器性能不够就不提倡了,我也无话可说。老板决心将服务迁移到AWS,连着新架构+团队新工作模式(敏捷开发),GitLab -> Jenkins -> Terraform+K8S模式被集成并持续改进。直到最近,Maintainer终于可以一键发布自己的服务了,So easy! 这里的感悟是:
- 要推动一件事情,首先自己要相信这件事情带来的益处,还要说服别人相信
- 切忌单打独斗,要怎么做自己可以和上司商量起来,因为这是团队的事情;和大家商量才能更好确定方向,也更容易推行
- CI/CD等DevOps一定要趁早确定推进
杂乱的网关
docker swarm时期很多子域名分散在各个划分的领域下,然后通过nginx做反向代理。划分没毛病,有毛病的是没有团队统一路由/路径的使用,更有甚者一个Controller配置一个location...(前人挖坑,后人照例挖一模一样的坑...)。上K8S后要求全部接口走一个子域名,这样只得前后端配合做路由调整,这样做很麻烦因为要考虑调整前后的兼容性,还要确定影响的范围。光这样一份洋洋洒洒的调整,断断续续处理了半年。所以,团队做东西要规范!规范!还是TM的规范!
微服务健壮性与定时任务
- 主程序退出控制台输出
其他:
前后端分工问题
后端的能提供一套标准化接口。当业务十分复杂或需求十分精致,标准化可能会被死无葬身之地,但给到前端的数据应遵循以下原则处理:
- 奥卡姆剃刀法则。业务上务必要,就不需要返回多余的字段。这部分直接从DB抓起,不需要的也不用查询出来,考虑后续维护都是个头疼的问题
- 逻辑后置。前端理应侧重用户交互、界面呈现,不应该过多的处理数据逻辑。所以,应该将计算或逻辑存放于后端处理,直接返回现成字段
- 图片浏览禁止参数化。有些图片相关的接口可能会支持类似service.com/pic/256x256/a.png的动态参数,即通过URL参数实现图片动态大小、剪裁或其他处理。这样会有两个情况需要考虑
- 图片服务是否能支撑或拒绝大量请求。如果不行不要
动态参数形式,而是固定“原图”、“大”、“中”、“小”若干规格的形式 - 不论是动态参数还是若干规格,前端需要什么样的URL应由后端返回,而不是前端选择
- 图片服务是否能支撑或拒绝大量请求。如果不行不要
- 版本化与前后兼容问题。前端发版尽量不要和App或后端版本相关,不然运维是个十分麻烦的事情。前端能做就是能做,不能做的那也应该通过其他方案或整个项目能力上去之后再实施。而不是做版本化这种十分影响运维的方式
RabbitMQ混乱
一开始大家还没几个服务的时候虽然有用到RabbitMQ,但使用case的十分有限。等微服务化后,MQ这个必备的组件就随着需求的实现而野蛮生长。直到目前都是没被优化到的技术债务之一。目前的问题由:
- 未遵从
VirtualHost划分。新增加的按照K8S namespace做了划分,但过去的则还是默认的VirtualHost: "/" - RabbitMQ是基于链接的。一个链接可以一个或多个生产或消费业务,这个由实际架构或业务情况去确定。我们的问题是由于没有一个统一的处理规范,有些代码一个业务一个链接实例的,配置文件一个交换机就一份链接配置...
- RabbitMQ对于交换机(
Exhcange)和队列(Queue)在生产者还是消费者声明并没有指导性的规范。可以从服务初始化角度出发,Exchange由生产者所在服务启动时声明,什么业务要订阅消息,那就由消费者所在服务启动时声明Queue并绑定对应的Exchange。如果有其他的方案,欢迎告知

浙公网安备 33010602011771号