Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pack问题汇总反馈 #563

Open
loveoobaby opened this issue Sep 24, 2019 · 19 comments
Open

pack问题汇总反馈 #563

loveoobaby opened this issue Sep 24, 2019 · 19 comments

Comments

@loveoobaby
Copy link
Contributor

大家好!本人最近做分布式事务服务框架,刚开始想直接将pack拿过来使用,但我们发现pack的问题太多了。逼不得已将pack的核心代码基本全部重写,但保持业务逻辑与pack一致。受制于公司政策,代码不方便公开。现将我们遇到的问题进行反馈:

  1. 服务发现机制不健全,如果新加入alpha,客户端无法感知;

  2. SQL代码复杂。这是由于数据库设计表不合理造成的。saga只有一个表,tcc有三个表,其实两张表最合理,一张表存事务的整体状态,一张表存事务的事件。这样可以使SQL写的很简单。

  3. 回滚逻辑复杂,回滚速度慢,如果出现要回滚1000条,立即卡死。建议可以引入状态机来描述分布式事务的状态,回滚逻辑就不需要那么复杂了,代码只需一百多行。下面是我们设计的SAGA状态机,TCC状态机会更复杂一些:
    image

  4. gRPC性能较低:gRPC同步模型性能很差,但受制于spring线程模型,只能采用同步模型。
    rpc OnTxEvent (GrpcTxEvent) returns (GrpcAck) {}
    我们是直接用netty手撸了定制的RPC框架,即使是在客户端阻塞的情况下,qps也比gRPC双工异步快。

  5. mysql性能较慢:其实这种中间件对性能要求是很高的,一个RPC请求最好在5ms内返回,内网环境下网络需消耗2ms,请求处理要在3ms内返回。使用mysql在高负载下经常超时,目前我们的处理方式是异步批量入库,但这种情况下客户端需将事件发到固定的alpha,以防止事件顺序不对,同时要处理关闭server时清空入库队列;

  6. jpa有些不好用:我们在做批量入库时,使用了saveAll方法,但会丢数据,查询时缓存也有影响,查这个事务的数据能出来一条别的事务的数据。我们团队没人会jpa,果断换成mybatis;

  7. RPC次数较多:每个分支有两次请求,加上开启与提交,如果有n个分支,SAGA RPC次数是2n+2,TCC是3n+2。我们觉得实在太多了,可以砍掉一部分。我们假定大部分情况下分布式事务是成功的,如果成功率太低,这个接口没有意义。所以,对于分支事务开始会发RPC请求,但结束时不会,除非发生异常会进行RPC请求。这样一个事务正常情况下,SAGA变为n+2,TCC变为2n+2;

经过开发分布式事务,我觉得这玩意就是鸡肋,能不用就不要用!我还是更推荐AP+最终一致性。使用这种框架增加了整个提供的复杂度、耦合性,如果alpha宕机会导致整个系统不可用,而且接口都要求幂等,对普通开发人员来说很容易生产bug。

最后,感谢各位开发者贡献这么好的框架,使本人学会了怎么处理这类问题。非常感谢!

@coolbeevip
Copy link
Member

Pack 0.5.0 版本已经基于 Akka Actor 重构了 Saga 部分

https://github.com/apache/servicecomb-pack/blob/master/docs/fsm/fsm_manual_zh.md

@WillemJiang
Copy link
Member

很高兴能看到你的反馈,欢迎加入Pack的开发队伍,这样可以把你的优化经验向社区分享。

不知道你之前是基于那个版本的pack来重写的,Omega自动进行Alpha服务发现的机制我们是在0.3.0的时候就提供了, 不知道你提到的第一问题具体是如何解决的。

在0.5.0里面,我们引入了Actor来作为来简化SQL以及回滚逻辑,同时执行事件存储过程中也会采用批处理的方式来进一步提升系统的执行效率。

感谢你提供了有关性能优化的方法,我们再后续的版本中会参考这部分的思路进行优化。

@WillemJiang
Copy link
Member

@loveoobaby 可否将你的联系方式 发送到我的邮箱 willem.jiang AT gmail 中,希望能跟你有深入的探讨。

@loveoobaby
Copy link
Contributor Author

Pack 0.5.0 版本已经基于 Akka Actor 重构了 Saga 部分

https://github.com/apache/servicecomb-pack/blob/master/docs/fsm/fsm_manual_zh.md

