后端接口设计开发经验分享

前言

作为后端研发人员,平时需要经常做服务接口设计及开发,需要与前端进行接口联调,排查生产环境线上问题。因此,后端工程师,核心基本工作就是如何把一个接口设计好,以下梳理一些接口设计开发规范及注意事项,希望对大家有所帮助。

1、接口参数校验(入参和出参)

接口入参和出参都需要进行校验, ① 例如入参是否不能为空,入参数据长度,入参是否符合预期规则,很多bug由于未做参数校验导致,对于可能改变的参数建议设计为对象类型; ② 对于返回值,当返回值为空时是否返回为空串、空对象、空数组,需要与前端约定好。

2、接口老版本兼容性

C端服务接口,可能移动端发版不会强升级或者存在前后端上线时间差异,就会导致线上环境存在使用老版本的用户,如果新添加了参数,需要考虑前端未传入时给默认值情况;例如

 

javascript

复制代码

//老接口 void oldMethod(A,B){ //兼容新接口,传个null或其他默认值代替参数C newService(A,B,null); } //新接口,暂时不能删掉老接口,需要做兼容。 void newMethod(A,B,C){ ... }

3、接口扩展性考虑

① 例如业务中,在用户调用拨打电话接口之后会进行消息推送,是直接就开发一个消息推送功能,还是将消息推送梳理为一个通用流程,在所有需要使用的地方进行调用即可,保留扩展性; ② 消息推送流程,设计为通用流程,同时采用接口定义,可以扩展实现多种消息推送方式。

传入参数

构建目标用户群

调用消息中心接口发送

记录发送结果

4、接口防重处理

① 对于查询类型、删除类型接口,不论调用多少次,都是不会产生错误的业务数据,因此不用做防重处理; ② 对于新增和修改,例如转账或者提现类接口,重复提交就会多次转账和提现,影响业务需要做防重处理,让前端传入请求序列号,可以采用redis、LRUMap、数据库防重表、分布式锁等处理。

id获取全局请求token

写入redis缓存

请求时带上token

后端删除

再次请求提示重复

5、核心接口,线程池隔离

登录接口、首页数据接口、转账提现接口等,都可能使用到线程池,某些普通接口也会使用线程池,如果不做线程池隔离,普通接口出bug线程池打满,会导致登录等主要业务受到影响。

普通接口

消息推送

写入日志

核心接口

首页加载

登录接口

线程池隔离

6、关键接口,日志打印

关键业务代码,需要打印日志进行保驾护航,在入参和出参位置或者其他关键位置,良好的日志打印具有如下好处: ① 方便排查定位线上问题,划清问题责任; ② 生产环境不能直接debug,必须依靠日志查问题和具体异常。

7、三方接口异常、重试、超时

如果调用第三方接口,或者分布式远程服务的的话,需要考虑: ① 异常处理 比如,你调别人的接口,如果异常了,怎么处理,是重试还是当做失败还是告警处理。 ② 接口超时 没法预估对方接口一般多久返回,一般设置个超时断开时间,以保护你的接口。之前见过一个生产问题,就是http调用不设置超时时间,最后响应方进程假死,请求一直占着线程不释放,拖垮线程池。 ③ 重试次数 你的接口调失败,需不需要重试?重试几次?需要站在业务上角度思考这个问题

8、接口功能单一性原则

单一性是指接口做的事情比较单一、专一。比如一个登陆接口,它做的事情就只是校验账户名密码,然后返回登陆成功以及userId即可。但是如果你为了减少接口交互,把一些注册、一些配置查询等全放到登陆接口,就不太妥。 其实这也是微服务一些思想,接口的功能单一、明确。比如订单服务、积分、商品信息相关的接口都是划分开的。将来拆分微服务的话,是不是就比较简便啦。

9、接口部分场景采用异步处理

举个简单的例子,比如你实现一个用户注册的接口。用户注册成功时,发个邮件或者短信去通知用户。这个邮件或者发短信,就更适合异步处理。因为总不能一个通知类的失败,导致注册失败吧。 至于做异步的方式,简单的就是用线程池。还可以使用消息队列,就是用户注册成功后,生产者产生一个注册成功的消息,消费者拉到注册成功的消息,就发送通知。

注册成功消息

发送通知

id2生产者生产消息

存储端

消费者消费

10、接口查询优化,串行改为并行

假设我们设计一个APP首页的接口,它需要查用户信息、需要查banner信息、需要查弹窗信息等等。那你是一个一个接口串行调,还是并行调用呢? 可以使用CompletableFuture 并行调用提高性能。

 

perl

复制代码

// 查询获奖经历 LambdaQueryWrapper<RewardExp> rewardExpQuery = new LambdaQueryWrapper<RewardExp>() .eq(RewardExp::getResumeId, resume.getId()) .eq(RewardExp::getDelFlag, NORMAL) .orderByDesc(RewardExp::getDate); CompletableFuture<List<RewardExp>> rewardExpFuture = CompletableFuture.supplyAsync(() -> rewardExpMapper.selectList(rewardExpQuery) ); // 查询资格证书 LambdaQueryWrapper<Credential> credentialQuery = new LambdaQueryWrapper<Credential>() .eq(Credential::getResumeId, resume.getId()) .eq(Credential::getDelFlag, NORMAL) .orderByDesc(Credential::getDate); CompletableFuture<List<Credential>> credentialFuture = CompletableFuture.supplyAsync(() -> credentialMapper.selectList(credentialQuery) ); // 查询工种 LambdaQueryWrapper<ResumeJobs> jobsQuery = new LambdaQueryWrapper<ResumeJobs>() .eq(ResumeJobs::getResumeId, resume.getId()).eq(ResumeJobs::getDelFlag, NORMAL); CompletableFuture<List<ResumeJobs>> jobsFuture = CompletableFuture.supplyAsync(() -> resumeJobsMapper.selectList(jobsQuery) ); CompletableFuture.allOf(rewardExpFuture, credentialFuture, jobsFuture).join();

11、接口合并与批处理

数据库操作或或者是远程调用时,能批量操作就不要for循环调用。一个简单例子,我们平时一个列表明细数据插入数据库时,不要在for循环一条一条插入,建议一个批次几百条,进行批量插入。同理远程调用也类似想法,比如你查询营销标签是否命中,可以一个标签一个标签去查,也可以批量标签去查,那批量进行,效率就更高。

 

scss

复制代码

//反例 for(int i=0;i<n;i++){ remoteSingleQuery(param) } //正例 remoteBatchQuery(param);

12、接口性能、Sql优化

我们做后端的,写好一个接口,离不开SQL优化。 SQL优化从这几个维度思考: ① explain 分析SQL查询计划(重点关注type、extra、filtered字段) ② 索引优化 (覆盖索引、最左前缀原则、隐式转换、order by以及group by的优化、join优化) ③ 大分页问题优化(延迟关联、记录上一页最大ID) ④ 数据量太大(分库分表、同步到es,用es查询)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值