微服务详解
单体架构
单体架构也称之为单体系统或者是单体应用。就是一种把系统中所有的功能、模块耦合在一个应用中的架构方式。
单体架构的特点主要有:
- 打包成一个独立的单元(导成一个唯一的 jar 包或者是 war 包)
- 以一个进程的方式来运行
优点如下:
- 易于开发:开发方式简单,IDE 支持好,方便运行和调试。
- 易于测试:所有功能运行在一个进程中,一旦进程启动,便可以进行系统测试。
- 易于部署:只需要将打好的一个软件包发布到服务器即可。
- 易于水平伸缩:只需要创建一个服务器节点,配置好运行时环境,再将软件包发布到新服务器节点即可运行程序(当然也需要采取分发策略保证请求能有效地分发到新节点)。
缺点如下:
-
维护成本大:当应用程序的功能越来越多、团队越来越大时,沟通成本、管理成本显著增加。
当出现 Bug 时,可能引起 Bug 的原因组合越来越多,导致分析、定位和修复的成本增加;并且在对全局功能缺乏深度理解的情况下,容易在修复 Bug 时引入新的 Bbug。
-
持续交付周期长:构建和部署时间会随着功能的增多而增加,任何细微的修改都会触发部署流水线。
-
新人培养周期长:新成员了解背景、熟悉业务和配置环境的时间越来越长。
-
技术选型成本高:单块架构倾向于采用统一的技术平台或方案来解决所有问题,如果后续想引入新的技术或框架,成本和风险都很大。
-
可扩展性差:随着功能的增加,垂直扩展的成本将会越来越大;而对于水平扩展而言,因为所有代码都运行在同一个进程,没办法做到针对应用程序的部分功能做独立的扩展
采用过时的单体架构的话,就会使得公司雇佣有潜力的开发者很困难,应用无法扩展,可靠性很低,那么我们再来看看微服务架构是怎样的呢?
微服务架构
微服务是一种架构风格。一个大型的复杂软件应用,由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。
每个微服务仅关注于完成一件任务并且能够很好的完成该任务。
核心部分:
- 网关集群:数据的聚合、实现对接入客户端的身份认证、防报文重放与防数据篡改、功能调用的业务鉴权、响应数据的脱敏、流量与并发控制等。
- 业务集群:一般情况下移动端访问和浏览器访问的网关需要隔离,防止业务耦合。
- Local Cache:由于客户端访问业务可能需要调用多个服务聚合,所以本地缓存有效的降低了服务调用的频次,同时也提示了访问速度。本地缓存一般使用自动过期方式,业务场景中允许有一定的数据延时。
- 服务层:原子服务层,实现基础的增删改查功能,如果需要依赖其他服务需要在 Service 层主动调用。
- Remote Cache:访问 DB 前置一层分布式缓存,减少 DB 交互次数,提升系统的TPS。
- DAL:数据访问层,如果单表数据量过大则需要通过 DAL 层做数据的分库分表处理。
- MQ:消息队列用来解耦服务之间的依赖,异步调用可以通过 MQ 的方式来执行。
- 数据库主从:服务化过程中必经的阶段,用来提升系统的 TPS。
常见的架构有:
- 客户端与服务端的
- 基于组件模型的架构(EJB)
- 分层架构(MVC)
- 面向服务架构(SOA)
特点如下:
- 系统是由多个服务构成
- 每个服务可以单独独立部署
- 每个服务之间是松耦合的。服务内部是高内聚的,外部是低耦合的。高内聚就是每个服务只关注完成一个功能。
优点如下:
边界清晰:比如说一个电商平台,我们以前是部署在一台服务器上,所有的代码打成一个 war 包。
现在,我们可以给它拆分开:用户服务,积分服务,支付服务,仓储服务,信息服务,地图服务等等。
每一个微服务只关注一个特定的业务功能,这样的话开发和维护单个服务都比较简单,因为它的边界足够清晰,业务也足够清晰,用户服务,只做好用户的事情就好了,相较于之前的大而全的单体服而言,每个微服务的代码量也比较少。
效率高:单体服务随着代码量变得越来越多,比如说百万行级别的代码,仅仅编译一次应用可能就需要花费很久。
但是现在,如果一个地方有问题,比如说支付模块有问题,只需要单独修改支付模块,修改完支付模块之后,单独测试支付功能,单独部署支付模块就可以了,而不会影响整体的部署速度。
技术栈不受限制:每一个服务可以使用不同的技术栈来实现,由于不同的服务之间是通过 restful API 来通信的,所以每个服务可以使用不同的技术框架,使用不同的存储库来实现。
拓展性更强:随着业务的发展,用户量变得越来越多,或者说订单量猛增,这时我们可以专门去优化这个订单服务,给这个订单服务提供更高配置的机器,而其他并没有遇到瓶颈的业务,比如说短信服务,我们可以暂时不用动。
缺点如下:
运维成本过高:以前只需要打个 war 包扔在 tomcat 下面就可以了,但现在,我们可能需要部署几个甚至几十个微服务,这样的话,如何保证这几十甚至上百个微服务正常的运行和互相通信协作,这给运维带来了很大的挑战。
分布式系统复杂:使用微服务这种架构,构建的是一个分布式的系统,在分布式系统当中会引入很多问题,比如说分布式锁,分布式事务等等。
这个时候我们需要对这个系统的,事务,幂等,网络延迟,分区,熔断,降级等问题都要有一个妥善的处理和应对方案。
通信成本高:由于之前的接口调用都在同一个进程内,我需要支付调用支付方法,需要积分直接调用添加积分的方法。
但现在,由于积分模块或者支付模块都被拆成了单独的服务,这个时候如果再想去调用的话,就是通过 http 方式的请求去调用。
这种频繁的跨服务通信是有很高的成本的,选择一个适合自己业务的轻量级低成本的通信方式,也很关键。
服务拆分难:如何做好微服务的拆分?这个是需要我们不断摸索的,从单体服务向微服务架构的演进,它是一个循序渐进的过程,在演进的过程中常常会根据业务变化来对微服务进行重构,甚至是重新划分,从而让这个架构更加合理。
架构区别
①MVC 架构
MVC 架构就是一个单体架构。我们常使用的技术:Struts2、SpringMVC、Spring、Mybatis 等等。
②RPC 架构
RPC(RemoteProcedureCall):远程过程调用。他一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。我们常使用的技术:Thrift、Hessian等等。
实现原理:首先需要有处理网络连接通讯的模块,负责连接建立、管理和消息的传输。
其次需要有编解码的模块,因为网络通讯都是传输的字节码,需要将我们使用的对象序列化和反序列化。
剩下的就是客户端和服务器端的部分,服务器端暴露要开放的服务接口,客户调用服务接口的一个代理实现,这个代理实现负责收集数据、编码并传输给服务器然后等待结果返回。
③SOA 架构
SOA(ServiceorientedArchitecture):面向服务架构。
ESB(EnterpariseServceBus):企业服务总线,服务中介。主要是提供了一个服务于服务之间的交互。
ESB 包含的功能如:负载均衡,流量控制,加密处理,服务的监控,异常处理,监控告急等等。我们常使用的技术:Mule、WSO2。
③微服务架构
微服务就是一个轻量级的服务治理方案。我们常使用的技术:Spring Cloud、Dubbo 等等。
微服务原则
①AKF 拆分原则
业界对于可扩展的系统架构设计有一个朴素的理念,就是:通过加机器就可以解决容量和可用性问题。
这一理念在“云计算”概念疯狂流行的今天,得到了广泛的认可!对于一个规模迅速增长的系统而言,容量和性能问题当然是首当其冲的。
但是随着时间的向前,系统规模的增长,除了面对性能与容量的问题外,还需要面对功能与模块数量上的增长带来的系统复杂性问题以及业务的变化带来的提供差异化服务问题。
而许多系统,在架构设计时并未充分考虑到这些问题,导致系统的重构成为常态,从而影响业务交付能力,还浪费人力财力!
对此,《可扩展的艺术》一书提出了一个更加系统的可扩展模型——AKF 可扩展立方(ScalabilityCube)。
这个立方体中沿着三个坐标轴设置分别为:X、Y、Z。
Y 轴(功能):关注应用中功能划分,基于不同的业务拆分。
Y 轴扩展会将庞大的整体应用拆分为多个服务。每个服务实现一组相关的功能,如订单管理、客户管理等。在工程上常见的方案是服务化架构(SOA)。
比如对于一个电子商务平台,我们可以拆分成不同的服务,组成下面这样的架构:
服务化架构 SOA
但通过观察上图容易发现,当服务数量增多时,服务调用关系变得复杂。为系统添加一个新功能,要调用的服务数也变得不可控,由此引发了服务管理上的混乱。
所以,一般情况下,需要采用服务注册的机制形成服务网关来进行服务治理。
系统的架构将变成下图所示:
X 轴(水平扩展):关注水平扩展,也就是”加机器解决问题”。
X 轴扩展与我们前面朴素理念是一致的,通过绝对平等地复制服务与数据,以解决容量和可用性的问题。
其实就是微服务运行多个实例,做集群加负载均衡的模式。为了提升单个服务的可用性和容量,对每一个服务进行 X 轴扩展划分。
X 轴(水平扩展)
Z 轴(数据分区):关注服务和数据的优先级划分,如按地域划分。
Z 轴扩展通常是指基于请求者或用户独特的需求,进行系统划分,并使得划分出来的子系统是相互隔离但又是完整的。
工程领域常见的 Z 轴扩展有以下两种方案:
单元化架构:在分布式服务设计领域,一个单元(Cell)就是满足某个分区所有业务操作的自包含闭环。
如上面我们说到的Y轴扩展的 SOA 架构,客户端对服务端节点的选择一般是随机的,但是,如果在此加上 Z 轴扩展,那服务节点的选择将不再是随机的了,而是每个单元自成一体。
如下图:
移动端用户
数据分区:为了性能数据安全上的考虑,我们将一个完整的数据集按一定的维度划分出不同的子集。
一个分区(Shard),就是是整体数据集的一个子集。比如用尾号来划分用户,那同样尾号的那部分用户就可以认为是一个分区。
数据分区为一般包括以下几种数据划分的方式:
- 数据类型,如:业务类型。
- 数据范围,如:时间段,用户 ID。
- 数据热度,如:用户活跃度,商品热度。
- 按读写分,如:商品描述,商品库存。
②前后端分离原则
何为前后端分离?前后端本来不就分离么?这要从尴尬的 jsp 讲起。分工精细化从来都是蛋糕做大的原则,多个领域工程师最好在不需要接触其他领域知识的情况下合作,才可能使效率越来越高,维护也会变得简单。
jsp 的模板技术融合了 html 和 java 代码,使得传统 MVC 开发中的前后端在这里如胶似漆,前端做好页面,后端转成模板,发现问题再找前端,前端又看不懂 java 代码……前后端分离的目的就是将这尴尬局面打破。
前后端分离
前后端分离原则,简单来讲就是前端和后端的代码分离,我们推荐的模式是最好采用物理分离的方式部署,进一步促使更彻底的分离。
如果继续直接使用服务端模板技术,如:jsp,把 java、js、html、css 都堆到一个页面里,稍微复杂一点的页面就无法维护了。
分离原则
这种分离方式有几个好处:
- 前后端技术分离,可以由各自的专家来对各自的领域进行优化,这样前端的用户体验优化效果更好。
- 分离模式下,前后端交互界面更清晰,就剩下了接口模型,后端的接口简洁明了,更容易维护。
- 前端多渠道集成场景更容易实现,后端服务无需变更,采用统一的数据和模型,可以支持多个前端:例如:微信 H5 前端、PC 前端、安卓前端、iOS 前端。
③无状态服务
对于无状态服务,首先说一下什么是状态:如果一个数据需要被多个服务共享,才能完成一笔交易,那么这个数据被称为状态。进而依赖这个“状态”数据的服务被称为有状态服务,反之称为无状态服务。
无状态服务
那么这个无状态服务原则并不是说在微服务架构里就不允许存在状态,表达的真实意思是要把有状态的业务服务改变为无状态的计算类服务,那么状态数据也就相应的迁移到对应的“有状态数据服务”中。
场景说明:例如我们以前在本地内存中建立的数据缓存、Session 缓存,到现在的微服务架构中就应该把这些数据迁移到分布式缓存中存储,让业务服务变成一个无状态的计算节点。
迁移后,就可以做到按需动态伸缩,微服务应用在运行时动态增删节点,就不再需要考虑缓存数据如何同步的问题。
④RestFul 的通讯风格
原则来讲本来应该是个“无状态通信原则”:
无状态通信
在这里我们直接推荐一个实践优选的 Restful 通信风格,因为他有很多好处:
- 无状态协议 HTTP,具备先天优势,扩展能力很强。例如需要安全加密,有现成的成熟方案 HTTPS 即可。
- JSON 报文序列化,轻量简单,人与机器均可读,学习成本低,搜索引擎友好。
- 语言无关,各大热门语言都提供成熟的 RestfulAPI 框架,相对其他的一些 RPC 框架生态更完善。
通信
①服务通信
WebService、WCF、WebAPI,以及 ASHX,ASPX:
- 主动触发
- 数据序列化传递
- 跨平台
- 跨语言
- Http 穿透防火墙
②进程通信
如下:
- Net Remoting:Net 平台督邮的,不支持跨平台。
- gRPC:高性能、开源和通用 RPC框架,面向服务端和移动端,基于 HTTP/2 设计,推荐使用。
WebService
注册中心 Eureka
注册中心主要提供三个核心功能:
①服务注册:服务提供者启动时,会通过 Eureka Client 向 Eureka Server 注册信息,Eureka Server 会存储该服务的信息,Eureka Server 内部有二层缓存机制来维护整个注册表。
②提供注册表:服务消费者在调用服务时,如果 Eureka Client 没有缓存注册表的话,会从 Eureka Server 获取最新的注册表。
③同步状态:Eureka Client 通过注册、心跳机制和 Eureka Server 同步当前客户端的状态。
Eureka 流程
网关 Zuul
API 网关是一个更为智能的应用服务器,它的存在就像是整个微服务架构系统的门面,所有的外部客户端访问都需要经过它来进行调度和过滤。
除了需要实现请求路由,负载均衡及校验过滤等功能外还需要与服务治理框架的结合,请求转发时的熔断机制,服务的聚合等一系列高级功能。
主要核心功能:
- 服务路由转发
- 鉴权校验过滤
- 熔断限制保护
网关
认证&授权
现在的应用开发层出不穷,基于浏览器的网页应用,基于微信的公众号、小程序,基于 iOS、Android 的 App,基于 Windows 系统的桌面应用和 UWP 应用等等。
这么多种类的应用,就给应用的开发带来的挑战,我们除了分别实现各个应用外,我们还要考虑各个应用之间的交互,通用模块的提炼,其中身份的认证和授权就是每个应用必不可少的的一部分。
而现在的互联网,对于信息安全要求又十分苛刻,所以一套统一的身份认证和授权就至关重要。
IdentityServer4 就是这样一个框架,IdentityServer4 是为 ASP.NET CORE 量身定制的实现了 OpenId Connect 和 OAuth2.0 协议的认证授权中间件。
IdentityServer4
分布式日志
一般我们需要进行日志分析场景:直接在日志文件中 grep、awk 就可以获得自己想要的信息。
但在规模较大也就是日志量多而复杂的场景中,此方法效率低下,面临问题包括日志量太大如何归档、文本搜索太慢怎么办、如何多维度查询。
需要集中化的日志管理,所有服务器上的日志收集汇总常见解决思路是建立集中式日志收集系统,将所有节点上的日志统一收集,管理,访问。
大型系统通常都是一个分布式部署的架构,不同的服务模块部署在不同的服务器上,问题出现时,大部分情况需要根据问题暴露的关键信息,定位到具体的服务器和服务模块,构建一套集中式日志系统,可以提高定位问题的效率。
Exceptionless 是一个开源的实时的日志收集框架。
它可以应用在基于 ASP.NET,ASP.NET Core,Web Api,Web Forms,WPF,Console,MVC 等技术栈的应用程序中,并且提供了 Rest 接口可以应用在 Javascript,Node.js 中。
它将日志收集变得简单易用并且不需要了解太多的相关技术细节及配置。
在以前,我们做日志收集大多使用 Log4net,Nlog 等框架,在应用程序变得复杂并且集群的时候,可能传统的方式已经不是很好的适用了,因为收集各个日志并且分析他们将变得麻烦而且浪费时间。
现在 Exceptionless 团队给我们提供了一个更好的框架来做这件事情,我认为这是非常伟大并且有意义的,感谢他们。
ELK是三个开源软件的缩写,分别为:Elasticsearch 、Logstash 以及 Kibana , 它们都是开源软件。
不过现在还新增了一个 Beats,它是一个轻量级的日志收集处理工具(Agent)。
Beats 占用资源少,适合于在各个服务器上搜集日志后传输给 Logstash,官方也推荐此工具,目前由于原本的 ELK Stack 成员中加入了 Beats 工具所以已改名为 Elastic Stack,推荐使用。
ELK
配置中心 Config
Spring Config 首推基于 git 的管理方式,提供了两个管理维度,一个是 label(即 branch),一个是 profile。
主要核心功能:
-
业务配置
-
功能开关
-
服务配置
Spring Config
异步队列 MQ
MQ 大家都熟悉,现在主流的 MQ 有 RabbitMQ,RocketMQ,Kafka。
核心功能:削峰填谷。
容错限流 Hystrix
它设计用来当分布式系统发生不可避免的异常的时候去隔离当前服务和远程服务、系统、三方包的联系,防止出现级联失败的情况,从而导致整个系统雪崩。
主要核心功能:
-
失败转移和优雅降级
-
实时监控更改相关配置
-
基于线程和信号量的断路器隔离
容错限流
负载均衡、服务调用
Ribbon是一个负载均衡客户端,可以很好的控制 http 和 tcp 的一些行为。
Feign 默认集成了 Ribbon。Ribbon 主要功能是提供客户端的软件负载均衡算法。
Ribbon 就属于进程内负载均衡,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。主要核心功能:负载均衡。
持续集成 Jenkins
在项目多的时候,重复操作极大的浪费时间。如果遇到同一时间不同项目组打包项目,打包和部署服务器就要排队使用,测试人员只能在等待中浪费时间。
为了解决这些问题,选择寻找合适的持续集成方案。来自动化完成重复的步骤。
Jenkins 还包含了很多插件,比如代码校验等等。是 CI/CD 的基本技术。
核心功能:
-
拉取代码
-
打包构建
-
将资源送往目标服务器
分布式缓存 Redis
Redis 是一款基于 ANSI C 语言编写的,BSD 许可的,日志型 key-value 存储组件,它的所有数据结构都存在内存中,可以用作缓存、数据库和消息中间件。
核心功能:
-
内存数据库
-
基于内存数据库的各种扩展功能
分布式事务
分布式事务的实现方式主要有:
- 2PC(two-phase commit protocol,强一致性,没有可用性)
- 3PC
- TCC(Try-Confirm-Cancel)
- 本地消息表,推荐 RabbitMQ
- Saga 模式
本地消息表:MQ 分布式事务—本地消息表—基于消息的一致性。
- 上有投递消息
- 下游获取消息
- 上游投递稳定性
- 下游接受稳定性
消息队列异步
分库分表 sharding jdbc
Sharding-JDBC 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供额外的服务,以 jar 包形式提供服务,无需额外部署和依赖,可以理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。
核心功能:
- 数据分片
- 读写分离
- 透明的使用 jdbc 访问各个数据库
Sharding-JDBC
Spring Cloud
SpringCloud,从命名我们就可以知道,Spring Cloud 是大名鼎鼎的 Spring 家族的产品, 专注于企业级开源框架的研发。
Spring Cloud 自从发布到现在,仍然在不断的高速发展,几乎考虑了服务治理的方方面面,开发起来非常的便利和简单。
Spring Cloud 架构:
- Service Provider:暴露服务的提供方。
- Service Consumer:调用远程服务的服务消费方。
- EureKa Server:服务注册中心和服务发现中心。
Spring Cloud 架构
支持协议:Spring Cloud 使用 HTTP 协议的 REST API。
Spring Cloud 组件运行:
-
所有请求都统一通过 API 网关(Zuul)来访问内部服务。
-
网关接收到请求后,从注册中心(Eureka)获取可用服务。
-
由 Ribbon 进行均衡负载后,分发到后端的具体实例。
-
微服务之间通过 Feign 进行通信处理业务。
Spring Cloud 组件运行
Dubbo
Dubbo 出生于阿里系,是阿里巴巴服务化治理的核心框架,并被广泛应用于中国各互联网公司;只需要通过 Spring 配置的方式即可完成服务化,对于应用无入侵,设计的目的还是服务于自身的业务为主。
虽然阿里内部原因 Dubbo 曾经一度暂停维护版本,但是框架本身的成熟度以及文档的完善程度,完全能满足各大互联网公司的业务需求。
Dubbo 架构:
-
Provider:暴露服务的提供方,可以通过 jar 或者容器的方式启动服务。
-
Consumer:调用远程服务的服务消费方。
-
Registry:服务注册中心和发现中心。
-
Monitor:统计服务和调用次数,调用时间监控中心。(Dubbo 的控制台页面中可以显示,目前只有一个简单版本。)
-
Container:服务运行的容器。
Dubbo 架构
支持协议:Dubbo 使用 RPC 通讯协议。
提供序列化方式如下:
-
Dubbo:Dubbo 缺省协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。
-
RMI:RMI 协议采用 JDK 标准的 java.rmi. 实现,采用阻塞式短连接和 JDK 标准序列化方式。
-
Hessian:Hessian 协议用于集成 Hessian 的服务,Hessian 底层采用 HTTP 通讯,采用 Servlet 暴露服务,Dubbo 缺省内嵌 Jetty 作为服务器实现。
-
HTTP:采用 Spring 的 Http Invoker 实现。
Webservice:基于 CXF 的 frontend-simple 和 transports-http 实现。
Dubbo 组件运行:每个组件都是需要部署在单独的服务器上,Gateway 用来接受前端请求、聚合服务,并批量调用后台原子服务。每个 Service 层和单独的 DB 交互。
Dubbo 组件运行储
Gateway:前置网关,具体业务操作,Gateway 通过 Dubbo 提供的负载均衡机制自动完成。
Service:原子服务,只提供该业务相关的原子服务。
Zookeeper:原子服务注册到 ZK 上。