乐高式微服务化改造(下)—— 注册中心、配置中心和授权中心

上篇讲了杏仁微服务化改造的项目背景和基本框架,这篇我将进一步介绍其中的三大核心组件,即注册中心,配置中心和授权中心。
- 注册中心:所有服务注册到 Consul 集群,然后通过 Consul Template 刷新Nginx配置实现负载均衡
- 配置中心:使用自研的 Matrix 系统,通过自定义构建插件覆写配置,最小化对已有应用的侵入性
- 授权中心:基于 Spring Security OAuth,同时支持基于微信企业号的 SSO

1. 注册中心

作为微服务架构最基础也是最重要的组件之一,服务注册中心本质上是为了解耦服务提供者和服务消费者。对于任何一个微服务,原则上都应存在或者支持多个提供者,这是由微服务的分布式属性决定的。更进一步,为了支持弹性扩缩容特性,一个微服务的提供者的数量和分布往往是动态变化的,也是无法预先确定的。因此,原本在单体应用阶段常用的静态 LB 机制就不再适用了,需要引入额外的组件来管理微服务提供者的注册与发现,而这个组件就是服务注册中心。

设计或者选型一个服务注册中心,首先要考虑的就是服务注册与发现机制。纵观当下各种主流的服务注册中心解决方案,大致可归为三类:

  • 应用内:直接集成到应用中,依赖于应用自身完成服务的注册与发现,最典型的是 Netflix 提供的 Eureka
  • 应用外:把应用当成黑盒,通过应用外的某种机制将服务注册到注册中心,最小化对应用的侵入性,比如 Airbnb 的 SmartStack,HashiCorp 的 Consul
  • DNS:将服务注册为 DNS 的 SRV 记录,严格来说,是一种特殊的应用外注册方式,SkyDNS 是其中的代表

注1:对于第一类注册方式,除了 Eureka 这种一站式解决方案,还可以基于 ZooKeeper 或者 Etcd 自行实现一套服务注册机制,这在大公司比较常见,但对于小公司而言显然性价比太低。

注2:由于 DNS 固有的缓存缺陷,这里不对第三类注册方式作深入探讨。

除了基本的服务注册与发现机制,从开发和运维角度,至少还要考虑如下五个方面:

  • 测活:服务注册之后,如何对服务进行测活以保证服务的可用性?
  • 负载均衡:当存在多个服务提供者时,如何均衡各个提供者的负载?
  • 集成:在服务提供端或者调用端,如何集成注册中心?
  • 运行时依赖:引入注册中心之后,对应用的运行时环境有何影响?
  • 可用性:如何保证注册中心本身的可用性,特别是消除单点故障?

下面就围绕这几个方面,简单分析一下 Eureka,SmartStack,Consul 的利弊。

Eureka

eureka

从设计角度来看,Eureka 可以说是无懈可击,注册中心、提供者、调用者边界清晰,通过去中心化的集群支持保证了注册中心的整体可用性,但缺点是
Eureka 属于应用内的注册方式,对应用的侵入性太强,且只支持 Java 应用。

SmartStack

smartstack

SmartStack 可以说是三种方案中最复杂的,涉及了 ZooKeeper、HAProxy、Nerve 和 Synapse 四种异构组件,对运维提出了很高的要求。它最大的好处是对应用零侵入,且适用于任意类型的应用。

Consul

Consul 本质上属于应用外的注册方式,但可以通过集成 Consul SDK 加上本地 Agent的 方式简化注册流程。当服务以容器方式运行时,可以更进一步通过Registrator 实现自动注册。服务调用端的服务发现默认依赖于 SDK,但可以通过 Consul Template 去除 SDK 依赖。

最终方案

最终我们选择了 Consul 作为服务注册中心的实现方案,主要原因有两点:

  1. 最小化对已有应用的侵入性,这也是贯穿我们整个微服务化改造的原则之一。
  2. 降低运维的复杂度,通过使用 Registrator 和 Consul Template 实现服务自注册和自发现。

