从 Spring Cloud 开始,聊聊今年微服务架构该怎么走

随着公司业务量的飞速发展,平台面临的挑战已经远远大于业务,需求量不断增加,技术人员数量增加,面临的复杂度也大大增加。在这个背景下,平台的技术架构也完成了从传统的单体应用到微服务化的演进。

系统架构的演进过程

这里写图片描述

单一应用架构(第一代架构)

这是平台最开始的情况,当时流量小,为了节约成本,并将所有应用都打包放到一个应用里面,采用的架构为 .NET SQL Server: 
这里写图片描述 
表示层:位于最外层(最上层),最接近用户。用于显示数据和接收用户输入的数 据,为用户提供一种交互式操作的界面,平台所使用的是基于.NET的Web形式。

业务逻辑层(Business Logic Layer):无疑是系统架构中体现核心价值的部分。它的关注点主要集中在业务规则的制定、业务流程的实现等与业务需求有关的系统设计,也即是说它是与系统所应对的领域(Domain)逻辑有关,很多时候,也将业务逻辑层称为领域层。 业务逻辑层在体系架构中的位置很关键,它处于数据访问层与表示层中间,起到了数据交换中承上启下的作用。由于层是一种弱耦合结构,层与层之间的依赖是向下的,底层对于上层而言是“无知”的,改变上层的设计对于其调用的底层而言没有任何影响。如果在分层设计时,遵循了面向接口设计的思想,那么这种向下的依赖也应该是一种弱依赖关系。对于数据访问层而言,它是调用者;对于表示层而言,它却是被调用者。

数据访问层:有时候也称为是持久层,其功能主要是负责数据库的访问,可以访问数据库系统、二进制文件、文本文档或是XML文档,平台在这个阶段使用的是 hibernate.net sqlserver。

第一代架构看似很简单,却支撑了平台的早期业务发展,满足了网站用户访问量在几万规模的处理需求。但是当用户访问量呈现大规模增长,问题就暴露出来了:

  • 维护成本不断增高:当出现故障时,有可能引起故障的原因组合就会比较多,这也会导致分析故障、定位故障、修复故障的成本相应增高,故障的平均修复周期会花费很多时间,并且任何一个模块出现故障将会影响其它应用模块;在开发人员对全局功能缺乏深度理解的情况下,修复一个故障,经常引入其他的故障,导致该过程陷入“修复越多,故障越多”的恶性循环。
  • 可伸缩性差:应用程序的所有功能代码都运行在同一个服务器上,将会导致应用程序的水平扩展非常困难,只能使用垂直扩展。
  • 交付周期变长:应用程序做任何细微的修改以及代码提交,都会触发对整个应用程序进行代码编译、运行单元测试、代码检查、构建并生成部署包、验证功能等,这也就版本的反馈周期变长,单位时间内构建的效率变得很低。
  • 新人培养周期变长:随着应用程序的功能越来越多,代码变得越来越复杂的同时,对于新加入团队的成员而言,了解业务背景、熟悉应用程序、配置本地开发环境,这些看似简单的任务,却会花费了更长的时间。

垂直应用架构(第二代架构)

为了解决第一代架构面临的问题,团队制定了如下的策略,并形成了第二代应用架构(垂直应用架构)。 
这里写图片描述

  • 应用拆成独立的应用模块。
  • 各个应用模块独立部署,并在负载均衡通过 session 保持解决应用模块的水平扩展问题。 
    Sticky 就是基于 cookie 的一种负载均衡解决方案,通过 cookie 实现客户端与后端服务器的会话保持,在一定条件下可以保证同一个客户端访问的都是同一个后端服务器。请求来了,服务器发个 cookie,并说:下次来带上,直接来找我!在项目中,我们使用了 taobao 开源的 tengine 中的 session_sticky 模块。
  • 数据库拆分成不同数据库,由对应应用访问。
  • 域名拆分
  • 动静分离。

可以看到第二代架构解决应用级别的水平扩展扩展,经过优化后,该架构支撑了几十万用户的访问需求,在这一阶段有部分应用已经使用 Java 完成了 MVC 架构的重写。当然也存在一些问题。

  • 应用之间耦合度高,相互依赖严重。
  • 应用模块之间交互复杂,有时直接访问对方模块数据库。
  • 数据库涉及过多的关联查询与慢查询,数据库优化困难。
  • 数据库单点访问严重,出现故障无法恢复。
  • 数据复制问题严重,造成大量数据不一致。 
    我们曾经尝试使用 SQL Server Always On 解决扩展问题,但是实验发现在复制过程中出现至少 10s 的延迟,因此放弃了这个方案。
  • 系统扩展困难。
  • 各个开发团队各自为战,开发效率低下。
  • 测试工作量巨大,发布困难。

微服务化架构(平台现状:第三代架构)

