【项目经验】实现一个对外调用功能
作者:互联网
业务场景:
开发一个模块,接收rpc计算请求,转发请求给外部,然后同步返回结果给调用方。本地调用是同步的,外部调用是异步的。
设计:调用方C(lient,客户端),本模块S(erver,服务端),外部接口应用 E(xternal,外部)
- 向C提供接口直接本地调用,功能分C端接口,和S端实现,mq作为协议层。
- 目前架构:后端所有模块采用rabbitMQ异步通信。在不引入新 组件的前提下,只能用mq模拟rpc。
- =》mq是支持RPC的,但由于项目架构所限,不能直接使用源生的rpc接口,参考源生实现:发送信息中附带请求ID和结果接收队列,在业务层模拟rpc。不管任何节点收到结果,都可以根据队列名放入同一队列,调用方就能从该队列拿到返回的结果。但是要求一点:一个调用方节点对应一个队列。
- 既然是rpc调用,C请求S应该是同步的,S在转发请求给 E 后收到结果之前,应该是阻塞的;而且S向E的请求线程Req和S接收返回的线程Resp是两条线程,且结果从Req返回,怎么将结果从Resp传给Req呢;而且不能一直阻塞,需要有一个超时时间
- =》联想到Futrue接口,查看实现,里面有个jdk8提供了CompletableFuture,看着名字感觉蛮符合我的要求的,查看方法,<T> T get(timeout,timeUnit),complete(T t),调用get阻塞,等待timeout超时,或者直到compelete返回结果,满足需求。
- 考虑到接口的结果返回形式可能是组播(任选节点返回)或群播(推送给所有节点),计算节点需要支持分布式,也就是节点S1收到C的请求,节点S2如果收到结果也能返回给C。且返回的结果要求填入请求报文的部分字段
- =》初步设计,S1收到请求后,将请求缓存到redis,供S2处理结果时使用
- 需要支持批量计算,但接口不支持,只能逐个调用:
- =》请求一次性发出,用哈希表,REQ+C的请求ID 作为key,缓存所有 请求ID到请求报文的映射 到redis,并另外用集合,RESULT+C的请求ID作为key,存放结果,等结果数量=请求数量时,返回;或者直到超时,返回部分结果
- 部分请求是失败的情况下,会造成返回两次,超时返回和正常返回,只能返回一个
- =》首先考虑下,并发问题,超时返回和正常返回一起返回会影响结果,要加分布式锁,请求id做key,节点ID做value,设置超时时间。在超时或者正常返回后,清空缓存,缓存中没有数据,就不执行超时或者返回逻辑。
出现问题:
和架构设计冲突,架构基于事件驱动,每次收到事件,分配一个线程进行处理。如果请求事件Req分配给A线程,阻塞等待结果;收到结果Resp后,又分配给A线程,则造成伪死锁,请求必定超时,且在超时后打印日志,“开始处理结果”。由于分配是根据hash码随机分配的,并不能保证请求和返回事件不会分配给同一事件
解决思路 =》关键是请求和返回的事件不能分配给同一线程处理
- 请求事件,另起一线程处理 =》 不行,rpc的请求线程必须阻塞
- 返回事件,另起一线程处理 =》需要弃用架构,并自行mq的消费逻辑,该实现需放在C端接口jar中,导致C应用本身不能再添加自身逻辑(可以理解为一个mq消费实现一个bean,这个bean由mq实例调用,要么调用计算功能实现的bean,要么调用应用本身实现的bean,这是冲突的)。或者再加一个mq实例,都不太可取。
- 弃用MQ模拟rpc的方式,引入rpc组件,解决
引入rpc组件后,产生新的问题:原先是支持分布式的,而rpc组件要求,请求给S1,结果就也得S1处理,如果S2处理,已经没有队列可以放了
- =》接口组播的情况:S2收到结果,判断是否是自身的请求结果,是则返回,不是则广播出去。S1收到广播,返回结果。其他S3,S4等节点收到广播,不处理,只有在收到的消息是组播且消息不是自己的,才广播。否则如果只判断不是自己的就广播,会造成无限广播的情况。
- =》接口广播的情况:不是自身的请求结果,丢弃,不处理。
总结:
问题的产生是,因为mq模拟rpc实现与架构设计的冲突。另外,接口只支持单个计算请求,和返回结果的方式可能是组播或者群播,这两个设计的不合理,导致增加了很多工作量。
标签:返回,经验,请求,结果,rpc,调用,超时,对外 来源: https://blog.csdn.net/u014542626/article/details/117115592