最近 Python 更的比较多,准备写一些 Java 的东西了。主要涉及微服务、Spring Boot 吧。今天就从微服务开始我们的旅程。
构建单体应用模型我们假设,开始开发一个打车应用,打算与滴滴竞争。经过初步交流和需求收集,这个应用的架构图出来了:
该应用的核心是一些业务逻辑,它定义了服务、领域对象和事件。围绕在核心周围的是与外界交互的适配器,包括数据库访问组件、生产消息和消费消息的组件、暴露给外部的 API 和实现了 UI 的 web 组件。
从业务逻辑上来看,这个涉及是挺模块化的,但是部署时,还是要做为整体打包部署。比如,要是 Java 写的一般需要打成 WAR 包,然后部署在 Tomcat 这样的应用服务器上。
以这种风格编写应用极其普遍,通常它们非常易于开发,因为我们的 IDE 和其他工具都是专注于构建一个单独的应用。这样的应用通常也易于测试,通过简单地运行应用就可以实现端到端的测试,使用 Selenium 就可以测试 UI。
另外,单体应用也易于部署。你仅仅需要复制打包的应用到服务器。你也可以通过在负载均衡器后面运行多个副本的方式扩展应用。在项目的早期阶段,这种方式可以运作得很好。
单体应用的灾难很不幸,这种简单的方式有一个很大的局限性。随着时间的推移、功能的堆积,应用变得越来越臃肿。经过几年之后,原本小而美的应用变成了一个可怕的庞然大物。
一旦你的应用变成了一个复杂的庞然大物,你的开发团队就可能生活在痛苦之中。敏捷开发的任何尝试和交付将会举步维艰:
- 应用过度地复杂,没有任何人能够完整地理解这个应用
- 修复 bug 和实现一些新功能会变得非常困难
- 形成恶性循环,问题越来越多、新功能难以开发
- 应用程序的规模越大,启动的时间越长
- 持续部署的障碍,因为你必须重新部署整个应用来更新程序的任何一小部分
- 当不同的模块具有资源需求冲突的时候,整体应用程序也将难以扩展
- 所有的模块都在同一进程内运行,所以任一模块的漏洞,比如内存泄露,将会影响整个进程
- 采用新技术、新框架、新语言极其困难
P.S. 现在我就是在等待 Docker 镜像编译,有点讽刺...
总而言之:这个应用已经长成为一个可怕的庞然大物,几乎没有开发者可以理解。这个应用使用着过时的技术,难以扩展而且不可靠。因此,敏捷开发和应用交付几乎成为不可能。
所以该怎么办呢?
救星来了:微服务许多大公司,已经采用现在被称为微服务架构模式的方法来解决这个问题,即将单体应用分割成更小的相互关联的服务。
一个服务通常实现一组独立的特性或功能,比如订单管理、客户管理等。每个微服务就像是是一个迷你应用,其中包含着自己的业务逻辑以及许多适配器。同时,一些微服务会暴露供其他微服务或者客户端调用的 API。另外一些微服务可能实现 web UI。在运行时,每个实例通常是运行在云虚拟机或者 Docker 容器中。
例如,前文所述的单体系统可以拆分成微服务架构:
应用的每个功能区域现在以微服务的方式实现。此外 web 应用程序分割成了一组更简单的 web 应用程序(一个面向乘客的,一个面向司机的),这使得更容易为特定的用户、设备或特定的用例部署不同的服务。
每个后端服务都对外暴露了 REST API,大部分服务都会调用其他服务提供的 API,例如司机管理模块使用通知服务告知空闲的司机可能的订单。
另外,还有一些 REST API 是要暴露给移动 app 的,然而 app 不能直接访问后端服务,它们之间的通信是通过称为 API 网关的中介来传递的。这个 API 网关是个小能手,需要负责负载均衡、缓存、访问控制、API 测量和监控,可以使用 NGINX 来实现。
比如,使用 Docker 来部署上文中提到的订单管理服务:
在运行时,订单管理服务包含多个服务实例,其中每个服务实例是一个 Docker 容器。为了实现高可用,这些容器会运行在多个云虚拟机上。在这些服务实例前面是一个诸如 NGINX 这样的负载均衡器,用来实现在多个实例之间分发请求。负载均衡器也可能处理一些别的事情,比如缓存、访问控制、API 度量和监控等。
很明显,微服务架构中应用程序与数据库之间的关系变得不一样了。在单体应用中,所有服务共享单个数据库,而微服务中每个服务都有自己的数据库。尽管这种方式与企业级数据模型的想法相悖,也会造成某些数据的冗余。但是,如果你想从微服务中获取好处,那么每个服务就应该有一个单独的数据库,因为这确保了程序的松耦合。
下图展示了应用的数据库架构:
每个服务有其自己的数据库,这也有利于各个服务选择使用最符合各自需求的数据库。
微服务的优点微服务架构模式有许多的优点:
- 技术异构性:开发者可以自由地选择合理的技术和框架,只要服务遵守 API 约定即可;
- 弹性:将不同服务部署在不同机器上,降低整体功能不可用的概率;
- 可扩展性:可以只对需要扩展的服务进行扩展;
- 简化部署:各个服务模块独立部署,可以快速对特定代码进行更新,而不是进行整体更新;
- 灵活组合:不同服务模块的接口可以再进行重用,成为其他产品中的一个组件;
- 可替代性:当一个服务模块出现问题时,可以进行轻易地重写或替换;
-
实现复杂度高
微服务间通过 REST、RPC 等形式交互,相对于单体模式,需要考虑被调用方故障、过载、消息丢失等各种异常情况,代码逻辑更加复杂。同时,在微服务间存在少部分共用功能但又无法提取成微服务时,各个微服务对于这部分功能通常需要重复开发,或至少要做代码复制,以避免微服务间的耦合,增加了开发成本。
-
测试微服务复杂。
使用 Spring Boot 这样的现代框架很容易开始一个整体 web 应用程序,编写测试类测试其 REST API。于此相反,对于微服务的一个类似的测试则需要运行该服务以及依赖的服务。
-
分割的数据库架构。
对于微服务间的事务性操作,因为不同的微服务采用了不同的数据库,将无法利用数据库本身的事务机制保证一致性,这对于开发者来说更具挑战性。
-
接口匹配问题
服务依赖于彼此间的接口进行通信。改变一个服务的接口会对其他服务造成影响。设想你实现的用户故事需要更改服务 A,因为 A 依赖 B,B 依赖 C,所以你又得更改服务 B 和 C。
-
部署复杂
整体应用程序简单地部署在一组相同的服务器上,在这些服务器前面是一个传统的负载均衡器。而微服务应用通常包含大量服务,每个服务有多个运行实例。有太多运行的实例需要配置、部署、扩展和监控。另外还需要实现一个服务发现机制,以确保服务之间相互发现。
-
运维复杂
在采用微服务架构时,系统由多个独立运行的微服务构成,需要一个设计良好的监控系统对各个微服务的运行状态进行监控。运维人员需要对系统有细致的了解才对够更好的运维系统。
-
其他
同时,微服务引入了复杂性和其他若干问题,比如说网络延迟、容错性、消息序列化、不可靠的网络、异步、版本化、应用层中的负载变化等等。
构建复杂的应用本身就很难。单体架构只适合简单的轻量级应用。对于复杂的应用,如果你选用单体架构,那么最终你会生活在痛苦之中。微服务架构对于复杂的、不断演进的应用是更好的选择,尽管微服务架构有些缺点和实现上的挑战。
总而言之,微服务架构有很多吸引人的地方,不过在拥抱微服务之前,你需要认清它所带来的挑战。
参考:
https://www.oschina.net/translate/introduction-to-microservices