在 ongodb 中实现强事务 - · pdf file分布式事务数据库 ! 二阶段提交 1....

Post on 03-Feb-2018

264 Views

Category:

Documents

6 Downloads

Preview:

Click to see full reader

TRANSCRIPT

在 ongoDB 中实现强事务

{ name: “TJ Tang/唐建法”,

title: “MongoDB 大中华区首席技术顾问”, des: “mongoing.com 中文社区发起人”,

email: “jianfa.tang@mongodb.com”, wechat:“tjtang826”

}

关于讲师

MongoDB 介绍

ACID事务

事务补偿设计模式

mongosaga – 事务补偿框架

关于今天的分享

MongoDB 介绍

     

600+  employees   MongoDB Inc. 原10gen公司 MongoDB开源代码贡献者

     

2,000+  customers  

     

13  offices  worldwide  

     

15,000,000+  Downloads  

RANK   DBMS          MODEL   SCORE   GROWTH  (20  MO)  

1.   Oracle   Rela+onal  DBMS   1,442   -­‐5%  

2.   MySQL   Rela+onal  DBMS   1,294   2%  

3.   Microso?  SQL  Server   Rela+onal  DBMS   1,131   -­‐10%  

4.   MongoDB   Document  Store   277   172%  

5.   PostgreSQL   Rela+onal  DBMS   273   40%  

6.   DB2   Rela+onal  DBMS   201   11%  

7.   Microso?  Access   Rela+onal  DBMS   146   -­‐26%  

8.   Cassandra   Wide  Column   107   87%  

9.   SQLite   Rela+onal  DBMS   105   19%  

Source: DB-engines database popularity rankings; May 2015

最流行、发展最快的分布式数据库

高性能,水平扩展

Always On 全球部署

灵活的模式 丰富的查询语句  

⼆二级索引  

事务支持,Join

企业工具集成

MongoDB:新一代架构NewSQL数据库

关系型 非关系型

NewSQL

高可用 Replica Set

水平扩展 Sharding

灵活动态 文档模型

MongoDB的特点

{ ! first_name: ‘Paul’,! surname: ‘Miller’,! city: ‘London’,! location: [45.123,47.232],! cars: [ ! { model: ‘Bentley’,! year: 1973,! value: 100000, … },! { model: ‘Rolls Royce’,! year: 1965,! value: 330000, … }! ]!}!

文档模型的优点

丰富的结构 JSON文档结构可以直接表达复杂对象结构 非常接近In Memory数据模型 动态的模式 可以随时增加、修改结构,无需数据迁移

快速开发 无需建模 程序接口自然无需ORM

10

高可用,自动故障切换

多数据中心支持,自动容灾

滚动维护无下线

读写分离

多变易用复制集

自动均衡

自动路由分发请求

运维透明化, 底层部署不影响应用

横向扩展支持海量的数据及并发

社区版 MongoDB Community 企业版 MongoDB Enterprise Advanced

MongoDB Core Server ✔   ✔  

Replication and Sharding ✔   ✔  

Aggregation Framework ✔   ✔  

Security: Role-Based Access Control, PKI Certificates, SSL, Field-Level Redaction ✔   ✔

Advanced Security: LDAP Authentication, Kerberos Authentication, x.509 Certificates, Auditing

Automated Deploy, Configuration, Maintenance, Zero Downtime Upgrades ✔

Backup and Point-In-Time Recovery ✔

Monitoring and Alerting ✔

Encrypted & In-Memory Storage Engines ✔

MongoDB Compass ✔

MongoDB Connector for BI ✔

On-Demand Training ✔

Support SLA 1 hr

License Type AGPL Commercial

•  用户管理 •  电商、产品目录 •  游戏 •  实时分析 •  仓储管理 •  购物车 •  IM应用 •  广告 •  社交应用

MongoDB适用场景

•  账单查询 •  客户管理 •  风险分析 •  ECM •  参考数据 •  360度视图

•  内容管理系统CMS •  物联网 •  车联网 •  数据即服务 •  通用数据平台 •  地理位置系统 •  高速缓存

RDBMS能做的,MongoDB基本都能做!

RDBMS不能做的,MongoDB还是能做!

可是,听说MongoDB不支持强事务?

!

常见数据库的事务支持

数据库 事务支持程度

Oracle DB2 SQL Server MySQL PostgreSQL

支持ACID

MongoDB Cassandra Couchbase

支持单文档ACID