image_1bokoa1n8g8nhga1phjdcs1s2s13

2. 配置中心

我们知道,大至一个 PaaS 平台,小至一个缓存框架,一般都依赖于特定的配置以正常提供服务,微服务也不例外。

配置分类

  • 按配置的来源划分,主要有源代码(俗称 hard-code),文件,数据库和远程调用。
  • 按配置的适用环境划分,可分为开发环境,测试环境,预发布环境,生产环境等。
  • 按配置的集成阶段划分,可分为编译时,打包时和运行时。编译时,最常见的有两种,一是源代码级的配置,二是把配置文件和源代码一起提交到代码仓库中。打包时,即在应用打包阶段通过某种方式将配置(一般是文件形式)打入最终的应用包中。运行时,是指应用启动前并不知道具体的配置,而是在启动时,先从本地或者远程获取配置,然后再正常启动。
  • 按配置的加载方式划分,可分为单次加载型配置和动态加载型配置。

演变

随着业务复杂度的上升和技术架构的演变,对应用的配置方式也提出了越来越高的要求。一个典型的演变过程往往是这样的,起初所有配置跟源代码一起放在代码仓库中;之后出于安全性的考虑,将配置文件从代码仓库中分离出来,或者放在 CI 服务器上通过打包脚本打入应用包中,或者直接放到运行应用的服务器的特定目录下,剩下的非文件形式的关键配置则存入数据库中。上述这种方式,在单体应用阶段非常常见,也往往可以运行的很好,但到了微服务阶段,面对爆发式增长的应用数量和服务器数量,就显得无能为力了。这时,就轮到配置中心大显身手了。那什么是配置中心?简单来说,就是一种统一管理各种应用配置的基础服务组件。

框架选型

选型一个合格的配置中心,至少需要满足如下 4 个核心需求:

  • 非开发环境下应用配置的保密性,避免将关键配置写入源代码
  • 不同部署环境下应用配置的隔离性,比如非生产环境的配置不能用于生产环境
  • 同一部署环境下的服务器应用配置的一致性,即所有服务器使用同一份配置
  • 分布式环境下应用配置的可管理性,即提供远程管理配置的能力

现在开源社区主流的配置中心框架有 Spring Cloud Config 和 disconf,两者都满足了上述4个核心需求,但又有所区别。

Spring Cloud Config

spring-cloud-config

Spring Cloud Config 可以说是一个为 Spring 量身定做的轻量级配置中心,巧妙的将应用运行环境映射为 profile,应用版本映射为 label。在服务端,基于特定的外部系统(Git、文件系统或者 Vault)存储和管理应用配置;在客户端,利用强大的 Spring 配置系统,在运行时加载应用配置。

disconf

disconf

disconf是前百度资深研发工程师廖绮绮的开源作品。在服务端,提供了完善的操作界面管理各种运行环境,应用和配置文件;在客户端,深度集成Spring,通过Spring AOP实现应用配置的自动加载和刷新。

最终方案

不管是 Spring Cloud Config 还是 disconf,默认提供的客户端都深度绑定了 Spring 框架,这对非 Spring 应用而言无疑增加了集成成本,即便它们都提供了获取应用配置的 API。最终我们还是选用了微服务化改造之前自研的 Matrix 作为配置中心,一方面,可以保持新老系统使用同一套配置服务,降低维护成本,另一方面,在满足 4 个核心需求的前提下,Matrix 还提供了一些独有的能力。

  • 分离配置文件和配置项。对于配置文件,通过各类配套打包插件(sbt,maven,gradle),在打包时将配置文件打入应用包中,同时最小化对CI的侵入性;对于配置项,提供 SDK,帮助应用从服务端获取配置项,同时支持简单的缓存机制。
  • 增加应用版本维度,即对于同一应用,可以在服务端针对不同版本或版本区间维护不同的应用配置。
  • 应用配置的版本化支持,类似于 Git,可以将任一应用配置回退到任一历史版本。