为了解决第一代与第二代架构存在的问题,我们对平台进行了梳理优化。根据平台业务需要以及对第一二代架构的总结,我们确定了第三代架构的核心需求:

  • 核心业务抽取出来,作为独立的服务对外服务。
  • 服务模块持续独立部署,减少版本交付周期。
  • 数据库按服务分库分表。
  • 大量使用缓存,提高访问。
  • 系统间交互使用轻量级的 rest 协议,摒弃 rpc 协议。
  • 去 .NET 化,开发语言使用 Java 来实现。

并以此为基础进行了平台的第三代架构的重构工作。 
这里写图片描述

如何搭建微服务架构

为了搭建好微服务架构,技术选型是一个非常重要的阶段,只有选择合适的”演员”,才能把这台戏演好。 
这里写图片描述 
我们使用 Spring Cloud 作为微服务开发框架,Spring Boot 拥有嵌入式 Tomcat,可直接运行一个 jar 包来发布微服务,此外它还提供了一系列“开箱即用”的插件,例如:配置中心,服务注册与发现,熔断器,路由,代理,控制总线,一次性令牌,全局锁,leader选举,分布式 会话,集群状态等,可大量提高我们的开发效率。 
这里写图片描述 
工程结构规范 
这里写图片描述 
上图是我们实践中每个服务应该具有的项目组成结构。

其中:

1、微服务名 Service: 
为对内其它微服务提供服务调用。服务名 API模块为服务间定义的接口规范,使用 swagger rest 接口定义。服务名 Server模块包含了能直接启动该服务的应用与配置。 
2、微服务名 Web: 
供上层 Web 应用请求的入口,该服务中一般会调用底层微服务完成请求。 
API 网关实践 
这里写图片描述 
API 网关作为后端所有微服务和 API 的访问入口, 对微服务和 API 进行审计,流控, 监控,计费等。常用的 API 网关解决方案有:

  • 应用层方案 最有名的当然是 Netflix 的 Zuul, 但这不意味着这种方案就最适合你, 比如 Netfilx 是因为使用 
    AWS,对基础设施把控有限, 所以才不得不在应用层做了 Zuul 这样的方案,如果通盘考虑, 这种方案不是最合适或者说最有的方案。 
    但如果自己的团队对整体技术设施把控有限,且团队结构不完善,应用层方案也可能是最适合你的最佳方案。
  • Nginx Lua 方案 
    也是我们采用并认为最合适的方案,OpenResty 和 Kong 是比较成熟的可选方案, 不过 Kong 使用 
    Postgres 或者 Cassandra, 国内公司估计选择这俩货的不多,但 Kong 的 HTTP API 设计还是很不错的。
  • 我们的方案 
    使用 Nginx Lua Consul组合方案,虽然我们团队大多是 Java,选择 ZooKeeper 会是更加自然的选择,但作为新锐派,对压测结果进行了分析, 我们最终选择使用 Consul。 
    良好的 HTTP API 支持, 可以动态管理 Upstreams, 这也意味着我们可以通过发布平台或者胶水系统无缝的实现服务注册和发现, 对服务的访问方透明。 
    这里写图片描述 
    在以上的方案里:

Consul 作为状态存储或者说配置中心(主要使用 Consul 的 KV 存储功能);Nginx 作为 API 网关, 根据 Consul 中 Upstreams 的相关配置,动态分发流量到配置的 Upstreams 结点;

Nginx 根据配置项, 连接到 Consul 集群;

启动的 API 或者微服务实例, 通过手工/命令行/发布部署平台, 将实例信息注册/写入 Consul;

Nginx 获取到相应的 Upstreams 信息更新, 则动态变更 Nginx 内部的 Upstreams 分发配置,从而将流量路由和分发到对应的 API 和微服务实例结点;

将以上注册和发现逻辑通过脚本或者统一的发布部署平台固化后,就可以实现透明的服务访问和扩展。

链路监控实践

我们发现,以前在单应用下的日志监控很简单,在微服务架构下却成为了一个大问题,如果无法跟踪业务流,无法定位问题,我们将耗费大量的时间来查找和定位问题,在复杂的微服务交互关系中,我们就会非常被动,此时分布式链路监控应运而生,其核心就是调用链。通过一个全局的ID将分布在各个服务节点上的同一次请求串联起来,还原原有的调用关系、追踪系统问题、分析调用数据、统计系统指标。

那么我们先来看一下什么是调用链,调用链其实就是将一次分布式请求还原成调用链路。显式的在后端查看一次分布式请求的调用情况,比如各个节点上的耗时、请求具体打到了哪台机器上、每个服务节点的请求状态,等等。它能反映出一次请求中经历了多少个服务以及服务层级等信息(比如你的系统 A 调用 B,B 调用 C,那么这次请求的层级就是 3),如果你发现有些请求层级大于 10,那这个服务很有可能需要优化了。

常见的解决方案有:

  • Pinpoint

GitHub 地址:GitHub - naver/pinpoint: Pinpoint is an open source APM 
(Application Performance Management) tool for large-scale distributed 
systems written in Java.

