• Feeds

  • 架构面试题 – 为什么我的朋友圈不见了?

    经常有朋友问到,“感觉你们的系统最近没什么太大变化,你们那么多工程师在忙什么?”,下面的这个场景,可能是工程师花费了不少时间的情况之一。

    有如下一个场景,某个服务需要构建一个列表数据返回给调用方(调用方通常是客户端),服务本身是一个数据聚合器,它由内部多个远程服务的数据聚合而生成。在正常情况下,需要将所有内部服务的结果全获取成功后再返回。但是在一个大系统中,多个服务中某个服务出现不稳定的概率会比较大,当出现如图远程服务3不可用的时候,有三种不同的解决思路。

    list1

    • 方案1:忽略出错的数据(图中数据3),直接返回数据1、2、4。
    • 方案2:遇到任意失败,整个请求返回错误503 service unavailable。
    • 方案3:忽略出错的数据(图中数据3),并告知调用方出错的范围,需要自定义的返回格式。如 {“load_data3_success”: false}

    如果你作为一个架构师,会选择哪种方案?

    方案一类似架构设计里面常说的优雅降级,在出现问题情况下,除了数据3不能返回之外,其它数据可以正常返回,原理上可以将损失降低到最低。但这种方案会给用户体验带来一定伤害,用户在使用系统时候会存在不确定性的心理感受。

    方案二比较依赖调用方的容错逻辑,如果调用方保存了上一次缓存,且容错逻辑处理得当,用户表面会感受不到这个异常。如果没有容错逻辑,最坏情况则将会返回白页。但是即使有容错逻辑,由于正常的数据也不能及时返回,从工程师到用户可能不太容易接受这个结果。

    方案三是一个看起来相对合理的方案,但是需要添加自定义的字段,本来这个调用是一个标准的LIST数据返回,但如要判断每个数据项是否返回失败,需要额外添加一些标识字段如 {“load_data3_success”: false},用于标识哪些数据返回失败了。因此,接口设计及实现变得更加繁琐,调用方也需要实现缓存及容错逻辑,从服务方到调用方的熵都增加了很多。

    因此,这个选择题已经不好做了。但雪上加霜的是,在大部分应用中,对于数据列表访问同时还存在未读数的功能,如下图中的小红点数字。如果这个未读数由另外一个API提供(本讨论假设未读数API功能正常),情况就更复杂。

    补充讨论一下,如果不提供单独的未读数API,客户端需要每次需要加载新的全量数据才能本地算出未读数,会带来访问速度的下降及客户端更多流量的消耗。因此大多数情况提供一个未读数API整体开销会更低。通过未读数API判断当服务端有新数据时候才去访问列表接口。

    list2

    这时候如果未读数都出来了,远程数据又取不到的情况下,你作为架构师,会选择何种方案?至少,碰到这种情况时如果还未找到理想方案,建议不要盲目优化,因为它除了增加系统的熵,不会将事情变得更好。

    如想及时阅读Tim Yang的文章,可通过页面右上方扫码订阅最新更新。

    « | »

    9 Comments  »

    1. TimWong

      首先要看看场景,对客户端来说,1,2,3,4是“一块”数据还是多块数据,如果只是一块数据(即客户端不需要区分1,2,3,4),我个人十分不认同方案3的做法,如果客户端要区分1,2,3,4,那就不适合将1,2,3,4放在一个接口处返回。

      用方案一还是方案三,要由需求方结合应用场景取舍了(大多数场景下,降级是一个好的选择),架构上能做的是,在中间接口层做好缓存,尽量屏蔽来自后端service层的fail

    2. wangchen1ren

      个人认为,首先方案3是不合适的,因为向客户端暴露了内部结构,架构重构代价会巨大。接口应屏蔽实现。

      方案1和2取决于场景,当数据3是非必要数据时,优雅降级可以接受;当数据3是必要数据时,直接503更好。
      但是问题是,如果数据3是必要数据,即业务是耦合的,为什么还要把服务分离出来呢?而且还是个不稳定服务

      感觉仅有一种情况是,如果数据3是必要数据,且服务3是第三方提供的,那要做的应该是先把服务3包成业务上可接受的稳定服务,再采用方案2。
      否则其他情况,采用方案1更合适。

    3. gongweixin

      同有 TimWong 的疑问 1,2,3,4对客户端来说是“一块”数据还是多块数据

    4. Tim

      对用户来说是“一块数据”,对应功能上的一个PAGE;对技术人员来说这个区分无关紧要,因为它是一个非常主观的判断,你认为它是一块就是一块,你认为它是多块就是多块。

    5. 关键问题,还是要在“服务3”上下功夫吧,在给定的结果下,只能根据业务对数据一致性的要求,来做选择。

      较好的选择,应该是深入分析“服务3”,到底有什么问题,自身可以如何降级,降级的开销如何?

    6. 乐百事

      1

    7. 轩脉刃

      我遇到的大部分场景都是适合选择1,直接降级

    8. TimWong

      不同意数据块的区分不重要。

      很多时候,虽然1,2,3,4是同一个page的内容,但可以不直接相关,而仅仅是为减少请求并发数和流量而采取的请求合并。就比如当前博客页面中的几个模块,有些设计就会把它们放在同一个接口返回。明显地,这种场景,只要博客正文这块不挂,Similar Posts,Most Commented这些fail了是可以直接降级的。

      就算直接相关,只要数据能区分轻重缓急(后端分了多个接口处理,大多数情况下就是事实上的区分了),降级都是一个优先选择。

      很多人会花很大的代价去处理一个小问题而不愿意优先考虑局部降级,我认为这是一个不好的做法

    9. C

      貌似把问题说的过于复杂化了,本质上就是调用方(客户端)的容错处理问题,是一个根据业务场景的容错决策问题,假设1 2 3 4是无相互依赖的,假设1+2+3+4才是一个整体等等,看具体应用,包括是否缓存或给一些安全数据等等也是决策和安全编码问题。

      个人更加倾向于把方案1 2 3的工作推到服务3里去(或在服务3前面加一个稳定的代理层),可以给返回的数据加一个脏数据标记等,以便调用者做相应处理。

    Leave a Comment