接口设计的最佳实践-上篇

大多数程序员,做得最多的事,也不过是写接口这件事而已。

今天总结下接口设计需要注意的事情。尽量每种都给出具体的场景、案例等,希望大家能有所收获。

1.逻辑梳理

除非是特别简单的需求,一定要在需求分析的时候去看看代码逻辑,看看数据关系,接口调用关系,对工作量以及实现方式有初步的想法,做到心中有数,遇事不慌。否则,开发的时候,才知道一个点变成了三个点,加班加点都干不完。
外部交互相关的需求,也要及时拉通产品去沟通协调,为开发铺路。如果要提测了,外部接口文档都没给,那还搞啥呀,是吧。
这里也给大家提供一些建议,希望能够提高大家的开发效率。

  1. SequenceDiagram

一款强大的时序图生成器,可以很方便了解接口的调用关系。
image.png
image.png

  1. 准备开发备忘录

主要可以分为三部分,设计文档、开发笔记和上线准备。
**设计文档:**开发之前可以用来接口设计文档,复杂的流程需要梳理流程图;
**开发笔记:**开发过程中记录问题,记录重要配置参数,修改的项目,分支,接口依赖关系,生产配置等内容。
**上线准备:**不要等到上线之前,还要花很多时间找上线代码。要是周期长,改了哪些项目都忘了吧。提前准备好相关的配置、SQL脚本等。

2.参数校验

后端不校验,轻则和前端扯皮,重则直接导致生产异常,甚至可能出现大事故。所以为了省事,还是花点时间做一做参数校验吧。慢就是快,也不需要节省这一点时间。
这方面,基本上引入spring-boot-starter-validation这个包,使用相关注解就可以完成90%的工作。

3.接口的兼容性

考虑是否影响老接口,是都有其他的调用方?是否适合后续扩展。
对应不熟悉的接口,建议多看看调用方,如果是HTTP接口,可以找前端看看调用方,或者如果有血缘关系工具或者精准测试工具,最好理一理关系。如果不清楚调用方的情况下,最好在不影响现有功能的前提下进行修改。

我记得组内曾经就出现过,同事修改了PC端的接口,影响到了移动端的功能,因为当时没有移动端的需求,边没有回归,最后导致了生产事故,紧急上线修复。

可以多用用下面这个工具:
Find Usages
idea很重要的工具,查找调用方,不熟悉的接口,一定要多看看,如果方向调用方不止一个,一定不要忘了去看看影响范围。
image.png

4.接口防重

接口防重,主要是针对页面的表单提交,如果不进行控制,容易因为用户的误操作或者网络延迟导致同一请求被发送多次,进而造成重复的数据记录。一般,前端需要对按钮进行置灰等操作,但是后端也需要处理,不能完全依赖前端。
当然,只有在短暂的时间内,相同内容的提交才算重复,在大的时间范围内来看,可能用户就是需要提交相同的请求。

实现方式
一般基于自定义注解和redis分布式锁实现。
注意
这种一般是针对页面的接口,如果是外部调用的接口,尽量不要做防重校验,而是在逻辑上、数据库层面进行控制,因为外部调用可能很频繁,短时间内很容易出现重复的调用。

5.接口限流、熔断、降级

对于分布式系统来说,为了避免某个应用不可用导致整个系统的不可用,需要降低服务间的强关联,避免服务雪崩,保证尽可能多的服务可用。

主要使用sentinel和Hystrix组件,在控制接口的请求数量、及时关闭对外部接口的调用。

6.外部调用的超时、异常、重试

外部调用的时候需要考虑超时、异常和重试几种场景。

超时

有个接口是和外部团队进行交互的场景,因为外部接口异常,导致有一批数据推送失败。最后只能去找日志,手动推送。像这种场景,可以和对方确认,是否支持重复推送,其次,在自身的系统里,也可以增加简单的重试机制。比如基于redis进行参数的缓存,并指定持久化机制等。

异常

对于重要的报错,是否需要进行短信或者站内信等方式进行通知?

重试

接口请求失败或者出现暂时性错误,重试是提高接口成哥率的重要手段。查询外部接口,最好是支持重试,设置超时时间。