3. 授权中心

有了服务注册中心和配置中心,下一步应该就可以发起服务调用了吧?Wait,还有一个关键问题要解决。不同于单体应用内部的方法调用,服务调用存在一个服务授权的概念。打个比方,原本一家三兄弟住一屋,每次上山打猎喊一声就行,后来三兄弟分了家,再打猎就要挨家挨户敲门了。这一敲一应就是所谓的服务授权。

严格来说,服务授权包含鉴权(Authentication)和授权(Authorization)两部分。鉴权解决的是调用方身份识别的问题,即敲门的是谁。授权解决的是调用是否被允许的问题,即让不让进门。两者一先一后,缺一不可。为避免歧义,如不特殊指明,下文所述授权都是宽泛意义上的授权,即包含了鉴权。

常见的服务授权有三种,简单授权,协议授权和中央授权。

  • 简单授权:服务提供方并不进行真正的授权,而是依赖于外部环境进行自动授权,比如IP地址白名单,内网域名等。这就好比三兄弟互相留了一个后门。
  • 协议授权:服务提供方和服务调用方事先约定一个密钥,服务调用方每次发起服务调用请求时,用约定的密钥对请求内容进行加密生成鉴权头(包含调用方唯一识别 ID),服务提供方收到请求后,根据鉴权头找到相应的密钥对请求进行鉴权,鉴权通过后再决定是否授权此次调用。这就好比三兄弟之间约定敲一声是大哥,敲两声是二哥,敲三声是三弟。
  • 中央授权:引入独立的授权中心,服务调用方每次发起服务调用请求时,先从授权中心获取一个授权码,然后附在原始请求上一起发给服务提供方,提供方收到请求后,先通过授权中心将授权码还原成调用方身份信息和相应的权限列表,然后决定是否授权此次调用。这就好比三兄弟每家家门口安装了一个110联网的指纹识别器,通过远程指纹识别敲门人的身份。

一般来说,简单授权在业务规则简单、安全性要求不高的场景下用的比较多。而协议授权,比较适用于点对点或者 C/S 架构的服务调用场景,比如 Amazon S3 API。对于网状结构的微服务而言,中央授权是三种方式中最适合也是最灵活的选择:

  1. 简化了服务提供方的实现,让提供方专注于权限设计而非实现。
  2. 更重要的是提供了一套独立于服务提供方和服务调用方的授权机制,无需重新发布服务,只要在授权中心修改服务授权规则,就可以影响后续的服务调用。

OAuth

说起具体的授权协议,很多人第一反应就是OAuth。事实上也的确如此,很多互联网公司的开放平台都是基于 OAuth 协议实现的,比如 Google APIs微信网页授权接口。一次标准的 OAuth 授权过程如下:

image_1bokoo1kq103u1smr19f13rtie61g

对应到微服务场景,服务提供方相当于上图中的 Resource Server,服务调用方相当于 Client,而授权中心相当于 Authorization Server 和 Resource Owner 的合体。

Beared Token

在标准的 OAuth 授权过程中,Resource Server 收到 Client 发来的请求后,需要到 Authorization Server 验证 Access Token,并获取 Client 的进一步信息。通过 OAuth 2.0 版本引入中的 Beared Token,我们可以省去这一次调用,将 Client 信息存入 Access Token,并在 Resource Server 端完成 Access Token 的鉴权。主流的 Beared Token 有 SAMLJWT 两种格式,SAML 基于 XML,而 JWT 基于 JSON。由于大多数微服务都使用 JSON 作为序列化格式,JWT 使用的更为广泛。

框架选型

在选型OAuth框架时,我主要调研了 CASApache OltuSpring Security OAuthOAuth-Apis,对比如下:

image_1bokoq7rq5gk1vnp1sbtvi9uud1t