我们基于0.4,6月份做完的,没有关注最近代码

@loveoobaby
Copy link
Contributor Author

很高兴能看到你的反馈,欢迎加入Pack的开发队伍,这样可以把你的优化经验向社区分享。

不知道你之前是基于那个版本的pack来重写的,Omega自动进行Alpha服务发现的机制我们是在0.3.0的时候就提供了, 不知道你提到的第一问题具体是如何解决的。

在0.5.0里面,我们引入了Actor来作为来简化SQL以及回滚逻辑,同时执行事件存储过程中也会采用批处理的方式来进一步提升系统的执行效率。

感谢你提供了有关性能优化的方法,我们再后续的版本中会参考这部分的思路进行优化。

第一个问题我们的解决方式比较简单。我们起了一个定时任务,我们只用Eureka,从其DiscoveryClient缓存中可找到所有alpha服务,然后Omega保存也保存了所有alpha,两者进行对比,如果是新加的实例会创建新的连接。如果服务下线或网络异常,Omega会报错尝试重连,超过一定的时间剔除该服务。最终,Omega与alpha可以自由的启停。

@coolbeevip
Copy link
Member

7. RPC次数较多:每个分支有两次请求,加上开启与提交,如果有n个分支,SAGA RPC次数是2n+2,TCC是3n+2。我们觉得实在太多了,可以砍掉一部分。我们假定大部分情况下分布式事务是成功的,如果成功率太低,这个接口没有意义。所以,对于分支事务开始会发RPC请求,但结束时不会,除非发生异常会进行RPC请求。这样一个事务正常情况下,SAGA变为n+2,TCC变为2n+2;

无论如何,这个想法很独特(如果是让我选择只能发送一个消息,那我可能会选择只发送子事务的结束消息),但是子事务只发送一个消息会存在一些缺陷

  1. 无法确定子事务是否已经执行完毕,如果靠判断当收到一个子事务开始事件就意味之前的子事务结束,那么我无法扩展子事务并行的场景
  2. 当无法判断一个子事务是否结束就进行补偿,这可能会导致不可预知的问题发生

关于 RPC 效率问题,0.5.0版本使用状态机,Alpha 收到 RPC 消息后不会操作数据库,所以吞吐率会提高很多。

@loveoobaby
Copy link
Contributor Author

  1. RPC次数较多:每个分支有两次请求,加上开启与提交,如果有n个分支,SAGA RPC次数是2n+2,TCC是3n+2。我们觉得实在太多了,可以砍掉一部分。我们假定大部分情况下分布式事务是成功的,如果成功率太低,这个接口没有意义。所以,对于分支事务开始会发RPC请求,但结束时不会,除非发生异常会进行RPC请求。这样一个事务正常情况下,SAGA变为n+2,TCC变为2n+2;

无论如何,这个想法很独特(如果是让我选择只能发送一个消息,那我可能会选择只发送子事务的结束消息),但是子事务只发送一个消息会存在一些缺陷

  1. 无法确定子事务是否已经执行完毕,如果靠判断当收到一个子事务开始事件就意味之前的子事务结束,那么我无法扩展子事务并行的场景
  2. 当无法判断一个子事务是否结束就进行补偿,这可能会导致不可预知的问题发生

关于 RPC 效率问题,0.5.0版本使用状态机,Alpha 收到 RPC 消息后不会操作数据库,所以吞吐率会提高很多。

第一个问题:其实我们假设子事务是串行执行的。
第二个问题:依靠补偿接口有处理无效补偿事件的能力。执行分支事务与RPC发送结束事件本来就无法保证原子性。我们遇到了这样的场景:分支事务执行完成,数据库已经提交,这时宕机,导致alpha没有收到结束事件,原先的逻辑是不会回滚当前分支的,重启业务服务也不会回滚。我们改成回滚当前分支,但如果分支没有完成,已经自动回滚了,这时要求补偿接口不要产生副作用。

用这种框架写业务要求太多,怎么做都有坑。怎么样想办法降低门槛才行,Seata可自动补偿,提供了部分隔离性,可参考一下。

@loveoobaby
Copy link
Contributor Author

用这种框架写业务要求太多,怎么做都有坑。怎么样想办法降低门槛才行,Seata可自动补偿,提供了部分隔离性,可参考一下。

Seata的自动补偿建立在解析SQL上,也是非常棒的想法

