浅谈微服务架构
; 4497 words
目录
是什么?
微服务以专注于单一责任与功能的小型功能区块为基础,利用模块化组合出复杂的大型应用程序,各区块可以使用与语言无关的api相互通讯。 服务框架的构建是一个持续演进的过程。
单体架构
最初的服务,如实现一个小说app,我们会把所有的功能都实现在一个程序,将各种功能按照高内聚低耦合的理念划分成多个模块(书成模块、登陆模块、书架模块等等…)。一个服务就是一个应用。
随着访问量增多,服务器压力变大,为了支持业务发展,首先选择了最直接的方法—加机器。
因此需要负载均衡来支持流量增长。所有的请求需要通过一层负载均衡,再打到最终的服务上。
但随着业务的发展、产品的迭代,单体架构的缺陷愈发明显:
- 所有功能高度耦合,互相影响,难以管理,团队合作受限
- 部分的逻辑修改也需要对整体服务进行开发和测试,采用瀑布式开发模型,开发周期长迭代慢。且需要影响全部服务的重建和部署,成本高
- 一个小bug可能会导致整个系统的崩溃
因此,需要对单体架构进行解耦!
面向服务
- 借助单体架构的功能划分,将单体架构划分为几个大的服务应用。
- 每个服务应用内部通过负载均衡横向扩展。
- 拆分后的每个服务应用创建自己的接口定义,以便被其他服务发现
- 服务之间的通讯类似于计租中的总线原理:通过一个独立的中间件提供消息通讯
以上是简易版企业服务总线的初步设计,系统的所有流量通过通信总线进行路由分发。但随着流量的激增,通信总线会出现问题,通信总线成为了整个系统的中心化节点和瓶颈,影响了系统的扩展性和稳定性。
SOA
去中心化。
对于通信总线分发流量而导致的问题,使用DNS+IP网络架构解决。
- 域名:为每个服务定义一个psm(product.subsystem.module)作为服务的域名
- IP:服务实例其实就是一个机器终端,拥有自己的ip地址
- 根服务器: 维护psm->ip的映射,实现服务发现
- 服务注册:每个服务实例在创建后不断的向注册中心上报自己的地址(psm-ip)和实例状态
- 服务治理:注册中心维护着每个服务对应的实例地址列表及其健康状态
- 服务发现:实例定时向注册中心获取要访问的服务的实例地址,访问时通过负载均衡选择其中一个p2p来访问,流量不经由注册中心转发。负载均衡通过服务本身来实现。
- 通信协议:服务提供IDL文件,通过统一的rpc进行通信。
引入gateway将外部流量转换为内部请求。注册中心可以多实例部署,集群内部使用分布式算法保证其最终一致性。可以对服务进行更细粒度的拆分。
一个注册中心所具有的基本功能: 注册服务实例信息;心跳机制;剔除失败的服务实例;查询服务实例信息。
如何注册和发现服务?
在微服务架构下,主要有三种角色:服务提供者(RPC Server)、服务消费者(RPC Client)和服务注册中心(Registry)。
RPC Server 提供服务,在启动时,根据服务发布文件 server.xml 中的配置的信息,向 Registry 注册自身服务,并向 Registry 定期发送心跳汇报存活状态。
RPC Client 调用服务,在启动时,根据服务引用文件 client.xml 中配置的信息,向 Registry 订阅服务,把 Registry 返回的服务节点列表缓存在本地内存中,并与 RPC Sever 建立连接。
当 RPC Server 节点发生变更时,Registry 会同步变更,RPC Client 感知后会刷新本地内存中缓存的服务节点列表。
RPC Client 从本地缓存的服务节点列表中,基于负载均衡算法选择一台 RPC Sever 发起调用。
至此,完成了微服务架构的设计:通过注册中心进行服务治理,使用一个rpc框架在实现服务注册、服务发现、负载均衡、熔断等功能。
注册中心使用AP模型还是CP模型?
一致性(Consistency):在分布式系统中的所有数据备份,在同一时刻是否同样的值。
可用性(Availability):保证每个请求不管成功或者失败都有响应。
分区容忍性(Partition-tolerance):系统中任意信息的丢失或失败不会影响系统的继续运作。
根据CAP原则,分布式系统只能同时满足两种性质,不能三者兼顾。
| zookeeper | Eureka | Consul | Nacos | etcd |
|---|---|---|---|---|
| CP | AP | CP | AP/CP | CP |
对于zk来说,并不专门用来作为注册中心,只能提供的通用树状存储结构和znode机制间接实现注册中心的必要功能。
zk中使用临时节点创建a1,a2来存储服务实例信息的节点。当服务实例关闭或通信异常,zk自动删除临时节点实现剔除机制。
但zk一旦服务挂掉,可用性会出现短暂的问题,停止服务,来换取一致性。
Eureka侧重AP,保证可用性,实现最终一致性。通过自我保护机制在网络异常的情况下保留大部分节点信息,来防止雪崩。
如果15分钟内超过85%的客户端节点没有正常心跳,eureka认为客户端和注册中心出现网络故障,eureka进入自我保护机制。
- 不再从注册表中移除长时间没有收到心跳的服务
- 仍然接受新服务的注册和查询,但不会同步到其他节点,等待网络稳定时同步
Nacos支持CP+AP模式,即Nacos可以根据配置识别为CP模式或AP模式,默认是AP模式。如果注册Nacos的client节点注册时ephemeral=true,那么Nacos集群对这个client节点的效果就是AP,采用distro协议实现。
注册Nacos的client节点注册时ephemeral=false,那么Nacos集群对这个节点的效果就是CP的,采用raft协议实现。根据client注册时的属性,AP,CP同时混合存在,只是对不同的client节点效果不同。Nacos可以很好的解决不同场景的业务需求。
所谓云原生
云原生利用和发挥云平台的弹性和分布式优势,在云上构建和运行应用程序。
2013年,docker横空出世,与微服务架构理念一拍即可。
- 轻量级容器
- 统一的运行环境
- 快速部署 使用k8s进行容器编排,支持容器等快速部署和资源调度。
虽然已经解决了单体架构很多问题,但是仍存在些问题:
微服务框架向业务程序注入了许多与业务无关的代码,服务内部可能有许多微服务框架相关的并发线程,业务侵入性强。且开发语言的不统一也会给整个基础模块的维护和迭代带来巨大成本。
服务网格
解耦。从高内聚低耦合的原则出发,将与业务无关的逻辑从框架代码中抽出,以代理的模式作为业务程序的基础服务存在,服务可以更专注于业务逻辑。作为配合,保留一个轻量级框架作为业务应用和框架代理之间沟通的中间层。即sidecar,可以把sidecar理解成一个代理实例,包含在每一个service中。这部分程序的升级发布,除非是与rpc编解码相关,否则不需要业务代码任何变更,实现基础服务与业务服务的解耦。伴生容器会接管业务服务的所有对外流量进行处理和分发,业务服务对此无感知。
更进一步,需要通过这一层sidecar对调用进行超时配置、接口监控、流量控制、权限控制、管理实例等操作,会使用一个控制中心为我们提供配置的能力,称为control plane。抽取的这一层sidecar模式部署的伴生容器部分,称为data plane(proxy、gateway)
以上其实就是service mesh的设计。
服务网格是将无侵入服务治理定义的更为深入的微服务架构方案,通过将微服务治理能力以独立组件整合并下沉到基础设施,服务网格可以实现应用业务逻辑与服务治理逻辑完全分离,也可以支持多语言、热升级等高阶特性。
可以把它看成应用程序或微服务之间的TCP/IP,负责服务之间的网络调用、限流、熔断和监控。使用service mesh无须关心服务之间原本通过服务框架实现的事情。service mesh作为sidecar运行,对应用程序来说是透明的,所有应用程序的流量都会通过它,所以对应用程序流量的控制都可以在service mesh中实现。
Serverless
开发者不需要过多考虑服务器的问题,计算资源作为服务而不是服务器的概念出现,构建和管理基于微服务架构的完整流程。由开发者实现的服务端逻辑运行在无状态的计算容器中,由事件触发。
到用户仅需关注业务和所需的资源。比如,通过K8S这类编排工具,用户只要关注自己的计算和需要的资源(CPU、内存等)就行了,不需要操心到机器这一层,并且为实际消耗的资源付费。可以说,随着Serverless架构的兴起,真正的云计算时代才算到来了。
Serverless涵盖的技术分为两类,Faas(Function as a service)和Baas(Backend as s service)。
FaaS无须自行管理服务器系统或服务器应用程序,可直接运行后端代码,唯一需要修改的代码是“主方法/启动”代码,FaaS中的函数可以通过供应商定义的事件类型触发,大部分供应商还允许函数作为对传入Http请求的响应来触发。
Baas是指我们不再编写或管理所有服务端组件,可以使用领域通用的远程组件(而不是进程内的库)来提供服务。BaaS只以API的方式提供应用依赖的后端服务,例如数据库和对象存储。BaaS可以是公共云服务商提供的,也可以是第三方厂商提供的。
Serverless在请求到来时才运行,当应用不运行的时候就会进入 “休眠状态”,下次当请求来临时,应用将会需要一个启动时间,即冷启动时间。因此Serverless不适合长期不间断运行、处理大量请求。
且对于faas来说,也有一些独有的缺点。
-
函数量爆炸
随着使用的深入,你管理的函数数量可能会有一个大爆发。而这意味着混乱,和更多出错的可能。
-
重复的函数逻辑
不同的应用端可能不得不写一套相同的函数逻辑。因为有可能发出的事件是不同的,但是处理方式是相同的。
-
无状态
因为函数进程是运行后即刻销毁,所以状态的保留在这里毫无意义。
FAQ
提出好的问题最重要
问题1: 对于通信总线去中心化的这一步中,去中心化该怎么样理解?
即不是所有的流量经过通信总线,对于注册中心可以多实例部署,并不依赖一个实例。
追问: 但是注册中心中,流量也需要经过注册,否则如何拿到ip和其他注册信息哇?
只需要定时去拉注册中心中的数据即可,拉取列表后数据就会存在于本地。
追问: 注册中心可以部署多个实例,那通信总线可以吗?
可以部署多个通信总线,但是所有流量还会经过通信总线。
问题2:现在的sidecar是把agent打到业务的镜像中,是出于什么目的?一般sidecar会单独存在一个容器,业务程序所在容器和sidecar的容器进行跨容器通信。 公司主要有两个平台提供sidecar机制。servicemesh和tce sidecar。tce sidecar相当于是独立的容器,servicemesh是伴生容器,两个进程都运行在一个容器上。
追问:公司主推的是伴生容器吗?
他们的应用场景不同。sidecar是有sdk的维护,servicemesh将library移动到外部作为独立进程存在,希望在服务独立的同时保持相同的迭代节奏。tce sidecar只需要把k8s一个pod内启动多个紧密耦合共享资源的共处容器的能力封装出来提供给用户。
问题3: 现在这套方式还存在什么缺点吗?
服务过多,会有一些开销是用在rpc调用和不同服务之间的数据pack,需要尝试把服务部署到同一个集群来优化。从这个角度来讲,有没有可能有一些动态化组件,如faas,现在的faas更多用在消息的消费上,假设接口放在faas上,在调用接口时将函数拉到本地,使用动态组装可以避免跨网络的调用。
参考
https://www.infoq.cn/article/2flhpe7yrge7iagy7946
https://www.cnblogs.com/gezp/p/13354180.html
https://learnku.com/articles/28321