微服务框架Spring Cloud介绍 Part1: 使用事件和消息队列实现分布式事务

http://skaka.me/blog/2016/04/21/springcloud1/

APR 21ST, 2016 11:21 AM | COMMENTS

不同于单一架构应用(Monolith), 分布式环境下, 进行事务操作将变得困难, 因为分布式环境通常会有多个数据源, 只用本地数据库事务难以保证多个数据源数据的一致性. 这种情况下, 可以使用两阶段或者三阶段提交协议来完成分布式事务.但是使用这种方式一般来说性能较差, 因为事务管理器需要在多个数据源之间进行多次等待. 有一种方法同样可以解决分布式事务问题, 并且性能较好, 这就是我这篇文章要介绍的使用事件,本地事务以及消息队列来实现分布式事务.

我们从一个简单的实例入手. 基本所有互联网应用都会有用户注册的功能. 在这个例子中, 我们对于用户注册有两步操作: 
1. 注册成功, 保存用户信息.
2. 需要给用户发放一张代金券, 目的是鼓励用户进行消费.
如果是一个单一架构应用, 实现这个功能非常简单: 在一个本地事务里, 往用户表插一条记录, 并且在代金券表里插一条记录, 提交事务就完成了. 但是如果我们的应用是用微服务实现的, 可能用户和代金券是两个独立的服务, 他们有各自的应用和数据库, 那么就没有办法简单的使用本地事务来保证操作的原子性了. 现在来看看如何使用事件机制和消息队列来实现这个需求.(我在这里使用的消息队列是kafka, 原理同样适用于ActiveMQ/RabbitMQ等其他队列)

我们会为用户注册这个操作创建一个事件, 该事件就叫做用户创建事件(USER_CREATED). 用户服务成功保存用户记录后, 会发送用户创建事件到消息队列, 代金券服务会监听用户创建事件, 一旦接收到该事件, 代金券服务就会在自己的数据库中为该用户创建一张代金券. 好了, 这些步骤看起来都相当的简单直观, 但是怎么保证事务的原子性呢? 考虑下面这两个场景:
1. 用户服务在保存用户记录, 还没来得及向消息队列发送消息之前就宕机了. 怎么保证用户创建事件一定发送到消息队列了?
2. 代金券服务接收到用户创建事件, 还没来得及处理事件就宕机了. 重新启动之后如何消费之前的用户创建事件?
这两个问题的本质是: 如何让操作数据库和操作消息队列这两个操作成为一个原子操作. 不考虑2PC, 这里我们可以通过事件表来解决这个问题. 下面是类图. 

EventPublish是记录待发布事件的表. 其中:
id: 每个事件在创建的时候都会生成一个全局唯一ID, 例如UUID.
status: 事件状态, 枚举类型. 现在只有两个状态: 待发布(NEW), 已发布(PUBLISHED).
payload: 事件内容. 这里我们会将事件内容转成json存到这个字段里.
eventType: 事件类型, 枚举类型. 每个事件都会有一个类型, 比如我们之前提到的创建用户USER_CREATED就是一个事件类型.
EventProcess是用来记录待处理的事件. 字段与EventPublish基本相同.

我们首先看看事件的发布过程. 下面是用户服务发布用户创建事件的顺序图. 
1. 用户服务在接收到用户请求后开启事务, 在用户表创建一条用户记录, 并且在EventPublish表创建一条status为NEW的记录, payload记录的是事件内容, 提交事务.
2. 用户服务中的定时器首先开启事务, 然后查询EventPublish是否有status为NEW的记录, 查询到记录之后, 拿到payload信息, 将消息发布到kafka中对应的topic.
发送成功之后, 修改数据库中EventPublish的status为PUBLISHED, 提交事务.

下面是代金券服务处理用户创建事件的顺序图. 
1. 代金券服务接收到kafka传来的用户创建事件(实际上是代金券服务主动拉取的消息, 先忽略消息队列的实现), 在EventProcess表创建一条status为NEW的记录, payload记录的是事件内容, 如果保存成功, 向kafka返回接收成功的消息.
2. 代金券服务中的定时器首先开启事务, 然后查询EventProcess是否有status为NEW的记录, 查询到记录之后, 拿到payload信息, 交给事件回调处理器处理, 这里是直接创建代金券记录. 处理成功之后修改数据库中EventProcess的status为PROCESSED, 最后提交事务.