不考虑实际业务场景,CAS 和 Spring Security OAuth 相对另外两种框架,无论是集成成本还是可扩展性,都有明显优势。前文提到,由于我们选用了 Spring Boot 作为统一的微服务实现框架,Spring Security OAuth 是更自然的选择,并且维护成本相对低一些(服务端)。

最终方案

最后我们基于 Spring Security OAuth 框架实现了自己的服务授权中心,鉴权部分目前支持私网认证,Scope 校验和域名校验。大致的服务授权流程如下:

image_1bokpif4c178t1m131olg2c4r8m3h

4. 更多

微服务是一个很大的话题,自 Martin Fowler 在 2014 年 3 月提出以来,愈演愈热,并跟另一个话题容器化一起开创了一个全新的 DevOps 时代,引领了国内外大大小小各个互联网公司的技术走向,也影响了我们这一代程序员尤其是后端和运维的思维方式。希望我的这两篇文章能给你带来一些新的启发和思考,欢迎留言交流。

少年读书如隙中窥月,中年读书如庭中望月,老年读书如台上玩月,皆以阅历之浅深为所得之浅深耳。-- 张潮 《幽梦影》

2+

喜欢该文章的用户:

  • avatar

乐高式微服务化改造(上)—— 微服务简介

技术圈流行一句话,凡脱离业务谈架构的,都是耍流氓。当新需求响应越来越慢,当加班成为家常便饭,你可曾怀念当年一下午徒手写一千行代码的爽快?面对一个不断吞噬团队时间的庞然大物(单体应用),分而治之往往是最有效的方法。今天我就和大家聊聊我对小公司如何进行微服务化改造的理解和一手经验。

1. 微服务简介

有关微服务的定义,最权威的版本莫属微服务之父 Martin Fowler 在 Microservices Resource Guide 一文中所述:

In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. -- James Lewis and Martin Fowler

注意其中有3个关键词:small,independently deployable 和 automated deployment。Small 对应的就是微服务的微,很多初次接触微服务的同学对微的理解往往会停留在实现层面,以为代码少就是微,但实际上,这里的微更多的是体现在逻辑层面。微服务的一个重要设计原则是 share as little as possible,什么意思呢?就是说每个微服务应该设计成边界清晰不重叠,数据独享不共享,也就是我们常说的高内聚、低耦合。保证了 small,才能做到 independently deployable。而实现 automated deployment 的关键是 DevOps 文化,可参见 Fowler 另一篇谈 DevOps 的文章。需要提醒的是,随着业务复杂度的上升,一个微服务可能需要拆分为更多更细粒度的微服务,比方说,一开始只是一个简单的订单服务,后面逐步拆分出清算,支付,结算,对账等其他服务。

从本质上来看,相对单体应用,微服务是以牺牲强一致性、提高部署复杂性为代价,换取更彻底的分布式特性,比如异构性和强隔离性。对应 CAP 理论,就是用 Consistency 换 Partition。异构性比较容易理解,通过定义统一的 API 规范(一般采用 REST 风格),每个微服务团队可以根据各自的能力矩阵选用最适合的技术栈,而不是所有人必须使用相同的技术栈。强隔离性指的是,对于一个典型的单体应用,隔离性最高只能体现到模块级别,由于共享同一个代码仓库,模块的边界往往比较模糊,需要人为定义很多规范来保证良好的隔离性,但无论如何强调,稍一疏忽,就会产生“越界”行为,时间愈长,维护隔离性的成本愈高。而到了微服务阶段,自带应用级别的隔离性,“越界”的成本大大提升,无需任何规范,架构本身就保证了隔离性。

另一方面,由于采用了分布式架构,微服务无法再简单的通过数据库事务来保证强一致性,而是通过消息中间件或者某种事务补偿机制来保证最终一致性,比如微信朋友圈的点赞,淘宝订单的物流状态。其次,在微服务阶段,随着应用数量的激增,一次发布往往涉及多个应用,加上异构性带来的部署方式的多样性,对团队的运维水平尤其是自动化水平提出了更高的要求,运维和开发的边界进一步模糊。

