参数的合法性校验应该是每个接口必备的,无论是前端发起的请求,还是后端的其他调用都必须对参数做校验,比如:参数的长度、类型、格式,必传参数是否有传,是否符合约定的业务规则等等。
推荐使用SpringBoot Validation
来快速实现一些基本的参数校验。
参考如下示例:
@Data
@ToString
public class DemoEntity {
// 不能为空,比较时会除去空格
@NotBlank(message = "名称不能为空")
private String name;
// amount必须是一个大于等于5,小于等于10的数字
@DecimalMax(value = "10")
@DecimalMin(value = "5")
private BigDecimal amount;
// 必须符合email格式
@Email
private String email;
// size长度必须在5到10之间
@Size(max = 10, min = 5)
private String size;
// age大小必须在18到35之间
@Min(value = 18)
@Max(value = 35)
private int age;
// user不能为null
@NotNull
private User user;
// 限制必须为小数,且整数位integer最多2位,小数位fraction最多为4位
@Digits(integer = 2, fraction = 4)
private BigDecimal digits;
// 限制必须为未来的日期
@Future
private Date future;
// 限制必须为过期的日期
@Past
private Date past;
// 限制必须是一个未来或现在的时间
@FutureOrPresent
private Date futureOrPast;
// 支持正则表达式
@Pattern(regexp = "^\\d+$")
private String digit;
}
@RestController
@Slf4j
@RequestMapping("/valid")
public class TestValidController {
@RequestMapping("/demo1")
public String demo12(@Validated @RequestBody DemoEntity demoEntity) {
try {
return "SUCCESS";
} catch (Exception e) {
log.error(e.getMessage(), e);
return "FAIL";
}
}
}
在设计接口时,我们应当对接口的负载能力做出评估,尤其是提供给第三方使用时,这样当实际请求流量超过预期流量时,我们便可采取相应的预防策略,以免服务器崩溃。
一般来说限流主要是为了防止恶意的Dos攻击或者爬虫等非正常的业务访问,因此一般来说采取的方式都是直接丢弃超出阈值的部分。
限流的具体实现有多种,单机版可以使用Guava的RateLimiter,分布式可以使用Redis+Lua,想要更加完善的成套解决方案则可以使用阿里开源的sentinel。
在如今这个微服务时代,一次后台请求可能都会经过好几次RPC调用的过程,因此服务与服务之间的正常通信就显得非常重要,我们不能因为某个服务不可用,导致整条链路上节点都受到影响,慢慢的导致整个服务都不可用,这也就是我们所说的雪崩效应。
如果此时B服务发生故障,导致A服务到B服务的所有请求都没有及时响应,那么最终造成的结果就是A服务的所有连接被耗尽。
要知道,实际上我们服务与服务之间一定是以网状的形式存在,我们当然还会有从其他链路发起的到A服务的请求。
比如有C直接请求A的,有D请求C,然后C再请求A的,有E直接请求A的,所以一旦A的连接被耗尽,也就意味着C/D/E的连接也会被耗尽,就这样越滚越多,最终导致整个网状中的节点连接资源全部被耗尽。
可以看出,服务雪崩问题带来的影响是非常严重的,我们必须要有所防范,而熔断、降级则是一种非常有效的方式。
首先我们需要做到快速失败,请求响应一旦超过一定的时间,就自动返回,提示客户端请求超时或是其他什么,总之,我们需要在服务能接受的范围内快速给出响应。
熔断可以帮助我们对于多次请求响应都超时的请求,给出一定解决措施,比如一定时间范围内就不再请求了,除此之外,还可以做到资源隔离,比如对于X接口的调用,就只分配M个资源(这里的资源一般指的就是线程)。
而降级可以理解为Plan B,也就是当请求响应超过了你设置的阈值后,你的后备方案是什么,接下来会怎么办?通常比较简单的方式就是给客户端一个友好的响应,比如服务开小差之类的,在保证服务稳定的前提下,也能够给客户端良好的体验性。
其中netlfix开源的hystrix组件,可以轻松与spring cloud生态结合,其对于资源隔离、服务熔断、降级等都有了成熟的解决方案,可以尝试使用。
使用重试机制,是相信请求还是可以成功的,有些情况下一次请求失败可能仅仅是因为网络不稳定造成的,也许再重新请求一次就好了,此时如果直接返回客户端失败就不是最佳的选择。
重试的策略也有很多种,比如重试的次数、频率,当目标节点是集群部署时,还可以采用切换节点重试等方式,比如:如果请求A节点失败,那就自动切换到请求B节点。
超时重试可以使用spring-retry
,这是spring封装好的API,直接通过注解的方式就可以设定各种重试策略,实现重试机制。
netifix开源的ribbon,作为客户端的负载均衡,也封装了重试机制,其底层也是基于spring-retry
实现的。
既然有重试就一定要考虑幂等性的问题,所谓幂等性,简单来说,就是对一个接口执行多次的重复请求,与一次请求所产生的结果是相同的,概念听起来非常容易理解,但要真正的在系统中要始终保持这个目标,是需要很严谨的设计的。
除了重试、像基于异步回调通知、MQ等方面的设计也决定了接口一定要保证幂等性,当然并不是说只要没有这些设计就不用考虑幂等性问题了,前面在讲重试时提到,网络本身也存在重试机制,我们知道TCP是可靠的传输层协议,既然要保证数据包一定能够到达,那就一定会有重试的机制。
保证幂等性的方式也有很多,比如:加锁、数据库唯一键、状态机等。
性能是一个非常广的话题,此文中没法完全展开,但可以提供一些方向,之后读者可再沿着每一条方向深思钻研。
并行是充分利用CPU资源的一种方式,JDK中也有非常多的并行工具类,比如:CompletableFuture,如果一次请求链路中存在多次IO请求,而这些请求也没有先后依赖关系,那么则可以尝试并行处理。
并行需要我们控制好并行度的问题。
我相信关于异步、响应式编程等概念一定也是耳熟能详,异步可以轻松获得远远超出同步请求的吞吐量,有很多的业务场景也特别适合使用异步来处理,比如新用户注册后的短信通知、邮件通知。
提到性能提升,那一定少不了对于缓存的使用,这本身就是由计算机的设计所决定的。
如果你对各个层面的访问延迟有所了解,你就可以针对性的采取措施,比如在CPU缓存,就是操作系统级别的缓存,一般我们能控制的就是访问的类型,我们需要的数据是从磁盘获取还是从内存获取,或者是通过一次TCP请求获取。
当然,缓存虽好,但也不能乱用,毕竟资源有限,内存可比磁盘贵的多,还有空间与时间的局部性原理,你也应该所有了解,缓存的命中率如果不高,那么最终将适得其反。
我们说得到同样的结果,实现过程可以各有不同,O(n^2)的时间复杂度与O(n)甚至是O(logn)的时间复杂度差距还是很明显的,这一点在数据量较小时感知不出来,不过一旦数据量比较多,就非常明显了。
除此之外,对于空间的使用也是必须要考虑到的,否则可能会导致频繁的GC,带来的结果就是CPU消耗过高,甚至最后内部不足,导致OOM
最后,还有另外一个绕不开的话题,就是接口安全方面,如果你的接口仅仅是在内部网络中使用,那到还不必过于担心,但如果是暴露在公网环境中的接口,那就一定要考虑安全方面的问题。
接口安全需要考虑的地方非常多,还是一样,本编文章只能提点一些,没办法逐一展开。
敏感信息一般包含,身份证、手机号、银行卡号、车牌号、姓名等等,应该按照脱敏规则进行处理。
一般包含,CSRF、XSS、SQL注入、文件上传漏洞等。
跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
跨站脚本攻击 (Cross Site Scripting, XSS) 发生在客户端,可被用于进行窃取隐私、钓鱼欺骗、偷取密码、传播恶意代码等攻击行为。 恶意的攻击者将对客户端有危害的代码放到服务器上作为一个网页内容, 使得其它网站用户在观看此网页时,这些代码注入到了用户的浏览器中执行,使用户受到攻击。一般而言,利用跨站脚本攻击,攻击者可窃会话 Cookie 从而窃取网站用户的隐私。
SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
Web 应用程序在处理文件下载时,接受用户指定的路径和文件名进行下载,攻击者利用此漏洞来下载服务器的其它文件甚至任意文件(源代码、数据库甚至 passwd 等)。
Token机制主要是用来做身份认证的,在对外提供的接口中,用来鉴别接口请求方的身份。
签名可以用来防止数据篡改、加密可以用来防止数据被偷窥。
使用白名单机制可以进一步加强接口的安全性,一旦服务与服务交互可以使用,接口提供方可以限制只有白名单内的IP才能访问,这样接口请求方只要把其出口IP提供出来即可。
与之对应的黑名单机制,则是应用在服务端与客户端的交互,由于客户端IP都是不固定的,所以无法使用白名单机制,不过我们依然可以使用黑名单拦截一些已经被识别为非法请求的IP。