Spring Cloud微服务架构

Spring Cloud是一个面向分布式系统构建的技术体系,为开发人员提供了构建分布式系统所需的核心和外围组件,其基于Spring Framework和Spring Boot技术框架,使得能够“开箱即用”,快速搭建如下微服务架构所需的功能,

  • 服务注册和发现
  • 服务路由
  • 负载均衡
  • 访问安全控制
  • 应用配置
  • 熔断限流
  • 调用监控
  • 健康检查

一个由Spring Cloud所搭建的微服务架构体系如下图所示,

主要组件模块的功能如下,

  • 网关Zuul:提供服务路由、负载均衡、访问安全控制、熔断限流
  • 注册中心Eureka:服务注册和发现
  • 配置中心Config:应用环境配置
  • 微服务Spring Boot:应用服务开发
  • 流量监控Turbine:流量监控、熔断限流监控
  • 监控看板Spring Cloud Dashboard:应用健康状态、心跳检查、熔断限流看板
  • 调用链监控Sleuth:通过日志跟踪调用链
  • 日志分析ELK:日志分析
  • 告警:健康和熔断告警

图中Spring Cloud组件都用相应的图标标记,注意的是,日志分析和告警两个模块组件,Spring Cloud尚未涉及相关组件,需要对接第三方所提供的组件模块。对于这两个组件,图中参考了拍拍贷基础架构设计,分别选用了ELK和Zalando Zmon,详细请见下篇拍拍贷的技术架构体系设计

参考资料

  1. Spring by Pivotal https://spring.io/

Mesos架构和工作流程简介

1. 什么是Mesos

Mesos是Apache软件基金会维护的一个开源软件,它负责管理一批服务器集群,并将所有服务器集群的CPU、GPU、内存、存储、端口和其它相关计算资源进行了抽象统一,让用户进行动态配置和使用,提高整个系统的资源利用率,并以集群分布式的方式保证系统运行的高可用和弹性扩展。

2. Mesos架构和工作流程

官网中有个Mesos的架构图如下,

主要的模块有,

  • Mesos Master:管理所有机器资源信息,一般会部署多台来保证master的高可用,以resource offers的方式定时告知scheduler可用资源。
  • ZooKeeper:实现当前工作Mesos Master的选举,并实现数据一致性。
  • Mesos Agent:集群中每个机器都会部署一台Mesos Agent,定时汇报当前机器可用资源给Master,并从Master获取分配的任务信息,调用Executor来运行。
  • Scheduler:定时得到Master发送过来的resource offers,将任务列表中各个任务的资源需求进行一一匹配,一旦资源需求获得满足,则告诉Master使用匹配到的资源启动任务。
  • Executor:被Agent调用,根据指定的任务和资源信息,执行任务。

其中Scheduler和Executor是开发者根据自己需要进行开发,业界里各种Mesos Framework也就是实现这两部分,例如图中的Hadoop Mesos/MPI Mesos,还有Mesosphere公司的marathon,HudSpot公司的Singularity,国内数人云的Swan等。

Mesos提供两层调度,一层是Agent调用Executor执行Master分配的任务,另外一层是任务的资源匹配和调度放到Scheduler,由Scheduler推送任务到Master。由于Scheduler和Executor的可动态调整,这样也使得任务调度策略和执行变得可动态配置。

整个Mesos系统的主要工作流程如下,

  1. Mesos Agent定期上报各个机器的资源(CPU、Memory、磁盘、端口号)
  2. Mesos Master收集所有Agent可用的资源,定期推送resource offer给Scheduler,offer中描述了可用的资源信息。
  3. Scheduler启动时,会找到Mesos Master并注册,定期获取Master推送给的resource offer,以此了解可用的资源。
  4. 用户向Scheduler申请任务需要的资源,执行相关任务,例如申请2CPU 4G Mem来运行程序Demo。
  5. Scheduler在获取到Master推送的offer后,当offer中的资源满足用户申请的任务需求,就向Master申请执行。
  6. Mesos master根据Scheduler申请,在相应的资源上调用Agent执行任务。

整个流程图见如下,

需要注意的是,Mesos的四大组件(Master/Slave/Scheduler/Executor)之间的通信,是通过libprocess实现actor model模型的进程间消息异步通信,每个进程是一个actor。

见上图,在Mesos的master节点中,每个Framework以及Slave都是一个远程的actor。而slave节点上,每个executor是一个actor,只不过内置的executor是在同一个进程中的,而其他自定义的executor是独立的进程,executor和slave之间通过进程间通信方式(网络端口)交互。

