skaka的博客

every flight begins with a fall

微服务框架Spring Cloud介绍 Part5: 在微服务系统中使用Hystrix, Hystrix Dashboard与Turbine

| Comments

通过前面几篇文章的介绍, 我们已经能够使用Spring Cloud开发出一个简单的系统了. 这篇文章里, 我们将关注点转移到如何提高微服务系统的容错性(Fault Tolerance), 并了解如何借助Hystrix开发健壮的微服务.

以往我们在开发单体应用, 或者调用RPC服务的时候, 可能没有考虑太多目标服务调用失败的情况, 经常一个Try/Catch加上打印日志就解决了. 但是在微服务系统中, 这种处理方法会给系统的稳定性带来很大隐患.举个例子, 假设我们系统中下单的功能要依赖50个服务, 每个服务正常响应的概率为99.99%, 如果我们不做容错处理, 只要任意一个服务没有响应下单就失败的话, 我们下单成功的概率为

99.9950 = 99.5%

日订单量为1W的话, 50个会出现下单失败, 这还是建立在依赖服务稳定性很高的情况下(4个9). 但是服务调用失败引起的问题不仅仅是这么简单, 在分布式环境下, 一个服务的调用失败可能会使其他被依赖服务发生延迟和超时, 而且这个影响会很快扩散到其他服务, 从而引发整个系统的雪崩(Avalanche).

1. hystrix介绍

这篇文章要介绍的Hystrix是一个Java类库, 它提供下面这些功能来帮助我们构建健壮的微服务系统:(对Hystrix已经比较熟悉的同学可以直接跳过这段到下面的Hystrix javanica介绍)
1.断路器机制
断路器很好理解, 当Hystrix Command请求后端服务失败数量超过一定比例(默认50%), 断路器会切换到开路状态(Open). 这时所有请求会直接失败而不会发送到后端服务. 断路器保持在开路状态一段时间后(默认5秒), 自动切换到半开路状态(HALF-OPEN). 这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN). Hystrix的断路器就像我们家庭电路中的保险丝, 一旦后端服务不可用, 断路器会直接切断请求链, 避免发送大量无效请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力.
2.Fallback
Fallback相当于是降级操作. 对于查询操作, 我们可以实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值. fallback方法的返回值一般是设置的默认值或者来自缓存.
3.资源隔离
在Hystrix中, 主要通过线程池来实现资源隔离. 通常在使用的时候我们会根据调用的远程服务划分出多个线程池. 例如调用产品服务的Command放入A线程池, 调用账户服务的Command放入B线程池. 这样做的主要优点是运行环境被隔离开了. 这样就算调用服务的代码存在bug或者由于其他原因导致自己所在线程池被耗尽时, 不会对系统的其他服务造成影响. 但是带来的代价就是维护多个线程池会对系统带来额外的性能开销. 如果是对性能有严格要求而且确信自己调用服务的客户端代码不会出问题的话, 可以使用Hystrix的信号模式(Semaphores)来隔离资源.

以上是对Hystrix的简单介绍, 如果想进一步了解Hystrix可以访问GitHub. 现在我们来看如何编写一个Hystrix Command, 代码来自Hystrix的Github:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CommandHelloFailure extends HystrixCommand<String> {

    private final String name;

    public CommandHelloFailure(String name) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
                      .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"))
                      .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")));
        this.name = name;
    }

    @Override
    protected String run() {
        return "hello world";
    }

    @Override
    protected String getFallback() {
        return "Hello Failure " + name + "!";
    }
}

编写一个Hystrix Command, 需要继承HystrixCommand类, 在run方法中完成你的业务逻辑. 你可以重写getFallback方法来在run方法抛出异常的时候返回备用的结果.

微服务框架Spring Cloud介绍 Part4: 使用Eureka, Ribbon, Feign实现REST服务客户端

| Comments

上一篇文章中我们开发了一个用户注册服务. 这篇文章我将介绍如何开发mysteam订单服务中的下单功能, 下单功能会涉及服务之间的交互与事件的处理, 并且我会对开发过程中用到的框架和类库进行简单地讲解. 开始写代码之前, 我们先来看看下单的处理流程:

其中1,2,3,4,11步的黑色箭头代表是同步操作, 5,6,7,8,9,10步是异步操作. 下单接口接收要订购的产品ID, 数量和要使用的优惠券ID, 然后调用产品服务的接口查询产品信息, 调用优惠券接口校验优惠券是否有效, 以及调用账户接口判断账户金额是否足够(mysteam是一个虚拟物品商城, 采用先充值后购买的形式). 如果这些校验都成功, 订单服务会发送账户扣款事件和优惠券使用事件到MQ, 账户服务和优惠券服务会从MQ读取事件进行处理, 如果处理成功, 订单服务将能接收到结果, 并且将订单状态置为下单成功, 如果处理失败或超时, 订单状态会被置为下单失败.