关于ACID事务

ACID

Consistency 一致性

Atomicity 原子性

Durability 持久性

Isolation 隔离性 数据持久存在, 哪怕宕机后

All or Nothing 要么全部提交, 要么回滚

在事务范围内看到的数据前后一致,并且符合既定数据库规则如外键

你对数据的操作对其他用户不可见,直到提交为止

!

ACID简化编程

@Transactional!public void transfer(from, to, amount) {! try{!

!begin()!!debit(from, amount);!!credit(to, amount);!!commit();!

}! catch(Exception e){!

!rollback();! }!}!

public boolean transfer(from, to, amount) { double origBalance = readBalance(from);! double origBalance2 = readBalance(to);! boolean creditOk = false, debitOk=false,! try{!

!synchronized(from) {!! synchronized(to) {!! debit(from, amount); ! credit(to, amount);!! double newBalance = readBalance(from);

if(newBalance != origBalance – amount)!! ! throw new Exception(“wrong”);!! }!!}!

}! catch(Exception e){!

! // if debit has been done! undoDebit(from, amount);!

! return false;! }! return true; !!}!

经典案例:小明转账100元给小红

数据库实现事务

应用实现事务

!

MongoDB的单文档事务性

场景 事务性

db.employee.update({ _id: 100}, {! $inc: { salary: 10000 },! $set: { level: 10 }! } !)!

支持单文档内原子性和隔离性: $inc 和 $set

db.employee.update({ level:9}, {! $inc: { salary: 10000 },! } !)!

更新多个文档时候不能保证全部更新或全部不更新,无回滚行为

db.employee.update(…) db.salary.update(…) !

不能保证全部更新或全部不更新,无回滚行为

为什么不支持Full ACID?

ACID 是为 单机设计的

POIN   POINT  分布式事务通常 需要二阶段提交

二阶段提交 性能差 问题多

如果不能提供 高性能ACID, 还不如先实现 更有意义的功能

目前还没有生产 级别的高性能

分布式事务数据库

!

二阶段提交

1. 基于锁机制,提交阶段用到资源都处于阻塞状态 2. TransactionManager 单点故障

Transac+on  Manager

Can commit?

Yes

commit

phase 1: prepare

Phase 2: commit

DB Queue

http://www.oracle.com/technetwork/products/clustering/overview/distributed-transactions-and-xa-163941.pdf

3. 无法保证节点之间强一致! T1: 01.000 DB和Queue同时commit T2: 01.050 Queue commit 完成 T3: 01.200 这时候去读DB&Queue? T4: 01.500 DB commit 完成(或者commit失败)

T2 T4 T1 T1

!

CAP 定理 – 2000年

Eric  Brewer  Professor,  UC  Berkeley    VP  Infrastructure,  Google

2000年版本

CAP 定理 – 2012年

数据允许暂时的不一致,只要最终一

操作对其他线程可见

强一致 vs 最终一致

Full ACID 强一致 非分布式 无大规模并发

Partial ACID 最终一致 分布式 大规模并发

强事务:你到底想要什么?

public void transfer(from, to, amount){!!debit(from, amount);!!credit(to, amount);!

}!

All or Nothing 原子性 全部提交或回滚

Saga事务:补偿机制

ATM 订单 跨行转账 电信扣费 . . .

•  多个相对独立的操作,各自提交

•  某个步骤出错时启用补偿机制,消除之前操作的效果

•  无全局事务锁,可实现分布式高并发

•  提供原子性的事务支持

Transaction Compensation

一个订单的例子

hRps://msdn.microso?.com/en-­‐us/library/dn589800.aspx

订单处理步骤: 1. 减库存 2. 保存订单 3. 扣款

               

               

补偿操作

最终状态 操作

库存减掉 订单保存 扣款成功

系统已经一致

库存已减 订单未保存

1.  恢复库存

2.  系统达成一致

库存已减 订单保存 但是扣款未成功

1.  恢复库存

2.  修改订单状态为取消

3.  系统达成一致

!

事务补偿伪代码

public boolean placeOrder(order) {!!!!boolean inventoryUpdated,orderSaved, paymentReceived;!

!!inventoryUpdated = subtractInventory(order.sku, order.quantity);!

!!if(inventoryUpdated){!! !orderSaved = saveOrder(order);!! !if(orderSaved)!! ! !paymentReceived = processPayment(order);!!}!

!!if(!paymentReceived){!! !if(orderSaved)!! ! !updateOrderStatus(order, "canceled");!! !if(inventoryUpdated)!! ! !addInventory(order.sku, order.quantity);!!}!!else if(!orderSaved){!! !if(inventoryUpdated)!! ! !addInventory(order.sku, order.quantity);!!}!

}!