回过头来看我们之前提出的两个问题:
1. 用户服务在保存用户记录, 还没来得及向消息队列发送消息之前就宕机了. 怎么保证用户创建事件一定发送到消息队列了?
根据事件发布的顺序图, 我们把创建事件和发布事件分成了两步操作. 如果事件创建成功, 但是在发布的时候宕机了. 启动之后定时器会重新对之前没有发布成功的事件进行发布. 如果事件在创建的时候就宕机了, 因为事件创建和业务操作在一个数据库事务里, 所以对应的业务操作也失败了, 数据库状态的一致性得到了保证.
2. 代金券服务接收到用户创建事件, 还没来得及处理事件就宕机了. 重新启动之后如何消费之前的用户创建事件?
根据事件处理的顺序图, 我们把接收事件和处理事件分成了两步操作. 如果事件接收成功, 但是在处理的时候宕机了. 启动之后定时器会重新对之前没有处理成功的事件进行处理. 如果事件在接收的时候就宕机了, kafka会重新将事件发送给对应服务.

通过这种方式, 我们不用2PC, 也保证了多个数据源之间状态的最终一致性.
和2PC/3PC这种同步事务处理的方式相比, 这种异步事务处理方式具有异步系统通常都有的优点:
1. 事务吞吐量大. 因为不需要等待其他数据源响应.
2. 容错性好. A服务在发布事件的时候, B服务甚至可以不在线.
缺点:
1. 编程与调试较复杂.
2. 容易出现较多的中间状态. 比如上面的例子, 在用户服务已经保存了用户并发布了事件, 但是代金券服务还没来得及处理之前, 用户如果登录系统, 会发现自己是没有代金券的. 这种情况可能在有些业务中是能够容忍的, 但是有些业务却不行. 所以开发之前要考虑好.

另外, 上面的流程在实现的过程中还有一些可以改进的地方:
1. 定时器在更新EventPublish状态为PUBLISHED的时候, 可以一次批量更新多个EventProcess的状态.
2. 定时器查询EventProcess并交给事件回调处理器处理的时候, 可以使用线程池异步处理, 加快EventProcess处理周期.
3. 在保存EventPublish和EventProcess的时候同时保存到Redis, 之后的操作可以对Redis中的数据进行, 但是要小心处理缓存和数据库可能状态不一致问题.
4. 针对Kafka, 因为Kafka的特点是可能重发消息, 所以在接收事件并且保存到EventProcess的时候可能报主键冲突的错误(因为重复消息id是相同的), 这个时候可以直接丢弃该消息.

Posted by skaka Apr 21st, 2016 11:21 am  spring cloud分布式事务微服务

[转帖]微服务框架Spring Cloud介绍 Part1: 使用事件和消息队列实现分布式事务的更多相关文章

  1. 微服务框架Spring Cloud介绍 Part1: 使用事件和消息队列实现分布式事务

    http://skaka.me/blog/2016/04/21/springcloud1/ 不同于单一架构应用(Monolith), 分布式环境下, 进行事务操作将变得困难, 因为分布式环境通常会有多 ...

  2. 微服务框架-Spring Cloud

    Spring Cloud入门 微服务与微服务架构 微服务架构是一种新型的系统架构.其设计思路是,将单体架构系统拆分为多个可以相互调用.配合的独立运行的小程序.这每个小程序对整体系统所提供的功能就称为微 ...

  3. 使用kafka消息队列解决分布式事务(可靠消息最终一致性方案-本地消息服务)

    微服务框架Spring Cloud介绍 Part1: 使用事件和消息队列实现分布式事务 本文转自:http://skaka.me/blog/2016/04/21/springcloud1/ 不同于单一 ...

  4. 微服务与Spring Cloud概述

    微服务与Spring Cloud随着互联网的快速发展, 云计算近十年也得到蓬勃发展, 企业的IT环境和IT架构也逐渐在发生变革,从过去的单体应用架构发展为至今广泛流行的微服务架构. 微服务是一种架构风 ...

  5. 什么是微服务架构 Spring Cloud?

    1 为什么微服务架构需要Spring Cloud 简单来说,服务化的核心就是将传统的一站式应用根据业务拆分成一个一个的服务,而微服务在这个基础上要更彻底地去耦合(不再共享DB.KV,去掉重量级ESB) ...

  6. 基于.NET CORE微服务框架 -surging的介绍和简单示例 (开源)

    一.前言 至今为止编程开发已经11个年头,从 VB6.0,ASP时代到ASP.NET再到MVC, 从中见证了.NET技术发展,从无畏无知的懵懂少年,到现在的中年大叔,从中的酸甜苦辣也只有本人自知.随着 ...

  7. 2.微服务开发框架——Spring Cloud

                     微服务开发框架—Spring Cloud 2.1. Spring Cloud简介及其特点 简介: Spring Cloud为开发人员提供了快速构建分布式系统中一些常见 ...

  8. 消息驱动微服务:Spring Cloud Stream

    最近在学习Spring Cloud的知识,现将消息驱动微服务:Spring Cloud Stream 的相关知识笔记整理如下.[采用 oneNote格式排版]

  9. 第十章 消息驱动的微服务: Spring Cloud Stream

    Spring Cloud Stream 是一个用来为微服务应用构建消息驱动能力的框架. 它可以基于Spring Boot 来创建独立的. 可用于生产的 Spring 应用程序. 它通过使用 Sprin ...

