基于Spring Cloud Netflix的TCC柔性事务和EDA事件驱动示例

Solar

Spring Cloud为开发者提供了快速构建分布式系统中的一些常见工具,如分布式配置中心,服务发现与注册中心,智能路由,服务熔断及降级,消息总线,分布式追踪的解决方案等。html

本次实战以模拟下单流程为背景,结合Spring Cloud Netflix和分布式事务解决方案中Try Confirm Cancel模式与基于事件驱动的服务架构做为实战演示。java

开发环境

  • Docker 1.13.1
  • Docker Compose 1.11.1
  • Docker MySQL 5.7.17
  • Docker RabbitMQ 3.6.6
  • Java8 with JCE
  • Spring Cloud Camden.SR6

系统结构


Try Confirm Cancel 补偿模式

本实例遵循的是Atomikos公司对微服务的分布式事务所提出的RESTful TCC解决方案。mysql

RESTful TCC模式分3个阶段执行git


  1. Trying阶段主要针对业务系统检测及做出预留资源请求,若预留资源成功,则返回确认资源的连接与过时时间。
  2. Confirm阶段主要是对业务系统的预留资源做出确认,要求TCC服务的提供方要对确认预留资源的接口实现幂等性,若Confirm成功则返回204,资源超时则证实已经被回收且返回404。
  3. Cancel阶段主要是在业务执行错误或者预留资源超时后执行的资源释放操做,Cancel接口是一个可选操做,由于要求TCC服务的提供方实现自动回收的功能,因此即使是不认为进行Cancel,系统也会自动回收资源。

Event Driven Architecture 基于事件驱动架构

本实例中的order-ms与membership-ms之间的通讯是基于事件驱动的。当订单被成功建立而且付款成功以后,该订单的部分信息将会发往membership-ms以进行积分的增长。github

从系统层面看,order-ms在EDA中是属于Publisher角色,天然而然地membership-ms就是Subscriber。web

Publisher中的事件状态转换以下:spring

  • NEW —> PENDING —> DONE
  • NEW —> PENDING —> FAILED / NO_ROUTE / NOT_FOUND / ERROR


Subscriber中的事件状态转换以下:sql

  • NEW —> DONE
  • NEW —> FAILED / NOT_FOUND / ERROR

部分功能介绍:docker

  1. Publisher发送消息以前先将消息落地,目的是防止消息的错误发布(业务数据被回滚而消息却发布至Broker)。
  2. Publisher会周期性地扫描NEW状态的消息,并发布至Broker。
  3. 启用mandatory与publisher confirms机制,在消息被持久化至磁盘后将会收到basic.ack,此时可选择将消息转换为DONE或者是直接将其删除。
  4. Publisher将消息发布至Broker后会将其状态由NEW更新为PENDING,PENDING状态的事件将会由另外一定时器扫描在当前时钟的3秒以前发布,可是却并未获得basic.ack的事件,并从新发布至Broker。意在消除在单实例的状况下因crash而致使消息状态丢失的边缘状况。
  5. Subscriber的消息幂等性。

基础组件

Zuul Gateway

Zuul在本实例中仅做为路由所使用,配置下降Ribbon的读取与链接超时上限。数据库

Eureka H.A.

多个对等Eureka节点组成高可用集群,并将注册列表的自我保护的阈值适当下降。

Config Server

  • 若是远程配置中有密文{cipher}*,那么该密文的解密将会延迟至客户端启动的时候. 所以客户端须要配置AES的对称密钥encrypt.key,而且客户端所使用的JRE须要安装Java 8 JCE,不然将会抛出Illegal key size相关的异常。 (本例中Docker Compose构建的容器已经安装了JCE,若是远程配置文件没有使用{cipher}*也没必要进行JCE的安装)
  • 为了达到开箱即用,选用公开仓库Github或者GitOsc。
  • 本项目中有两个自定义注解 @com.github.prontera.Delay 控制方法的延时返回时间;@com.github.prontera.RandomlyThrowsException 随机抛出异常,人为地制造异常。
    默认的远程配置以下
    solar: delay: time-in-millseconds: 0 exception: enabled: false factor: 7
    这些自定义配置正是控制方法返回的时延,随机异常的因子等。
    我在服务orderproductaccounttcc中的全部Controller上都添加了以上两个注解,当远程配置的更新时候,能够手工刷新/refresh或经过webhook等方法自动刷新本地配置. 以达到模拟微服务繁忙或熔断等状况。

监控服务

Spring Boot Admin

此应用提供了管理Spring Boot服务的简单UI,下图是在容器中运行时的服务健康检测页

Hystrix Dashboard

提供近实时依赖的统计和监控面板,以监测服务的超时,熔断,拒绝,降级等行为。


Zipkin Server

Zipkin是一款开源的分布式实时数据追踪系统,其主要功能是汇集来自各个异构系统的实时监控数据,用来追踪微服务架构下的系统时延问题. 下图是对order服务的请求进行追踪的状况。

业务服务

首次启动时经过Flyway自动初始化数据库。

对spring cloud config server采用fail fast策略,一旦远程配置服务没法链接则没法启动业务服务。

account

用于获取用户信息,用户注册,修改用户余额,预留余额资源,确认预留余额,撤销预留余额。

product

用于获取产品信息,变动商品库存,预留库存资源,确认预留库存,撤销预留库存。

tcc coordinator

TCC资源协调器,其职责以下:

  • 对全部参与者发起Confirm请求。
  • 不管是协调器发生的错误仍是调用参与者所产生的错误,协调器都必须有自动恢复重试功能,尤为是在确认的阶段,以防止网络抖动的状况。

