什么是API接口

什么是API接口

原文:腾讯云—这才叫API接口开发

写接口看似是一个很简单的事情,但是往往越简单的事情越不容易做好,让我们看看如何写好一个接口。

什么是接口

接口其实是一种规范,在生活中随处可见,比如:不同厂商的水管使用统一的水管接口对接、电脑厂商和配件厂商按照统一的USB接口标准进行生产完成配对、应用程序之间通过API进行信息交互。

亚马逊集团创始人贝佐斯规定,亚马逊的所有系统、团队之间的交互,都必须通过服务接口,现在云计算技术非常火爆,其起源实际上是亚马逊的这种API文化,可以说,API构建了当前数字化世界的通路。

mmexport5fcf23a94c539fcd02dc2368e612edc7_1738900784299

翻译过来就是:

所有的团队必须通过服务来对外暴露数据和功能。所有的团队之间的功能,必须通过这些股务接口来进行交互。

网络上不允许任何除股务接口的其它交互方式。(不能通过Excel等文件传输)

不管具体使用任何的技术,都必须符合上面的3条原则

所有的股务接口必须都被外部化。

任何不遵循这个指令的人将会被解雇。

所以,软件世界中到处存在的API,到底应该如何实现?有哪些设计技巧?接下来让我们一起探索吧。

接口设计

契约式设计原则

Design by Contract (DbC)是一种设计计算机软件的方法。这种方法要求软件设计者为软件组件定义正式的,精确的并且可验证的接口,这样,为传统的抽象数据类型又增加了先验条件、后验条件和不变式,可以说它是API设计的指导书,一个好的API 实现之前,先要回答三个关键问题:

API期望什么?

API要保证什么?

API要保持不变的什么?

首先,API必须保证输入是接收者期望的输入条

件。比如:API需要A、B、C三个参数,那么使用

者要提前准备好这三个参数,当条件不满足时,会拒绝请求,当条件满足时,才会处理请求。

其次,API要保证输出结果的正确性。API在处理过程中遇到各种异常或错误,需要在内部做好处理,并最终将正确或期望的结果输出给使用者,而不应该把错误抛给其他系统去处理。

关注点分离

作为面向对象SOLID原则中的“1”,对我们的API 接口提供了非常有实战意义的指导价值,所以当你在设计一个API的时候,不要在一个接口中杂糅多种功能,更不要用很多个Flag联动多个入参,这样只会让你的接口变得异常混乱。举个例子,客户只需要一个USB接口,而你却给了他一个交换机,他怎么知道那根线插哪里?要插多少根?排列组合都有N种,客户直接原地崩溃。所以说,我们在设计接口时,要力争减少使用者的认知负担,提供单一纯粹的功能。

自我表达

一个好的接口,客户在拿到后能很简单的读懂,在开发联调过程中,能根据错误信息自己修正,那么这个接口就具备了良好的自我表达能力,你可以从以下两大方面去考虑:

接口命名

接口命名应该给使用者提供一种简单且直观的体验,从接口名即可了解此接口的具体功能。

使用被大家接受的通用缩写,如:Information->info, delete->del,message->msq等。

通过命名含义描述接口,比如获取用户列表:

GetUsers。注意此方式适用于RPC协议接口风格,RESTful风移不使用此形式,REST使用名词URI+动词形式。

错误信息

具有自表达的错误信息字段,且应该包含两点内容:

A:明确的错误指示。例如:“amount金额字段格式有误”。

B:错误后的解决方案。例如:“token字段缺失,请先登录”。

错误码设计

A:屏蔽内部逻辑。不要轻易暴露内部处理逻辑规律,防止接口被攻击。

B:便于快速定位问题。

错误码定义可以参考HTIP状态码,采用分段式,

形成“肌肉记忆”,方便定位问题,也易于扩展。

IMG_20250207_120843

互斥穷举

接口之间应该尽量遵守MECE(互斥穷举)原则,不

应该提供相互叠加的接口。例如:如果PUT

/orders/1/items/1接口用于修改订单项,那接口PUT /orders/1就不应该具备处理某个order-item的能力。

这样做的好处是不会存在功能重复的接口,造成维护和理解上的复杂性。如何做到完全穷举和彼此独三呢?

方案一:使用在线表移设计,标出每个URI的能力。

方案二:使用00D领域建模,聚合根作为根URL,实体作为二级URI设计。聚合根之间无任何联系,实体和聚合根之间责任明确。

举例:

聚合根和实体定义

Order 聚合根

句含多个Orderltem实体

负责管理订单的生命周期和相关操作

IMG_20250207_121255

快速失败

接口要快速验证条件,不满足要返回失败,不要等到已经击穿到应用最底部后,再去报错。这是“防御式偏程”的一种理念,一个具体的实践就是对于接口入参进行校验。

给接口设置超时时间,异常情况快速失败,也能提高接口并发,但是会造成一些不可用,需要权衡利弊。

幂等性

什么是幂等?简单来说,就是当一个操作多次执行所产生的影响均与一次执行的影响相同,则它是赛等的。现代服务逐渐往分布式架构发展,股务是分散的,如何保证并行调用的幂等?

以下是常见的几种方式:

使用唯一标识符

为每个请求生成一个唯一的标识符,并在股务端进行验证。如果服务端已经处理过该标识符对应的请求,则直接返回之前的结果,而不进行重复处理。

使用状态机

对于一些有明确状态的资源(如订单、支付等),可以通过状态机来确保操作的幂等性。每个状态转换只能发生一次,重复请求不会改变状态。

使用数据库唯一约束

在数据库层面使用唯一约束来防止重复数据插入。如果重复请求尝试插入相同的数据,数据库会抛出异常,股务端可以捕获并处理该异常。

使用乐观锁

我们在更新资源时检查版本号是否匹配。如果不匹配,说明资源已经被其他事务修改过,当前事务应该回滚。

使用悲观锁

悲观锁假设数据在大多数时间会发生冲突,读取数据时就加锁,防止其他事务修改数据,通常通过数据库的行级锁来实现,如使用“for update”。

使用分布式锁

分布式锁与悲观锁本质上相似,都通过串行化请求处理来实现幂等性。与悲观锁不同的是,分布式锁更轻量。在系统接收请求后,首先尝试获取分布式锁。如果成功获取锁,则执行业务逻辑;如果获取失败,则三即拒绝请求。

版本化

一个对外开放的服务,极大的概率会发生变化。业务变化可能修改API参数或响应数据结构,以及资源之间的关系。一般来说,字段的增加不会影响旧的客户端运行。但是当存在一些破坏性修改时,就需要使用新的版本将数据导向到新的资源地址。

版本信息传输通常有以下几种方式:

URI前微控制

这是最常见和直接的版本化方法,通过在URL路径中包含版本号来区分不同版本的API。

Header控制

通过在HTTP请求头中包含版本信息来实现版本化。这种方法可以保持URL的简洁。

示例:

使用自定义头:X-API-Uersion:1

使用Accept头:Accept:

application/und.example.v1+json

Query 控制

通过在URL中添加查询参数来指定版本号。

总结

比较推荐的做法是使用URI前缀。常见的反模式是

通过增加URI后缀来实现的,例

如/users/1/updateU2。这样做的缺陶是版本信息侵入到业务逻辑中,对路由的统一管理带来不便。

使用Header 和Query发送版本信息则较为相似,不同之处在于,使用URI前微在MUC框架中实现相对简单,只需要定义好路由即可。使用Header和Query 还需要偏写额外的拦截器。

接口优化

批量

示例:

Redis单次查询使用Mget, Pipline等方式,批量查询。

循环调用RPC接口耗时严重,优化RPC接口支持批量查询,替代循环调用。