订单处理三部曲:减库存,保存订单,订单支付

!

我明白了,可是这个代码有点不好看⋯

mongosaga

基于Spring Framework的补偿式事务组件

帮助你在MongoDB里面实现分布式事务

https://github.com/tjworks/mongosaga

工作原理– 正常模式

•  基于Spring AOP

•  Java Annotation

•  自动注入Proxy

•  记录事务范围内所有可补偿操作(enlist)

工作模式 – 补偿模式

•  Credit 步骤失败

•  确定需要补偿的操作 getCompensations()

•  自动调用补偿器 undoCredit()

如何使用 1-2-3

1. 下载mongosaga jar 文件或者直接在POM.xml里面用 maven dependency

<dependency> <groupId>com.mongoing.mongosaga</groupId> <artifactId>mongosaga</artifactId> <version>0.5 </version> </dependency>

2. 对需要事务的方法加一个 @Compensatable 标注 3. 对该事务内的每一个操作实现一个补偿器(方法)

演示步骤 1

Clone或者下载 mongosaga 项目(包含测试代码)

# git clone https://github.com/tjworks/mongosaga!

AccountManager.java    

transfer()

Account.java              debit()            debit_compensator()            credit()            credit_compensator()  

LoadTest.java    

testTransfer()

演示步骤 2

在test目录下定义AccountManager类,把需要在一个事务内完成的操作封装在一个方法里,使用@Compensatable 来标注该方法启用补偿事务 package com.mongoing.mongosaga;!!public class AccountManager {!! @autowired! Account account;!! @Compensatable !!

public void transfer(from, to, amount) {!!account.debit(from, amount);!!account.credit(to, amount);!

}!!}!

演示步骤 3

