17 如何设计一锤子买卖的SDK

在前三个模块里,我将微服务根据目的性划分为三大类:读、写与扣减类,并针对每一大类涉及的各项技术问题讲解了应对方案。其实,每一类微服务除了本身业务特点涉及的技术问题外,在纯技术维度也有很多共性问题,比如 SDK 如何设计、服务如何部署等。

本模块将针对上述微服务中的共性技术问题进行深入讲解,首先咱们先来讨论微服务对外的门面——对外接口的 SDK 如何设计。

微服务骨架全局观

参考维基百科对于微服务的定义:微服务是指通过技术语言无关的协议(如 HTTP、ProtoBuf等)向外提供业务服务(常以接口的形式)的独立进程。它具有规模小、支持异步消息通信、可独立部署,以及可实现构建和分发自动化的特点。

结合上面的描述和前几模块里讲解过的架构图,便可以得到一个微服务所包含的内容,具体由以下 6 个部分组成:

  • 对外暴露的接口,由它直接对外提供各类业务服务能力;

  • 消费其他微服务发送过来的消息;

  • 可独立部署的微服务的代码;

  • 微服务持久化数据所依赖的数据库、缓存等存储;

  • 微服务完成一项业务能力需要依赖的其他微服务,比如提供提单的微服务就需要依赖库存微服务的扣减接口;

  • 微服务对外也会发送消息,来完成微服务间除接口以外的通信。

基于上述介绍的微服务中涉及的几大组件,可以将它们进行归类,梳理出如下图 1 所示的架构:

图 1:微服务骨架图

我将上述微服务里提到的 6 个组件分为三大类:

  • 第一类为对外提供的接口和接收的外部消息,称为上游;

  • 第二类为微服务本身;

  • 第三类为微服务依赖的其他组件,称为下游(如存储、其他微服务接口等)。

把这个分类和前几模块里提及的各种架构图对比,你会发现它们都包含上述三大类中的全部或部分内容,比如有的微服务依赖存储,有的依赖其他微服务。

在构建高可用微服务时,可以从上述三大类微服务进行入手。在本讲以及后续的两讲,我将按此思路讲解微服务对上游、自身以及下游(外部依赖)如何进行设计,以便构建一个更加健壮的微服务。

在详细讲解之前,我先剧透下构建微服务这三大类部分的高效法则(技术顺口溜):防备上游、做好自己、怀疑下游。

关于上述三个口号表达的意思,以及它们的由来,我将在下面三讲慢慢道来。

为什么说 SDK 是一锤子买卖

微服务对外是以接口形式提供服务的,当接口开发完成上线,运行一段时间之后,形成的全局架构如下图 2 所示:

图 2:全局架构图

从上述的架构图里可以看到,接口上线后外部使用方会不断增多。假设上述外部调用方使用的某一个接口里的某一个方法的格式如下:

void func(long args1,int args2)

此时,因为新的业务需求,你需要对该接口的上述方法的名称和入参数量进行变更。修改后的格式如下:

void func_new(long args1,int args2,long args3)

如果要改成上述格式,你不能直接升级,因为上述两个格式不兼容。如果你先上线,所有的调用方都会报错,因为接口名和方法个数都变了。上述接口你需要提供灰度过程,大致如下:

  • 在微服务里同时提供上述两个接口;

  • 推动所有的外部调用方切换到修改后的接口;

  • 确认老接口没有调用量后,方可将老接口下线。

从表面来看,只需要三个步骤就可以完成灰度发布过程,但上述第二步操作所需的时间远远超乎你的想象。如果调用方很多,推动所有调用方完成切换的时间短则几周,长则需要半年或者更久,成本非常高。

所以,定义新的接口时需要考虑未来兼容性,如果接口上线后再想要修改,则需要花费较高的成本。因此,包含一个微服务所有对外接口的 SDK 是一锤子买卖,设计时需要考虑清楚。

如何设计稳固的 SDK

因为 SDK 一旦上线后,修改成本会非常高。因此在设计 SDK 时,有一些基本原则建议你遵守,减少上线后的维护成本。

第一个原则:增加接口调用鉴权

当微服务对外提供的接口上线后,理论上所有需要此接口功能的使用方都可以随意调用此接口,微服务的提供方不应该设计鉴权等手段限制调用方的使用。

考虑如下场景后,可能你的想法会稍微改变。

  • 你的接口当前能够支持的最大 QPS 为 1W,而新的调用方会带来每秒 10W 的 QPS。如果这个新的调用方在你还没有完成扩容前,就直接上线,导致的结果可能是你的微服务被瞬间打挂。

  • 接口的入参有一个 Map 字段,文档未有明确标注,但实际此 Map 字段最大支持 100 个 Key 的设置,如果超过 100 个 Key 就会报错。因为没有调用前的申请审批,新接入的调用方的场景里有可能会传入 150 个 Key,导致的结果是直接报错,进而可能产生线上问题。

  • SDK 提供了查询和写入的接口,但查询的接口是基于缓存或 ElasticSearch 实现的,是有毫秒级延迟的。而使用方期望写入后,通过查询接口可以立马查询到数据。如果新的接入方没有前置的审批沟通,直接接入后,会发现接口和预期并不一致,可能会使得此次接入变成无用功,导致成本浪费。

通过增加鉴权,所有的调用方在使用前都需要申请接口调用的权限,在申请的过程中,你可以针对上述提到的问题和调用方一一进行确认,防止出现意外的情况。

第二个原则:接口里的入参需要是对象类型,而不是原子类型

原子类型是指非面向对象里的类,在里面不能再定义字段的类型。比如编程语言里的 int、long、float 等类型。

对象类型是指面向对象里的类,比如如下格式:

class ObjectA{ private long args1; private int args2; }

对象类型的好处是当有新的需求时,可以在其中新增字段,而不是修改接口的签名。

在上一小节介绍了 SDK 是一锤子买卖的示例,如果原始接口定义的是如下格式:

void func_new(ObjectA object1)

当一个新的需求需要在入参增加 args3 字段时,便可以直接在 ObjectA 这个类里添加,而不是修改接口的签名。这样设计的好处是向后兼容,只有此次新需求需要使用 args3 字段的调用方才需要升级,而不关心此字段的历史调用方都不需要升级,可以节约推动外部所有客户升级的时间。

第三个原则:接口的出入参不要设计为 Map<String,String> 等集合格式

出入参使用了 Map 格式的设计如下:

Map<String,String> func_new(Map<String,String> args);

这样设计的好处是特别灵活,当接口在日常的升级中需要新增一个字段,如第二个原则里提到的,新增 args3 字段时,整个接口都不需要做任何更改。因为 Map 的 Key 是动态的,可以随意由外部客户传入的。

虽然这样设计有灵活性的优势,但劣势也比较明显。

  • 首先,代码非常难维护。因为 Map 里的 Key 是动态的且是文本的,要识别这些 Key,你需要在代码里使用魔术数或者硬编码进行识别。随着时间的流逝,这种方式会导致代码里随处可见的硬编码,代码阅读起来非常不直观。

  • 其次,Map 的方式是动态,理论上调用方可以往 Map 中插入成百上千的数据。极端情况下,这些数据会把微服务的内存瞬间打挂,对系统的稳定性影响非常大。

第四个原则:入参需要增加条件限制和参数校验

可以分别对读和写接口进行分析。

首先,对于对外暴露的写接口,如果不增加参数校验,可能会导致后续业务无法正常流转。

  • 外部调用方可以传入超过数据库长度限制的参数,有些数据库会直接拦截,并生成数据超长的错误,而有些数据库可能会默认地将数据截断并存储。

  • 对于如手机号、邮箱地址等自带业务格式的数据,如果不做格式拦截,将不符合格式的数据写入数据库之后,后续的业务可能无法流转。比如订单里的收货人的手机号码,如果写错,可能导致订单无法正常配送。

其次,对于对外暴露的读接口,如果不增加参数校验,可能会把数据库打挂。

  • 如果你提供的一个翻页查询功能,常见的查询是使用数据库的"limit startIndex,size order by xx 字段"来进行实现的。如果你不进行参数验证,理论上调用方可以传入值为 100000 的 startIndex。实际上,随着 startIndex 的增大,limit 的性能会非常差,极端情况下,如果量太大,数据库很容易挂。

  • 如果你提供了如 like 等模糊匹配功能,如果外部传入一些正则表达式里非常耗费性能的语法,也是有可能把数据库打挂的。

第五个原则:写接口需要保证幂等性

考虑一种场景,如果外部客户调用你的接口超时,它能如何处理?

答案是:只能进行重试或者反查,不然别无他法。

因为超时后,调用方并不知道此次写入是否成功,有可能成功,也有可能不成功。通过反查调用方可以确定此次调用是否成功;通过重试,调用方期望你告诉它,上次写入已经成功,无须重试。

上述的反查和重试,技术上称为幂等性。写接口的幂等可以在入参增加一个当次调用的全局唯一标识来实现,同时该唯一标识需要写入数据库中,并在数据库里将该字段设置为唯一索引即可。架构如下图 3 所示:

图 3:写接口幂等性架构

通过上述的架构,当超时后,调用方对于当次调用再次重试时,如果前一次超时的请求已经写入成功,那么数据库的唯一索引会对重试请求进行拦截,并提示唯一索引冲突,无法写入。此外,如果调用方选择反查而不是重试,它也可以使用唯一标识进行反查,如果上一次超时的请求已写入成功,反查也能够查询到数据。

关于如何生成全局唯一标识,可以参考“08 | 如何使用分库分表支持海量数据的写入”里介绍的几种方法。

第六个原则:接口返回的结果需要统一,可以直接抛出异常或者使用结果包装类(如 RPCResult)

对外的 SDK 会包含一组接口,这些接口对外返回的格式需要保持统一,要么全是正常业务对象+异常的格式,要么全是通过 RPCResult 包装业务对象的格式。这两个格式没有绝对的优劣之分,但统一的格式有利于调用方统一处理,两种格式混合的方式会增加调用方的处理成本。

显式抛出异常的格式如下:

Object func_new(Object args1) throws RPCException

其中 RPCException 中需要包含如下字段:

Class RPCException{ private boolean success;//是否成功 private int code;//如果错误,详细的错误码 private String msg; }

使用 RPCResult 包装类的格式如下:

RPCResult<Object> func_new(Object args1)

其中 RPCResult 中需要包含的字段和上述 RPCException 需要包含的格式基本一样,此处不再赘述。

可以看出,这两种方式中包含的错误信息基本一致。唯一的区别是:异常的方式除了会包含上述信息外,也会包含一些报错的堆栈信息,如下格式:

"thread name" prio=0 tid=0x0 nid=0x0 runnable at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) at java.net.SocketInputStream.read(SocketInputStream.java:171)

因为 RPCException 里已经包含当次请求是否错误,以及导致错误的详细原因,即其中的错误码(code 字段),此外异常的堆栈信息是为了方便微服务的提供方进行问题排查,调用方无须关心,因此,在实际开发中,你可以显式地把 RPC 中抛出异常的堆栈信息屏蔽掉。现在主流的编程语言均已提供上述功能。

最后,不管是 RPCException 还是 RPCResult 里都包含的错误码,即 code 字段,这样做是为了方便调用方能够快速知道导致出错的具体原因,进而根据不同的原因做相对应的处理。比如在有些情况下:

  • 调用方传入的参数不合法,如电话号码传入了字符,导致检验不通过;

  • 微服务提供方依赖的存储故障,如缓存、数据库等宕机等,进而导致当次调用产生错误。

当出现上述两种错误时,对应的处理方式是不一样的。

  • 如果是传入的参数格式错误了,你需要提示客户修改格式重新输入,而不需要联系此微服务的提供方进行处理。

  • 如果是上述第二种错误,你需要立马通知微服务提供方,让对方尽快修复故障,因为下游出现错误,你能做的便是尽快通知。

在实际实践中,使用 RPCException 还是 RPCResult 其实都可以,只要保持统一即可。不过不管格式如何,上述两个对象都需要包含上述字段。

第七个原则:返回的数据量必须要分页

如果存在以下格式的接口定义,它表示此接口的功能是返回一批数据:

List<String> func_new(Object args1);

如果接口的入参里没有显式地设置当次查询数据的具体数量,假设当次查询条件命中的数据量非常多,那么一次返回的数据量就会非常多,可能达到上千 KB 或者上百 MB 的数据。

上述这个批量获取数据的接口,如果不分页,会存在以下两个问题:

  • 首先,获取这么大量数据的查询条件,在查询的时候,可能会把数据库或缓存打挂;

  • 其次,数据量越多,网络传输的时间也越长,直接的体现就是接口的性能非常差。

因此,建议所有对外批量接口都增加分页,而不是一次吐出所有数据。这样既可以提升稳定性、又可以提升性能。

第八个原则:所有的接口需要根据接口能力前置设置限流

最后,即使经过上述的几个步骤后,仍有可能一个通过鉴权审批后的调用方,它的系统在某一个时间点出现故障,或者因为一些热门活动导致流量出现飙升,假如这个突发流量超过你的微服务的最大承载量,即使遵循了上述的第一个原则:调用前的鉴权,也无法限制通过鉴权后的调用方带来的突发流量。

对于可能产生的异常流量,可以使用在“16 | 秒杀场景:热点扣减如何保证命中的存储分片不挂?”里提到的前置限流策略来预防。

消息的消费

消息消费指的是你的微服务接受其他微服务发送的消息的场景,在实践中梳理时,此方式的高可用较容易忽略。其实,此种方式和上一小节里的接口方式非常类似,只是消息是异步的形式。它和微服务间的同步调用架构如下图 4 所示:

图 4:消息异步消费和接口间同步架构的对比

如果消息消费和接口调用相类似,那么上述接口里的一些原则在消息里依然可以复用,可以参考以下内容。

  • 消息消费需要有前置限流。当消息发送方发送量暴增时,限流可以保证消息消费服务的稳定。

  • 对于消息消费需要保证幂等,不然当消息出现重试后,会出现业务上的脏数据。

  • 消息的数据在消费处理时需要进行前置参数检验。如果未做前置参数校验,同样也有可能写入一些不合法的脏数据。

本节总结

在本讲里,梳理了如何设计对外 SDK 里的接口的原则,以及如何设计和它架构上相类似的消息消费的原则。在实际工作中,你可以通过这些原则,构建一个更加高可用和兼具兼容性的系统。

你应该还记得,在本讲的开头我直接给出了对外接口的设计准则:防备上游。通过本讲介绍的 SDK 的几个落地的细节手段便可以看出原因,它们都是对上游调用方进行鉴权、限流、入参前置校验与拦截,这些都属于防备外部调用的具体手段。

因此,防备上游是对外接口设计的基本准则。

最后,留一道思考题。你们团队在微服务对外接口里还有那些准则?可以在留言区和大家一起分享。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/580687.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

房产中介小程序高效开发攻略:从模板到上线一站式服务

对于房产中介而言&#xff0c;拥有一个高效且用户友好的小程序是提升业务、增强客户黏性的关键。而采用直接复制模板的开发方式&#xff0c;无疑是实现这一目标的最佳途径&#xff0c;不仅简单快捷&#xff0c;而且性价比极高。 在众多小程序模板开发平台中&#xff0c;乔拓云网…

docker容器通俗理解

前言 如果大家没使用过Docker,就在电脑上下载一个VMware Workstation Pro&#xff0c;创建一个虚拟机安装一个windows操作一下感受一下,为什么我的电脑上还以再安装一台windows主机&#xff1f;其实你可以理解为Docker就是Linux系统的一个虚拟机软件。 我的Windows也可以安装…

WMS仓库库存管理软件如何优化工厂的仓库管理-亿发

如果一家工厂没有专业的WMS仓储软件支撑&#xff0c;管理原材料、辅料、半成品和产成品等环节可能会面临诸多问题。 在仓库管理方面&#xff0c;缺乏安全库存的管理会导致库存不足或过剩&#xff0c;而没有及时的缺货分析可能会导致生产中断。全凭人工核算剩余库存和订单质检的…

金价大跳水,美梦变噩梦!2024真正适合普通人的靠谱创业项目!2024适合30-40岁轻资产小生意

4月22日晚间&#xff0c;向上“狂飙”了一个多月的金价突然就“大跳水”。当日&#xff0c;每克金价均下调14块。在这次跳水中&#xff0c;有人欢喜有人愁&#xff1a;有投资者自报做空金价一夜狂赚14万&#xff0c;也有投资者哭诉&#xff0c;头晚进货到早上就净亏损2万&#…

Android 11 bindService 流程分析

我们可以使用bindService来跨进程通信&#xff0c;其使用方法如下 Intent intent new Intent("xxx"); intent.setPackage("xxx"); boolean result bindService(intent,new ServiceConn(),BIND_AUTO_CREATE);private class ServiceConn implements Servi…

STM32入门_江协科技_1~2_OB记录的自学笔记_STM32简介

1.综述 1.1. 课程简介 手打代码是加入了实操&#xff0c;增加学习效果&#xff1b; STM最小系统板面包板的硬件平台&#xff1b; 配套0.96寸的显示屏&#xff0c;便于调试&#xff1b; 因为使用面板板&#xff0c;所以如果程序现象不出来也有可能是硬件连接的问题&#xff1b; …

Allegro画PCB时如何只删除走线的一部分

如何只删除走线的一部分 1、第一步&#xff1a; 2、第二步&#xff1a; 3、第三步&#xff0c;点击相应的走线段就能删除了。 说明&#xff1a;上面的Cline和Line只的是电线和线,您按下删除后,就可以删除这两种东西,但删除的是一整条折线.把这两个取消掉,换成Cline Segs和Ot…

【代码随想录刷题记录】LeetCode283移动零

题目地址 1. 思路 1.1 基本思路及假设 拿到这个题&#xff0c;首先想到&#xff0c;这是类似删除元素的方法&#xff0c;因为删除元素也是移动元素&#xff0c;但是移动的方向和删除元素的方法刚好相反&#xff0c;我们都知道&#xff0c;如果在数组中删除某个元素&#xff…

【Docker】docker部署lnmp和wordpress网站

环境准备 docker&#xff1a;192.168.67.30 虚拟机&#xff1a;4核4G systemctl stop firewalld systemctl disable firewalld setenforce 0 安装docker #安装依赖包 yum -y install yum-utils device-mapper-persistent-data lvm2 #设置阿里云镜像 yum-config-manager --add…

vue2主体页面进行拆分

目录 一.组件化 二.新建Header.vue页面 三.Aside.vue代码 四.Main.vue代码如下 五.Home.vue代码如下 六.index.js代码如下&#xff1a; 七.项目效果图 在Vue.js 2中&#xff0c;将主体页面进行拆分是一种常见的做法&#xff0c;它有助于提高代码的可维护性和可读性。页面…

js实现简单的级联下拉列表

代码如下&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><script src"js/jquery.min.js" type"text/javascript" charset"utf-8"></script><st…

Linux的磁盘分区,格式化,挂载

1.需要提前添加几个磁盘&#xff0c;以做实验 2.把nvme0n2磁盘用来分区实验 3.分了一个主分区&#xff0c;和一个扩展分区&#xff08;扩展分区是不能使用的&#xff0c;所以又在扩展分区里分了一个逻辑分区&#xff09;分区的大小自己定义 4.格式化分出来的区&#xff0c;这…

618不可错过的数码好物精选!等等党必看清单汇总

无论是追求高效工作的职场人士&#xff0c;还是热爱科技、追求品质生活的消费者&#xff0c;都希望能找到那些既实用又富有创新精神的数码好物&#xff0c;现在正值618购物狂欢节来临之际&#xff0c;我精心为大家挑选了一份不可错过的数码好物清单&#xff0c;这份清单不仅汇聚…

App一键直达,Xinstall助力提升用户体验

在这个移动互联网时代&#xff0c;App已经成为了我们日常生活中不可或缺的一部分。然而&#xff0c;每当我们在浏览器或社交平台上看到一个有趣的App推荐&#xff0c;点击下载后却往往要经历一系列繁琐的跳转和确认过程&#xff0c;这无疑大大降低了用户体验。那么&#xff0c;…

数据结构 - C/C++

快速跳转 数组链表栈队列串树 目录 数据结构 逻辑结构 物理结构 数据结构 数据 数据不仅仅包括整型、实型等数值类型&#xff0c;还包括字符及声音、图像、视频等非数值类型。 计算机可以理解并按照指定格式处理。 结构 元素相互之间存在一种或多种特定关系的数据集合。 …

Git 常用命令大全

&#x1f680; Git安装与基础知识学习 &#x1f310; &#x1f3af; Git作为一款全球开发者广泛使用的分布式版本控制系统&#xff0c;能够有效帮助团队协作并追踪项目历史版本。接下来&#xff0c;我们将详细展开Git的安装流程、基础命令操作、高级用法以及应对常见问题的方法…

西湖大学赵世钰老师【强化学习的数学原理】学习笔记1节

强化学习的数学原理是由西湖大学赵世钰老师带来的关于RL理论方面的详细课程&#xff0c;本课程深入浅出地介绍了RL的基础原理&#xff0c;前置技能只需要基础的编程能力、概率论以及一部分的高等数学&#xff0c;你听完之后会在大脑里面清晰的勾勒出RL公式推导链条中的每一个部…

信息系统集成对企业的影响到底有多大?

什么是信息系统集成 系统集成&#xff08;System Integration&#xff09;是指将若干个独立运作的系统或服务联通并整合的过程&#xff0c;旨在将那些存在交集或重复功能的分散模块融合为一个协同工作的整体&#xff0c;以实现效能的最大化和资源的最优配置&#xff0c;避免不…

找不到mfc140u.dll文件如何处理?这三种方法帮你快速修复mfc140u.dll

当你的电脑出现提示&#xff0c;显示找不到mfc140u.dll文件&#xff0c;从而无法继续执行代码&#xff0c;你需要知道如何应对这种情况。今天我们就来详细说明如何解决mfc140u.dll文件丢失的问题&#xff0c;并对该文件进行详细分析。这个文件是Microsoft Visual Studio的一个重…

AI文章写作网站

最强AI文章写作网站——心语流光&#xff08; Super Ai Writer &#xff09; 特点 多轮问答写作&#xff0c;自动携带历史记录进行问答可以自定义携带历史记录的轮数&#xff0c;为0则携带全部历史记录&#xff0c;有效避免token浪费&#xff08;类似coze平台&#xff09;AI生…