本质上是减少不必要的网终10,但会对下游服务造成一定压力,需要综合考虑,不可滥用。

异步

示例:

用户请求接口中,需要做业务操作,发站内通知.可以同步执行操作写库,然后异步发通知。

区分核心和非核心操作,核心逻辑同步执行,非校

心逻辑异步执行。通常异步方案有:线程/协程池,消息队列,任务调度框架,任务回调。

并行

示例:

两个操作任务,各耗时200ms,顺序调用需要花费y00ms,改为协程调用,总花费只需要200ms。

将单个任务或串行任务拆分为多个子任务,利用多核处理器或分布式系统进行并行处理,提升处理效率。Golang中可以使用go关键字轻松实现并行调用,本文不做赘述。

空间换时间

示例:

用户信息访问频繁,变更小,通过redis缓存用户信息,用内存空间来提升响应速度。

代码中用列表存储要素,循环判断某个要素是否存在,改为使用字典存储,虽然空间占用变大,但是时间复杂度由0(n)降为0(1)。

MySQL数据库额外存储一份索引数据也是一种空间换时间的方案,提升查找了速度。

空间换时间是一种常见的优化策略,通过额外的内存、磁盘等存储介质,减少程序的执行时间。

池化

示例:

通常我们在使用MySQL时,会采用池化技术,复用MySQL链接,来达到重复利用的效果,避免了不必要的创建和销毁所带来的性能损耗。

在高并发场景下,Golang使用协程池来实现协程的管理,提升程序性能,如经典的高性能协程池开源库ants:https://qithub.com/panjf2000/ants。

通过预先创建一定数量的连接对象,当需要使用连接时,从池中获取一个可用的连接对象,使用完毕后归还给池,而不是每次都创建和销毁连接对象,从而提高系统性能和资源利用率。

预处理

示例:

用户打完王者荣耀对局后,通过客股入口举报违规玩家,我们通过预处理,调用各种数据接口和模型计算,并将信息存储下来,当工单流转到工作台时,接口可瞬时将信息拉取出来。

类似空间换时间的缓存思想,但是关注点不一样,我们可以将想要的数据提前计算好,放到缓存或数据库中,当真正需要的时候再进行取用,而不是实时计算,以此来提升接口性能。

SQL 优化

接口开发过程中难免会和数据库打交道,但数据库非本文的介绍重点,不对数据库的具体优化方案进行详细介绍,只提供一些优化思路,感兴趣请自行查阅相关资料。

深分页

这是一常见的分页操作,看起来没啥问题,但是加入X=5000000时,数据库server调用innodb接口,在非主键索引中获取第0到第(5000000+30)条数据,返回给server之后,根据offset=30,逐个抛弃,留下后面的30条,返回给业务。

优化方案:

如果并发较高,可以考虑使用缓存的方案解决性能问题。

避免大事务

为了提高接口并发量,需要避免大事务。当需要更新多条数据时,避免一次性更新过多的数据。因为update, delete语句会对索引加锁,如果更新的记录数过多,会锁住太多的数据,由于执行时间较长,会严重限制数据库的并发量。因此,要考虑多个操作是否真的有必要放在一个事务中,将核心操作放到同一事务中,非核心操作在事务外面单独执行。

索引

加索引能大大提高数据查询效率,这个在接口设计之初也会考虑到,其中的一些索引设计和优化技巧,这里不做过多赘述。

分库分表

分库:是为了解决数据库连接资源不足问题,和磁盘10的性能瓶颈问题。

分表:是为了解决单表数据量太大,sgl 语句查询数据时,即使走了索引也非常耗时问题,此外还可以解决消耗cpu资源问题。

不过,分库分表的成本太高,非必要尽量不要采用。单表千万级也不一定要分,要看字段和索引的具体的存储情况而定。

归档历史数据

