记一次服务间调用死锁

记一次服务间调用死锁

背景

有产品服务S,有内外部接口转发服务P。
P有接口 /user_name,输入 user,输出 name
P有接口 /push,转发S的/push_from_p接口
S有接口 /push_from_p,输入id,请求P的/user_name后入库

现象

数据库中有重复数据插入user_name,基本都是两条

排查

查看S日志,发现有些 /push_from_p 接口耗时5000ms+,有些耗时20ms。
查看P日志,发现有请求S的/push_from_p发生timeout,报错处代码发现timeout=5。

起初怀疑P请求S/push_from_p的timeout设置小了,S接口中请求P/user_name耗时太长,应该讲timeout设置大一些。

但为什么P/user_name耗时这么长,而且恰好每次都在5000ms,让我开始怀疑P是不是只有一个线程资源。

然后查看P的启动代码CMD ["gunicorn", "--reload", "--bind", "0.0.0.0:8111", "server:app"]

然后查看gunicorn的默认并发数量,发现workersthreads的默认值都是1

gunicorn --help
  -w INT, --workers INT
                        The number of worker processes for handling requests.
                        [1]
  --threads INT         The number of worker threads for handling requests.
                        [1]

如果P只有一个线程,那就可以解释通为什么P/user_name耗时总是5000ms了。

某个服务X调用了P/push,P接着调用S/push_from_p,S接着调用P/user_name,但是这次请求会一直pending,因为P唯一的线程被X调用P/push给占用,直到过了5秒,P调用S/push_from_p超时,然后P/push返回失败的结果,释放了线程,然后线程分配给了P/user_name请求,虽然客户端单方面报错接口超时,但是服务端仍然会继续请求执行,所以S把查到的user_name入库。

为什么有些请求是20ms呢?查看S的代码发现,S把/user_name的结果放入redis了,所以当X重试P/push时,S并不会再次调用P/user_name

解决方法

  1. 修改P服务的并发数量
  2. 修改/push_from_p接口,将user_name结果直接放进入,避免S再次查询,造成潜在死锁问题

感想

服务间调用设计时,要避免流程上的嵌套调用。我调用你的接口中不能包含你调用我的流程。

服务间调用问题难排查,这实际上是个死锁问题,但表面上只是一个timeout问题。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值