场景的重试机制包括循环重试、并发框架异步重试、消息队列重试、redis重试以及使用Spring Retry库等方式。

  • 循环重试:判断接口的返回值,报错时再次调用。
  • 异步重试:比如CompletableFuture框架并提供了失败时的处理方法,可以再次调用
  • 消息队列和redis重试:需要把请求参数封装成消息放到消息队列或者redis的队列中,进行消费,失败后继续重试,成功后删除消息。
  • Spring Retry 库可以很方便地实现接口请求的重试机制,基于注解指定重试接口和处理策略。
@Retry(value = Exception.class, backoff = @Backoff(delay = 1200,multiplier = 1.5),maxAttempts = 3)
public void notifyToiletryCreateOrder(String originalCode) throws ServiceException {
    // 代码逻辑

}

7.日志打印

虽然日志不是越多越好,但是必要的日志对解决问题是很重要的。
常见的需要打印日志的地方:

  • 入参和出参,特别是外部调用的入参、出参;
  • 重要逻辑的数据信息;
  • 异常信息;

同时建议设置traceId,使用skywalking等工具实现接口的链路追踪,便于快速处理问题。

8.异步

首先,肯定得使用线程池,其次,要根据不同的场景,对线程池进行隔离。避免某个线程池打满影响其他业务场景。
工作中曾经遇到过一个发短信的场景,调用方没有采用异步的方式,后来因为短信服务积压,导致短信发送接口超时,直接导致了调用方同时超时,影响后续流程。
这个场景就出现了好几个问题:

  1. 外部调用,不需要返回值的场景,应该采用异步的方式,不能影响自身业务;
  2. 对于外部调用,最好能够统一操作,和自身逻辑拆分开,比如短信调用,可以放在最后。

比如报表接口,需要统一账单金额、收款金额、退款金额,就可以采用不同的线程进行数据统计,最后汇总,减少响应时间。


for (String communityCode : communityCodes) {
	// 分项目计算
    CompletableFuture.supplyAsync(() -> sumOne(), ThreadPool);
    // 汇总结果集
    totalResult.add(communityResult);
}

CompletableFuture<Void> future = CompletableFuture.allOf(totalResult.toArray(completableFutures));
future.join();
// 汇总结果
result.addAll(Stream.of(completableFutures).map(CompletableFuture::join).flatMap(Collection::stream).collect(Collectors.toList()));
// 后续处理
...

9.影响内存或者数据库的场景

当我们处理数据的时候,要对处理的数据量有一定的了解,内存占用,耗时等,不要在生产环境直接导致事故。场景的比如大对象,长事务问题要注意。

大对象

常见的容易内存溢出的场景便是导出、导入、大表查询。
尽量使用分批查询的操作。对于查一个列表的接口,一定要注意查询的量,有可能以前没有出现问题,随着数据量的增长,数据量陡增。

长事务问题

@Transactional注解简化了我们使用事务的工作,但是也让我们不注意事务的一些问题。比如长事务问题等。长事务,顾名思义就是运行时间比较长,操作的数据比较多的事务。
比如下面这种,在事务中嵌套了很多RPC调用、HTTP接口调用这种非数据库的操作,正常的情况下问题不大,对流程没有影响,但是如果后续的接口超时,事务一直不提交,就会一直占用数据库连接,影响数据库性能,影响从库数据同步。

@Transactional
public int createOrder(String orderNo){
    
    // 数据库操作
    Order  order = orderDbStorage.selectByCondition(orderNo)
    orderDbStorage.save(order);
    orderItemDbStorage.save(order.getItems());
    
    // RPC调用
    sendRpc();
    
    // 消息推送
    sendMessage();
    
    return order.getId();

}

这种就要尽可能把事务的代码拆出来,减少事务的范围。

public int createOrder(String orderNo){
    Order  order = orderDbStorage.selectByCondition(orderNo);
    // 拆分数据库操作作为一个独立的事务
    int  id = OrderBService.createOrder(order );
    sendRpc();
    sendMessage();
    return id;
}




@Transactional
public int createOrder( Order  order){
    orderDbStorage.save(order);
    orderItemDbStorage.save(order.getItems());
    return order.getId();
}
  • 42
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值