业务背景: 有一份数据,需要双写到两份存储( Mysql、Neo4j )
项目基于 springboot.我已经配置了 mysql 数据源的事务管理器,neo4j 没有配置。在不使用分布式事务的前提下,以下这种做法能否保证两份数据的一致性:
@Transactional
public void doService() {
// 第一步,插 mysql
insertMysql();
// 第二步,插 neo4j
insertNeo4j();
}
思路:
看起来,这样简单的做法能保证一致性,老哥们能指出这种做法有啥坑吗?( PS:假设 mysql、neo4j 都是一条操作语句)
1
qiyuey 2019-11-12 16:05:05 +08:00
放弃分布式事务,使用幂等重试
|
2
ayonel OP 这种做法也没有使用分布式事务。老哥看下这种 trick 做法可以保证一致性吗
|
3
reus 2019-11-12 16:25:45 +08:00
成功插入 neo4j 之后出现异常,mysql 回滚,而 neo4j 没有,不就不一致了?
|
4
cmingxu 2019-11-12 16:33:13 +08:00 1
感觉能完全说明白这个问题得一本厚书, 期待惊艳回答。
|
5
iIli1iIliIllLiL 2019-11-12 16:34:09 +08:00
3L+1
|
6
ayonel OP @reus @iIli1iIliIllLiL 成功插入 neo4j 之后都没有其他逻辑了,还能发生异常吗?
|
8
lhx2008 2019-11-12 16:42:18 +08:00 via Android
@ayonel 能,网络问题,如果插完 neo4j 的瞬间拔了网线,则无法确定 neo4j 是否插入成功。
|
9
lhx2008 2019-11-12 16:43:26 +08:00 via Android
如果都在一台机器上面其实问题不大,除非进程自己挂了
|
10
Yuicon 2019-11-12 16:44:24 +08:00
在回滚的时候添加逻辑删除 neo4j 多余数据
|
11
optional 2019-11-12 16:45:07 +08:00
关键是你下面还有代码,,如果你下面的代码抛异常了呢?
|
12
ysweics 2019-11-12 16:47:26 +08:00
用 MQ 做最终一致性吧
|
13
lhx2008 2019-11-12 16:54:04 +08:00 via Android
比较简单的方法就是异步同步,先把资料持久化一份( kafka 等),然后开一个后台程序去读,然后写到 neo4j,当然,代价是没有强一致性。不过强一致性不是那么容易搞的。
|
14
ebony0319 2019-11-12 16:55:27 +08:00 via Android
用最终一致性和事务补偿。
|
17
wangyzj 2019-11-12 17:04:17 +08:00
tcc
|
18
lhx2008 2019-11-12 17:06:14 +08:00 via Android
强一致一般可以用 2PC 两阶段提交,两个被调用方要保证落盘一份并能保证插入,然后再返回给调用方 2 个 OK,调用方再发提交给被调用方。如果调用方挂掉,也必须快速重启后跟上进度,否则就会卡住等人工处理。
|
19
ayonel OP |
21
reus 2019-11-12 17:08:23 +08:00
|
22
ayonel OP @reus mysql 先与 neo4j 操作的。如果 mysql 失败,应该直接回滚了,不会执行后面的 neo4j 逻辑。是这样吗?
|
23
lhx2008 2019-11-12 17:14:46 +08:00 via Android
如果 neo4j 插入到一半拋异常,那么必须确保回滚到之前的状态再重新抛出,如果 neo4j 挂掉的话,也要确保这一点。
|
24
lhx2008 2019-11-12 17:16:04 +08:00 via Android
还是我说的,由于网络原因,可能会出现插入成功,但是这边不知道的情况。
|
25
VensonEEE 2019-11-12 17:16:53 +08:00
两个不同的处理单元,理论上不可能强一致性。不过可以通过其他方式实现,就看你可以承受系统的性能损耗了。其实数据库存储也有很多步骤,没见过谁去纠结这个。
最简单的是包装这个单元,关闭事务,弄完之后做完整性检查,逻辑检查结果返回最终,否则删除。 |
26
lazyfighter 2019-11-12 17:19:20 +08:00
这应该拆分开吧,放在 MQ 异步处理更好,做好消息的重复消费,同时还需要考虑消息丢失的情况
|
27
reus 2019-11-12 17:19:39 +08:00 1
|
28
KentY 2019-11-12 17:25:01 +08:00 4
假设
1.你的"一份数据"就是单一的一条记录 2.你的这个 method 就这两行 3. 所有网络不会断, 机器不死机 一样会有问题. 你用了 @Transactional, 也就是说 mysql 那个 insert 是有 transaction scope 的, 但是 transcation commit 会在整个 method 运行完发生. 但是 insertMysql()没 exception 不代表就会成功 commit, 比如当 flush()后有 key 的冲突, 或者 commit 的时候有 lock 等等导致 commit 失败. 可是你的 neoinsert 不在这个 txn 里, 已经"committed", 这样你就有了 inconsistent data. 如果你以"几率很小"来判断, 就没必要考虑这些了, 因为 exception 之所以叫 exception 就是因为几率小, "异常"么. 如果都是"常态"就没必要做 exception handling 了. |
29
wangyzj 2019-11-12 17:25:30 +08:00
先做好幂等的 api
然后 delete api 前面加上一个 mq post 之前实在不行加个用户级别 nx 锁 |
30
1ffree 2019-11-12 18:06:06 +08:00
neo4j 成功,mysql 提交失败 楼上讲的很清楚了
加一点 通常事务内部不会加其他的远程调用( neo4j) 如果通信超时 占用 mysql 连接池 涉及的 mysql 操作不可用 可能把整个应用拖垮 |
35
Raymon111111 2019-11-12 19:29:35 +08:00
分布式事务有个很大的毛病是性能会有瓶颈
如果业务可接受, 用 MQ 保证最终一致会好一点 |
36
optional 2019-11-12 19:36:34 +08:00 via Android
@ayonel 下面没代码了。。。。没有讨论意义。
如果你确定下面没有了,最好也来一个 requires_new |
37
wysnylc 2019-11-12 20:48:27 +08:00
别想太多,强一致性做不了的
目前的分布式事务解决方案都在出现异常时回滚保证重复执行的幂等 你要是能解决,可以开讲座,不开玩笑 |
38
ayonel OP @wysnylc 只要数据源支持 XA 协议,我理解可以做到强一致吧。不过对于我的业务场景,最终一致也能接受。我关注的是如何『最简单地』以及『尽可能地』保障一致性
|
39
codeyung 2019-11-12 21:26:35 +08:00
网络中断,机器挂了,Neo4j 写成成功了你抛异常 mysql 回滚数据不一致
如果业务可以接收像楼上讲的用 MQ。 都是 MySQL 还好 ebay 之前有一套解决方案。 如果核心业务就需要 做 TCC 自己写业务验证。 MQ 做最终一致性能高,有些假提交对连接性能压力交大。 还有你这样在 mysql 的事务里调用 Neo4j,如果 Neo4j 延迟交大 事务提交缓慢 连接池打满啥的 都有可能 有过教训,LZ 如果是小业务那当然能用就行 但是量上来了可能就因为你一个这个操作 tps 降低吞吐都低了 |
40
crclz 2019-11-12 21:27:02 +08:00
普通解决方案:
表的字段设置一个 UpdatedAt,一个 CreatedAt。写一个脚本,定期( maybe 0.1s )将新插入的记录或者修改的记录刷写到在 neo4j 中。同时在 neo4j 中记录这张表上次刷到哪个时间戳了,下次就从这个开始。注意时间戳的精度问题:java,c#的精度会和数据库中的不一样,会出大问题。所以就应该用 int64 存 UtcTicks。 进阶的: 这其实就是一主多从中的复制。看看有没有一个实用性广的解决方案,避免自己写过多的复制逻辑。 最高级的: 采用 CQRS+事件溯源。业务通过事件的形式写入(持久化的)消息队列。需要单独的程序根据事件定期增量式刷新物化视图(主要的)、更新某些(可能更加 denormalized 的)用于加速查询的数据库(例如 redis,neo4j )。 缺点:实现复杂,并且业务多多少少需要一些强一致性来简化开发。 |
41
codeyung 2019-11-12 21:30:19 +08:00
不是核心业务就 mq 异步 或这半异步 自己开个线程池写顺便发个 mq 写 做个防重 核心你就要做最终一致 用代码或者组件写你核心逻辑的 然后做业务逻辑的验证 Confirm 和 如果业务失败的 Cancel 一半情况就是自己写验证 看看 MQ 事务 类似的道理
|
42
codeyung 2019-11-12 21:32:51 +08:00
强一致性能 emm
|
44
iIli1iIliIllLiL 2019-11-13 14:49:12 +08:00
@ayonel 会,只要你是双写,就会有这个问题,相同的场景还有缓存数据库双写一致性的 wen ti,简单来说你这个就是分布式事务的问题。
|
45
shinyzhu 2022-02-07 16:15:32 +08:00
Neo4j 支持 ACID ,而 MySQL 是另外一个数据库产品。所以?为什么要这样一致?
|