1. 实现Model

流程清楚了, 现在我们来看代码. 订单类是$YOUR_PATH/mysteam/order/core/src/main/java/com/akkafun/order/domain/Order.java, 订单类和前面的用户类类似, 其中有两个字段需要注意一下:

1
2
3
4
5
6
@Column
@Enumerated(value = EnumType.STRING)
private OrderStatus status;

@OneToMany(fetch = FetchType.LAZY, mappedBy = "order")
private List<OrderItem> orderItemList = new ArrayList<>(0);

OrderStatus是一个枚举, 表示订单状态. OrderItem是订单项.

微服务框架Spring Cloud介绍 Part3: Mysteam项目结构与开发用户注册服务

| Comments

上一篇文章中我们简单的了解了一下Spring Cloud. 因为Spring Cloud相关的内容较多, 所以我建了一个项目mysteam来演示Spring Cloud的使用, GitHub地址.

1. 项目结构

这是一个Maven项目, 下载下来之后直接导入IDE, 你会看到如下的项目结构(我用的是Intellij IDEA):

普通目录:
docs: 存放文档资料, 例如数据库脚本, astah文件(UML工具)等.
logs: 运行日志存放目录.
公共模块:
apiutils: api模块公共父模块.
common: 服务模块公共父模块, 存放微服务共同依赖的逻辑, 例如事件处理, 定时任务等.
utils: 工具类模块.
基础服务模块:
eureka: eureka服务. 提供服务注册与服务发现. 这个服务之后会有专门的文章来介绍.
config: config服务. 提供配置管理服务. 这个服务之后会有专门的文章来介绍.
turbine: hystrix服务监控. 这个服务之后会有专门的文章来介绍.
服务模块:
account: 账户服务.
coupon: 优惠券服务.
order: 订单服务.
product: 产品服务.
user: 用户服务.
其他模块:
integration-test: 集成测试模块.

这些模块内部的项目结构大多类似, 以服务模块user为例.
api: api接口模块. 其他依赖user服务的服务会依赖这个模块.
core: user服务实现模块.
api和core模块内容都是标准的maven项目结构, 其中core模块主要有这么一些子目录:
context: 存放Spring Boot启动类.
dao: DAO层.
domain: Model层. service: Service层.
web: 存放Spring MVC Controller.

值得特别说明的是, 在真实的项目中, 一般每个服务都是一个独立的项目, 彼此之间只是通过pom引用. 如果代码都放到一个项目中, 过一段时间你会发现每次打开IDE都是件痛苦的事情, 而且IDE运行速度会奇慢无比. 这样做也违背了微服务开发的本意: 各个服务之间相对独立. mysteam把所有的服务都放到一个项目中只是为了方便演示和运行. 如果你想将mysteam的模块都拆到独立项目中去也是相当的简单, 只要修改pom文件即可.

好了, 项目结构介绍完, 接下来我们要做点正事了: ) 实现用户注册服务.

2. 实现Model

用户表的结构相当简单, 只有三个字段. sql文件在$YOUR_PATH/mysteam/user/docs/user-service.sql. 我们首先创建实体类. 文件位置在$YOUR_PATH/mysteam/user/core/src/main/java/com/akkafun/user/domain/User.java.

微服务框架Spring Cloud介绍 Part2: Spring Cloud与微服务

| Comments

之前介绍过微服务的概念与Finagle框架, 这个系列介绍Spring Cloud.

Spring Cloud还是一个相对较新的框架, 今年(2016)才推出1.0的release版本. 虽然Spring Cloud时间最短, 但是相比我之前用过的Dubbo和Finagle, Spring Cloud提供的功能最齐全.

Spring Cloud完全依赖于Spring Boot, 我先简单介绍下Spring Boot. Spring Boot是Pivotal在Spring基础上推出的一个支持快速开发的框架. 如果是新项目, 建议基于Spring Boot而不是Spring. 以前使用Spring的项目, 需要自己指定一大堆项目依赖, 例如依赖Spring Core, Spring MVC, Mybatis等等, Spring Boot将这些依赖都模块化好了, 你不再需要自己手动去添加多个依赖项. 另外Spring Boot默认内嵌了一个Servlet容器, 你的页面可以直接通过main方法启动访问了, 不再需要部署到单独的应用服务器中, 这样应用的开发调试都会方便很多. Spring Boot的这些特点使得它比较适合用来做微服务的基础框架, 但是要开发一个完整的微服务系统可不仅仅是从命令行启动一个web系统这么简单. Pivotal看到了这点, 推出了Spring Cloud.

