启嘉网微服务架构实践

关于微服务架构的思考、设计和实践


本文章基于以下CC协议进行知识共享:署名(BY)-非商业性使用(NC)-禁止演绎(ND)


为什么选择微服务

在之前的启嘉网后端中,我们使用传统的单体架构 .Net + PHP (中间层) 进行开发。但是在实际的开发过程中,遇到了以下几点问题:

  1. 功能模块越来越多的时候,项目维护越来越困难,在推送版本更新时,经常出现未知问题导致整站崩溃。
  2. 一些高负载的功能模块无法抽离,导致负载均衡较难实施。
  3. 无意识写出的高耦合代码,导致产品需求变更时,连带修改太多。
  4. 开发人员出现变动时(如请假、离职),较难调整开发安排及排期。

而使用微服务架构的话,各个功能模块(服务)可以单独部署、单独更新,即便出现问题,也可以保证其他模块的正常使用。同时,也可以将高负载的微服务抽离到性能更高的服务器上进行优化。其次,微服务的设计思想所带来的低耦合,对产品设计、开发都比较有利。最后,在后端人员出现变动时,可以随时抽离人数更多的前端人员,使用 NodeJS / Python / Golang 等任意一种语言开发微服务,来保证排期的正常进行。

架构设计概述

架构设计图示

如上图所示,微服务部分主要分为 3 层 —— 网关层、服务发现层、应用层。

网关层

客户端请求(浏览器)首先经过 负载均衡服务器(用于提升网关层的并发能力)。由 网关层 分析请求所涉及的微服务,然后调用 服务发现层 的接口,判断服务存活状态,并对多实例的微服务进行负载均衡。获取到微服务实例的地址后,进行 RPC 远程调用,然后将结果返回给客户端。

应用层

应用层即实际的业务逻辑部分,由基于 NodeJS / Pytonn / Golang 等任意框架或语言开发的微服务组成。服务间通信使用谷歌的 gRPC 框架进行远程调用。应用层的服务在启动时会调用 服务发现层 的接口进行服务注册,同时,在服务不可用时,服务发现层 会自动剔除该节点来保证服务的稳定运行。虽然说以启嘉网目前的规模,使用 RPC 还是 HTTP 并不会产生较大的性能差异,但是为了适配未来的发展和学习研究等目的,我们仍然选择了 gRPC。

服务发现层

网上主流的解决方案中,通常是使用 gRPC + etcd 这样的组合,并由 etcd 进行服务注册与发现的工作。但是由于时间关系,在方案落地过程中,我没有太多的时间去完全学习所有的技术栈组合。因此决定使用自己擅长的 NodeJS 自行开发,同时由于 JS 是动态语言,那么在构建 服务发现层网关层 这样不确定(动态)因素较多的应用时,不必像 Golang 一样频繁的使用反射来实现相关功能。

落地实施

在启嘉网这边落地微服务有几个原则:

  1. 不让架构过分入侵业务代码。
  2. 优先使用类库而不是框架。
  3. 渐进式。

为了实现以上几点,我们针对 NodeJS / Python / Goloang 封装了基础库,将服务发现层的相关操作对外简化,来达到 不让架构过分入侵业务代码,如 Golang 建立服务的所有操作仅需要4行代码,其中包括检测服务器自身环境、自动寻找可用地址和端口等等。

1
2
3
4
5
6
func main() {
server := microservice.CreateServer()
service := new(service)
pb.RegisterGreeterServer(server, service)
microservice.Register("helloworld", server)
}

优先使用类库而不是框架 实际也是一个不入侵业务代码的体现,但是更重要的是希望能简化处理,不要因为使用某个框架而学习框架带来的很多抽象概念。

最后,由于项目功能模块比较多,想快速完美的切换到微服务架构是基本不可能的。因此,需要 渐进式,用细分化的微服务逐渐替换掉原有单体应用中的功能。这部分只需要网关层判断一下即可。

分享到