关于saga:基于Saga的分布式事务调度落地

全文4104字,预计浏览工夫12分钟。 一、背景随着微服务架构的衰亡,越来越多的公司都对本身的业务架构进行了微服务化。在微服务架构中,随着服务的逐步拆分,数据库的私有化已成为业界不成文的规定。因而随同着微服务拆分所带来的数据一致性的问题也愈发重大,如何解决该问题成为微服务架构落地过程中一个十分重要的问题。由此咱们引出分布式事务这一概念,用来解决上述背景带来的问题。在介绍分布式事务之前先让咱们回顾一下什么是事务。 二、事务事务是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向零碎提交,要么都执行、要么都不执行;事务具备ACID四大属性。 A(Atomic):原子性,形成事务的所有操作,要么都执行实现,要么全副不执行,不可能呈现局部胜利局部失败的状况。 C(Consistency):一致性,在事务执行前后,数据库的一致性束缚没有被毁坏。比方:数据库束缚账户余额必须大于0,所以设置为无符号数,在此束缚下,A只有100元,但要转出200元,此时数据库会放弃对束缚的一致性,触发执行回滚。 I(Isolation):隔离性,数据库中的事务个别都是并发的,隔离性是指并发的两个事务的执行互不烦扰,一个事务不能看到其余事务的运行过程的中间状态。通过配置事务隔离级别能够比防止脏读、反复读问题。 D(Durability):持久性,事务实现之后,该事务对数据的更改会长久到数据库,且不会被回滚。 理解完了事务的基本概念,接着让咱们看看什么是分布式事务。 三、分布式事务在分布式系统中,一个利用零碎拆分为独立部署的多个服务,因而须要服务与服务之间近程合作能力实现事务操作,这种分布式系统环境下由不同的服务之间通过网络近程合作实现的事务称为分布式事务。 从架构角度登程,分布式事务根本波及到两大类。 第一类:一个事务申请只波及单体服务,然而会操作多张数据库表,多个数据库表操作实现才示意最终实现。 第二类:一个事务波及多个服务,同时每个服务可能连贯着一个或者多个数据库,须要协同多个独立的服务拜访多个数据存储最终能力实现。 咱们常见的分布式事务来保证数据的一致性的办法分为两类:强一致性、最终一致性。 采纳强一致性的分布式事务的计划:通常采纳两段式提交协定2PC、三段式提交协定3PC。在微服务架构中,该种形式不太适宜,起因如下: 因为微服务间无奈间接进行数据拜访,微服务间相互调用通常通过RPC或Http API进行,所以曾经无奈应用TM对立治理微服务的RM 不同的微服务应用的数据源类型可能齐全不同,如果微服务应用了NoSQL之类不原生反对事务的数据库,业务的事务很难实现 即便微服务应用的数据源都反对事务,那么如果应用一个大事务将许多微服务的事务管理起来,这个大事务维持的工夫,将比本地事务长几个数量级。如此长时间的事务及跨服务的事务,将为产生很多锁及数据不可用,重大影响零碎性能 因而咱们个别采纳最终一致性来保障分布式系统的一致性。 常见的最终一致性的分布式事务解决方案有:事件告诉模式(本地异步事件服务模式、内部事件服务模式、MQ事务音讯模式、最大致力告诉模式)、事务弥补模式(Saga、TCC)。咱们通过调研上述解决方案总结出了以下个性: 上面咱们通过一个业务场景来理解一下什么是分布式事务,并且咱们翻新行业是用什么计划来解决数据一致性问题的。 四、业务场景假如有一个积分签到零碎,外面有一个签到兑换积分从而兑换物品的性能场景。 咱们要做的事件如下: 用户签到胜利、减少用户积分用户创立兑换物品订单,订单状态为已领取扣除用户积分扣减物品库存创立物流出库单针对以上的业务场景,咱们应该如何实现分布式事务来满足业务须要。上面次要讲事务弥补模式来实现分布式事务。 4.1 TCC 模式TCC是将整体业务逻辑的每一个事务提交分成了Try,Confirm,Cancel三个操作。 Try:实现业务的筹备工作Confirm:实现业务的提交工作Cancel:实现业务的回滚工作针对上述业务场景,依照业务背景要做的事件,实现一个TCC的分布式事务。 如果要实现一个TCC的分布式事务,首先要做的是理解业务的主流程以及各个接口提供的业务含意,不间接实现这个业务操作,而是实现一个Try(预处理)操作。 例如:给用户增加积分,咱们不间接增加积分,先预处理增加积分。扣物品库存咱们也不间接扣库存,先解冻将扣掉的库存。具体如下图 如果Try的逻辑都胜利,TCC开始执行业务的Confirm操作,实现整个事务流程。增加用户积分扣库存等操作。如果Try的逻辑局部胜利,有局部有问题,那么开始执行Cancel操作,撤销之前执行的所有操作。 总体对于TCC模式来说,要做一个分布式事务,业务中的一个接口须要实现3个逻辑的革新,Try-Confirm-Cancel。 服务调用链路顺次执行Try逻辑如果都失常的话,执行Confirm逻辑,实现整个业务流程如果局部服务的Try逻辑有问题,会执行Cancel逻辑,撤销之前执行的所有操作TCC模式对于业务的侵入性比拟强,流程比拟繁琐。各个业务侧都须要反对降级。对于咱们翻新行业来说,老本有点大,咱们抉择了另一种模式来实现分布式事务——Saga模式。 4.2 Saga模式Saga是一种纯业务弥补模式,其设计理念为,业务在调用的时候失常提交,当一个服务失败的时候,所有其依赖的上游服务都进行业务弥补操作。 Saga的基本概念: saga:长事务,long live transaction每个本地事务有对应的弥补事务执行状况失常:T1 -> T2 -> T3 -> … -> Tn异样:T1 -> T2 -> T3(异样)-> C3 -> C2 -> C1Saga两种复原策略: backward recovery,向后复原,弥补所有已实现的事务(回滚操作)forward recovery,向前复原,重试失败的事务,假如每个子事务最终都会胜利(重试操作)Saga事务的优缺点: 长处:模型比TCC更简略,只需业务方提供事务执行接口transaction、事务勾销弥补接口cancel毛病:间接执行事务执行接口transaction,可能有副作用(无论是否回滚,都会执行事务接口的逻辑,举例:A账号向B账号转账,T1事务对A用户扣款,T2事务对B用户加款,T1执行胜利,同时产生了一条扣款记录,T2执行失败须要回滚T2和T1,在这个过程中的副作用是A账号能感知到金额变动和扣款记录)针对上述业务背景,咱们对于业务侧只须要反对事务提交的接口( T )和失败弥补的接口( C )即可。具体流程如下图: ...

May 12, 2022 · 1 min · jiezi

2019-最新-ReactNativeTypeScriptReduxSaga-实践

最近研究 React Native、Redux Saga 以及 TypeScript 相关的内容,整理成了一个 React Native Template,可以直接使用下面的命令创建一个新的应用: react-native init MyApp --template=parcmg初始化完成之后,按下面的方式执行命令: cd MyAppnode setup.jsnpm installreact-native link react-native-gesture-handler完成之后,即可像往常一样开发了: react-native run-ios模板还在完善中,另外,相关技术要点与总结,稍后有时间再整理一下。

May 31, 2019 · 1 min · jiezi

从无到有-在create-react-app基础上接入react-router、redux-saga

搭建项目框架新建项目执行如下代码,用create-react-app来建立项目的基础框架,然后安装需要用到的依赖。$ npx create-react-app my-test-project$ cd my-test-project$ yarn add react-router-dom react-redux prop-types redux redux-saga$ yarn start完成后,应用启动在localhost的3000端口。接入react-router-domreact-router-dom其实就是react-router 4.0,与之前的3.0有什么区别呢?react-router被一分为三。react-router、react-router-dom和react-router-native。react-router实现了路由的核心的路由组件和函数。而react-router-dom和react-router-native则是基于react-router,提供了特定的环境的组件。react-router-dom依赖react-router,安装的时候,不用再显示的安装react-router, 如果你有机会去看react-router-dom的源码,就会发现里面有些组件都是从react-router中引入的。新建layout在/src下新建layout目录。为什么要新建layout目录,因为有可能我们会用到多个layout,layout是一个什么样的概念?例如这个应用需要提供一部分功能在微信使用。那么进入所有微信的相关界面下都要进行鉴权。没有鉴权信息就不允许访问,但是这个服务仍然有所有人都可以访问的路由。使用layout可以很好的帮我们解决这个问题。将所有的需要鉴权的页面放在例如WechatContainer下,只有在有微信相关鉴权的信息存在,才允许访问接下来的界面,否则,容器内甚至可以直接不渲染接下来的界面。在/src/layout下新建两个文件,分别是AppLayout.js、WechatLayout.js。AppLayout.js的代码如下。在这个layout中,首页就是单纯的一个路由,导向至首页。而接下来的/wechat则是把路由导向至了一个微信端专用的layout。import React, { Component } from ‘react’;import Home from ‘../routes/home’;import WechatLayout from ‘./WechatLayout’;import { Route, Switch } from ‘react-router-dom’;/** * 项目入口布局 * 在此处根据一级路由的不同进入不同的container * 每个container有自己不同的作用 * * 在react-router V4中,将原先统一在一处的路由分散到各个模块中,分散到各个模块当中 * 例如: WechatLayout的路由为/wechat 表示到该layout下的默认路径 /class AppLayout extends Component { constructor(props) { super(props); this.state = {}; } render() { return ( <div className=‘App’> <main> <Switch> <Route path=’/’ exact component={Home} /> <Route path=’/wechat’ component={WechatLayout} /> </Switch> </main> </div> ); }}export default AppLayout;WechatLayout.js的代码如下。在这个layout中,我们就可以对访问该路由的用户进行鉴权。如果没有权限,我们可以直接限制用户的访问,甚至直接不渲染render中的数据。例如,我们可以在componentWillMount中或者在render中,根据当前的state数据,对当前用户进行鉴权。如果没有权限,我们就可以将当前页面重定向到没有权限的提示界面。import React, { Component } from ‘react’;import Home from ‘../routes/wechat/home’;import { Route, Switch } from ‘react-router-dom’;import { connect } from ‘react-redux’;class WechatLayout extends Component { constructor(props) { super(props); this.state = {}; } componentWillMount() { } render() { const className = ‘Wechat-Layout’; return ( <div className={${className}}> <header> Our Manage Layout </header> <main> <Switch> <Route path={${this.props.match.path}/home} component={Home} /> </Switch> </main> </div> ); }}const mapStateToProps = state => ({ reducer: state.wechatLayout});export default connect(mapStateToProps)(WechatLayout);新建routes新建/src/routes/home/index.js,代码如下。import React, { Component } from ‘react’;import {Link} from ‘react-router-dom’;class Home extends Component { constructor(props) { super(props); this.state = {}; } render() { const className = ‘Home’; return ( <div className={${className}}> <h1>This is Home</h1> <div><Link to={’/wechat/home’}>Manage Home</Link></div> </div> ); }}export default Home;新建/src/routes/wechat/home/index.js, 代码如下。在代码中可以看到,触发reducer很简单,只需要调用dispatch方法即可。dispatch中的payload就是该请求所带的参数,该参数会传到saga中间层,去调用真正的后端请求。并在请求返回成功之后,调用put方法更新state。import React, { Component } from ‘react’;import {connect} from “react-redux”;class Home extends Component { constructor(props) { super(props); this.state = {}; } componentWillMount() { this.props.dispatch({ type: ‘WATCH_GET_PROJECT’, payload: { projectName: ’tap4fun’ } }); } render() { const className = ‘Wechat-Home’; return ( <div className={${className}}> <h1>Home</h1> <h2>The project name is : { this.props.reducer.projectName }</h2> </div> ); }}const mapStateToProps = state => ({ reducer: state.wechat});export default connect(mapStateToProps)(Home)新建container在/src下新建container,在container中新建文件AppContainer.js。我们整个react应用都装在这个容器里面。AppContainer.js的代码如下。而其中的Provider组件,将包裹我们应用的容器AppLayout包在其中,使得下面的所有子组件都可以拿到state。Provider接受store参数作为props,然后通过context往下传递。import React, { Component } from ‘react’;import PropTypes from ‘prop-types’;import { Provider } from ‘react-redux’;import { BrowserRouter as Router } from ‘react-router-dom’;import AppLayout from ‘../layout/AppLayout’;class AppContainer extends Component { constructor(props) { super(props); this.state = {}; } static propTypes = { store: PropTypes.object.isRequired }; render() { const { store } = this.props; return ( <Provider store={store}> <Router> <AppLayout /> </Router> </Provider> ); }}export default AppContainer;修改项目入口文件更新/src/index.js,代码如下。在此处会将create出来的store容器当作属性传入到Appcontainer中,作为我们应用的状态容器。import React from ‘react’;import ReactDOM from ‘react-dom’;import ‘./index.css’;import * as serviceWorker from ‘./serviceWorker’;import AppContainer from ‘./container/AppContainer’;import createStore from ‘./store/createStore’;const store = createStore();ReactDOM.render(<AppContainer store={store} />, document.getElementById(‘root’));// If you want your app to work offline and load faster, you can change// unregister() to register() below. Note this comes with some pitfalls.// Learn more about service workers: http://bit.ly/CRA-PWAserviceWorker.unregister();新建store新建/src/store/craeteStore.js,代码如下。通过以下的方式,我们可以给redux添加很多中间件,甚至是自己写的中间件。比如,我们可以自己实现一个日志中间件,然后添加到中间件数组middleWares中,在创建redux的store的时候,应用我们自己写的中间件。import { applyMiddleware, compose, createStore } from ‘redux’;import createSagaMiddleware from ‘redux-saga’;import rootReducer from ‘../reducers’;import rootSaga from ‘../saga’;export default function configureStore(preloadedState) { // 创建saga中间件 const sagaMiddleware = createSagaMiddleware(); const middleWares = [sagaMiddleware]; const middlewareEnhancer = applyMiddleware(…middleWares); const enhancers = [middlewareEnhancer]; const composedEnhancers = compose(…enhancers); // 创建存储容器 const store = createStore(rootReducer, preloadedState, composedEnhancers); sagaMiddleware.run(rootSaga); return store;}在这引入了redux-saga。我之前在使用redux的时候,几乎在每个模块都要写相应的action和reducer,然后在相应的模块文件中引入action的函数,然后在使用mapDispatchToProps将该函数注入到props中,在相应的函数中调用。并且,一个action不能复用,即使触发的是相同的reducer。这样就会出现很多重复性的代码,新增一个模块的工作也相对繁琐了很多。但是使用了redux-saga之后,只需要在reducer中定义好相应类型的操作和saga就可以了。不需要定义action的函数,不需要在文件中引入action中函数,甚至连mapDispatchToProps都不需要,直接使用this.props.dispatch({ ’type’: ‘WATCH_GET_PROJECT’ })就可以调用。而且,action可以复用。新建saga新建/src/saga/index.js,代码如下。import { put, takeEvery } from ‘redux-saga/effects’;import { delay } from ‘redux-saga’;export function fetchProject() { yield delay(1000); yield put({ type: ‘GET_PROJECT’ })}export default function * rootSaga() { yield takeEvery(‘WATCH_GET_PROJECT’, fetchProject);}新建reducer新建/src/reducers/wechat.js,代码如下。const initialState = { projectName: null};export default function counter(state = initialState, action) { let newState = state; switch (action.type) { case ‘GET_PROJECT’: newState.projectName = action.payload.projectName; break; default: break; } return {…newState}}新建/src/reducers/index.js,代码如下。import { combineReducers } from ‘redux’;import Wechat from ‘./wechat’;export default combineReducers({ wechat: Wechat});在这里我们使用了combineReducers。在之前的基于redux的应用程序中,常见的state结构就是一个简单的JavaScript对象。重新启动应用到此处,重新启动应用,就可以在http://localhost:3000/wechat/home下看到从reducer中取出的数据。在页面中,我们就可以通过代码this.props.dispatch的方式,来触发action。参考https://github.com/mrdulin/bl…https://cn.redux.js.org/docs/…项目源代码Github仓库 ...

December 17, 2018 · 3 min · jiezi