在设计数据库表之初,就要结合业务发展,考虑数据库量级增长趋势,考虑好归档方案,防止库表膨胀过大,对接口性能产生影响。

锁粒度控制

为解决高并发场景下,多个线程同时修改数据时会造成数据不一致情况,通常我们会:加锁。但是如果锁加的不好,导致粒度太粗,也会影响接口性能,比如你要用卫生间,就不要把客厅门也锁上。

为了避免高并发场景下创建相同目录,第二次创建目录失败影响业务功能,我们加锁,但是也对上传文件和发送消息上锁,导致粒度太粗,影响了接口性能,降低了并发,好的做法是只对mkdir函数进行加锁,减小锁粒度。

合适的存储

MySQL换Redis:当查询压力较大时,可以使用redis缓存数据。

Redis换本地缓存:Redis相比传统数据库速度要快很多,大概是毫秒级别,但是使用本地缓存的耗时可以忽略不计,而且从redis取数据会存在大量的序列化和反序列化过程,增加CPU负担。

Redis 换Memcached:Redis基于单线程,可能存在阻塞导致性能降低,但是Memcached是多线程模型,在某些高并发场景下,性能可能会更高。

MySQL换ES:后台系统假如需要复杂的查询,使用索引的查询场景比较灵话,使用MySQL可能就存在瓶颈了,可以考虑同步数据到ES中,提高查询检索性能。

MySQL换HBase:如果MySQL中的数据过于魔大,可以考虑将历史数据转存至HBase中,以提供高效的查询性能,通过设置合理的Rowkey能进一步提高查询效率。

压缩传输内容

protobuf 协议相较于 json或xml,有更高的性能是因为其采用varints编码算法进行了二进制数据压缩。

HTTP 协议中的一个头部字段Content-Encoding,服务端可采用Gzip、Deflate、Br等算法压缩,提升传输效率。

接口安金

加密

敏感数据在传输过程中可能被抓包,想要保证数据安全,需要对数据进行加密,常见的加密方式有对称加密和非对称加密:

敏感数据在传输过程中可能被抓包,想要保证数据安全,需要对数据进行加密,常见的加密方式有对称加密和非对称加密:

对称加密:加密和解密过程中,使用相同的密钥,常见的对称加密算法有:DES、AES。优点是计算速度快,缺点为任一方密钥泄露都会导致信息不安全

非对称加密:股务端生成一对密钥,私钢放在服务端,公钥发布给其他人使用,这种方式比较安全,但是速度会慢一些。

加签验密

数据加签即由股务端用传输数据生产一串无法伪造的字符串,保证数据在传输过程中不被篡改。通常使用比较多的是MD5算法,将提交的数据通过某种方式组合,使用MD5生成加密串作为签名。当服务端接受请求时,就可以使用特定的组合方式生成签名进行对比,判断请求是否合法,此外可以将时间戳带入,增加超时校验,防止被抓包恶意攻击。

token 授权

客户端输入用户名和密码,服务端校验成功后返回一个唯一值Token并缓存在服务端,后续客户端访问需要携带此Token,服务端验证如果存在,则表明请求合法。同时我们要对Token设置合理的有效期,避免被抓包爬取数据。但是Token方案有缺点,比如:在分布式系统中,Token方案依赖分布式缓存,会让我们的系统变得复杂。现在大部分系统会使用JWT来生成Token,这种方式是无状态的,天然支持分布式。

防重放

某些特定场合下要保证一个请求只能处理一次,如支付场景。这是我们可以使用timestamp+nonce 方案,每次请求会生成nonce到集合中,股务端处理请求时会先判断该nonce是否在集合中,如果存在则为非法请求。永久保存nonce代价非常大,可以结合timestamp优化,比如超过3min认定为非法请求。

限流

用户是真实用户,但是想频繁调用接口搞垮你的系统,或者调用量很大的情况下保证系统不宕机,这时我们可以使用限流方案来保护系统。常见的限流算法:令牌桶算法、漏桶算法、滑动窗口、计数器等。

黑白名单机制

对用户进行分析,将恶意用户纳入黑名单,避免系统被频繁攻击。白名单类似,比如对某个用户开放特殊调用权限,通过加白实现。

数据脱敏掩码

密码、手机号、身份证等敏感信息一般需要脱敏掩码展示,在保存时也不能明文存储,一般使用哈希算法进行加密,常见的如SHA-256,也可加盐来增加安全性。

防 SQL 注入

常见的SOL注入方式

基于字符串的SQL注入:’OR’1=’1”,这种方式通常用于绕过登录验证。

基于联合查询的SQL注入:使用UNION关键字将恶意查询与原始语句结合,获取数据库敏感信息。

如:’UNION SELECT username, password FROM user’

基于字符串的SQL注入:’OR’1=’1’,这种方式通常用于绕过登录验证。

基于联合查询的SQL注入:使用UNION关键字将恶意查询与原始语句结合,获取数据库敏感信息。

如:UNION SELECT username, password FROM user

基于盲注的SOL注入:攻击者无法直接看到查询结果,通过观察响应来推断数据库信息。

基于堆叠查询的SQL注入:在一个语句中插入多个

SQL语句,如:;OROP TABLE users;”

防SOL注入应对方案

使用参数化查询:

使用参数化查询(也称为预处理语句)可以有效防止SQL注入,因为参数化查询会将输入数据与SOL 语句分开处理。

在Go语言中,可以使用database/sql包的Prepare 和Exec方法。

使用ORM框架:

使用ORM框架(如GORM)可以自动处理参数化查询,减少手动拼接SQL的风险。

输入验证和过滤:

对用户输入进行严格的验证和过滤,拒绝包含特殊字符的输入。

可以使用正则表达式来限制输入移式。

最小权限原则:

数据库用户应仅具有执行所需操作的最小权限,防止在SQL注入成功后造成更大损害。

使用存储过程:

将SQL逻辑封装在存储过程中,限制直接执行SQL 语句的机会。

错误信息隐藏:

在生产环境中隐藏详细的错误信息,防止攻击者获取数据库结构信息。

权限控制

我们需要对用户进行权限校验,来保证接口能被正确的使用。一般有三种粒度,第一校验是否登录,第二校验接口功能权限,第三校验接口数据权限。

通常内网系统,我们会使用RBAC来进行权限控制。

使用HTTPS

数据加密:HTTPS使用SSL/TLS协议对数据进行加密,确保数据在传输过程中的机密性和完整性,防止数据被窃取或篡改。

身份验证:通过SSL证书验证股务器的身份,确保用户问的是真实的网站,防止中间人攻击。

完整性保护:确保数据在传输过程中不被篡改,如果数据被篡改,接收方可以检测到这种篡改,并拒绝接受这些数据。

接口维护

文档

本人强烈反对单独使用文档维护接口,因为一旦文档和代码割裂只会逐渐缺失维护不再更新,要善于利用工具让代码和文档关联,自动更新。比如:开源的swaqger。

日志

合理区分和使用error、warn、info、debug 、trace5种日志级别。error较严重,对业务有影响,warn 是警告,对业务影响不大,需要开发关注。

日志要打印方法、入参和出参,方便定位问题,携带关键的信息参数,如userld, traceld等关键信息。统一使用合理的日志格式,包含基础的信息如:当前时间戳、日志级别、线程10等。

遇到条件分支,如有必要,可以在分支首行打印日志,方便定位问题,理清逻辑走向。

使用异步方式输出日志,可以提升接口性能。

禁止在线上开启DEBUG,可能会打满磁盘影响业务系统运行。

日志文件可以考虑单招等级分离,比如Nqinx的access.log 和error.log。

核心功能模块建议打印完整的日志,方便快速定位问题。

© 版权声明
THE END
喜欢就支持一下吧
点赞2赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容