什么是微服务
“微服务”是软件架构大师Martin Fowler提出来的,但至今为止其都没有一个确切的定义。只有在Martin Fowler的官方网站上给出了一段关于微服务框架的说明。该段说明原文如下:
简单来讲,微服务架构风格是将一个单体的应用程序开发拆解为一组“小”的服务,值得注意的是,这里所说的“小”是以业务边界来区分的,而不是根据代码的多少来区分的。每个服务都运行在一个单独的进程中,服务之间通过轻量级的机制进行通信,例如使用HTTP资源接口:每个服务都可以通过全自动化的部署机制来独立部署;微服务中的各个服务可以以多种语言来编写,但是在实际开发中,由于各个公司的技术栈有限,通常会指定一门擅长的技术语言,例如Java;每个服务都可以使用不同数据存储技术,例如MySQL、Cassandra及MongoDB等,但是为了统一,通常各个服务还是会选用同一种存储技术。
为什么需要微服务
为什么需要微服务?通常会将微服务机构和单体架构进行比较。二者架构图对比如图1-1所示。
左边是单体架构图,右边是微服务架构图。传统的单体架构图主要包括三部分:一个展示层,用于将一些信息展示给客户端人员或者为客户端人员提供一些交互页面;一个数据存储层,通常就是提供一个数据库,用于存储一些需要持久化的数据;最后一个部分,是一个服务端的应用程序,该程序主要用于处理请求、执行业务逻辑、操作数据库,以及将相关结果返回给前端等。在该架构中,所有的请求都在一个进程中处理,而且水平扩展也很简单,只需要多添加几台部署了该服务的机器,之后再这些服务器的前边部署一台负载均衡器就可以了。
单体架构有这么几个问题:首先,由于所有的业务逻辑都写在了一个应用server中,因此只要对该Service进行修改,哪怕只是添加一行代码,也需要编译打包部署整个应用,需要的时间会比较久,耗时耗力**;其次,假设整个应用只有一个接口到达了瓶颈,我们想要水平扩展该接口,这个时候只能通过水平扩展整个应用来达到目的**;最后,随着应用程序规模增加,即使我们使用Maven进行模块化开发,也很难保证对一个模块的修改不会影响其他模块。
**为了解决这些问题,微服务架构出现了!**在微服务架构中,我们将一个整体应用切分成多个小的服务,这些小的服务可以独立部署并且每个服务运行在自己的进程中,所以如果修改了一个服务的代码,那么我们只需要单独部署该服务就行,而且如果需要水平扩展一个服务的接口,只扩展该服务就可以了。
微服务架构的缺点
微服务架构可以解决这么多的问题,那么它有什么缺点吗?
首先,由于微服务将一个整体应用拆解成了一系列的服务,这样不可避免地就会在不同的服务中存在一些冗余代码,例如,如果server1和server2中都用到了Redis集群,可能就需要在两个service中都编写一个RedisCluster的工具类,而这个工具类的代码几乎是一模一样的。
微服务架构的使用使原本的一个应用程序变为几十甚至几百个服务。维护一个应用程序简单,但是维护几十甚至几百个服务就不是那么简单了,所以需要配备功底较深厚的运维人员,也需要搭建完善的运维系统,例如,完善的计数监控系统、完善的日志系统以及完善的链路跟踪系统等。
在微服务架构中,服务众多,服务之间的调用也由原来的单体架构中的进程内调用变为了进程之间调用,这就需要考虑很多问题,例如使用什么通信方式,如何处理网络延迟及服务容错等问题。这是分布式系统都会存在的问题,但是在微服务架构中这些问题尤其严重,因为“分布”得非常厉害。既然是分布式系统,就还存在一个很大的问题:分布式事务处理。分布式事务的解决方式虽然比较多,但是几乎没有十全十美的方式。通常会使用补偿方式。
另外,如果从零开始搭建一套微服务系统,那么需要掌握的组件技术会比较多,从而开发的难度较大,开发周期会比较长。在开源世界中有一个比较好的微服务架构:Spring Cloud。但是如果使用Spring Cloud,可定制性就远不如自己搭建的微服务系统。另外,即使使用Spring Cloud,一些系统还是需要自己搭建,比如日志系统、计数监控系统等。这都需要具有一定的计数功底。
最后,采用微服务架构就会涉及怎样将一个整体应用拆分成多个微服务,我们说了拆分的原则是根据业务拆分,但是这个业务边界有时候是比较难划分的。这个时候就需要有一个超牛的架构师来做这个事儿了。
微服务中组件与技术选型
1.服务注册于服务发现
什么是服务注册和服务发现?为什么需要服务注册和服务发现?怎么实现服务注册和服务发现?下面就来回答这几个问题。
服务注册形象地讲就是将服务的ip和port注册到注册中心,这里可以简单地将注册中心理解为一个Map,其中的key是服务的唯一标识(可以是serviceID,也可以是serviceName),而value是一个包含ipAndPort的结构体的集合,例如是一个List集合,该List集合中存放了指定service所在的所有服务器。
服务发现简单地讲就是根据服务的唯一标识,从注册中心获取指定服务所在的服务器列表,根据上述Map中的key来获取value。
使用服务注册和服务发现的好处很多,其中最主要的就是实现了服务之间的解耦。如果不使用服务注册中心,那么我们需要将被调用服务的服务器列表直接写在调用服务的配置文件中,这造成了两个服务的直接耦合。之后,在调用服务的时候,通过一定的负载均衡算法,从服务器列表中获取一台服务进行调用。此时,如果被调用服务在服务器有宕机情况或者新添加了服务器,调用服务也不会发现,除非修改配置文件,之后还需要重启服务。而使用了服务注册和服务发现后,调用服务会及时发现新增的机器或者宕掉的机器,而且不需要重启服务。 常用的可以用来实现注册中心的技术有Consul、Zookeeper、Etcd和Eureka等。
2. 健康检查
什么是健康检查?为什么要健康检查?怎样实现健康检查?下面就来回答这几个问题。
健康检查是检查两个东西是否处于正常状态:一个是服务所在服务器的运行状态;一个是服务本身的运行状态。健康检查的目的其实就是为了在服务发现和服务路由的时候,可以将服务的调用请求发送到处于健康状态的机器上,不至于使服务调用因为请求被发送到不健康的机器上而失败。
常见的可以用于实现健康检查的技术有Cosul、Spring Boot的Actuator。
3. 配置管理
为什么需要配置管理?怎样实现配置管理?下面就来回答这几个问题。
配置管理主要做三件事。第一件事是在一个地方将服务集中管理,例如在Consul-KV中集中配置,这样可保护配置信息的安全,例如,开发人员在上线一个项目的时候,只是将配置信息(其中的数据库配置信息是测试环境下的)提交给运维人员,运维人员可以将线上数据库的配置信息更改到配置文件中。这样的话,开发人员是看不到线上的配置信息的,起到了一定的安全防护作用。第二件事是实现服务的配置与代码分离,这样修改了配置信息之后不需要再编译、打包、部署整个服务。第三件事是实现“热配置”,既当修改了配置信息后,不需要重启服务就可以自动获取修改后的配置信息。
常用的可以用来实现配置管理的技术有Consul、Archaius等。具体实现见第8章相关内容。
4.服务通信
什么是服务通信?为什么需要服务通信?怎样实现服务通信?下面就来回答这几个问题。
这里所说的服务通信指的是在服务之间的相互调用。服务之间的调用协议可以使用TCP协议,也可以使用HTTP协议。而基于HTTP协议的通信方式是Martin Fowler所推荐的,而且基于HTTP协议的代码要比基于TCP协议的代码好写很多,因为不需要考虑丢包、粘包等比较底层的问题。当然,也可以使用现成的框架来屏蔽这些底层细节,例如Netty。但是即使使用了Netty,也远不如直接使用HTTP协议进行通信来得简单。
常用的可以用来实现服务通信的技术有Netty、Mina、Retrofit、Okhttp和AsyncHttpClient等。其中Netty和Mina出自同一人之手,风格类似,主要用于基于TCP或UDP协议的通信;OkHttp和AsyncHttpClient主要用于基于HTTP协议的通信,后者的效率要高于前者,推荐使用后者;Retrofit以一种接口方式封装方法,并且采用动态代理实现方法的调用,这使得调用远程方法就和调用本地方法一样简单明了。
5. 服务路由
什么是服务路由?怎样实现服务路由?下面就来回答这几个问题。
服务路由的过程是这样的:当一个请求过来时,通过服务发现和健康状态检查选出健康的服务器列表,之后采用一定的负载均衡策略(路由策略)从这些服务器中选出一台,最后将请求发送到这台服务器上去。服务路由器通常会包含一个内建的负载均衡器,而且会包含多个负载均衡策略,这些策略都是可插拔的,并且会在本地缓存一份可用的服务器列表,当然,也会通过一定的策略来更新该服务器列表,例如定时地使用服务发现技术来刷新本地服务器列表缓存,进而达到在宕机或者添加了新机器的时候,本地服务器列表缓存可以及时更新。
常用的用来实现服务路由的技术有Ribbon。
6.服务容错
什么是服务容错?为什么需要服务容错?怎样实现服务容错?下面就来回答这几个问题。
服务容错指的是当服务集群中的一台机器宕机了,也不会导致整个服务不可用,甚至不会因为级联失败导致多个服务不可用,形成雪崩。在实际开发中,服务容错是必须要考虑的事情,不仅要考虑之前说到的级联失败的问题,还要考虑到怎样让一个宕掉的服务自动由不可用状态转为可用状态,并且让这个状态切换的时间尽可能短。
常用的可以用来实现服务容错的技术有Hystrix。
7. 日志系统
为什么需要日志系统?怎样实现日志系统?下面就来回答这几个问题。
日志系统主要用于收集散落在各台机器上的日志,并提供高效的存储和查询方式,通过清晰易懂的界面进行结果展示。当然,也会提供方便的分析功能等。
常用来实现日志系统的技术有Logback、ELK、Redis、Flume、Hadoop、Kafka等。
8. 全链路追踪系统
什么是全链路追踪系统?为什么需要全链路追踪系统?怎样实现全链路追踪系统?下面就来回答这几个问题。
全链路追踪系统指的是在微服务架构中,由于服务比较多,通常需要多个服务彼此协作调用,这个时候就产生了调用链,我们想理清服务之间的依赖关系,可以分析调用链信息,并且最好还能以图形的形式展现出来,否则,即使统计了调用链信息,也不容易分析出依赖关系。而且如果调用链较长,想要找出耗时的服务进行调优比较难,一旦发现错误,也很难定位是整个环节中哪一个服务发生了错误,那么通过查看调用链,我们可以尽快地找到发生问题的服务。
常用的可以用来实现链路追踪的技术有Zipkin、Brave和Spring Cloud Sleuth等。
9. 计数监控系统
为什么需要计数监控系统?怎样实现计数监控系统?下面就来回答这几个问题。
在微服务架构中,服务众多,需要对这些服务的一系列指标进行记录监控。这样,既可以根据监控数据(例如,CPU使用率、内存占用率等)将服务调用调到最优,也可以让我们对自己的服务有一个实时的了解,在发生错误时,可以尽快地去出来。
常用的可以用来实现计数监控的技术有Graphite、Grafana、Promethus、HystrixDashboard和Turbine等。
10. 文档输出
什么是文档输出?为什么需要文档输出?怎样实现文档输出?下面就来回答这几个问题。
文档输出其实就是将API接口进行文档化,更简单地说就是通过编写代码的方式来自动展现出API接口的各种信息。编写代码是开发人员的强项,如果只是添加几个注解就能将文档输出,那么这对于开发人员来讲就是信手拈来的事情。如果能将接口以一个清晰的界面展示出来,就更好了,这样开发人员就不必单独地写文档,尤其是不必刻意写一个Word文档。用Word文档输出接口信息不仅工作量巨大,而且容易出错,接口信息一旦发生了变动,开发人员需要手动更改Word文档,这就有可能出错。所以使用一个良好的文档输出工具是很有必要的,不仅可以将API接口信息方便地展现给服务调用端,而且还可以防止在一段时间后连开发人员自己都忘记了接口信息。
常用来实现文档输出的技术有Swagger等。
11. 持续集成与持续部署系统
为什么需要持续集成与持续部署系统?怎样实现持续集成与持续部署系统?下面就来回答这几个问题。
如果没有持续集成与持续部署系统,我们需要手动管理代码,控制版本;如果没有持续集成与持续部署系统,我们需要手动将代码打成jar包,手动上传到一台服务器,手动关掉之前的服务,手动启动jar包进程,如果是按照Docker镜像来部署的话,我们可能还需要手动将服务打成镜像,手动将镜像文件push到镜像仓库,手动将镜像文件从仓库pull下来,手动将镜像运行起来。这一切都是手动的!效率极低,并且容易出错。所以,搭建一套持续集成与持续部署系统是刻不容缓的!
常用来实现自动化部署的技术有GitLab、Jenkins、Docker、Kubernetes(k8s)、Mesos及Marathon等。
12. 服务网关
服务网关=路由转发+过滤器
路由转发指接收一切外界请求,将它们转发到后端的微服务上去;过滤器指通过类似过滤器的方式在服务网关中可以完成一系列的横切功能,例如权限校验、限流及监控等。为什么需要服务网关呢?我们看一下这个情景:我们要为我们的微服务系统添加权限校验功能,这个代码可以写在三个位置。
每个服务自己实现一遍。
写到一个公共的服务中,然后其他所有服务都依赖这个服务。
写到服务网关的前置过滤器中,对所有过来的请求进行权限校验。
第一种,缺点太明显,基本不用;第二种,相较于第一种好很多,代码不会有冗余,但是有两个缺点。
由于每个服务都引入了这个公共服务,那么相当于每个服务都引入了相同的权限校验代码,使得每个服务的jar包大小无故增加了一些,而jar包,尤其是在使用Docker镜像进行部署的场景中,越小越好。
由于每个服务都引入了这个公共服务,那么后续升级这个服务可能就比较困难,而且公共服务的功能越多,升级就越难。例如,假设要改变权限校验方式,想让所有的服务都使用新的权限校验,就需要将之前所有的服务都重新引包并编译部署。
服务网关恰好可以解决这样的问题。首先,将权限校验的逻辑写在网关的过滤器中,后端服务不需要关注权限校验的代码,所以服务的jar包中也不会引入权限校验的逻辑,从而不会增加jar包大小;其次,如果想修改权限校验的逻辑,只需要修改网关中的权限校验过滤器即可,而不需要升级所有已存在的微服务。
常用来实现服务网关的技术有Zuul、Kong等。
13. 服务编排
服务编排主要是基于容器技术来实现一个服务的自动容错功能。例如,我们制定一个service有三个容器实例比较合适,如果少于三个,服务编排系统会自动创建容器实例达到三个。假设我们指定当CPU使用率达到90%时,需要再启动一个容器实例进行容错处理,服务编排系统可以自动做这个事情。当下热门的服务编排系统有Kubernetes、Mesos+Marathon等。前者发展势头迅猛,对微服务中的概念抽象得比较好,但是还未经过大量的考验,在生产环境中使用需要慎重;后者出现较早,在生产环境中已久经考验,但是对微服务的概念抽象得不太好。具体选型,按需而来。