随机推荐

  1. css去掉button点击后的蓝框

    转自:http://www.inbeijing.org/archives/1139 css控制Button 按钮的点击时候出现蓝色边框的问题 添加css属性,这样在点击安按钮的时候就不会有蓝色边框了. ...

  2. 使用Composer安装阿里云短信失败

    安装步骤 请参考以下步骤,使用Composer安装依赖. 如果在您的系统上全局安装Composer,您可以在项目目录中运行以下内容,将 Alibaba Cloud Client for PHP 添加为 ...

  3. 章节十四、9-Actions类鼠标悬停、滚动条、拖拽页面上的元素

    一.鼠标悬停 1.在web网站中,有一些页面元素只需要我们将鼠标指针放在上面就会出现被隐藏的下拉框或者其它元素,在自动化的过程中我们使用Actions类对鼠标进行悬停操作. 2.案例演示 packag ...

  4. STP:生成树协议解决网络冗余问题

    STP(Spanning Tree Protocol)是生成树协议的英文缩写,可应用于计算机网络中树形拓扑结构建立,主要作用是防止网桥网络中的冗余链路形成环路工作.但某些特定因素会导致STP失败,要排 ...

  5. 渗透技巧——Windows系统的帐户隐藏

    渗透技巧——Windows系统的帐户隐藏 2017-11-28-00:08:55  0x01 帐户隐藏的方法 该方法在网上已有相关资料,本节只做简单复现 测试系统:·Win7 x86/WinXP 1. ...

  6. Linux下CPU利用率和负载的关系

    1.CPU利用率和负载 CPU利用率显示的是程序在运行期间实时占用的CPU百分比:cpu使用率反映的是当前cpu的繁忙程度,忽高忽低的原因在于占用cpu处理时间的进程可能处于io等待状态但却还未释放进 ...

  7. django项目中cxselect三级联动

    下载cxselect插件放在static文件夹下 前端引入 <script src="/static/js/jQuery-1.8.2.min.js"></scri ...

  8. Codeforces 1278F: Cards

    题目传送门:CF1278F. 题意简述: 有 \(n\) 个独立随机变量 \(x_i\),每个随机变量都有 \(p = 1/m\) 的概率取 \(1\),有 \((1-p)\) 的概率取 \(0\). ...

  9. idea快捷键的使用

    IntelliJ IDEA 问题解决:1.乱码,主要是快捷键的字样显示乱码 中文字体显示乱码? 2.菜单项等的字体太小,怎么能设置下? -------------------------------- ...

  10. 429 too many requests错误出现在wordpress后台更新及官网的5种解决方法

    从今年10月份开始wordpress服务经常出现429 too many requests错误,包括后台更新和访问wp官网,如下图所示,这是为什么呢?怎么处理呢?有大佬向官方论坛提问了,论坛主持人Ja ...