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

问个微服务调用问题。

  •  
  •   Hanggi · 2020-10-14 11:38:51 +08:00 via iPhone · 4667 次点击
    这是一个创建于 1486 天前的主题,其中的信息可能已经有所发展或是发生改变。
    假设有三个服务,订单服务,商品服务和用户服务。

    三个服务均独立部署,有独立数据库。

    现在要调用订单列表接口,获取订单数组,每个订单要带着对应订单的商品信息,每个商品信息又要带着对应的用户信息(可能是卖家信息)。服务间均可以通过 Id 获取想要的数据。

    这种场景下,最佳实践要怎么实现?
    34 条回复    2020-10-15 10:30:06 +08:00
    dzdh
        1
    dzdh  
       2020-10-14 11:41:49 +08:00
    服务设计问题.

    比如 调用订单服务获取的数据 是否能直接包含商品数据?(订单服务自己去调用商品服务)
    86091566
        2
    86091566  
       2020-10-14 11:43:28 +08:00
    用 key-value 数据库,记录商品信息 id 和用户信息 id 就行
    Hanggi
        3
    Hanggi  
    OP
       2020-10-14 11:45:10 +08:00 via iPhone
    @dzdh 对,如果现在要获取假设 50 个订单信息,但不想调用 50 个服务详情,也不想直接调用 50 个详情再整合呢?
    Hanggi
        4
    Hanggi  
    OP
       2020-10-14 11:48:09 +08:00 via iPhone
    @86091566 订单信息里已经存了商品 id,商品信息里也存了用户 ID 。有没有简单的调用整合方法?减少开销。
    linauror
        5
    linauror  
       2020-10-14 11:50:49 +08:00
    先调用订单列表,订单列表中包含了商品 id 和用户 id,订单服务再分别用 id 集合去商品服务和用户服务查询信息,最后再组装起来。就是商品服务和用户服务搞个批量查询的接口
    Hanggi
        6
    Hanggi  
    OP
       2020-10-14 11:58:48 +08:00
    @linauror 对,这是正常调用逻辑,但是都要增加服务内调用接口,没有更简单的方法了是吗?
    SachinBeyond
        7
    SachinBeyond  
       2020-10-14 12:12:38 +08:00 via Android
    @Hanggi
    增加服务内调用接口是什么意思?

    搞个批量调用,调一次订单服务穿 50 个订单 id,就拿到订单和商品 id 的关系

    再调商品服务传多个商品 id,拿到商品卖家信息不就可以了吗

    每个服务都是调一次,只不过有多个数据 id 。
    kop1989
        8
    kop1989  
       2020-10-14 12:17:27 +08:00
    事前进行字段冗余。
    即把订单列表所需要的商品、用户信息冗余进订单中。
    当然,这要你来抉择是否划算。因为还有冗余信息更新的问题
    Leigg
        9
    Leigg  
       2020-10-14 12:21:48 +08:00 via Android
    批量查询
    optional
        10
    optional  
       2020-10-14 12:32:16 +08:00 via Android
    根据 id 获取 根据 id 列表批量获取。
    Bazingal
        11
    Bazingal  
       2020-10-14 12:50:46 +08:00 via Android   ❤️ 3
    订单必须冗余商品信息,因为商品信息可以修改,但是生成了订单时候的商品是不能改的
    wudaye
        12
    wudaye  
       2020-10-14 13:06:35 +08:00 via Android
    我们目前是五楼的做法,但我看到过更优雅的实现是用一个服务专门用来写 bff 接口,聚合后端的领域接口
    xuanbg
        13
    xuanbg  
       2020-10-14 13:07:25 +08:00   ❤️ 2
    一个是交互的问题,列表就是列表,详情是点击进入详情页面再去获取。
    二是数据结构的设计问题,订单需要有个快照,里面包含订单中商品和其他相关信息在下单时的快照数据,这样做的目的是为了你修改商品信息不会导致客户查看订单的内容与下单时不一致。所以,订单详情是一个完整的快照对象,不需要实时去获取其他的信息。
    xuanbg
        14
    xuanbg  
       2020-10-14 13:10:55 +08:00
    通过合理的交互设计,和合理安排 UI,就能避免这种批量接口调用。如果实在是不能避免,那就捏着鼻子上一个支持批量查询的接口吧。
    blessyou
        15
    blessyou  
       2020-10-14 13:12:14 +08:00
    批量查询接口 + BFF 数据聚合

    BFF 层去调用服务组装数据。
    leopod1995
        16
    leopod1995  
       2020-10-14 13:14:58 +08:00
    上 BFF
    xuanbg
        17
    xuanbg  
       2020-10-14 13:20:49 +08:00   ❤️ 2
    总之,在产品设计的时候,对需求进行严格约束,千万别把查询做成了统计。

    统计是报表干的事,怎么能整合在管理页面里面呢?但我所见的业务,都想让业务管理页面的列表,在提供对业务的管理功能的同时,也提供统计功能。这种模棱两可的需求,一定要厘清,万万不可稀里糊涂地被带偏,否则查询变慢还是你的锅。
    libook
        18
    libook  
       2020-10-14 16:16:24 +08:00
    Backends For Frontends,简称 BFF 。

    简单来将就是一个专门面向 Client 端的服务,Client 只和它通信,涉及到调用微服务的情况由 BFF 来发起请求调用,然后把返回的数据进行整合、格式化再返回给 Client 端。
    zarte
        19
    zarte  
       2020-10-14 16:25:40 +08:00
    @libook 按这个例子 bff 查个列表也是要一个个去商品和用户服务获取信息再组装?
    death00
        20
    death00  
       2020-10-14 17:00:20 +08:00
    十分同意 17 楼的观点,但奈何产品也认为这样是对的,所以开发也不能当人了。
    whevether
        21
    whevether  
       2020-10-14 17:03:27 +08:00
    商城服务我觉得应该和订单服务为一个一个服务。 因为订单和商城耦合度高。且不通用。
    libook
        22
    libook  
       2020-10-14 17:49:28 +08:00   ❤️ 1
    @zarte 我用微服务的方式跟用数据库表的方式是一样的,宏服务提供数据要聚合多个表的数据,那么微服务提供数据也要聚合多个服务的数据。

    从例子上看,查询的主体是订单,订单里会存商品 ID,商品里会存卖家用户的 ID,所以首先是查订单,查出订单后根据订单里的商品 ID 去商品服务查商品信息,然后再根据商品信息里的卖家用户 ID 来查用户信息,因为商品、用户数据可能有重复,所以每一步查询条件可以去重,查出来的数据可以重用。
    乍一看可能会觉得这样比较麻烦,这就是微服务拆分需要权衡的事情,我自己做订单业务的时候是把订单、商品、SKU 、活动优惠等放在一个支付微服务中,用户数据集中放在用户微服务上,相应的订单成交后发货和产生的权益又有各自的微服务,这样能做到既不会有过于繁重的跨服务调用,又能做好微服务之间的开发、性能、故障的隔离以及复用。
    整体上建议微服务设计以加法为主(服务由少逐渐变多),一开始业务简单的时候可以做宏服务,等某一功能模块具备一定的规模和独立性后再考虑拆成微服务。
    bl
        23
    bl  
       2020-10-14 17:54:02 +08:00
    搞一个聚合服务,由一方打包好数据给你,比如订单服务把商品服务打包一起给你。
    zarte
        24
    zarte  
       2020-10-14 18:04:00 +08:00
    @libook 受教了
    zc1249274251
        25
    zc1249274251  
       2020-10-14 18:06:25 +08:00
    订单信息是晚于商品和用户信息的,换个思路我们去获取某用户的订单信息,其实我理解在订单服务里已经包含了该订单相关的信息,比如用户相关,商品相关,商家相关,物流相关等等(冗余),我们去获取订单列表信息时候只能拿到一些主要信息,如果冗余信息不能满足,再去调用其他的服务来获取,而且每一笔订单对应的流水号以及商品库的 sku 是固定的,一般不会修改
    zc1249274251
        26
    zc1249274251  
       2020-10-14 18:13:24 +08:00
    @libook 请教个问题 查询主题是订单 后续由订单来引起其他操作 比如查用户 查卖家 查商品 有一个问题 同一商品 有相关营销策略的调整 sku 变化吗?
    wangyzj
        27
    wangyzj  
       2020-10-14 18:14:21 +08:00
    中间放一层 node 做这种组装
    让前端自己去写
    KingOfUSA
        28
    KingOfUSA  
       2020-10-14 18:23:53 +08:00   ❤️ 1
    好问题。我也经常拿这个问题来面试。
    正如#17 所说,「千万别把查询做成了数据统计」,但是在实际中很难(也懒得去分太清楚)查询和统计之间的边界,据我接触的需求来看,一开始订单列表包含商品,进而包括卖家信息,再包括买家信息,后续可能包括其他的信息等,所以这样的需求一般情况下是逐步的迭代形成的。

    我个人有以下思路:
    1. 和#5 一样
    这样好处是不用考虑商品表结构变化(前提是商品服务不是由你维护),坏处是写 n 多代码

    2. 放到数据仓库里面去查询,例如 es 里面
    坏处是要搭建 es,使用成本有点高

    3. 新开一个数据库实例,将用户库、订单库、商品库同步到这个实例里面,做跨库查询(具体同步的技术可以用 canal 、DTS,实在不行,自己用 mq 解决)
    坏处是要维护表结构的同步,且需要了解基本的业务(例如哪个字段代表什么意思之类的),好处是查询起来很爽很爽
    libook
        29
    libook  
       2020-10-14 18:27:36 +08:00   ❤️ 1
    @zc1249274251 没大看懂问题。

    假设像楼主例子描述一样,因为各种原因最终这几部分确实拆成了几个独立的微服务,那么微服务之间的关系也是需要根据实际情况考虑的,比如你可以让 BFF 分别去调用订单服务、用户服务、商品服务,也可以让 BFF 去调用订单服务、让订单服务去调用商品服务、让商品服务去调用用户服务,一个微服务可能内部是由多个更小的微服务组成,调用链路可以根据需求优化缩短,总之一切都是要按照实际需求来权衡。
    519718366
        30
    519718366  
       2020-10-14 18:53:06 +08:00
    #28 楼总结的很好了,一看就是大佬👍
    ---
    首先把我最想说的说出来:楼主这三个微服务一定不能为了信息聚合而相互依赖。他们就是 3 个独立的域。

    所以我反对#29 楼里提到的在订单服务里去调用其他两个服务组装的方案。应该是在 BFF 里分别调用这三个独立的域去组装。(微服务难就难在边界,以及再未来的迭代中,大家一直能认识 /遵守着这个边界,保持这个微服务的干净)

    大公司我比较赞成采用#28 楼的方法 2
    小公司如果没有 solr 或者 es 这种条件,就先老老实实按方法 1,BFF 里分别调三个域的服务组装实现了先
    newtype0092
        31
    newtype0092  
       2020-10-14 19:56:56 +08:00
    @KingOfUSA #28 大佬请教下,如果是查询的情况,有排序需求的话是不是只能按 3 来跨库连表?
    ShylockGou
        32
    ShylockGou  
       2020-10-14 20:16:00 +08:00
    订单交易数据不需要快照 /历史记录的么?😂
    namelosw
        33
    namelosw  
       2020-10-14 20:47:31 +08:00   ❤️ 2
    这个东西没有完美解,微服务就是这样的,跟这些搏斗了几年,感觉有几种不同解:

    1. 互相调用:优点直接,缺点增加依赖和循环依赖,后期会难维护

    2. 外面一层用 Service 或者 BFF 聚合:优点是能让微服务继续独立,缺点有两种情况:
    2.2 Service 也分层,分层的问题是分层会越来越多,ABC 公用 DE,DE 又公用 F,最后变成金字塔,总需要重构,要不问题就会大量重现
    2.2 BFF,尽量避免 2.1 保持一层,问题是很多逻辑会跑到 BFF,最后发现微服务没干啥事

    3. Event+读模型:各个 Service 互相消费 Event,然后建立自己所需的读模型。优点是不会导致以上设计问题,而且每个 Service 都有自己舒服的模型,缺点是复杂,最终一致,对基础设施要求高,而且 migration 啥的很难弄。

    4. 共享数据库:不建议,这根本就不是微服务,不如把代码合回单体

    5. 还有一些比较新的方案,比如 GraphQL Federation,有一个通用的 Gateway 可以根据同一个主键全自动聚合。看起来很不错,还没有深入使用过,不过感觉比较面向前端,后端 Service 绕回 Gateway 请求不知道会不会有问题

    6. PS:很多时候真心还不如单体了,很多时候可测试性和可靠性都反被削弱了。之前用了一阵 Phoenix Framework,感觉只要技术栈本身编译快,运行快,其实没啥搞微服务的必要,除非 50 人以上的大 team,或者数据库有严重瓶颈,可能拆几个。很多 team 一共没 20 个人就搞一大堆微服务,基本都是自讨苦吃,浪费时间。
    KingOfUSA
        34
    KingOfUSA  
       2020-10-15 10:30:06 +08:00
    @newtype0092 ES 也可以做的。只是单纯的数据库(比如 mysql)查询对于大多数开发者来说上手最快。

    我上面说的第三种指的是,把多个业务库的只读库放在同一个实例里面,单纯的用于查询(业务、一般的报表都可以)。

    我个人倾向这种做法。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1120 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 42ms · UTC 18:49 · PVG 02:49 · LAX 10:49 · JFK 13:49
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.