sketch

讲完这些有关微服务的背景知识之后,现在就切入今天的正题,面对快速增长的业务需求,小公司如何进行微服务化改造?下面就以我在杏仁主导实施的微服务化改造的全过程为背景,给大家简单说一下我们微服务化改造的总体思路和核心中间件的技术选型过程。

2. 项目背景

首先介绍一下微服务化改造的背景。去年年初,在历经2年多的产品迭代之后,整个后台应用越来越庞大,已经成为一个典型意义上的 monolithic application:1.各个业务模块犬牙交错,重复代码随处可见,补丁代码越打越多。2.任何一个改动都需要一次全量发布,哪怕是修改一句文案,极大的拖慢了迭代速度。

与此同时,由于公司电商业务变得越来越复杂,老的业务模型越来越难以满足新的需求,急需对原有的订单模块进行重构,或者抽取一个独立的订单服务来进行支撑。反复考量之后,我们选择了后者。由于是团队第一次试水微服务,并且初期人员有限(一人主导,多人配合),最后我们决定走一条比较实用的改良式路线:

  • 最小化对已有应用的侵入性
  • 偏好主流的微服务框架
  • 只做必要的微服务治理

第一条定下了此次改造的基调,降低了方案无法落地的风险,确保了项目的整体可行性。第二条让我们站在巨人的肩膀上,不重复造轮子,聚焦在问题本身,而不是工具。第三条缩减项目范围,避免过度工程,以战养兵,不打无用之仗。

下图展示了目前杏仁微服务的整体架构。

image_1bokpsoft19241ap2i1qmjfich3u

3. 基本框架

基本框架我们选择的是 Spring Boot。Spring Boot 是 Spring开源社区提供的一个去容器、去XML配置的应用框架。和标准的基于 war 包的 Web 应用相比,Spring Boot 应用可以直接以 java -jar 的方式运行,也就是说不再需要部署到一个独立的 Web 容器(比如
Tomcat)中才能运行。其背后的运行机制简单来说就是,当一个 Spring Boot 应用启动时,在加载完核心框架类之后,会启动一个内嵌的 Web 容器(默认是 Tomcat),然后再加载应用本身的各种配置类和 Bean。也就是说不再是容器包应用,而是应用包容器。正是由于这个特性,Spring Boot 非常适用于开发微服务,毫不夸张的说 Spring Boot 就是为微服务而生。

image_1bokn9bli1st585p1q4515kjqsf9

有同学可能会问,不是还有 DubboSpring Cloud 吗?Dubbo 是阿里开源的第一代 RPC 框架,早在 2011 年就已经发布了 2.0 版本,三年后也就是 2014 年,Martin Fowler 才提出了微服务的概念。虽然用 Dubbo 也能开发微服务,但这就好比用EJB的规范去开发 Spring Bean,怎么用怎么别扭。Dubbo 最大的问题是升级缓慢,最近一次发布还是 2014 年 10 月,支持的 Spring 版本是 2.5.6.SEC03,要知道 Spring 5 都快出来了。

Spring Cloud 可以说是目前 Java 社区最好最完整的微服务框架(没有之一),底层用的也是 Spring Boot,照着 Spring Cloud 的新手指南,分分钟就可以搭建出一整套微服务应用,非常适合改革式但不是改良式的微服务改造,因为非 Spring 应用难以集成。

作为硬币的另一面,选用 Spring Boot,意味着我们需要做大量的自定义工作,以弥补 Spring Boot 在微服务治理方面所欠缺的能力,比如即将在下篇介绍的注册中心、配置中心和授权中心。欢迎留言交流你的心得和见解。

3+

喜欢该文章的用户:

  • avatar