求教,有状态的分布式系统应该如何设计

272 天前
 mawen0726

应对业务扩张,将服务拆分并支持横向扩展,现在拆了共 a,b,c 三个服务出来,然后这三个服务要设计成可以随时横向扩展。其中有如下调用链路

然后 c 是主要处理业务的服务,会上分布式锁,加上处理时间长,会后台执行并执行完通知上游来做其他业务操作,业务操作之后才能解锁。

因为一个业务只能在一个 c 中执行,所以解锁要找到对应的 c 。比如 a 执行业务分配到节点 1 的 c,后续交互都要找节点 1 的 cb是批量执行业务,这一批都要到同一个c中执行完。

目前设计方案是将 c的 IP 和业务 id 写到 redis ,然后通过 redis 确定业务在哪个 c 节点上面跑着。然后要在业务处理过程中,查看状态都通过 redis 查对应 ip 再去调用服务。

比如批量任务中,a 要确定哪个 b 节点承接了这个业务,b 节点确定哪个 c 节点正在处理这批次业务。

感觉这种设计不是很好,想请教下还有没有更优雅的做法

可能描述的有点复杂,大概意思是

怎么记录在多个节点时,知道这个任务在哪个节点上,并且应用之间相对不耦合

3270 次点击
所在节点    程序员
46 条回复
THESDZ
271 天前
1.状态带着走;
2.更进一步,状态单独存,状态唯一 id 带着走。
xiaogu2014
271 天前
mark2025
271 天前
分布锁? 这是给自己掘墓吧……
mark2025
271 天前
@mawen0726 锁是 C 加上的,为啥要用 A 去解锁而不是 C 自己解锁?
cheng6563
271 天前
“会后台执行并执行完通知上游来做其他业务操作,业务操作之后才能解锁。”

所以你的实际调用链应该是 a->b->c->b->a
huzhizhao
271 天前
消息队列最合适,特定的队列处理特定的数据就好了
pangzipp
271 天前
上一个分布式调度框架例如
例如 airflow
阿里云的 SchedulerX
sujin190
271 天前
@Plutooo #19 分布式锁底层都是加锁方生成一个只有自己指定的 ID 来防止他人异常解锁,那换句话说你加锁后主动把这个 ID 告诉别人,或者提前生成一个 ID 要求加锁方使用这个 ID ,,那别人就能解锁了啊,和线程啥的无关

而且从实现来看,如果执行时间很长,那这个执行中的状态应该保存在数据库中,不应该单纯加锁,加锁范围只到查询修改这个数据库中状态的过程,否则直接在入口加锁同步调用就好了啊,没必要异步吧

有状态逻辑可比把这个状态写入数据库需要的地方读取判断复杂多了,存入数据库需要的地方读取判断,那整个系统还是无状态的,简单多了,想要性能好一点那存入 redis 也不是不行啊,干嘛非要纠结使用加锁的方式
ychost
271 天前
每个任务 ID 创建一个 topic ,后续只需要往这个 Topic 推数据就行了,处理的那台机器拿到任务之后去监听它,后续都是此机器来处理任务
luofuchuan668
271 天前
看得不是很明白,但是这样设计大概是有问题的
Plutooo
271 天前
@sujin190 #28 对啊拥有线程 id 是可以解锁,那还跟请求到哪个 c 的节点有什么关系呢,既然 a 需要再次请求解锁,那么这个重新发送的请求必然跟先前的请求大概率不同线程,那些跟请求到了其他 c 节点有什么不同吗
ConkeyMonkey1024
270 天前
b 和 c 之间再加一层,专门负责调度
aarontian
270 天前
我不认为有必要查询到具体是在哪个实例上执行。把服务实现为有状态的,并假定它在释放资源前不会挂是个很糟糕的设计,你记录了 c1 的 IP ,c1 挂了呢?你是不是又要有更多复杂的“业务逻辑”去处理实例状态变更导致的问题。


你这里 c 锁定资源的正确姿势或许是把锁定的凭证存放到某个分布式存储(比如 redis )中,等解锁时任意一个本服务的实例都应该能拿到凭证去解锁。(但我甚至怀疑这个设计有根本性的问题,或许不需要加这种资源锁,或者可能可以有一个统一的调度器负责一个流程的资源上锁与解锁)
mawen0726
260 天前
@1402851639
这几天忙忘记回了,后面重新拆了下。
将所有要执行的东西都提交到消息队列中,让下游去抢。如果执行的要中断,上游也是发消息队列,下游监听自己有没有在处理这个业务,没有就忽略。
将所有相关场景都改成这样了,不知道这样的设计好不好...
mawen0726
260 天前
@cowcomic
这里的最底层的资源其实是 jupyter ,用来运行一些大型算法得出结果。然后印个 jupyter 同时只能跑一个算法,所以需要资源锁定。c 服务就是干这个事的。然后 a 和 b 其实就是一个算法只跑单次和按时间纬度跑多次的区别,因为跑多次且结果有上下关联,所需内存大,所以也抽两个了(原本放一块)

然后之前 c 跟业务耦合了,最初拆的就来回解锁了。后面把 c 拆得只跟 jupyter 交互,是个无状态的。a 、b 服务发算法任务让 c 自己去抢,中断执行 ab 也是发消息队列,c 监听到检查自己有没有正在运行对应的业务(算法 id 唯一),没有则忽略。

我之前纠结要维护 ip 是因为觉得中断,暂停等操作要通知对应的 c 服务。但是后面捋了下,一个业务只会在一个 c 里面执行,要做什么操作直接广播所有 c (通过消息队列),c 来判断有没有在运行这个就可以了。

然后上锁放在上游( a 、b 服务)里面,在让 c 运行前上锁,c 运行完通知后解锁。
mawen0726
260 天前
@czsas
@server
感谢感谢,现在回头看看这些框架。
之前纠结要维护 ip 是因为觉得中断,暂停等操作要通知对应的 c 服务。但是后面捋了下,一个业务只会在一个 c 里面执行,要做什么操作直接广播所有 c (通过消息队列),c 来判断有没有在运行这个就可以了。没有运行则不做任何操作
mawen0726
259 天前
@mark2025
因为业务场景锁有业务在生命周期内固定使用某个资源的场景,感觉自己写一套去维护资源的状态,还不如用分布式锁来的直接...
mawen0726
259 天前
@mark2025
当时没设计好,后面重新捋了下,将上锁和解锁都放在同一端了
当时不想大动代码(屎山),就想着 c 上锁,a 、c 解锁
mawen0726
259 天前
@huzhizhao
现在的设计是消息队列中:
主题 1 关于任务发布( a 、c 发布的任务,类型字段区分开)
主题 2 关于任务接收成功的响应,让 a 、c 知道任务在执行了
主题 3 关于执行中的数据
主题 4 关于任务的中断、取消

所有的 c 实例对于主题都属于同一个消费组,共同监听主题 1 抢任务执行,发布到主题 2 响应上游正在执行,同时将结果发布到主题 3 。主题 4 则是不同消费组,收到取消的消息判断自身有没有在执行对应业务,没有则忽略

所有的 a 、b 实例都是一个单独的消费组,监听主题 2 确定任务发布成功(没响应则重试 5 次),监听主题 3 获取结果(结果实体包含消费组信息,消费组不一致忽略消息)

感觉这样设计强依赖了消息队列,但是可以不管下游的 ip 了,也不知道好不好,但是也没机会再改了 - -
mawen0726
259 天前
@luofuchuan668
大佬能看下我新回复的,评价一下吗

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1097140

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX