GraphQL真的比REST好吗?我搞了一年,最后换回了REST

🔑 关键词:GraphQL,REST,API设计,性能对比

📖 摘要:从实际运维角度对比GraphQL和REST,分析GraphQL在中小团队中可能带来的问题,分享换回REST的决策过程。

我之前在做一个旅游预订平台,后台有十几个服务,前端小程序需要一次性拿景点、酒店、价格、评论,REST接口每次都要拼好几个请求。当时看GraphQL吹得天花乱坠,就花了三周把核心服务迁过去了。现在半年多下来,我自己的感觉是,GraphQL确实把前端的联调压力减轻了,但把复杂度转移到了后端和运维,而且这复杂度不是写几个resolver那么容易客服的。如果你的团队没有专职后端或者没有很强的架构能力,我真的建议你谨慎选择。

图片

先说我观察到的具体数据。我们的聚合查询,原来REST要调5个接口,平均响应时间760ms,GraphQL一个query只要200ms,因为机器在同一个内网,网络往返少了。但是,GraphQL服务端的平均负载上升了大概2.5倍。为什么?因为很多客户端发出的查询没有一个固定的模式,比如有的页面只需要list数组里的id和name,有的要全部字段,导致我们没法像REST那样用内置的include前缀做针对性缓存。我们用Redis做缓存,REST接口可以按URL直接缓存,命中率在82%左右,GraphQL的query是嵌套的,就算把签名hash了,字段一多就碎片化,命中率掉到了45%不到。后来只能在后端session里做缓存,代码写了一堆。

图片

除了缓存,GraphQL的坑还有几个。第一个是N+1查询,在list景点的时候,每个景点要查各自的评分库,resolver里如果不做DataLoader,磁盘IO直接爆炸。这个问题网上文章都写过,但是实际写起来真的很别扭。第二个是安全,我们接了一个大客户,他们的客户端用了一个无限嵌套查询,直接把我们的网关CPU打满,REST有路径长度限制,但GraphQL没有,后来只能去数深度,深度超过6就拒掉。你知道那套深度配置有多恶心吗?因为我们正常业务深度就有5,你设成6有风险,设成7又不够安全。第三个是错误处理,GraphQL让服务端返回200状态码,但body里有errors,我们的监控告警全乱了,不得不自己写中间件去解析。相比REST直接500、404,对运维太不友好了。

图片

后来换回REST是经过一次夜间大促。大促流量是平时十倍,我们动态库的连接数直接被打满,因为GraphQL的解析器和resolver牵扯了太多的join查询,每一条都拿着数据库连接不放。排查下来发现是几个手机端的旧版本在后台循环重试同一个深查询,造成了雪崩。那晚之后我们开了个会,大家一致决定降级成REST聚合层,也就是保留一个BFF服务,专门为小程序拼几个大接口。说白了就是用REST的方式做前后端对接,而GraphQL只是作为BFF内部的一个查询语言来用。现在跑了一年了,没再出过缓存失效的问题。

图片

我的观点可能跟主流不太一样。GraphQL是好工具,但不是给大部分中小团队用的。如果你只有一个前端一个后端,一套标准REST足够,很多问题根本遇不到。GraphQL适合那种数据模型很复杂、客户端类型非常多、而且你真的有专职平台组去维护这种高复杂度网关的大厂。否则你会陷入无休止的类型定义、字段合并和权限控制。我自己现在如果新项目,第一选择肯定还是先REST,除非业务模型复杂度已经明显超过REST能管理的地步。另外如果你想跟风试试,建议别把核心业务直接怼上去,从独立的读服务先做,并且提前想好怎么去做深度限制和缓存,这个比选型更重要。

图片

🏷️ 标签: