node mysql分布式_Nodejs 分布式事务

本文探讨了Node.js在事务处理上的挑战,包括异步操作带来的困难和解决方案,如Promise和async/await。同时,文章讨论了分布式事务的CAP理论,介绍了两阶段提交、本地消息表和回滚接口等策略。通过一个抢购系统的例子,阐述了如何在不同并发量下采用Redis、MQ和存储过程来确保数据一致性。最后,提出了在系统出现异常时,如何通过时间差和人工修复来保证最终一致性。
摘要由CSDN通过智能技术生成

事务是恢复和并发控制的基本单位,保证 ACID:原子性、一致性、隔离性、持久性。

对于全是异步的 Nodejs 而言, 并不适合做事务操作:

代码书写上:

try ... catch ... 是写给人看的,但是属于同步方法,局限性很大。

callback 简直是噩梦。

Promise.then(...).catch(...) 相对而言好一点。

ES7 的 async ... await ... 比较清爽,使用 Babel 编译,这是笔者目前找到的最人类的方式,但是和原来的 Promise 混合使用的时候有时候会出问题,因为编译之后的代码没法看,最后还得重构回 Promise。

异步:

异步导致有可能有意料之外的 uncaughtExceptionError 。

对于 JAVA/C++ 这样语言,出错能直接转到 catch 中,但是 Node 不是,uncaughtExceptionError 将直接导致处理链断掉,你只能通过其他方式保证数据一致性。

虽然 Node 做事务相当非人类,但是考虑开发效率 / 成本,使用 Node 进行开发并不比换语言开差,毕竟事务只有核心业务需要用到。

单点

单机的事务相当容易保证,特别在依赖 MySQL 或者其他关系数据库时。

Nodejs 有 ORM (如 Sequelize ) 支持事务,也可以直接使用 PROCEDURE/FUNCTION。

两者各有优势:

ORM 适合复杂逻辑的事务;

存储过程可以有效减少 IO 次数,防止使用 ORM 时回滚失败。

实际开发过程中可以将两者结合起来一起使用,使用 ORM 完成逻辑,使用存储过程减少 IO 次数。

1460000007484458?w=660&h=360

分布式

对于分布式系统,相信很多人都知道 CAP 理论,即任何一个分布式系统无法同时满足:

Consistency (一致性)

Availability (可用性)

Partition tolerance (分区容错性)

但是实际上 Consistency 是任何一个系统都不可能放弃的,分布式事务亦是为了保证数据一致性,有时候为了妥协另外两个特性,会放弃强一致性,保证最终一致性。

解决方式

目前业界有很多解决分布式事务的方案,根据对数据一致性的强弱要求,可以选择不同的方案,但是解决思路大致如下:

两阶段提交

如 XA 协议(TM(事务管理器)和RM(资源管理器)之间的接口)。

假设有 A、B、C 三个操作,第一阶段,等待 A B C 均就绪,第二阶段,提交 A B C;如果第一阶段 A 失败了,则第二阶段回滚 B C。

1460000007484459?w=994&h=442

本地事务

使用本地消息表,将远程事务拆分成一个个本地事务,写入本地表中,然后 定时 / 使用 MQ 通知事务方。

两者各有利弊,定时扫描可能大部分时候都在做无用功,而只使用 MQ 可能会有失败 / 多次消费的问题。

使用回滚接口

如 A B 两个接口,串行处理,B 失败了回滚 A ,但是回滚也可能失败,所以也需要使用本地事务表 / MQ。

1460000007484460?w=668&h=394

使用 Node 开发,1 比较重型,不适合;2 和 3 是比较好的选择:

选择一款可靠的 MQ 服务(单次消费 / 失败重试);

拆分本地事务;

不能拆分的事务,保证回滚。

举个栗子

做一个抢购系统,用户使用虚拟币进行抢购,虚拟币是另外一套系统。为了考虑到公平,每个用户还可能要限制购买上限。

这样用户一次抢购的完整流程如下:

检查购买上限

检查总数

扣除虚拟币

写入数据库

需要事务保证的地方就是 3 和 4,3 是远程事务,4 是本地事务,此栗子中必然是串行操作,3 在前,4 在后。

0x0001

这个时候流量很少,并发不高,将 3 和 4 作为一个事务,保证一起成功,而失败一起回滚。

事务 4 即使使用 ORM 完成,也能完成功能,这个时候系统能很好的工作。

0x0010

流量上升中,抢购的商品变多,并发也变大,这个时候,考虑使用 Redis 来提高性能了(牺牲强一致性):

将 购买上限 与 总数 写入 Redis,在压力转嫁到数据库之前就挡掉,由于 Redis 的强大性能,可以假设 Redis 等同于内存操作,做好回滚就可以了。

同时可以将事务 4 重构成 PROCEDURE 防止 ORM 可能回滚失败。

0x0011

流量大到数据库扛不住了,加入 MQ 服务:

1460000007484461?w=1296&h=598

使用 Redis 抗住流量,使用 MQ 抗住压力,使用 PROCEDURE 降低 IO 。

0x0011 看起来像一个可靠的系统了,但是还有一个隐患: uncaughtExceptionError 或者 程序宕掉了,这个会影响最终一致性,导致 Redis 数据与 Database 中的数据在抢购临界结束的时候不一致。

0x0100

增加最终一致性保证。

抢购的栗子有个很特别的地方,就是 total limit ,达到总数上限之后,就只有 MQ 中的部分需要处理了,因此可以很巧妙的利用时间差,即考虑在达到上限之后,取一次数据库快照,延迟一段时间之后,再对比一次数据库,判断是数据不一致还是正常逻辑。

1460000007484462?w=650&h=570

这是一个投机取巧的处理方式,一定程度上可以保证最终一致性。当然,还是人最靠谱了,程序搞不定,人工修复嘛,ORZ~。

终语

分布式事务是一个很大的话题,依据业务量大小可以给出很多实现。

Nodejs 做分布式事务勉勉强强,异步里面的雷很多,不过依赖良好的设计和逻辑一样可以实现。

Node.js 的 MySQL 分表分库数据访问中间件,实现MySQL数据的分布式集群储存管理。在处理海量数据、高并发访问时,获得更加优越的性能及横向扩展能力。它包含以下主要特性: 可伸缩、高扩展的架构 自动路由分库,维护数据库连接池 支持数据表的“横向”和“纵向”分表 支持“一主多从”式读写分离 分布式并行处理,成倍提升性能 对应用层隐藏数据来源及技术细节 拥有以上特点意味着,可随时通过增加普通级别数据库服务器的方式,方便地扩展整体系统性能,而无需修改业务层架构和代码。理论上TribeDB的扩展能力上线在于主库单表插入性能和主从数据同步开销。通过合理设计“横向”和“纵向”分表和数据切分粒度,可轻松应对上亿级别的数据量和访问请求。 快速上手: var tribe = require('tribedb'); //载入配置文件,sync选项为true 表示同步读取解析配置文件 tribe.configure('/path/to/tribe.conf',{sync:true}); //通过数据库表名建立查询请求 var db = tribe.createQuery('my_table'); //插入封装 db.data({title:'标题'}).insert(function(err, data){   console.log(err);   console.log(data); }); //查询封装 db.where('title','标题').order_by('time','DESC').limit(1).select(function(err, data){   console.log(err);   console.log(data); }); //不使用封装的操作,直接执行sql tribe.query('SELECT * FROM user_0 WHERE id=1 LIMIT 1',function(err, data){   console.log(err);   console.log(data); }); TribeDB 通过全局唯一的表名,自动连接对应的数据库,并通过分表配置,将操作映射到涉及的分表,同时完成读写分离。 一切都由 TribeDB 自动完成,业务层不必关心数据的位置。当数据库负载过高需要添加服务器时,只需简单修改配置文件而不必修改业务代码,甚至将整个架构推倒重来。继续阅读文档详细了解如何使用。 标签:TribeDB
好的,你的第一个问题是关于 node.js 和 MySQL 插入语句的语法错误提示,是吗? 首先,你需要确保你的 MySQL 语句的语法是正确的。MySQL 插入语句的基本语法如下: ``` INSERT INTO table_name (column1, column2, column3, ...) VALUES (value1, value2, value3, ...); ``` 其中,`table_name` 是你要插入数据的表的名称,`column1, column2, column3` 是你要插入的列的名称,`value1, value2, value3` 是你要插入的值。 在 node.js 中,你可以使用 `mysql` 模块来连接和操作 MySQL 数据库。下面是一个示例代码,演示如何使用 `mysql` 模块来插入数据到 MySQL 数据库中: ```javascript const mysql = require('mysql'); const connection = mysql.createConnection({ host: 'localhost', user: 'root', password: 'password', database: 'mydatabase' }); connection.connect((err) => { if (err) throw err; console.log('Connected!'); const sql = "INSERT INTO customers (name, address) VALUES ('John Doe', 'Highway 71')"; connection.query(sql, (err, result) => { if (err) throw err; console.log('1 record inserted'); connection.end(); }); }); ``` 在上面的代码中,我们创建了一个 `mysql` 连接对象 `connection`,并在连接成功后,使用 `connection.query()` 方法来执行 MySQL 插入语句。 如果你使用类似上面的代码来执行 MySQL 插入语句,但是仍然遇到语法错误提示,那么你需要检查你的 MySQL 语句是否正确。你可以尝试在 MySQL 命令行中执行你的 MySQL 语句,以确保它没有语法错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值