order

order服务是本项目的入口,尽管所提供的功能很简单:

  • 下单. 即生成预订单,为了更好地测试TCC功能,在下单时就经过Feign向服务accountproduct发起预留资源请求,而且记录入库。
  • 确认订单. 确认订单时根据订单ID从库中获取订单,并获取预留资源确认的URI,交由服务tcc统一进行确认,若是发生冲突即记录入库,等待人工处理。

membership

用于订单付款成功后,对下单用户的积分进行增长操做。该服务与订单服务是基于消息驱动以进行通讯,达到事务的最终一致性。

Swagger UI

下图为product服务的Swagger接口文档,根据下文的服务字典可知,本接口文档可经过http://localhost:8040/swagger-ui.html进行访问. orderaccounttcc的文档访问方式亦是如出一撤。

运行

Docker Compose运行

在项目根路径下执行脚本build.sh,该脚本会执行Maven的打包操做,并会迭代目录下的*-compose.yml进行容器构建。

构建完成后须要按照指定的顺序启动,须要注意的一点是容器内服务的启动是须要备留预热时间,并不是Docker容器启动后容器内的全部服务就能立刻启动起来,要注意区分容器的启动容器内的服务的启动,建议配合docker-compse logs来观察启动状况。并且容器之间的服务是有依赖的,如account-msproduct-ms此类业务服务的启动是会快速失败于config-ms的失联。因此建议按照如下顺序启动Docker容器,而且在一组Docker容器服务彻底启动后,再启动下一组的Docker容器。

  1. 启动MySQL,RabbitMQ等基础组件docker-compose -f infrastructure-compose.yml up -d
  2. 启动Eureka Server与Config Serverdocker-compose -f basic-ms-compose.yml up -d
  3. 启动监控服务docker-compose -f monitor-ms-compose.yml up -d
  4. 启动业务服务docker-compose -f business-ms-compose.yml up -d

IDE运行

由于程序自己按照Docker启动,因此对于hostname须要在hosts文件中设置正确才能正常运行:

## solar
127.0.0.1 eureka1
127.0.0.1 eureka2
127.0.0.1 rabbitmq
127.0.0.1 zipkin_server
127.0.0.1 solar_mysql
127.0.0.1 gitlab复制代码

根据依赖关系,程序最好按照如下的顺序执行

docker mysql > docker rabbitmq > eureka server > config server > zipkin server > 其余业务微服务(account-ms, product-ms, order-ms, tcc-coordinator-ms等)

示例

根据附表中的服务字典,咱们经过Zuul或Swagge对order服务进行预订单生成操做。

POST http://localhost:7291/order/api/v1/orders
Content-Type: application/json;charset=UTF-8

{
  "product_id": 7,
  "user_id": 1
}复制代码

成功后咱们将获得预订单的结果

{
  "data": {
    "id": 15,
    "create_time": "2017-03-28T18:18:02.206+08:00",
    "update_time": "1970-01-01T00:00:00+08:00",
    "delete_time": "1970-01-01T00:00:00+08:00",
    "user_id": 1,
    "product_id": 7,
    "price": 14,
    "status": "PROCESSING"
  },
  "code": 20000
}复制代码

此时咱们再确认订单

(若是想测试预留资源的补偿状况,那么就等15s后过时再发请求,注意容器与宿主机的时间)

POST http://localhost:7291/order/api/v1/orders/confirmation
Content-Type: application/json;charset=UTF-8

{
  "order_id": 15
}复制代码

若是成功确认则返回以下结果

{
  "data": {
    "id": 15,
    "create_time": "2017-03-28T18:18:02.206+08:00",
    "update_time": "2017-03-28T18:21:32.78+08:00",
    "delete_time": "1970-01-01T00:00:00+08:00",
    "user_id": 1,
    "product_id": 7,
    "price": 14,
    "status": "DONE"
  },
  "code": 20000
}复制代码

至此就完成了一次TCC事务,固然你也能够测试超时和冲突的状况,这里就再也不赘述。

拓展

使用Gitlab做为远程配置仓库

本例中默认使用Github或GitOsc中的公开仓库,出于自定义的须要,咱们能够在本地构建Git仓库,这里选用Gitlab为例。

将如下配置添加至docker compose中的文件中并启动Docker Gitlab容器:

gitlab:
    image: daocloud.io/daocloud/gitlab:8.16.7-ce.0
    ports:
        - "10222:22"
        - "80:80"
        - "10443:443"
    volumes:
        - "./docker-gitlab/config/:/etc/gitlab/"
        - "./docker-gitlab/logs/:/var/log/gitlab/"
        - "./docker-gitlab/data/:/var/opt/gitlab/"
    environment:
        - TZ=Asia/Shanghai复制代码

将项目的config-repo添加至Gitlab中,并修改config-ms中git仓库的相关验证等参数便可。

服务字典

鉴于Spring Boot Actuator的端点所带来的两面性,除了能够增长spring-boot-starter-security来得到强度较弱的HTTP Basic认证外,咱们还能够修改management.portmanagement.context-path来提升攻击成本. 是的,我对每个服务都修改了以上两个属性,而且兼容了Eureka Server,Hystrix Dashboard,Spring Boot Admin,使这些监控服务仍能正确工做. 由于对以上两个参数修改,咱们的监控路径有所变化,以下表:


原文:Java架构笔记

相关文章
相关标签/搜索