Actor模型通信带来的好处是省去了对消息队列的依赖,但同时由于消息都是异步的,需要actor处理消息的丢失以及超时逻辑,Mesos无法保证消息的可靠投递,提供的投递策略是 at-most-once(至多一次,不会重试)。

3. 如何开发一个Mesos Framework

Mesos提供支持多种语言的Framework开发,包括Java/Python/Scala等,下面以Java语言为例介绍Mesos Framework的开发,其它语言类似。

一个Mesos Framework主要实现两个模块,

  1. Scheduler
  2. Executor

其中Scheduler会是单独可运行的项目程序,比如一个Java程序或者一个Java Web后端服务;Executor则会是Jar包项目,打出Jar包后由Mesos Agent引用并启动。

在两个项目中各自引入依赖包,

<dependency>
<groupId>org.apache.mesos</groupId>
<artifactId>mesos</artifactId>
</dependency>

在包org.apache.mesos中提供如下主要抽象接口,

  • Scheduler:这是Framework要实现的Scheduler接口,用于Mesos Master回调。
  • Executor:这是Framework要实现的Executor接口,用于Agent回调。
  • SchedulerDriver:这是Scheduler和Mesos进行通信的抽象接口。
  • ExecutorDriver:这是Executor和Mesos进行通信的抽象接口。

在两个项目中需实现上述的Scheduler和Executor抽象接口,各个接口方法的实现需求请参见Apache Mesos项目源代码中对其的接口描述,可以通过如下git命令下载代码,
git clone git@github.com:apache/mesos.git

在Apache Mesos项目代码中有对Mesos Framework开发提供的样例实现,在其文件目录中,

  • mesos\src\examples\java\TestFramework.java
  • mesos\src\examples\java\TestExecutor.java

前一个实现了org.apache.mesos.Scheduler,是一个可单独运行的Java程序;后一个实现了org.apache.mesos.Executor,是一个Jar包。

注:在包org.apache.mesos中还有两个抽象接口:SchedulerDriver和ExecutorDriver,Apache Mesos提供两个Driver的默认具体实现MesosSchedulerDriver和MesosExecutorDriver,所以Mesos Framework可以直接使用,用于和Mesos Master通信,不用自己实现这两个Driver。

4. 参考资料

Json Web Token的介绍和最佳实践

1. 什么是JSON Web Token

JSON Web Token是一个开放的、行业规范(RFC7519)的方法,用于通信双方进行可靠的数据传输。它规定了一种紧凑的数据格式,将要传输的数据以JSON对象方式的组织,然后进行编码和签名,转化成为Token。

JSON Web Token包含三部分信息数据,

Header.Payload.Signature

其中,

  • Header:数据头部,声明Token类型和加密算法(比如HMAC SHA256或者RSA)
  • Payload:数据的信息体,包含了要传输的信息体
  • Signature:数据的数字签名,用于对数据进行校验来源可靠性和不被篡改

在三部分中,header/payload都是公开信息,JSON对象数据格式,使用Base64方式进行编码,这意味着header/payload可以被任何人进行解码和阅读,所以不要在header/payload放入敏感信息。

一个Token样例为,
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

其中,

Header Base64编解码 {"alg":"HS256","typ":"JWT"}
Payload Base64编解码 {"sub":"1234567890","name":"John Doe","admin":true}
Signature HS256签名算法 HS256(eyJhb……RydWV9, secret)

注:签名算法中的eyJhb……RydWV9为Base64编码后的Header.Payload

2. 应用场景

JWT一般用在如下两个场景,

  • 认证:这是最常用JWT应用场景,用于用户登录后,后端服务器颁发一个带有时效性的JWT token给前端应用,然后前端应用在后续的请求中使用该Token进行访问后端资源。
  • 信息交换:由于JWT对数据进行了签名,信息不能被篡改,可以可靠地在通信双方进行数据信息交换。

下图简单描绘了JWT在认证场景的使用流程,

使用方法

JWT的使用方法包括:签发、解码、校验,

  • 签发:用于对信息进行签名,然后颁发给使用方,供后续进行认证和信息交换
  • 解码:对JWT的信息体解码,获取信息,一般用在不进行校验的场合,比如在浏览器端
  • 校验:对JWT的校验,确认信息的来源可靠性和不被篡改、时效不过期

目前已经有如下语言的支持,

  • Java
  • Node.js
  • JavaScript
  • Microsoft .NET、.NET RT(Windows 8.1 and Windows Phone 8.1)
  • Python
  • Perl
  • Ruby
  • Go
  • Lua
  • Scala
  • Object-C
  • Swift
  • PHP

等等。

