单体应用拆分为微服务遭遇水土不服,服务间资源调用不畅,如何修改架构? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
chinaguaiu
V2EX    程序员

单体应用拆分为微服务遭遇水土不服,服务间资源调用不畅,如何修改架构?

  •  1
     
  •   chinaguaiu 2023-11-01 18:02:38 +08:00 4236 次点击
    这是一个创建于 745 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如题,公司团队此前比较缺乏微服务开发的经验,开发前期也只是简单按照模块划分的思路设计了微服务,思路简述为:在业务上呈现内聚的资源将被整合为一个服务,服务的业务逻辑实现是需要频繁访问这部分资源的;而如果仍然需要额外的资源(这个资源划分到了其它服务),通过服务间通信解决(使用 Restful 请求或者中间件);一些服务间通用的资源通过 jar 包依赖的形式引入。

    一段时间的开发之后产生了一些阻碍,主要发生在服务间资源调用中,如下:

    1. Restful 接口方式调用服务间资源十分缓慢和繁琐。服务对外暴露的 Restful API 全部是直接面向外部调用设计的,每一次调用都需要走完完整的参数处理以及鉴权验证逻辑。服务间之间互相调用依然是使用这种 API ,每一次调用都基本等同于外部调用,需要重新走一次参数处理和鉴权验证的逻辑,运行效率很差,也导致方法实现中充斥大量冗余参数和冗余返回结果(对于服务间调用来说) 。
    2. 某些功能的实现需要频繁进行服务间资源调用。这个意思不是服务调用链路太长,而是诸如服务 A 的某一业务逻辑需要同一时间需要重复访问服务 B 五到六次,或者说服务 A 的某一业务逻辑需要同时访问服务 B 、C 、D 、E 、F 等。又由于 1 的问题,效率极差。

    如何针对性解决两个问题?重新设计一类面向内部服务调用的 Restful 接口?还是服务间调用引入 MQ 中间件?

    想请教一下 V 友们有没有相关的解决思路,由于公司团队包括我自己也没有太多开发微服务的经验,还望大家可以详细讲讲。

    40 条回复    2023-11-02 10:53:08 +08:00
    kkk9
        1
    kkk9  
       2023-11-01 18:39:01 +08:00   1
    1 、参数处理以及鉴权验证逻辑应该由网关前置处理,后面的相关服务默认网关给定的数据是完全可信的。同时拆分中间服务的参数和结果,只保留必须的传递。最后由网关后置处理进行统一输出。

    2 、优化调用就需要具体问题具体分析了。比如:服务 A 的某一业务逻辑需要同一时间需要重复访问服务 B 五到六次,是否可以优化成一次全部读取,只在服务内各取所需。
    emSaVya
        2
    emSaVya  
       2023-11-01 18:39:06 +08:00
    rpc 调用怎么会慢? 直接上 brpc 又快又好。

    内部服务不需要鉴权。

    同时访问的服务 整理一个 tag 出来, 没有依赖的直接并行请求。
    devopsdogdog
        3
    devopsdogdog  
       2023-11-01 18:40:08 +08:00 via Android   2
    不合适就不拆,拆出毛病才是正常的。
    业务量真的很大?大到每个模块都得独立?
    ktqFDx9m2Bvfq3y4
        4
    ktqFDx9m2Bvfq3y4  
       2023-11-01 18:40:23 +08:00 via iPhone
    我也在给公司拆单体,我的方案更柔和:

    - 先确保每个服务(我这里是模块)的独立性,尽量不要引用其他模块
    - 模块功能抽象成接口,功能定义和实现分离
    - 提出运行环境的概念,即负责跑这些模块的层
    - 运行环境也负责对调用者返回实际实例
    - 将大部分常见操作抽象,实现放运行层,即避免模块接触实现细节
    - 模块只管触发事件,不订阅
    - 设定一个统一的业务模块负责订阅所有消息,再通过运行层调用其他模块


    关键部分:
    - 一个运行环境可以托管任意个模块,如果模块调研在同一个环境则直接本地调用。
    - 迁移过程中基本上只有一个运行环境就是单体本身,本地调用不影响性能
    - 模块和它抽象的接口在单体完成依赖注入,这样单体可以直接调用新的模块
    - 之后你可以实现跨运行环境通讯,也就是 RPC
    - 可以将经常调用的模块放同一个运行环境中,以提升性能
    - 模块所需基础能力(数据库访问/配置读取/缓存访问等)都抽象并由运行环境提供后,那么模块基本上可以动态加载了

    基于此:

    - 如果运行环境只跑一个模块,那就是微服务
    - 如果运行环境跑所有模块,那就是单体
    - 介于两者之间的,那就是混合模式
    - 上述设计保障了只要你在运行环境你就可以访问所有模块
    - 考虑到业务模块订阅了所有消息,那么每个运行环境都部署一个业务模块会避免更多的 RPC

    目前这个计划实施得相当好。
    teble
        5
    teble  
       2023-11-01 18:42:26 +08:00
    一般来说鉴权不应该是微服务网关统一鉴权吗,所有微服务处于同一内网的情况理论上应该是相互可信的,不太清楚具体业务,个人感觉服务间通信的鉴权开销是完全没必要的
    kkk9
        6
    kkk9  
       2023-11-01 18:52:16 +08:00
    @Chad0000 #4 你这个感觉就是容器概念啊,单独一个模块的时候是微服务,多个的时候是单体。感觉出问题就是 All in BOOM 个人见解,也可能我没理解透彻
    wu00
        7
    wu00  
       2023-11-01 18:55:40 +08:00
    先搞个简单粗暴的改造,外层网关 => BFF => 业务服务
    网关处理鉴权认证,请求只转发到 BFF ,BFF 内网并行请求各服务&&整合数据,业务服务不对外暴露,一开始不要把服务拆的太细,尽可能保证独立。

    最后,单体改造成支持分布式再铲铲屎山就行了,最好别盲目搞微服务,如果是为了“练手”搞“履历”,当我没说。
    iyaozhen
        8
    iyaozhen  
       2023-11-01 18:58:01 +08:00
    其实没遇到实际问题不用拆,我们都开始合了。上千个服务,维护起来也受不了
    ktqFDx9m2Bvfq3y4
        9
    ktqFDx9m2Bvfq3y4  
       2023-11-01 19:16:40 +08:00 via iPhone
    @kkk9 #6
    问题他它可以让你随时回到单体或者微服务架构。“后悔”来得及。谷歌好像也在搞一个类似的框架,可独立也可同进程,go 写的。忘了名字了。
    franktopplus
        10
    franktopplus  
       2023-11-01 19:22:16 +08:00 via Android
    领域切分清晰,单一职责原则;
    不相干的服务不依赖;
    一个微服务的异常不会影响其他服务
    Sean46
        11
    Sean46  
       2023-11-01 19:36:45 +08:00
    @Chad0000 Service Weaver ?
    4kingRAS
        12
    4kingRAS  
       2023-11-01 19:47:21 +08:00   2
    无状态的服务才适合做微服务,如果状态很多,还是单体好
    4kingRAS
        13
    4kingRAS  
       2023-11-01 19:49:20 +08:00
    另外,从单体切换到微服务这么大的架构变动,应该是一点点的切,而不是一下子打散了。微服务的目的是做到平滑水平扩容,牢记自己的目的
    chinaguaiu
        14
    chinaguaiu  
    OP
       2023-11-01 19:51:04 +08:00
    @wu00 项目的要求,有可能是为了后期移交或者整合第三方系统。肯定不是技术团队的要求,团队成员大多不具备微服务开发的经验,能选的话肯定不会写成微服务的。
    chinaguaiu
        15
    chinaguaiu  
    OP
       2023-11-01 19:58:32 +08:00
    @teble 应该这么做,但是开发前期团队其实脑子里都不存在微服务网关这个概念,所以采用的是每个服务单独调用鉴权 SDK 包来实现鉴权,实话说就是单体应用暴露 API 接口的写法,认为每一个暴露的 API 均为外部调用。后面虽然加上了微服务网关,但是之前写的 Restful 接口签名已经很难改了,要改的话等于重构,那就是下一个开发周期的事情了
    chinaguaiu
        16
    chinaguaiu  
    OP
       2023-11-01 20:03:05 +08:00
    @4kingRAS #13 霸王硬上弓,上面有意通过这个项目积累团队开发微服务的经验
    lanlanye
        17
    lanlanye  
       2023-11-01 20:13:15 +08:00 via iPhone
    1. 添加 Gateway ,把验证和鉴权放进去,其他服务中去掉(要保障内网安全的话另说)。更重要的是首先检查你的服务间通信路径,不要内网通信先去公网绕一圈,那样肯定慢…

    2. 看起来更像一个设计问题,需要具体分析,没法直接给建议。
    julyclyde
        18
    julyclyde  
       2023-11-01 20:16:51 +08:00
    @emSaVya 对,内部服务不需要鉴权,留着等将来内卷的时候再改造
    ZZ74
        19
    ZZ74  
       2023-11-01 20:43:39 +08:00
    别加 gateway ,到时会成为瓶颈。
    鉴权做简单,比如只验证 token 是否合法,拦截器层就能做掉。参考 jwt ,或者验证下是否内部 ip ,或者干脆不做!!!内部调用何必呢。

    第二点属于服务没拆好,别为了所谓的微服务,真的拆的稀碎。,建议耦合度相对高的合并就行了。所谓的宏服务。

    RPC 就不用试了也解决不了你的问题,最终都是网络请求,和鉴权要不要啥的两码事。
    ZZ74
        20
    ZZ74  
       2023-11-01 20:46:17 +08:00
    @iyaozhen
    你们这算是瞎拆还是 KPI 拆啊。某全球在用的协同办公软件(系统)都没有这么多
    tool2d
        21
    tool2d  
       2023-11-01 21:01:47 +08:00 via Android
    rest 短链接对于维护复杂状态还是挺麻烦的,最好就是改成长链接的 rpc 来维护。
    wyx119911
        22
    wyx119911  
       2023-11-01 21:08:29 +08:00   1
    1. 微服务之间调用鉴权改为验证加密票据,票据由对外网关统一验证用户信息后办法,后续判断票据有效跳过没必要的验证逻辑。
    2. 首先问题可能是不熟悉微服务的架构模式,导致调用不合理。另外有些调用确实很多无法避免的情况,可以用并发请求来解决(例如多协程),这里要注意瞬时请求量,保护被调服务不被冲垮。
    lsk569937453
        23
    lsk569937453  
       2023-11-01 21:12:57 +08:00
    并发不到 200 的业务还搞这么复杂吗???确定不是过度设计?
    xiangyuecn
        24
    xiangyuecn  
       2023-11-01 21:21:53 +08:00
    话说天下大势,分久必合,合久必分。
    iyaozhen
        25
    iyaozhen  
       2023-11-01 21:22:46 +08:00
    @ZZ74 不是拆出那么多的,是大家都新建一个服务,慢慢的就多了
    chinaguaiu
        26
    chinaguaiu  
    OP
       2023-11-01 21:31:46 +08:00
    @lanlanye 2 首先肯定是设计问题,然后又由于 1 加剧了状况,需要写大量的冗余代码拼装请求去进行服务调用,响应时间还很长
    chinaguaiu
        27
    chinaguaiu  
    OP
       2023-11-01 21:42:4 +08:00
    @ZZ74 #19 “内部调用何必呢。” 是的内部调用没有必要鉴权,现在问题是暴露出的所有接口都是面向外部调用设计的,例如说 api 方法签名里包含请求参数 appid 、userid 、sign 、time 等,就没考虑内部调用的场景,导致现在内部调用都是先拼接参数字段再发起请求,和维持一个外部用户 agent 没有区别。所以现在是想绕过这些已经定义的 Restful api 去进行内部请求,但是没有思路。
    chinaguaiu
        28
    chinaguaiu  
    OP
       2023-11-01 21:44:38 +08:00
    @lsk569937453 项目要求,不得不上,当时选取微服务架构的原因就不是技术因素
    chinaguaiu
        29
    chinaguaiu  
    OP
       2023-11-01 21:49:40 +08:00
    @wyx119911 1. 思路不错,感谢; 2. 可以考虑。并发指的是同步并发还是异步并发?这种内部业务逻辑要求即时处理数据的场景不能使用异步吧。
    tangAtang
        30
    tangAtang  
       2023-11-01 22:12:05 +08:00
    @chinaguaiu 应该说的是同步并发,比如需要拿 A+B+C 数据,数据之间没依赖就并发拿。但是感觉这也治标不治本
    wmper
        31
    wmper  
       2023-11-01 22:18:18 +08:00
    这样才不会失业啊,啥搞才有搞头。
    ktqFDx9m2Bvfq3y4
        32
    ktqFDx9m2Bvfq3y4  
       2023-11-02 02:24:50 +08:00 via iPhone
    @Sean46
    嗯,就是这个。我都把方案搞出来了,开始写代码了,才看到这个框架。当然我也用不到因为我是 C#
    xuanbg
        33
    xuanbg  
       2023-11-02 07:49:04 +08:00
    1 、内部 Restful API 调用不可能慢,慢一定是别的原因造成,必须要把这个原因找出来
    2 、内部访问不需要鉴权,所以,鉴权放到网关上去实现。具体可以参考我的开源项目,点我头像自己找
    3 、服务 A 的某一业务逻辑需要同时访问服务 B 、C 、D 、E 、F 等,这种情况一定是设计问题,需要重新设计才能解决
    yinmin
        34
    yinmin  
       2023-11-02 08:33:42 +08:00 via iPhone
    跨服务器的 1 次请求至少 1 毫秒。微服务不是函数,别在循环里几百/几千次去调用,一定是慢的。

    用户一次 click 操作,调用 1 次 a 服务,每次 a 服务调用 10 次 b 服务,每次 b 服务调用 10 次 c 服务,每次 c 服务运行了 10 条 sql 数据库服务,就是妥妥的耗时>1 秒。
    yinmin
        35
    yinmin  
       2023-11-02 08:54:21 +08:00 via iPhone   1
    如果运行太慢,项目无法交付,短平快的方式是:
    1. 去掉用户权鉴改 token 直接校验
    2. 将调用密度高的微服务安装在一台机器里,内部使用 unix socket 替代 tcp 。然后依样画葫芦安装出多台机器跑负载均衡。

    用以上 2 条之后,速度可能会有十倍,甚至百倍的提升。
    fkdog
        36
    fkdog  
       2023-11-02 09:41:04 +08:00
    乱拆的结果就是这样的。
    如果现在连这种 controller 层的问题都能难倒你们,后边各种事物、同步延迟、一致性岂不是能把你们搞炸锅?
    你们业务量多大要拆出服务来啊
    dahuahua
        37
    dahuahua  
       2023-11-02 09:42:48 +08:00   1
    在第一层校验通过后,往请求头里写个 verify-pass 的标志,内部服务先判断是否有这个标志,再来决定是否进行二次校验,将来单独交付内部服务的时候,也不会缺失这部分校验能力。
    cp19890714
        38
    cp19890714  
       2023-11-02 10:01:08 +08:00   1
    * 服务对外暴露的 Restful API 全部是直接面向外部调用设计的.
    内部接口与外部接口需要分开.

    * 需要重新走一次参数处理和鉴权验证的逻辑
    鉴权应该在 gateway. 既然现在已经在每个服务中都有鉴权了, 那可以为内部 API 添加一个统一前缀, 来跳过鉴权.
    参数处理本来就是必要的, 省不掉.

    * 重复访问服务 B 五到六次
    如果是同一个功能, 应该考虑 多个接口合并到一个接口
    远程调用是 IO, 不能循环调用, 应该改为批量.
    对于数据变化不大的, 使用本地缓存.

    * 或者说服务 A 的某一业务逻辑需要同时访问服务 B 、C 、D 、E 、F 等。又由于 1 的问题,效率极差。
    大部分情况下, 一个功能不会同时调用 BCEDF 这么多的服务接口. 可以考虑下是否是服务拆得太细.
    jackwv
        39
    jackwv  
       2023-11-02 10:41:44 +08:00
    最上层做一个 API 聚合,下面的 RPC 服务 循环依赖就循环依赖,简单快捷。
    wwwz
        40
    wwwz  
       2023-11-02 10:53:08 +08:00
    好家伙,自创微服务是吧
    上 Spring Cloud Alibaba 全家桶
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2591 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 07:31 PVG 15:31 LAX 23:31 JFK 02:31
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86