likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 45

前言

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

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

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

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

2、接口老版本兼容性

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

//老接口
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 并行调用提高性能。

 // 查询获奖经历
        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循环一条一条插入,建议一个批次几百条,进行批量插入。同理远程调用也类似想法,比如你查询营销标签是否命中,可以一个标签一个标签去查,也可以批量标签去查,那批量进行,效率就更高。

//反例
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查询)

转载自:https://juejin.cn/post/7233809287531872316
评论
请登录