@coolbeevip
Copy link
Member

  1. RPC次数较多:每个分支有两次请求,加上开启与提交,如果有n个分支,SAGA RPC次数是2n+2,TCC是3n+2。我们觉得实在太多了,可以砍掉一部分。我们假定大部分情况下分布式事务是成功的,如果成功率太低,这个接口没有意义。所以,对于分支事务开始会发RPC请求,但结束时不会,除非发生异常会进行RPC请求。这样一个事务正常情况下,SAGA变为n+2,TCC变为2n+2;

无论如何,这个想法很独特(如果是让我选择只能发送一个消息,那我可能会选择只发送子事务的结束消息),但是子事务只发送一个消息会存在一些缺陷

  1. 无法确定子事务是否已经执行完毕,如果靠判断当收到一个子事务开始事件就意味之前的子事务结束,那么我无法扩展子事务并行的场景
  2. 当无法判断一个子事务是否结束就进行补偿,这可能会导致不可预知的问题发生

关于 RPC 效率问题,0.5.0版本使用状态机,Alpha 收到 RPC 消息后不会操作数据库,所以吞吐率会提高很多。

第一个问题:其实我们假设子事务是串行执行的。
第二个问题:依靠补偿接口有处理无效补偿事件的能力。执行分支事务与RPC发送结束事件本来就无法保证原子性。我们遇到了这样的场景:分支事务执行完成,数据库已经提交,这时宕机,导致alpha没有收到结束事件,原先的逻辑是不会回滚当前分支的,重启业务服务也不会回滚。我们改成回滚当前分支,但如果分支没有完成,已经自动回滚了,这时要求补偿接口不要产生副作用。

用这种框架写业务要求太多,怎么做都有坑。怎么样想办法降低门槛才行,Seata可自动补偿,提供了部分隔离性,可参考一下。

是的,第一原则就是避免分布式事务,可是我的场景面临较多的第三方服务的整合以及一些非关系数据库的业务系统,所以 Seata 的 SQL 模式不太适合我。

@WillemJiang
Copy link
Member

很高兴能看到你的反馈,欢迎加入Pack的开发队伍,这样可以把你的优化经验向社区分享。
不知道你之前是基于那个版本的pack来重写的,Omega自动进行Alpha服务发现的机制我们是在0.3.0的时候就提供了, 不知道你提到的第一问题具体是如何解决的。
在0.5.0里面,我们引入了Actor来作为来简化SQL以及回滚逻辑,同时执行事件存储过程中也会采用批处理的方式来进一步提升系统的执行效率。
感谢你提供了有关性能优化的方法,我们再后续的版本中会参考这部分的思路进行优化。

第一个问题我们的解决方式比较简单。我们起了一个定时任务,我们只用Eureka,从其DiscoveryClient缓存中可找到所有alpha服务,然后Omega保存也保存了所有alpha,两者进行对比,如果是新加的实例会创建新的连接。如果服务下线或网络异常,Omega会报错尝试重连,超过一定的时间剔除该服务。最终,Omega与alpha可以自由的启停。

我觉得咱们可以把这部分的内容在新的版本中实现, 我刚刚建了一个JIRA SCB-1496来跟这个问题, 欢迎提供PR。

@zhfeng
Copy link
Contributor

zhfeng commented Sep 25, 2019

非常高兴能看到您的反馈。Pack做为一个通用的分布式事务解决框架和方案,还有很多地方可以提高和改进。欢迎加入我们的社区!

@zhfeng
Copy link
Contributor

zhfeng commented Sep 25, 2019

RPC次数较多:每个分支有两次请求,加上开启与提交,如果有n个分支,SAGA RPC次数是2n+2,TCC是3n+2。我们觉得实在太多了,可以砍掉一部分。我们假定大部分情况下分布式事务是成功的,如果成功率太低,这个接口没有意义。所以,对于分支事务开始会发RPC请求,但结束时不会,除非发生异常会进行RPC请求。这样一个事务正常情况下,SAGA变为n+2,TCC变为2n+2;

如果不发送结束请求的话,如何判断事务是正常结束还是已经超时?在超时的情况下,现在的框架是要进行回滚的。

@loveoobaby
Copy link
Contributor Author

