应对业务扩张,将服务拆分并支持横向扩展,现在拆了共 a,b,c 三个服务出来,然后这三个服务要设计成可以随时横向扩展。其中有如下调用链路
然后 c
是主要处理业务的服务,会上分布式锁,加上处理时间长,会后台执行并执行完通知上游来做其他业务操作,业务操作之后才能解锁。
因为一个业务只能在一个 c
中执行,所以解锁要找到对应的 c 。比如 a
执行业务分配到节点 1 的 c
,后续交互都要找节点 1 的 c
。b
是批量执行业务,这一批都要到同一个c
中执行完。
目前设计方案是将 c
的 IP 和业务 id 写到 redis ,然后通过 redis 确定业务在哪个 c 节点上面跑着。然后要在业务处理过程中,查看状态都通过 redis 查对应 ip 再去调用服务。
比如批量任务中,a 要确定哪个 b 节点承接了这个业务,b 节点确定哪个 c 节点正在处理这批次业务。
感觉这种设计不是很好,想请教下还有没有更优雅的做法
可能描述的有点复杂,大概意思是
1
k9982874 4 天前 via Android
为什么要把问题搞这么复杂,为什么不用消息队列
|
2
tairan2006 4 天前 via Android
这很简单,你改成 pub/sub ,比如 mqtt 。接到任务的节点会自己订阅相关 topic ,其他的节点不会订阅。你发布任务的时候也无需关心到底目的地在哪。
|
3
JasonGrass 4 天前
我的理解:a 执行业务分配到节点 1 的 c ,执行完成之后,C1 就把中间结果保存到数据库或者 Redis ,得到一个 key,
后面 a 要继续处理这个业务,随便找一个 c 的节点,把 key 给 c ,然后继续处理。 感觉是业务拆得还不够彻底?理想情况是,把业务拆成一步一步执行的, 执行完第一步,丢到消息队列,中间结果(如果有比较大的中间结果)保存到 redis 或者数据库,下一个服务从消息队列中拿到任务,然后继续跑,这样才好真正的横向扩展。 |
5
mawen0726 OP @tairan2006 貌似跟 1 楼提的消息队列方式很像,我思考一下怎么设计
|
6
k9982874 4 天前 via Android
不是,你为什么关心认为在哪个节点
a 有任务扔进队列,c 去捡了执行,执行完了再扔个消息,a 受到走完后面流程,就这么简单的事 |
7
mawen0726 OP @k9982874
因为 c 在执行任务的时候,上了锁,锁定了某个资源,因为处理时间长,锁定时间久 代码在写的时候,不是常规的写法.... lock.lock try{ dosomething() } finally{ lock.unlock } 而是起了个线程来上锁,等 a 确定完业务执行完,再去通知持锁的 c 解锁。因为用的 redisson 的 lock ,所以要找回对应的节点 c 才能解锁... |
8
mawen0726 OP |
10
1402851639 3 天前 via Android
感觉你这个耦合这么重是不是拆的就有问题呢。。
|
11
1402851639 3 天前 via Android 1
我觉得你更应该解释一下是什么条件限制了必须在同一个实例上,去考虑能否在请求中将关键信息传递过去,又或者 redis 暂时存一下,而不是 b 服务去循环将请求打到同一个 c 上面?
|
12
yeqizhang 3 天前 via Android
@1402851639 我也觉得,这样拆到底解决了啥问题,我感觉瓶颈都在数据库了,那还要解决分布式的事务问题
|
13
wateryessence 3 天前
一致性哈希?
|
14
cowcomic 3 天前
1 ,当时为什么要拆,从描述看 abc 的耦合很严重,而且是双向依赖,这不拆更合理
2 ,如果一定要拆,那我理解可以把 C 看成一些工作节点,把 C 向上依赖的部分再拆出来变成 C 的下游,首先保证链条是单向的,AB 变成纯上游,C 变成总业务入口,这样看能不能消除 C 的状态变成无状态的 3 ,如果消除不了,那就 C 前面架消息队列,由 C 决定要哪些请求,如果并发不是很大的话,就直接表记录,如果并发很大,那这个整体的系统功能就很有问题,重新梳理吧 |
15
czsas 3 天前
可以借鉴一下 temporal/Cadence 这种 workflow 框架
|
16
nuk 3 天前
给每个任务加一个路径栈
|
17
server 3 天前
temporal
|
18
wenjun19931112 3 天前
想办法把有状态的服务,转换为无状态的服务。
而不是优化有状态服务的分布式处理逻辑。 |
20
RightHand 3 天前 via Android
有状态的,去做复杂均衡啊。干嘛非要上无状态的微服务
|
21
THESDZ 3 天前
1.状态带着走;
2.更进一步,状态单独存,状态唯一 id 带着走。 |
22
xiaogu2014 3 天前
|
23
mark2025 3 天前
分布锁? 这是给自己掘墓吧……
|
25
cheng6563 3 天前
“会后台执行并执行完通知上游来做其他业务操作,业务操作之后才能解锁。”
所以你的实际调用链应该是 a->b->c->b->a |
26
huzhizhao 3 天前
消息队列最合适,特定的队列处理特定的数据就好了
|
27
pangzipp 3 天前
上一个分布式调度框架例如
例如 airflow 阿里云的 SchedulerX |
28
sujin190 3 天前
@Plutooo #19 分布式锁底层都是加锁方生成一个只有自己指定的 ID 来防止他人异常解锁,那换句话说你加锁后主动把这个 ID 告诉别人,或者提前生成一个 ID 要求加锁方使用这个 ID ,,那别人就能解锁了啊,和线程啥的无关
而且从实现来看,如果执行时间很长,那这个执行中的状态应该保存在数据库中,不应该单纯加锁,加锁范围只到查询修改这个数据库中状态的过程,否则直接在入口加锁同步调用就好了啊,没必要异步吧 有状态逻辑可比把这个状态写入数据库需要的地方读取判断复杂多了,存入数据库需要的地方读取判断,那整个系统还是无状态的,简单多了,想要性能好一点那存入 redis 也不是不行啊,干嘛非要纠结使用加锁的方式 |
29
ychost 3 天前
每个任务 ID 创建一个 topic ,后续只需要往这个 Topic 推数据就行了,处理的那台机器拿到任务之后去监听它,后续都是此机器来处理任务
|
30
luofuchuan668 3 天前
看得不是很明白,但是这样设计大概是有问题的
|
31
Plutooo 3 天前
@sujin190 #28 对啊拥有线程 id 是可以解锁,那还跟请求到哪个 c 的节点有什么关系呢,既然 a 需要再次请求解锁,那么这个重新发送的请求必然跟先前的请求大概率不同线程,那些跟请求到了其他 c 节点有什么不同吗
|
32
ConkeyMonkey1024 2 天前
b 和 c 之间再加一层,专门负责调度
|
33
aarontian 2 天前
我不认为有必要查询到具体是在哪个实例上执行。把服务实现为有状态的,并假定它在释放资源前不会挂是个很糟糕的设计,你记录了 c1 的 IP ,c1 挂了呢?你是不是又要有更多复杂的“业务逻辑”去处理实例状态变更导致的问题。
你这里 c 锁定资源的正确姿势或许是把锁定的凭证存放到某个分布式存储(比如 redis )中,等解锁时任意一个本服务的实例都应该能拿到凭证去解锁。(但我甚至怀疑这个设计有根本性的问题,或许不需要加这种资源锁,或者可能可以有一个统一的调度器负责一个流程的资源上锁与解锁) |