随着互联网业务复杂性慢慢提高,单机服务的架构慢慢出现了生产效率问题
微服务架构带来的有优点也有缺点,使用前需要调研清楚
微服务架构的网关设计、服务注册/发现、配置管理都是关键点
Web应用程序发展的早期,大部分web工程是将所有的功能模块(service side)打包到一起并放在一个web容器中运行,很多企业的Java应用程序打包为war包。
比如构建一个在线商店系统:客户下订单、核对清单和信用卡额度,并将货物运输给客户。可能会构造出如下图所示的系统:
单体架构
这种将所有功能都部署在一个web容器中运行的系统就叫做单体架构(也叫:巨石型应用)。
单体架构有很多好处:IDE都是为开发单个应用设计的、容易测试——在本地就可以启动完整的系统、容易部署——直接打包为一个完整的包,拷贝到web容器的某个目录下即可运行。
但是,上述的好处是有条件的:应用不那么复杂 。
对于大规模的复杂应用,单体架构应用会显得特别笨重:要修改一个地方就要将整个应用全部部署(PS:在不同的场景下优势也变成了劣势);
编译时间过长;回归测试周期过长;开发效率降低等。
另外,单体架构不利于更新技术框架,除非你愿意将系统全部重写,那将是一件工作量极大的任务。
易于开发 :开发方式简单,IDE 支持好,方便运行和调试。
易于测试 :所有功能运行在一个进程中,一旦进程启动,便可以进行系统测试。
易于部署 :只需要将打好的一个软件包发布到服务器即可。
易于水平伸缩 :只需要创建一个服务器节点,配置好运行时环境,再将软件包发布到新服务器节点即可运行程序(当然也需要采取分发策略保证请求能有效地分发到新节点)。
维护成本大 :当应用程序的功能越来越多、团队越来越大时,沟通成本、管理成本显著增加。当出现 bug 时,可能引起 bug 的原因组合越来越多,导致分析、定位和修复的成本增加;并且在对全局功能缺乏深度理解的情况下,容易在修复 bug 时引入新的 bug。
持续交付周期长 :构建和部署时间会随着功能的增多而增加,任何细微的修改都会触发部署流水线。
新人培养周期长 :新成员了解背景、熟悉业务和配置环境的时间越来越长。
技术选型成本高 :单块架构倾向于采用统一的技术平台或方案来解决所有问题,如果后续想引入新的技术或框架,成本和风险都很大。
可扩展性差 :随着功能的增加,垂直扩展的成本将会越来越大;而对于水平扩展而言,因为所有代码都运行在同一个进程,没办法做到针对应用程序的部分功能做独立的扩展。
在微服务架构中,业务逻辑被拆分成一系列小而松散耦合的分布式组件,共同构成了较大的应用。
每个组件都被称为微服务,而每个微服务都在整体架构中执行着单独的任务,或负责单独的功能。
每个微服务可能会被一个或多个其他微服务调用,以执行较大应用需要完成的具体任务;
1)一组小的服务(大小没有特别的标准,只要同一团队的工程师理解服务的标识一致即可)
2)独立的进程(java的tomcat,nodejs等)
3)轻量级的通信(不是soap,是http协议或者是Tcp协议)
4)基于业务能力(类似电商的订单服务、用户服务、商品服务等等)
5)独立部署(迭代速度快)
6)无集中式管理(无须统一技术栈,可以根据不同的服务或者团队进行灵活选择,只要提供调用就可以)
优点一:拆分系统
通过分解巨大单体应用为多个服务方法解决了复杂性问题。
在功能不变的情况下,应用被分解为多个可管理的分支或服务。
每个服务都有一个用 RPC或者消息驱动 API 定义清楚的边界。
优点二:分治管理
微服务架构使得每个服务都可以有专门开发团队来开发。
开发者可以自由选择开发技术,提供 API 服务。
优点三:简化部署和增强扩展
微服务架构模式使得每个微服务独立部署,开发者不再需要协调其它服务部署对本服务的影响。
微服务架构模式使得每个服务独立扩展。
可以根据每个服务的规模来部署满足需求的实利。
缺点一:拆分大小限制
跟他的名字类似,“微服务”强调了服务大小,实际上,有一些开发者鼓吹建立稍微大一些的,10-100 LOC服务组。
尽管小服务更乐于被采用,但是不要忘了微服务只是结果,而不是最终目的。
微服务的目的是有效的拆分应用,实现敏捷开发和部署。
缺点二:增加编码的复杂性
微服务应用是分布式系统,由此会带来固有的复杂性。
开发者需要在 RPC 或者消息传递之间选择并完成进程间通讯机制。
此外,他们必须写代码来处理消息传递中速度过慢或者不可用等局部失效问题。
缺点三:数据存储难度增大
另外一个关于微服务的挑战来自于分区的数据库架构。
同时更新多个业务主体的事务很普遍。这种事务对于单体式应用来说很容易,因为只有一个数据库
在微服务架构应用中,需要更新不同服务所使用的不同的数据库,需要处理好分布式事务。
缺点四:测试完整服务复杂
测试一个基于微服务架构的应用也是很复杂的任务。
比如,采用流行的Spring Boot架构,对一个单体式web应用,测试它的REST API,是很容易的事情。
反过来,同样的服务测试需要启动和它有关的所有服务(至少需要这些服务的stubs)。再重申一次,不能低估了采用微服务架构带来的复杂性。
缺点五:微服务架构模式应用的改变将会波及多个服务。
比如,假设你在完成一个案例,需要修改服务A、B、C,而A依赖B,B依赖C。
在单体式应用中,你只需要改变相关模块,整合变化,部署就好了。
对比之下,微服务架构模式就需要考虑相关改变对不同服务的影响。
比如,你需要更新服务C,然后是B,最后才是A,幸运的是,许多改变一般只影响一个服务,而需要协调多服务的改变很少。
缺点六:受网络带宽影响大
服务与服务之间都是通过RPC的方式来调用
分布式系统是跨进程、跨网络的调用,极受网络延迟和带宽的影响。
微服务体系按照请求接入,由外到内的顺序,一般将整体架构分为 接入层 、 网关层 、 业务服务层 、 支撑服务层 、 平台服务层 、基础设施层 六层。
负载均衡作用,运维团队负责
反向路由,安全验证,限流等
基础服务和领域服务
支撑服务层提供非业务功能,以支撑业务服务层和网关层软件的正常运行。
核心模块有服务注册发现、集中配置、容错限流、认证授权、日志聚合、监控告警、后台中间件(异步队列、缓存、数据库、任务调度)
平台服务层站在系统平台的角度上,处理系统发布、资源调度整合等功能。
核心模块有发布系统、资源调度、容器镜像治理、资源治理。
包括计算、网络、存储、监控、安全、IDC等。(云计算服务)
服务网关可以这样简单理解:
服务网关 = 路由转发 + 过滤器
1.路由转发:接收一切外界请求,转发到后端的微服务上去;
2、过滤器:在服务网关中可以完成一系列的横切功能,例如权限校验、限流以及监控等,这些都可以通过过滤器完成(其实路由转发也是通过过滤器实现的)。
现在我们采用微服务架构了,在一个项目中微服务节点很多
如果让每一个节点都去处理上面这些 “鉴权认证功能、Session处理、安全检查、日志处理等” 会多出很多冗余的代码,也会给增加业务代码的复杂度
因此我们就需要有一个「 API网关 」把这些公共的功能独立出来成为一个服务来统一的处理这些事情。
举个例子,以
权限校验
的需求来讲,我们有三个方案来解决:
方案一 :每个服务都实现一遍权限校验
的代码
方案二 :将权限校验
的代码抽取出来并作为公共服务,然后其他所有服务都依赖这个服务
方案三 :写到服务网关的前置过滤器中,所有请求过来进行权限校验
方案一
每个服务都有大量的权限校验的代码,代码严重冗余
如果还要扩展功能,代码量将会更多,基本上方案不会被采用
方案二
由于每个服务引入了这个公共服务,那么相当于在每个服务中都引入了相同的权限校验的代码,使得每个服务的jar包大小无故增加了一些,尤其是对于使用docker镜像进行部署的场景,jar越小越好;
由于每个服务都引入了这个公共服务,那么我们后续升级这个服务可能就比较困难,而且公共服务的功能越多,升级就越难;
假设我们改变了公共服务中的权限校验的方式,想让所有的服务都去使用新的权限校验方式,我们就需要将之前所有的服务都重新引包,编译部署。
基本上方案二和方案一是换汤不换药,仍然存在不合理的地方。
方案三
将权限校验的逻辑写在网关的过滤器中,后端服务不需要关注权限校验的代码,所以服务的jar包中也不会引入权限校验的逻辑,不会增加jar包大小;
如果想修改权限校验的逻辑,只需要修改网关中的权限校验过滤器即可,而不需要升级所有已存在的微服务
所以网关的设计可以解决上面两种方案的不足之处,所以再微服务架构中我们需要网关
网关
路由转发
「API网关」是内部微服务的对外唯一入口,所以外面全部的请求都会先发到这个「API网关」上,然后由「API网关」来根据不同的请求去路由到不同的微服务节点上。
例如可以 根据接口映射路径来转发,也可以根据参数来转发。
并且由于内部微服务实例也会随着业务调整不停的变更,增加或者删除节点,「API网关」可以与「服务注册」模块进行协同工作,保证将外部请求转发到最合适的微服务实例上面去。
负载均衡
既然「API网关」是内部微服务的单一入口,所以「API网关」在收到外部请求之后,还可以根据内部微服务每个实例的负荷情况进行动态的负载均衡调节。
一旦内部的某个微服务实例负载很高,甚至是不能及时响应,则「API网关」就通过负载均衡策略减少或停止向这个实例转发请求。
当所有的内部微服务实例都处理不过来的时候,「API网关」还可以采用限流或熔断的形式阻止外部请求,以保障整个系统的可用性。
安全认证
「API网关」就像是微服务的大门守卫,每一个请求进来之后,都必须先在「API网关」上进行身份验证,身份验证通过后才转发给后面的服务,转发的时候一般也会带上身份信息。
同时「API网关」也需要对每一个请求进行安全性检查,例如参数的安全性、传输的安全性等等。
日志记录
既然所有的请求都需要走「API网关」,那么我们就可以在「API网关」上统一集中的记录下这些行为日志。
这些日志既可以作为我们后续事件查询使用,也可以作为系统的性能监控使用。
数据转换
「API网关」对外是面向多种不同的客户端,不同的客户端所传输的数据类型可能是不一样的。
因此「API网关」还需要具备数据转换的功能,将不同客户端传输进来的数据转换成同一种类型再转发给内部微服务上,这样兼容了这些请求的多样性,保证了微服务的灵活性。
Zuul 是由 Netflix 所开源的组件,基于JAVA技术栈开发的。
Zuul网关的使用热度非常高,并且也集成到了 Spring Cloud 全家桶中了,使用起来非常方便。
zuul架构
请求过程
首先将请求给zuulservlet处理,zuulservlet中有一个zuulRunner对象,该对象中初始化了RequestContext:作为存储整个请求的一些数据,并被所有的zuulfilter共享。
zuulRunner中还有 FilterProcessor,FilterProcessor作为执行所有的zuulfilter的管理器。
FilterProcessor从filterloader 中获取zuulfilter,而zuulfilter是被filterFileManager所加载,并支持groovy热加载,采用了轮询的方式热加载。
有了这些filter之后,zuulservelet首先执行的Pre类型的过滤器,再执行route类型的过滤器,最后执行的是post 类型的过滤器,如果在执行这些过滤器有错误的时候则会执行error类型的过滤器。
执行完这些过滤器,最终将请求的结果返回给客户端。
主要特色是,这些过滤器可以动态插拔,就是如果需要增加减少过滤器,可以不用重启,直接生效。
原理就是:通过一个db维护过滤器(上图蓝色部分),如果增加过滤器,就将新过滤器编译完成后push到db中,有线程会定期扫描db,发现新的过滤器后,会上传到网关的相应文件目录下,并通知过滤器loader进行加载相应的过滤器。
Tyk是一个基于GO编写的,轻量级、快速可伸缩的开源的API网关。
支持配额和速度限制,支持认证和数据分析,支持多用户多组织,提供全 RESTful API
Kong是基于OpenResty技术栈的开源网关服务,因此其也是基于Nginx实现的。
Kong可以做到高性能、插件自定义、集群以及易于使用的Restful API管理。
Traefik 是一个现代 HTTP 反向代理和负载均衡器,可以轻松部署微服务
Traeffik 可以与现有的组件(Docker、Swarm,Kubernetes,Marathon,Consul,Etcd,…)集成,并自动动态配置
Ambassador 是一个开源的微服务 API 网关,建立在 Envoy 代理之上,为用户的多个团队快速发布,监控和更新提供支持
支持处理 Kubernetes ingress controller 和负载均衡等功能,可以与 Istio 无缝集成
API 网关实现对比
从开源社区活跃度来看,无疑是Kong和Traefik较好;
从成熟度来看,较好的是Kong、Tyk、Traefik;从性能角度来看,Kong要比其他几个领先一些;
从架构优势的扩展性来看,Kong、Tyk有丰富的插件,Ambassador也有插件但不多,而Zuul是完全需要自研,但Zuul由于与Spring Cloud深度集成,使用度也很高,近年来Istio服务网格的流行,Ambassador因为能够和Istio无缝集成也是相当大的优势。具体使用选择还是需要依据具体的业务场景
引入服务网关后的微服务架构如上,总体包含三部分:服务网关、open-service和service。
微服务
访问流程
服务网关、open-service和service启动时注册到注册中心上去;
用户请求时直接请求网关,网关做智能路由转发(包括服务发现,负载均衡)到open-service,这其中包含权限校验、监控、限流等操作
open-service聚合内部service响应,返回给网关,网关再返回给用户
注意点
增加了网关,多了一层转发(原本用户请求直接访问open-service即可),性能会下降一些
但是下降不大,通常,网关机器性能会很好,而且网关与open-service的访问通常是内网访问,速度很快;
网关的单点问题:在整个网络调用过程中,一定会有一个单点,可能是网关、nginx、dns服务器等。
防止网关单点,可以在网关层前边再挂一台nginx,nginx的性能极高,基本不会挂,这样之后,网关服务就可以不断的添加机器。但是这样一个请求就转发了两次,所以最好的方式是网关单点服务部署在一台牛逼的机器上(通过压测来估算机器的配置)
网关要尽量轻量级。
微服务架构中,一般集群都会部署很多个微服务节点。这些节点一般也具备这2种角色,称为:“服务的提供者” 和 “服务的消费者”
“服务消费者”需要调用“服务提供者”的API来获得服务。当“服务提供者”的节点有增加或减少的时候,也得让调用者(“服务消费者”)及时的知晓。
在大规模集群中,一般节点数目都很多,节点变化频繁,通过手动去维护这些节点的状态是不现实的,因此需要一个叫做“服务注册中心”的组件来实现。
“服务提供者”将自己的服务地址等信息登记到“服务注册中心”中,调用者(“服务消费者”)需要的时候,每次都先去“服务注册中心”查询即可。既解决了人工维护微服务节点状态的问题,也能解决多节点间负载均衡的问题。
在分析其原理之前,我们先来看一下这里包含的一些角色,有三类:“服务提供者”、“服务消费者”、“服务注册中心”。
其中“服务提供者”需要将自己的服务信息注册到“服务注册中心”里面。而“服务消费者”需要到“服务注册中心”里面去查询有哪些服务可以调用。
因此,我们可以分为两个角度去分析原理:
角度一:“服务提供者”向“服务注册中心”进行注册
自己注册就是指微服务节点在启动的时候,自己去服务注册中心登记注册了,把自己的信息和状态传过去。
这种方式整体结构比较简单,对于注册中心而言也比较省事,但是对于微服务节点而言,每个微服务都得包含这么一段注册的逻辑代码,架构上看起来不是很优美。
自己注册服务
第三方注册就是指有一个“服务管理器”(图中的Service Manager),这个“服务管理器”会去管理所有的微服务和进程,以轮询或其它方式去检查有哪些微服务实例正在运行,会将这些微服务实例自动更新到服务注册中心。
这是目前比较常用的方式,例如Eureka就是采用这个模式。
第三方注册
角度二:“服务消费者”向“服务注册中心”查询和调用服务
client
在客户端模式下,“服务消费者”(图中的Client)在向“服务注册中心”查询到自己需要调用的“服务提供者”的地址
“服务消费者”(客户端)就会自己根据查询到的地址去访问微服务(图中的第3步 API Gateway是可选项,有API Gateway的情况下,API Gateway起到负载均衡作用,没有第3步的话,那就是Client直接调用Microservice,需要Client自己写负载均衡逻辑)。
代理模式
在代理模式下,“服务消费者”(图中的Client)与 微服务、“服务注册中心”中间有一个 API Gateway组件相隔着。
“服务消费者”只管去找API Gateway访问即可。至于去注册中心查询服务地址,以及访问服务地址的动作都由API Gateway效劳了,最后API Gateway在把结果返回给“服务消费者”即可。
这种模式,看起来“服务消费者”省事了,但是API Gateway模块却复杂了,因为API Gateway就是整个系统的一个非常核心关键节点了,不仅需要保障自己的稳定性和性能,而且还需要处理一些负载均衡的逻辑。
在大型架构中,这种模式用的还比较多,原理就是在网关加上了网关服务注册和发现的逻辑。
Eureka是由Netflix开源,其架构如下图:
Eureka
从图中可以看到,我们的服务(图中Application Clinet与Application Service)要使用Eureka就需要集成它的SDK(图中Eureka Client)。
图中的Eureka部署在了三个异地机房,也就是说Eureka是支持多中心部署的。
服务提供者(Application Service)通过Eureka Client实现服务的注册、更新和注销等。服务消费者(Application Clinet)通过Eureka Client实现服务的查询和调用。
Eureka支持了与Spring Cloud的集成,所以使用起来也非常方便,目前属于比较流行的方案。
Consul是在服务外进行完成一系列动作的,也就是说并不需要服务节点去依赖它的SDK,没有侵入性,所以跨语言的解决能力更强一些。
它一般是在服务节点外通过一些探针的方法去检查应用是否存活,是否需要注册或注销。
Consul也支持Spring Cloud集成,所以使用起来也很方便,也属于比较流行的方案。
Consul
etcd是可通过HTTP访问的键/值存储。它是分布式的,具有可用于构建服务发现的分层配置系统。
它易于部署,设置和使用,提供可靠的数据持久性,安全性和非常好的文档。
由于其简单性,etcd是比Zookeeper更好的选择。但是,它需要与少数第三方工具结合才能提供服务发现目标。
「配置中心」,顾名思义,就是用来统一管理项目中所有配置的系统。
如果一个中型互联网项目,不采用配置中心的模式,一大堆的各类配置项,各种不定时的修改需求,管理起来会十分混乱。
现在的服务都是集群模式,如果每台服务器都需要手动去更新,工作量可谓是递增
传统项目中,我们是怎么处理各类配置参数问题的:
1.一般是静态化配置
大多数在项目中单独写一个配置文件,例如 "config.yaml",然后将各类参数配置、应用配置、环境配置、安全配置、业务配置 都写到这个文件里。
当项目代码逻辑中需要使用配置的时候,就从这个配置文件中读取。
这种做法虽然简单,但如果参数需要修改,就非常的不灵活,甚至需要重启运行中的项目才能生效。
2.配置文件无法区分环境
由于配置文件是放在项目中的,但是我们项目可能会有多个环境,例如:测试环境、预发布环境、生产环境。
每一个环境所使用的配置参数理论上都是不同的,所以我们在配置文件中根据不同环境配置不同的参数,这些都是手动维护,在项目发布的时候,极其容易因开发人员的失误导致出错。
3.配置文件过于分散
如果一个项目中存在多个逻辑模块独立部署,每个模块所使用的配置内容又不相同.
传统的做法是会在每一个模块中都放一个配置文件,甚至不同模块的配置文件格式还不一样。
那么长期的结果就是配置文件过于分散混乱,难以管理。
4.配置修改无法追溯
因为采用的静态配置文件方式,所以当配置进行修改之后,不容易形成记录,更无法追溯是谁修改的、修改时间是什么、修改前是什么内容。
既然无法追溯,那么当配置出错时,更没办法回滚配置了。
上面只是拿配置文件的形式来举例,有的项目会采用数据库配置,虽然灵活一点,但是依旧不能完全解决上述问题。
「配置中心」的方案是如何解决这些痛点的:
「配置中心」的思路就是把项目中各种配置、各种参数、各种开关,全部都放到一个集中的地方进行统一管理,并提供一套标准的接口。
当各个服务需要获取配置的时候,就来「配置中心」的接口拉取。
当「配置中心」中的各种参数有更新的时候,也能通知到各个服务实时的过来同步最新的信息,使之动态更新。
配置集中管理、统一标准
配置与应用分离
实时更新
高可用
那么,具有上述特性的「配置中心」是如何解决上面传统配置所面临的问题的呢?
解决传统的“配置文件过于分散”的问题 :采用“配置集中管理”,所有的配置都集中在配置中心这一个地方管理,不需要每一个项目都自带一个,这样极大的减轻了开发成本。
解决传统的“配置文件无法区分环境”的问题 :采用“配置与应用分离”,配置并不跟着环境走,当不同环境有不同需求的时候,就到配置中心获取即可,极大的减轻了运维部署成本。
解决传统的“静态化配置”的问题 :具备“实时更新”的功能,线上系统需要调整参数的时候,只需要在配置中心动态修改即可。
解决传统的“配置修改无法追溯”的问题 :配置统一管理,并且会记录每一次修改的版本,通过修改版本可以追踪历史修改记录。
既然配置都统一管理了,那配置中心在整个系统中的地位就非常重要了,一旦配置中心不能正常提供服务,就可能会导致项目整体故障,因此“高可用”就是配置中心又一个很关键的指标。
Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置
配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
服务端基于Spring Boot和Spring Cloud开发,不依赖外部容器,便于部署。
Java客户端不依赖任何框架,能够运行于所有Java运行时环境,同时对Spring/Spring Boot环境也有额外支持。
原生支持Java和.Net客户端,同时也支持其他语言客户端,目前已支持Go、PHP、Python、NodeJS、C++。
apollo
Apollo项目的Github地址
全局配置中心,存储应用的配置项,解决配置混乱分散的问题。
名字来源于淘宝的开源项目Diamond,前面加上一个字母X以示区别。
XDiamond项目的Github地址
QConf 是一个分布式配置管理工具。
用来替代传统的配置文件,使得配置信息和程序代码分离,同时配置变化能够实时同步到客户端,而且保证用户高效读取配置,这使的工程师从琐碎的配置修改、代码提交、配置上线流程中解放出来,极大地简化了配置管理工作。
Qconf项目的Github地址
专注于各种「分布式系统配置管理」的「通用组件」和「通用平台」,
提供统一的「配置管理服务」包括 百度、滴滴出行、银联、网易、拉勾网、苏宁易购、顺丰科技 等知名互联网公司正在使用!
Disconf项目的Github地址
以上主要总结了单点结构和微服务结构的区别和特点
然后介绍了微服务架构的网关设计、服务注册和发现、分布式配置中心的内容
微服务要探索的内容还有:服务框架、服务监控、服务治理、服务熔断、服务降级等等