RPC次数较多:每个分支有两次请求,加上开启与提交,如果有n个分支,SAGA RPC次数是2n+2,TCC是3n+2。我们觉得实在太多了,可以砍掉一部分。我们假定大部分情况下分布式事务是成功的,如果成功率太低,这个接口没有意义。所以,对于分支事务开始会发RPC请求,但结束时不会,除非发生异常会进行RPC请求。这样一个事务正常情况下,SAGA变为n+2,TCC变为2n+2;

如果不发送结束请求的话,如何判断事务是正常结束还是已经超时?在超时的情况下,现在的框架是要进行回滚的。

  1. 正常结束不用管
  2. 异常结束会发RPC请求,补偿会指令立即发出
  3. 超时是alpha控制的,最后一个分支会执行回滚,但不是马上发补偿指令,对于这种情况有可能最后一个分支仍在执行,我们默认30s后才会发补偿

@zhfeng
Copy link
Contributor

zhfeng commented Sep 25, 2019

@loveoobaby 这样的话,可以考虑给omega一个选项不发end_event。你可以提一个PR看看 ?

@loveoobaby
Copy link
Contributor Author

@loveoobaby 这样的话,可以考虑给omega一个选项不发end_event。你可以提一个PR看看 ?

这个改动omega比较简单,在注解加个字段,切面处理一下即可。改动更多的是alpha补偿逻辑。我觉的pack的补偿逻辑太复杂,基本被我删光了,表也改了,复杂的SQL全部没了,所以提PR可有些困难,改动可不小。

我的思路是这样的:

  1. 为了加强可靠性,补偿事件允许多次发送,所以补偿接口需要幂等;
  2. 既然补偿可以多次发送,那么补偿命令放入内存即可,不再需要写入command表;
  3. 一共需要两种表,一张表放全局事务的状态,每个事务一条记录,另一张表放事务具体的事件;这样的话可避免SQL
  4. 将事务的状态封装成状态机,对需要回滚的事务,找出其全部事件,输入状态机,从状态机中获取补偿指令list,按顺序依次回滚即可;

@loveoobaby
Copy link
Contributor Author

RPC次数较多:每个分支有两次请求,加上开启与提交,如果有n个分支,SAGA RPC次数是2n+2,TCC是3n+2。我们觉得实在太多了,可以砍掉一部分。我们假定大部分情况下分布式事务是成功的,如果成功率太低,这个接口没有意义。所以,对于分支事务开始会发RPC请求,但结束时不会,除非发生异常会进行RPC请求。这样一个事务正常情况下,SAGA变为n+2,TCC变为2n+2;

如果不发送结束请求的话,如何判断事务是正常结束还是已经超时?在超时的情况下,现在的框架是要进行回滚的。

砍RPC这是我参考蚂蚁金服TCC的做法

@loveoobaby
Copy link
Contributor Author

@loveoobaby 可否将你的联系方式 发送到我的邮箱 willem.jiang AT gmail 中,希望能跟你有深入的探讨。

您的邮箱对吗?willem.jiang@gmail? 我发不出去

@coolbeevip
Copy link
Member

@loveoobaby 可否将你的联系方式 发送到我的邮箱 willem.jiang AT gmail 中,希望能跟你有深入的探讨。

您的邮箱对吗?willem.jiang@gmail? 我发不出去
你少写了一个 .com , 应该是 @gmail.com

@WillemJiang
Copy link
Member

RPC次数较多:每个分支有两次请求,加上开启与提交,如果有n个分支,SAGA RPC次数是2n+2,TCC是3n+2。我们觉得实在太多了,可以砍掉一部分。我们假定大部分情况下分布式事务是成功的,如果成功率太低,这个接口没有意义。所以,对于分支事务开始会发RPC请求,但结束时不会,除非发生异常会进行RPC请求。这样一个事务正常情况下,SAGA变为n+2,TCC变为2n+2;

如果不发送结束请求的话,如何判断事务是正常结束还是已经超时?在超时的情况下,现在的框架是要进行回滚的。

  1. 正常结束不用管
  2. 异常结束会发RPC请求,补偿会指令立即发出
  3. 超时是alpha控制的,最后一个分支会执行回滚,但不是马上发补偿指令,对于这种情况有可能最后一个分支仍在执行,我们默认30s后才会发补偿

这部分有关操作协议的简化可以参考一下, RPC调用的问题在服务的发起方是能够知晓的, 但是服务发起方到事务协调器这块的网络有可能出现异常,(目前看来我们只能通过超时方式进行相关处理。)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants