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

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

  •  1
     
  •   chinaguaiu · 178 天前 · 2959 次点击
    这是一个创建于 178 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如题,公司团队此前比较缺乏微服务开发的经验,开发前期也只是简单按照模块划分的思路设计了微服务,思路简述为:在业务上呈现内聚的资源将被整合为一个服务,服务的业务逻辑实现是需要频繁访问这部分资源的;而如果仍然需要额外的资源(这个资源划分到了其它服务),通过服务间通信解决(使用 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  
       178 天前   ❤️ 1
    1 、参数处理以及鉴权验证逻辑应该由网关前置处理,后面的相关服务默认网关给定的数据是完全可信的。同时拆分中间服务的参数和结果,只保留必须的传递。最后由网关后置处理进行统一输出。

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

    内部服务不需要鉴权。

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

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


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

    基于此:

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

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

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

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

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

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

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

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

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

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

    * 或者说服务 A 的某一业务逻辑需要同时访问服务 B 、C 、D 、E 、F 等。又由于 1 的问题,效率极差。
    大部分情况下, 一个功能不会同时调用 BCEDF 这么多的服务接口. 可以考虑下是否是服务拆得太细.
    jackwv
        39
    jackwv  
       177 天前
    最上层做一个 API 聚合,下面的 RPC 服务 循环依赖就循环依赖,简单快捷。
    wwwz
        40
    wwwz  
       177 天前
    好家伙,自创微服务是吧
    上 Spring Cloud Alibaba 全家桶
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2944 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 14:05 · PVG 22:05 · LAX 07:05 · JFK 10:05
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.