V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
springmarker
V2EX  ›  程序员

在微服务中是用队列好还是 RPC 好

  •  
  •   springmarker · 2019-07-04 18:11:01 +08:00 · 16694 次点击
    这是一个创建于 1972 天前的主题,其中的信息可能已经有所发展或是发生改变。

    个人浅薄的认识,在大多数场景下可以用队列替换 RPC(指代通常的 RPC,不是由队列实现的 RPC)。
    队列的优点:
    1.消息可以堆积,只要队列稳定,消息丢失的概率就比直接 RPC 低。
    2.由接收端主动获取消息的话,负载就由接收端控制了,不会像 RPC 一样无法均衡的负载。
    3.没有类似 Zookeeper 的注册中心,发送端和接收端只要面对队列就可以,发送端不用同时面对注册中心和多个接收端。

    缺点的话就是性能不如直接 RPC、需要手动写异步转同步。

    我看很多微服务都用的是 GRPC 和 Dubbo 之类的 RPC,甚至 Spring Cloud 的 RPC,如果替换成队列会有什么影响吗?不需要高性能的场景下可以互相替换吗?

    本人了解甚少,上面都是班门弄斧的瞎说,如有不对,希望大神能指教一二。

    第 1 条附言  ·  2019-07-04 20:48:27 +08:00
    额,大家讨论起了异步和同步的问题来了,其实我想问的不是这个,我的设想是如果队列包装成类似 RPC 的功能,拥有队列的特性的同时也有 RPC 的功能 这样是否可行?
    第 2 条附言  ·  2019-07-04 22:10:38 +08:00
    大家好像对 RPC 的同步调用很有执念啊,RPC 实现同步本质还是异步转同步,大家就不用讨论异步同步的问题了,不要妖魔化了 RPC/rest
    129 条回复    2020-07-30 10:36:38 +08:00
    1  2  
    sampeng
        101
    sampeng  
       2019-07-05 12:51:31 +08:00 via iPhone
    @springmarker 业务上的同步和异步。不是技术上的。
    sampeng
        102
    sampeng  
       2019-07-05 12:52:56 +08:00 via iPhone
    @springmarker 业务上的同步和异步。不是技术上的。

    @feelinglucky 是啊。不然怎么摸鱼
    uyhyygyug1234
        103
    uyhyygyug1234  
       2019-07-05 12:58:56 +08:00
    解决冲突的方案之一:在合适的粒度串行化并行请求 //@许式伟: 是的,这才是核心原因。如果我告诉你有个 Golang 的服务器框架,让你所有的请求都串行化执行,自然整个 Golang 程序不需要用锁了 //@haman_karn: 不仅仅是因为变量只能绑定一次 用的并发模型也有关系吧 用 receive 就相当于把并发的 send 给串行化 @许式伟 我说了两个误解:一个是单线程不需要锁这个是误解,这个以前讨论 nodejs 的时候谈过,有些人认为我在玩文字游戏;另一个是 erlang 服务器写代码大家不用考虑锁,是因为 erlang 变量不可变(注意我关注的重心是大家写代码有没有锁,不是 erlang 虚拟机);第二个误解更深点 将所有锁一次性在这个消息队列中高效实现了,本身就很好了 //@西祠响马: 使用队列将并行转为串行,只不过是重新实现了一个轻量的锁。锁不就是这么实现的吗?只不过大家嫌它重罢了。


    //@邓草原: 解决访问可变的共享资源冲突,加锁是一种方案,还有就是将访问请求全部排队,将并行在此转为串行,如 actor actor 中(比如 Erlang 的 process)已经实现了高效的消息队列,直接用就是。异步消息驱动下对共享资源的访问机制,实际上归一和简化了并行下的冲突解决方案。//@西祠响马: 从出错风险上来说,队列可能还更大。只不过学习门槛高,把更多的人挡在外面。 //@许式伟:其实队列更重一点,只不过队列不容易出错。 愿闻其详。//@许式伟: 为什么我说 Erlang 服务器框架是半吊子:轻量级进程模型核心是同步方式写程序降低负担,但 Erlang 蛮多设计又极其依赖异步消息,这是自相矛盾的。与其这样不如一上来就异步回调 //


    @邓草原: 将所有锁一次性在消息队列中高效实现,本身就很好 new 一个 process 请求数据库,这个 process A.访问完回调; B.访问完发通知消息; C.干脆 blocked 成同步。//@许式伟: 最简单的例子:我响应请求代码中,访问数据库应该怎么写?//@邓草原: 愿闻其详。//@许式伟: 为什么我说 Erlang 服务器框架是半吊子:轻量级进程模型核心是同步方式写程序降低负担... 不会吧,至少在 Scala 中我没觉得有难受的地方。//@西祠响马: 应用级的代码用异步写就是个灾难。比如 Node.js 。 //@许式伟:最简单的例子:我响应请求代码中,访问数据库应该怎么写?//@邓草原: 愿闻其详。//@许式伟: 为什么我说 Erlang 服务器框架是半吊子:轻量级进程模型核心是同步方式写程序降低负担... 要看是哪国的程序员了[嘻嘻]//@许式伟: 你猜猜大部分 erlang 程序员会怎么写?//@邓草原: new 一个 process 请求数据库,这个 process A.访问完回调; B.访问完发通知消息; C.干脆 blocked 成同步。//



    @许式伟: 最简单的例子:我响应请求代码中,访问数据库应该怎么写?//@邓草原: 愿闻其详。//@许式伟: Erlang 其实做 IO 时 block 成同步也没啥,重要的是它要在一个新的 process 中,它被 block 并不会影响整个系统,系统中总是还有大量的 process 在活动。对 API,我通常会在异步回调函数基础上再包装一个同步的。//@邓草原: new 一个 process 来请求,这个 process A.访问完回调; B.访问完发通知消息; C.干脆 blocked 成同步。 看具体情况。所以我才提供两种函数。//@许式伟: 那唤起这个新 process 的人在干嘛呢?等还是不等?//

    @邓草原: 其实做 IO 时 block 成同步也没啥,重要的是它要在一个新的 process 中,它被 block 并不会影响整个系统,系统中总是还有大量的 process 在活动。对 API,我通常会在异步回调函数基础上再包装一个同步的 Io is tough, let's go shopping.//@许式伟:等,不如自己发起 io 请求,所以我的理解是不等;既然不等,那 gen_server 就要为这次 io 多个临时状态,纠结呀 //@邓草原: 看具体情况。所以我才提供两种函数 //@许式伟: 唤起这个新 process 的人等还是不等?
    webee
        104
    webee  
       2019-07-05 13:01:12 +08:00
    看来楼主没明白一个问题,没有最好的技术,只有适合的技术,一切对比都是要有基准的。如果你觉得这么做在你的项目中合适且满足需求,那我觉得就没问题,至于是不是比其他人的方案更好就不一定了,这都取决于自己的认知水平。
    springmarker
        105
    springmarker  
    OP
       2019-07-05 13:09:08 +08:00
    @webee server 端加 buffer 并不能解决负载的不均衡问题(相比较而言)啊。关于满不满足我的需求,我也不知道效果怎么样,首先我能明显知道的缺点就是性能不如传统 RPC,但是差多少?我不知道,我的标题就是我想说的,我不知道是什么效果,我是来想问问而已,大家只是一味的在抨击。

    @sampeng #101 业务上的同步异步。那如果我用 队列 和 传统 RPC 来实现消息通讯,那两者有可比性了吗?两者理论上也都可以实现同步和异步不是吗
    sampeng
        106
    sampeng  
       2019-07-05 13:18:22 +08:00 via iPhone
    @springmarker 跟你说业务呢,你又说原理,跟你说原理你又去扯业务…这没法聊啊…
    springmarker
        107
    springmarker  
    OP
       2019-07-05 13:20:08 +08:00
    @sampeng #106 不是,那你想说什么?那他俩到底有没有可比性?是那个观点让你觉得我在胡说?
    menyakun
        108
    menyakun  
       2019-07-05 13:33:51 +08:00
    以前上网络协议的课时,ONC RPC (也就是用在 NFS 里面的那个),是和 HTTP 同属于应用层的网络协议。但现在各种 framework 里面说的 RPC 其实和早期的 RPC 已经不太一样了,已经加了很多机制(比如 ACK,服务发现 /注册)去保证通信的可靠性,对开发者来说已经是个高可用的 RPC 了。但拿 MQ 来说,如果我拿个 MQ 官方提供的 client 来用,虽然我可以再去实现一套 RPC,但原生的 client 接口应该基本都是 publish,等待 ACK,至于之后怎么 subscribe,又是另一个接口的事。
    webee
        109
    webee  
       2019-07-05 13:42:55 +08:00
    @springmarker 就拿发一个 mq 消息到接收,这其中至少用到两次 rpc 调用( 4 次消息传递)了吧。再加上回复,一共四次 rpc 调用( 8 次消息传递)了吧。
    你要的负载均衡是以增加至少 3 次 rpc 为代价换取的。

    你据说的这种主动式负载均衡确实需要一个协调器的角色,在这里你使用了消息队列。但是在现存的任务负载均衡中也可以容易实现啊,只要增加 server 端和负载均衡的主动通信就可以了。

    另外,服务器认为自己空闲,不代表它处理得就更快,也就不一定能降低平均延迟。
    在现实情况下,主动式负载均衡除了增加系统交互复杂度,好像不比其他的负载均衡策略更好。
    passerbytiny
        110
    passerbytiny  
       2019-07-05 13:51:20 +08:00
    因为回复太多,我就没看。看了楼主的追加,貌似也没提到核心问题。
    针对楼主的优点,纠正一下:
    1.消息可以堆积,只要队列稳定,消息丢失的概率就比直接 RPC 低。
    ——想要让队列稳定,不是很难,是相当相当困难:队列只能保证先进先出,但不能保证先出就先消费,先消费就先执行,****队列无法顺序执行这一个缺点,就能让你永远优先选择 RPC 而不是消息队列****;除了 Kafka 外,很少有消息中间件能保证不丢失,RabbitMQ 要是只有生产者和交换器而没有消费者,发出去的消息直接丢失,通常来说,生产者宿主服务要在发送消息前提前备份消息,用以在消息丢失后重发,消费者要处理排除重复发送的干扰;除非是 RPC 方式的消息,发出去的消息只允许接受或丢失,不允许抛出异常,业务上失败时消费者只能记录失败然后追加补偿措施以实现最终一致性。
    2.由接收端主动获取消息的话,负载就由接收端控制了,不会像 RPC 一样无法均衡的负载。
    —— RPC 照样可以做负载均衡,两者的负载均衡也并无多大区别,这个不能算优点。
    3.没有类似 Zookeeper 的注册中心,发送端和接收端只要面对队列就可以,发送端不用同时面对注册中心和多个接收端。
    ——虽然没有 Zookeeper,但是需要消息中间件呀(早期 Kafka 版本,还需要同时面向 Zookeeper 和 Kafka ),复杂型只高不低。
    no1xsyzy
        111
    no1xsyzy  
       2019-07-05 14:00:30 +08:00
    @sampeng 业务上不存在同步,只有说一致(强一致 / 最终一致)。
    springmarker
        112
    springmarker  
    OP
       2019-07-05 14:04:58 +08:00
    @webee #109 我说的是类似于饿汉式的抢任务。


    @passerbytiny #110 第一点我确实不是很了解,第二点我的意思是类似与饿汉式的主动抢任务,不是由队列主动分配。其实第三点我想的是解耦,注册中心我只是举个例子,调用者只需面对中间件,不用面对众多的 Server。
    springmarker
        113
    springmarker  
    OP
       2019-07-05 14:09:44 +08:00
    @passerbytiny #110 顺便问一下“队列无法保证顺序执行”,为什么不可以用呢?现在的消息不都是带标识 head 头吗
    raysmond
        114
    raysmond  
       2019-07-05 14:12:14 +08:00
    一般 MQ 的并发性能好,消息不丢,专门干这事,干的专业,对于突然高并发的网络请求,直接给 RPC 一般都扛不住,MQ 比较能扛,可以瞬时并发缓解
    no1xsyzy
        115
    no1xsyzy  
       2019-07-05 14:12:27 +08:00
    @passerbytiny > 队列只能保证先进先出,但不能保证先出就先消费,先消费就先执行
    这其实从开头就按照队列式设计业务逻辑就行了,但这需要架构师开头就想好
    实际上我从来没遇见到或者遇见也没意识到 “是前提” (比如说 B 是 A 的因变量,那么 A 是 B 的前提)以外而非得顺序的情况……
    springmarker
        116
    springmarker  
    OP
       2019-07-05 14:25:50 +08:00
    @feelinglucky #100 1 楼和你想到的可能是 队列和传统 RPC 之间的本质区别,我的意思是在用 传统 RPC 和 队列 实现的消息通讯上有什么区别,在微服务上有什么效果,不是他俩的本质区别。
    passerbytiny
        117
    passerbytiny  
       2019-07-05 14:30:21 +08:00
    @springmarker #111 通过上游抢任务来分配请求到上游的负载均衡网关,我至今没见到过,你需要例子来证明你说的话。第三点你已经在胡扯了,我猜想你所说的只需面对中间件,指的是生产者只需要知道中间件端口就去发送消息,而不去管发啥内容,不去管有没有消费者。生产者制定消息内容,不比选择哪个 RPC Server 更简单;生产者本身不需要管有没有消费者,但是要有额外的监控中心去管,而负责任的生产者,是应当预判消费者不存在而增加自动重发机制的。
    wysnylc
        118
    wysnylc  
       2019-07-05 14:42:58 +08:00
    微服务,RPC,队列三者完全不在一个层面,各自服务的目标也不一致
    微服务用户多系统之间通信,使用 http
    RPC 是项目多模块之间通信,一般为二进制通信
    队列是解决并发使用的方案
    可以理解为 微服务>RPC>队列
    springmarker
        119
    springmarker  
    OP
       2019-07-05 14:44:57 +08:00
    @passerbytiny #117 我没有例子,我不知道是什么效果,我也不是来发论文说队列比传统 RPC 好。关于制定消息内容,编写的时候不就已经确定好了吗,head 头可以标识或者消息过滤器不可以实现吗?关于重发机制,在超时之前我认为发出消息就是一直有用的,堆积在队列中不一样可以等待 server 启动吗,传统 RPC 好处就是可以快速判断所有连接是否断开,但是既然消费者都挂了,重试不也没用不是。
    springmarker
        120
    springmarker  
    OP
       2019-07-05 14:50:17 +08:00
    @wysnylc #118 这三个单拿使用场景确实不是一个从层面,我的意思类似于“我拿个电风扇可以改成搅拌机不,和原来搅拌机有什么区别”
    passerbytiny
        121
    passerbytiny  
       2019-07-05 14:57:11 +08:00
    @springmarker #112 举个例子。
    生产者:1 分 0 秒发送消息一,张三把自己的性别改成女; 1 分 10 秒发送消息二,张三反悔了,把性别改回男。
    消费者一,1 分 0 秒收到消息一,因为线程阻塞,1 分 15 秒才开始执行,1 分 17 秒执行完成。
    消费者二,1 分 10 秒收到消息二,立刻执行,1 分 12 秒执行完成,数据已同步保存到数据库。
    最终的结果是,虽然先改女后改男,消息也是顺序接受的,但执行顺序是先改男后改女,执行顺序与发出顺序不一样。

    我大致浏览了第一页的回复,发现你的回复一直是高姿态的回复,翻页之后也没有变化。这种情况虽然没什么不对,但是我已经没有继续讨论的积极性了。
    springmarker
        122
    springmarker  
    OP
       2019-07-05 15:09:27 +08:00
    @passerbytiny #121 一般这种 web 改性别都是需要同步了吧,而且这种情况用传统 RPC 不也一样吗,消费者也可能不是一个消费者,消费速度也不一定一样,RPC 收到的顺序也不一定是按顺序的,这就涉及到分布式锁的问题了吧。
    额,我的姿态很高吗?我平常打字容易口语化吧。
    yc8332
        123
    yc8332  
       2019-07-05 15:34:31 +08:00
    完全不同的东西怎么能凑一起比较呢。。。。
    jswh
        124
    jswh  
       2019-07-05 17:13:57 +08:00
    单纯从业务上考虑。如果业务之间的归属一个 transaction 里面的,用 rpc。如果业务之间不是同一个 transaction 只不过是流水线关系的数据传递,用队列。
    langxuan
        125
    langxuan  
       2019-07-05 18:10:49 +08:00
    @springmarker 可以认为 RPC 的堆积是实现了某种程度的背压
    ErrorMan
        126
    ErrorMan  
       2019-07-06 09:45:16 +08:00
    @springmarker #22 用队列实现 RPC,那这个还是被视为 RPC。另外看了你的回复感觉你就是怎么样都想把队列往 RPC 里塞,如果你非得塞,可以这么做,做出来也没什么毛病。但是从设计模型上来讲,RPC 模型依然是同步模型,就是说调用你实现的人是把它当同步来使用的,你底层实现成异步同步用不用队列什么的对人家没区别,互相之间都是黑盒。所以不要纠结于用不用队列,对使用者来说没区别,顶多说你实现的 RPC 版本有队列的特性,仅此而已。。总的说设计模型和实现模型是两码事,如果不好好弄清楚他们的关系你会继续钻这个牛角尖。
    tenghuanhe
        127
    tenghuanhe  
       2019-07-06 10:49:38 +08:00 via iPhone
    记得 hn 上也有关于这个话题的大讨论,待会我找找链接贴过来
    diyazhu
        128
    diyazhu  
       2019-07-09 10:05:45 +08:00
    @tenghuanhe 找到了咩
    KyoChou
        129
    KyoChou  
       2020-07-30 10:36:38 +08:00   ❤️ 1
    看到了就回复一下吧. 我们有个基于 GRPC 的项目, 每小时的调用日志就有上 10G. 后来为了做分布式, 转成了通过消息(nats)的 request-reply 方式通信, 没有一点问题.
    1  2  
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4726 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 10:06 · PVG 18:06 · LAX 02:06 · JFK 05:06
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.