public class Account {! . . .! private DBCollection accounts;! ! @Compensate! public void debit(String from, double amount){! log.info("debit " + amount+" from account "+from);! accounts.update(new BasicDBObject("name", from), ! new BasicDBObject("$inc", new BasicDBObject("balance", -1* amount)));! }! public void debit_compensator(String from, double amount){! log.info(“compensating debit operation, adding " + amount+" back to "+from); ! accounts.update(new BasicDBObject("name", from), ! new BasicDBObject("$inc", new BasicDBObject("balance", amount)));! }! ! @Compensate! public void credit(String to, double amount){! log.info("credit "+ amount+" to "+to);! accounts.update(new BasicDBObject("name", to), ! new BasicDBObject("$inc", new BasicDBObject("balance", amount)));! }! public void credit_compensator(String to, double amount){! log.info(”compensating credit operation, subtract " + amount+" from "+to); ! accounts.update(new BasicDBObject("name", to), ! new BasicDBObject("$inc", new BasicDBObject("balance", -1* amount)));! } !!

增加一个Account类, 为每一个需要补偿的操作(方法)在同一个类内创建一个补偿器方法, 补偿器方法命名规则: 原方法名+ “_compensator”

补偿器

演示步骤 4

. . .! final class Worker implements Runnable {! int id;! public Worker(int id){ this.id=id; }! public void run(){! int amount = (int)(Math.random()*100)+1;! for(int k=0;k<1000;k++){! try{! counter[0]++;! accountManager.transfer("mona"+ id, "tj"+ id, amount); ! }! catch(Exception e){ counter[1]++; } ! } ! }! } ! int poolSize = 100;! ExecutorService executor = Executors.newFixedThreadPool(poolSize); ! for (int i = 0; i < poolSize; i++) { ! executor.execute(new Worker(i));! } !

写一个Junit测试来验证: •  创建100对用户,初始balance均为0,允许负值 •  100个线程,每个线程执行1000次左右转账操作 •  5-10% 随机错误(引起补偿动作) •  检查最终结果(所有balance加起来应该为0)

演示步骤 5

MacBook-­‐Pro-­‐13:compensatable  tjworks$  mvn  clean  test  -­‐Dtest=LoadTest  -­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐    T  E  S  T  S  -­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐  Running  com.mongoing.mongosaga.LoadTest  224  [main]  INFO  org.springframework.beans.factory.xml.XmlBeanDefini+onReader  -­‐  Loading  XML  bean  defini+ons  from  class  path  resource  [applica+onContext.xml]  …  …  ####  Time  used:  25s  ####  Total  compensa+ons:  6994  out  of  total  op:  99788    Results  :    Tests  run:  2,  Failures:  0,  Errors:  0,  Skipped:  0    !

运行测试

演示步骤 6

>  db.accounts.aggregate({$group:{_id:"",  sum:  {$sum:  "$balance"}}})  {  "_id"  :  "",  "sum"  :  0  }  >  >  db.accounts.find().limit(10)  {  "_id"  :  ObjectId("57319bacc026280ce28867a3"),  "name"  :  "tj0",  "balance"  :  89000  }  {  "_id"  :  ObjectId("57319bacc026280ce28867a4"),  "name"  :  "mona0",  "balance"  :  -­‐89000  }  {  "_id"  :  ObjectId("57319bacc026280ce28867a5"),  "name"  :  "tj1",  "balance"  :  58000  }  {  "_id"  :  ObjectId("57319bacc026280ce28867a6"),  "name"  :  "mona1",  "balance"  :  -­‐58000  }  {  "_id"  :  ObjectId("57319bacc026280ce28867a7"),  "name"  :  "tj2",  "balance"  :  5000  }  {  "_id"  :  ObjectId("57319bacc026280ce28867a8"),  "name"  :  "mona2",  "balance"  :  -­‐5000  }  {  "_id"  :  ObjectId("57319bacc026280ce28867a9"),  "name"  :  "tj3",  "balance"  :  57000  }  {  "_id"  :  ObjectId("57319bacc026280ce28867aa"),  "name"  :  "mona3",  "balance"  :  -­‐57000  }  {  "_id"  :  ObjectId("57319bacc026280ce28867ab"),  "name"  :  "tj4",  "balance"  :  16000  }  {  "_id"  :  ObjectId("57319bacc026280ce28867ac"),  "name"  :  "mona4",  "balance"  :  -­‐16000  }  >    

验证结果

简易测试结果 - MongoDB

SUB POINT

ATM 订单 跨行转账 电信扣费 . . .

测试场景

•  一台 Macbook Pro •  8G RAM •  4 Core •  100线程并发执行,每个线程执行1000次左右转账 •  MongoDB 3.2.0 运行于本机,无任何优化 •  Spring + MongoDB Java Driver

测试结果

•  每秒 3990 次转账操作,7%的模拟出错/补偿操作

•  结果100% 准确

•  相当于每天3.5亿次 – 银联支付每天7亿业务

简易测试结果 - MySQL

SUB POINT

ATM 订单 跨行转账 电信扣费 . . .

测试场景

•  一台 Macbook Pro •  8G RAM •  4 Core •  100线程并发执行,每个线程执行1000次左右转账 •  MySQL 5.6 InnoDB •  Spring Transaction Management

测试结果

•  每秒 ~200 次转账操作

0  500  1000  1500  2000  2500  3000  3500  4000  4500  

Transfers/s  

MongoDB  

MySQL  

事务补偿设计

SUB POINT

ATM 订单 跨行转账 电信扣费 . . .

SUB POINT

注意事项 •  补偿!=回滚

•  回滚: 动作从未被提交过 •  补偿: 通过提交另一个操作来中和

前一个动作的效果

•  不是所有场景都适合事务补偿,如多进程同时更新一条记录,并且不是增加或减少的情况

•  尽量实现幂等性:失败时可以自动重试(功能正在实现中)

ATM 订单 跨行转账 电信扣费 . . .

场景举例:

订单处理

转账类业务

电信扣费

ERP进销存

. . .

补充说明

SUB POINT

ATM 订单 跨行转账 电信扣费 . . .

SUB POINT

注意事项 •  补偿!=回滚

•  回滚: 动作从未被提交过 •  补偿: 通过提交另一个操作来中和

前一个动作的效果

•  不是所有场景都适合事务补偿,如多进程同时更新一条记录,并且不是增加或减少的情况

•  尽量实现幂等性:失败时可以自动重试(功能正在实现中)

ATM 订单 跨行转账 电信扣费 . . .

关于mongosaga的使用,此PPT内所含信息不是最新的。 具体使用请参见github页面。 订单处理

电信扣费 https://github.com/tjworks/mongosaga

ERP进销存

. . .

MongoDB 官网 http://www.mognodb.com MongoDB 中文社区 http://www.mongoing.com

top related