对 APM 有兴趣的朋友都应该看看这个开源项目,这个是一个韩国团队开源出来的,通过 JavaAgent 的机制来做字节码代码植入(探针),实现加入 traceid 和抓取性能数据的目的。 NewRelic、OneAPM 之类的工具在 Java 平台上的性能分析也是类似的机制。

  • Zipkin

官网:OpenZipkin · A distributed tracing system GitHub 地址:GitHub - 
openzipkin/zipkin: Zipkin is a distributed tracing system

这个是 Twitter 开源出来的,也是参考 Dapper 的体系来做的。 
Zipkin 的 Java 应用端是通过一个叫 Brave 的组件来实现对应用内部的性能分析数据采集。 
Brave 的 GitHub 地址:https://github.com/openzipkin/brave 
这个组件通过实现一系列的 Java 拦截器,来做到对 http/servlet 请求、数据库访问的调用过程跟踪。然后通过在 Spring 之类的配置文件里加入这些拦截器,完成对 Java 应用的性能数据采集。

  • CAT

GitHub 地址:GitHub - dianping/cat: Central Application Tracking

这个是大众点评开源出来的,实现的功能也还是蛮丰富的,国内也有一些公司在用了。不过 CAT 实现跟踪的手段,是要在代码里硬编码写一些“埋点”,也就是侵入式的。 
这样做有利有弊,好处是可以在自己需要的地方加埋点,比较有针对性;坏处是必须改动现有系统,很多开发团队不愿意。 
前面三个工具里面,如果不想重复造轮子,我推荐的顺序依次是Pinpoint—>Zipkin—>CAT。原因很简单,就是这三个工具对于程序源代码和配置文件的侵入性,是依次递增的。

我们的解决方案:

针对于微服务,我们在 Spring Cloud 基础上,对微服务架构进行了扩展,基于 Google Dapper 的概念,设计了一套基于微服务架构的分布式跟踪系统(WeAPM)。 
这里写图片描述 
如上图所示,我们可以通过服务名、时间、日志类型、方法名、异常级别、接口耗时等参数查询响应的日志。在得到的TrackID可以查询到该请求的整个链路日志,为重现问题、分析日志提供了极大方便。 
这里写图片描述 
断路器实践

在微服务架构中,我们将系统拆分成了一个个的微服务,这样就有可能因为网络原因或是依赖服务自身问题出现调用故障或延迟,而这些问题会直接导致调用方的对外服务也出现延迟,若此时调用方的请求不断增加,最后就会出现因等待出现故障的依赖方响应而形成任务积压,最终导致自身服务的瘫痪。为了解决这样的问题,因此产生了断路器模式。 
这里写图片描述 
我们在实践中使用了 Hystrix 来实现断路器的功能。Hystrix 是 Netflix 开源的微服务框架套件之一,该框架目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix 具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。

断路器的使用流程如下:

启用断路器

@SpringBootApplication@EnableCircuitBreakerpublic class Application {public static void main(String[] args) { SpringApplication.run(DVoiceWebApplication.class, args); }}
  • 1

代用使用方式

@Componentpublic class StoreIntegration {@HystrixCommand(fallbackMethod = "defaultStores")public Object getStores(Map<String, Object> parameters) { //do stuff that might fail}public Object defaultStores(Map<String, Object> parameters) { return /* something useful */; }}
  • 1

配置文件 
这里写图片描述 
资源控制实践

聊到资源控制,估计很多小伙伴会联系到 Docker,Docker 确实是一个实现资源控制很不错的解决方案,我们前期做调研时也对是否使用 Docker 进行了评审,但是最终选择放弃,而使用 Linux 的 libcgroup 脚本控制,原因如下:

  • Docker 更适合大内存做资源控制、容器化,但是我们线上服务器一般都是32G左右,使用 Docker 会有资源浪费。
  • 使用Docker会使运维复杂、来自业务的压力会很大。 
    为什么要有 group?

Linux 系统中经常有个需求就是希望能限制某个或者某些进程的分配资源。也就是能完成一组容器的概念,在这个容器中,有分配好的特定比例的 CPU 时间,IO 时间,可用内存大小等。

于是就出现了 cgroup 的概念,cgroup 就是 controller group,最初由 Google 的工程师提出,后来被整合进 Linux 内核中,Docker 也是基于此来实现。

libcgroup 使用流程:

安装

yum install libcgroup 
启动服务

service cgconfig start 
配置文件模板(以 memory 为例):

cat /etc/cgconfig.conf 
这里写图片描述 
看到 memory 子系统是挂载在目录 /sys/fs/cgroup/memory 下,进入这个目录创建一个文件夹,就创建了一个 control group 了。

mkdir testecho “服务进程号”>> tasks(tasks 是 test 目录下的一个文件) 
这样就将当前这个终端进程加入到了内存限制的 cgroup 中了。

总结

总结一下,本文从我们微服务实践的背景聊起,介绍了微服务实践的工作方式,技术选型,以及相关的一些微服务技术。包括:API 网关、注册中心、断路器等。相信这些技术会给大家在实践中带来一些新的思路。

猜你喜欢

转载自www.cnblogs.com/hang520/p/9184880.html
今日推荐