Spring Cloud基于Spring Boot, 由众多的子项目组成. 例如Spring Cloud Config是一个中心化的配置管理服务, 用来解决微服务环境下配置文件分散管理的难题, Spring Cloud Stream是一个消息中间件抽象层, 目前支持Redis, Rabbit MQ和Kafka, Spring Cloud Netflix整合了Netflix OSS, 可以直接在项目中使用Netflix OSS. 目前Spring Cloud的子项目有接近20个, 如果要使用Spring Cloud, 务必先将子项目都了解一遍, 得知道哪些功能Spring Cloud已经提供了, 避免团队花费大量时间重复造轮子.

Spring Cloud是伴随着微服务的概念诞生的. 毫无疑问, 微服务真正落地是一项艰巨的任务. 不但是技术的变革, 也是开发方式的转变. 仅仅依靠Dubbo或Spring Cloud开发几个互相调用的服务不能算做是微服务. 一个合格的微服务系统必然包括从设计(从业务层面划分服务, 独立数据库), 到开发(选用合适的架构和工具, 解决CAP问题), 到测试(持续集成, 自动化测试), 到运维(容器化, 服务监控, 服务容错)的一系列解决方案.

我这个系列的博客就是介绍如何借助Spring Cloud和Netflix OSS, 来解决上面提到的问题. 之后的博客主要会涉及下面这些技术:
使用eureka和Netflix Ribbon进行服务注册和服务发现
使用Spring Cloud Stream, zookeeper和kafka实现分布式事务
使用hystrix实现服务隔离, 并且用hystrix dashboard和turbine监控hystrix服务
使用Spring MVC和Swagger实现REST API
使用Spring Cloud Config实现配置集中管理
使用Spring Cloud Sleuth与Zipkin实现服务监控

内容比较多, 我会分成多篇博客. 我不想泛泛地谈概念, 这样有点无趣, 对实际工作也起不到什么帮助. 我为演示这些技术的使用, 搭建了一个项目: mysteam. 我选择了一个简单的问题域, 电商系统里最基础的下单功能. 围绕下单功能, 系统拆分成了五个服务:
用户服务(user service)
账户服务(account service)
产品服务(product service)
优惠券服务(coupon service)
订单服务(order service)
下面是mysteam的架构示意图: 我们的关注点主要在Backend Services和MQ, MySQL这一部分. 服务之间通过Rest API和事件进行通信. Rest API主要用来进行一些只读等不需要事务的操作, 涉及事务的操作一般使用事件来完成. 具体怎么做后面有专门的博客来介绍.

首先, 让我们来个Hello World, 先介绍如何将mysteam下载下来并启动. 一旦涉及微服务, 项目结构和环境都会比较复杂, 我已经尽量简化了, 请系好安全带: )

1. 环境准备

JDK 8+
MySQL
kafka 0.8.22
zookeeper (可以下载, 也可以直接使用kafka自带的zookeeper)
Intellij IDEA或Eclipse (这个项目结构比较复杂, IDE能起到很大帮助)

微服务框架Finagle介绍 Part3: 在Finagle中开发基于Thrift协议的应用

| Comments

上篇文章中我们开发了一个基于Http协议的echo服务端和客户端. 这篇文章我们将开发一个基于Thrift协议的客户端和服务端. 这两篇文章对应的源代码地址在Github. 代码中有Java和Scala版本两套版本的实现, 但是这里我只会介绍Java版本.

Thrift最早由Facebook开源, 后被Apache收录成为顶级项目. Thrift严格来说不只是一种协议, 而是一个RPC框架. 使用Thrift, 我们只需要定义好使用的类型和接口声明, Thrift的代码生成工具能够自动为我们生成客户端和服务端代码. 我们现在来看如何在Finagle中使用Thrift.

首先定义一个Thrift的IDL文件, 文件位置在java-finagle-example/src/main/thrift/DemoService.thrift:

1
2
3
4
5
6
7
8
9
10
namespace java com.akkafun.service.thrift

service DemoService {
  string method1();

  i32 method2(1: i32 a, 2: i32 b);

  void method3();

}

定义了一个DemoService服务, 这个服务有三个示例方法. namespace的语法是为接口定义一个命名空间(对应Java里的包). method1没有参数, 方法的返回值类型是字符串. method2有两个参数, a和b, 参数和返回值类型都是int32类型. method3无参数, 无返回值.