下面主要介绍JavaScript/Node.Js/Java三种语言对Jwt的签发、校验、解码的代码实现,这三种语言分别代表着web客户端/web服务器端/后端服务。

1) JavaScript

下面使用jwt-decode类库在web客户端(浏览器端)对token进行解码操作。

点击这里下载jwt-token.js脚本。

在HTML页面中引入上面的JavaScript脚本,下面为简单的解码演示,

<script type="text/javascript" src="jwt-token.js"></script>
<script>

    let jwtToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ";
    let payload = jwtToken.split('.')[1];
    let payloadInfo = JSON.parse(jwt.base64urldecode(payload));

    for (let key in payloadInfo) {
        console.log(key + ": " + payloadInfo[key]);
    }

</script>
2) Node.js

下面使用Node Auth0类库在nodejs的web服务器端进行token的签发和校验
安装命令,从npm中安装jsonwebtoken
npm install jsonwebtoken

签发

let jwt = require('jsonwebtoken');
let token = jwt.sign({name: 'John'}, 'secret', { algorithm: 'HS256', expiresIn: '1d', issuer: 'pphh'});

校验

let jwt = require('jsonwebtoken');
let info = jwt.verify(token, 'secret');
console.log(info.name) // John

解码

let info = jwt.decode(token, {complete: true});
console.log(info.header);
console.log(info.payload);
3) Java

下面使用Java Auth0类库来进行token的签发和校验。

在Maven项目中添加依赖项,

<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>

签发

try {
    Algorithm algorithm = Algorithm.HMAC256("secret");
    String token = JWT.create()
                      .withIssuer("auth0")
                      .sign(algorithm);
} catch (UnsupportedEncodingException exception){
    //UTF-8 encoding not supported
} catch (JWTCreationException exception){
    //Invalid Signing configuration / Couldn't convert Claims.
}

校验

String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
try {
    Algorithm algorithm = Algorithm.HMAC256("secret");
    JWTVerifier verifier = JWT.require(algorithm)
                              .withIssuer("auth0")
                              .build(); //Reusable verifier instance
    DecodedJWT jwt = verifier.verify(token);
} catch (UnsupportedEncodingException exception){
    //UTF-8 encoding not supported
} catch (JWTVerificationException exception){
    //Invalid signature/claims
}

解码

String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
try {
    DecodedJWT jwt = JWT.decode(token);
} catch (JWTDecodeException exception){
    //Invalid token
}

更多语言和JWT类库使用,请参考官方网站和样例。

最佳实践

  1. 在JWT信息体Payload中不要包括敏感信息,若一定要,可以尝试使用JSON Web Encryption 。
  2. 一定要添加Token过期时间expiration time (exp claim) ,用于时效性检查。
  3. 不建议以无签名算法的方式(签名算法为none)来颁发JWT token,这个有安全风险,会导致攻击者绕过某些JWT类库的校验,具体可以查看该资料
  4. 建议使用HTTPS来传输JWT token信息,在最大程度保证信息在传输过程中的安全性,防止被中间人截取。
  5. 确保签名密钥只能让颁发者和使用者知道,尽可能使用非对称加密算法来颁发Token(比如RSA算法)。
  6. 如果担心重放攻击(replay attacks),可以尝试设置jti声明(JWT ID Claim),该声明将使得每个JWT token拥有唯一的ID,使其可以只使用一次,可以用来防止重放攻击。
  7. 在浏览器端,token可以存放到sessionStorage/localStorage,但注意XSS(跨站点脚本)攻击;还有一种方法是token放到了Cookie中,建议为其加上HttpOnly和Secure标记,HttpOnly保证JavaScript无法获取该Cookie,而Secure标记将保证该Cookie信息只能通过Https传输,但注意Cookie的方式可能会引起CSRF(跨站请求伪造)攻击。

演示项目

admin demo项目代码中有一一使用上述的JWT token颁发、校验、解码方法,

  1. 在前端项目admin-front中,通过jwt-decode解析JWT token来获取登录用户账号。
  2. 在前端项目admin-front中,在配置文件webpack.config.js中有提供/test/auth接口,通过jsonwebtoken颁发JWT token。
  3. 在后端项目admin-server中,在UserService中,通过com.auth0.jwt.JWT来颁发JWT token。
  4. 在后端项目admin-server中,在JwtFilter中,通过com.auth0.jwt.JWT来校验用户登录token。

具体可以获取项目代码查看。

admin demo代码仓库地址:https://gitee.com/pphh/simple-admin,可以通过如下git clone命令获取仓库代码,

git clone git@gitee.com:pphh/simple-admin.git

参考资料