小小工作繁忙,不过还好,这次学习的内容是接口幂等性相关的内容,

幂等性

这里使用的是幂等性相关的内容进行学习。针对幂等性实现构建一个健壮的API接口的第一步,即,幂等性的构建

定义

小小对接口的幂等性定义进行相关的阐述。
幂等性是指重复使用同样的参数调用同一方法时总能获得同样的结果。比如对同一资源的GET请求访问结果都是一样的。
小小本次更新的选题为构建一个API接口,需要更加的健壮。

用途

这里对于node.js 来说,其幂等性的最核心的用途无外乎重复下单,以及重复提交。
指出,对应于商城系统来说,当用户在点了两次提交的时候,可以实现第二次提交和第一次提交的效果完全一样,而不是产生两者完全不一样的场景。

幂等性的操作

这里针对于基本的CRUD操作。

查询

对于查询而言,查询一次和查询多次,查询天然是幂等性操作。
结果完全都是相同的。

删除操作

对于删除操作来说,其也是天然的幂等,所以,也不需要各种锁,实现删除操作的幂等性。

更新和增加

由于这两个操作不是幂等性操作,需要增加相关的锁机制,实现幂等性操作。
其操作有以下的几种手段

token机制

这个机制可以防止页面重复提交,当客户端请求页面的时候,服务器会生成一个随机的token,并且把token放入到session中,每次只验证其中一个token机制,实现表单提交的幂等性的验证。

悲观锁

这里在获取数据的时候加锁实现。伴随着事物一起使用。

乐观锁

更新数据的时候锁,效率更高,性能更好,但是同时也会产生一定的风险。

分布式锁

构建全局的分布式锁,实现整个系统的分布式,添加redis和zookeeper实现分布式机制的验证。

一致性

这里进行一致性相关的验证,这里的一致性指的是methods相关的一致性。
其中HTTP协议提供了多种的协议的一致性。一致性如下

这里主要谈设计接口

Methods

HTTP协议提供了很多methods来操作数据:

GET: 获取某个资源,GET操作应该是幂等(idempotence)的,且无副作用。
POST: 创建一个新的资源。
PUT: 替换某个已有的资源。PUT操作虽然有副作用,但其应该是幂等的。
PATCH(RFC5789): 修改某个已有的资源。
DELETE:删除某个资源。DELETE操作有副作用,但也是幂等的。

Status Code

这里由于实现了其一致性,所以使用status code 实现其一致性。根据状态码,来实现相关的一致性。
好比 200 表示成功。
404 表示未找到等。

安全性

针对一个接口而言,需要实现其安全性,这里使用jwt完成一个接口的安全性的校验。
有三种授权机制,分别为 token 授权,时间戳授权,API签名机制。

Token 授权

用户使用用户名密码登录后服务器给客户端返回一个Token(必须要保证唯一,可以结合UUID和本地设备标示),并将Token-UserId以键值对的形式存放在缓存服务器中(我们是使用Redis),并要设置失效时间。服务端接收到请求后进行Token验证,如果Token不存在,说明请求无效。Token是客户端访问服务端的凭证。

# uuid 是手机设备的唯一标示
String token = UUID.randomUUID().toString() + "_" + uuid;

时间戳授权

用户每次请求都带上当前时间的时间戳timestamp,服务端接收到timestamp后跟当前时间进行比对,如果时间差大于一定时间(比如30秒),则认为该请求失效。时间戳超时机制是防御重复调用和爬取数据的有效手段。
当然这里需要注意的地方是保证客户端和服务端的“当前时间”是一致的,我们采取的对齐方式是客户端第一次连接服务端时请求一个接口获取服务端的当前时间A1,再和客户端的当前时间B1做一个差异化计算(A1-B1=AB),得出差异值AB,客户端再后面的请求中都是传B1+AB给到服务端。

// timeStamp是客户端从Header传过来的值
Long timeStamp = RequestHeaderContext.getInstance().getTimeStamp();
boolean checkTime = checkTime(timeStamp, 30 * 1000);
if (!checkTime) {
    return responseErrorAPISecurity(response);
}

// checkTime方法
public static boolean checkTime(Long time, Integer variable){
    Long currentTimeMillis = System.currentTimeMillis();
    Long addTime = currentTimeMillis + variable;
    Long subTime = currentTimeMillis - variable;
    if (addTime > time && time > subTime){
        return true;
    }
    return false;
}

API 签名

这是一种,把前两种的机制进行结合起来的方式。
将“请求的API参数”+“时间戳”+“盐”进行MD5算法加密,加密后的数据就是本次请求的签名signature,服务端接收到请求后以同样的算法得到签名,并跟当前的签名进行比对,如果不一样,说明参数被更改过,直接返回错误标识。签名机制保证了数据不会被篡改。

“`
// 请求的API参数,如果是再body,则MD5;如果是param,则原字符串
StringBuffer urlSign = new StringBuffer();

if ("POST".equals(request.getMethod()) || "PUT".equals(request.getMethod())) {
String bodyStr = RequestReaderUtil.ReadAsChars(request);
String bodySign = "";
if (!StringUtils.isEmpty(bodyStr)){
bodySign = DigestUtils.md5DigestAsHex((bodyStr).getBytes());
}
urlSign = new StringBuffer(bodySign);
} else if ("GET".equals(request.getMethod()) || "DELETE".equals(request.getMethod())) {
String params = request.getQueryString();
if (params == null){
params = "";
}
urlSign = new StringBuffer(params);
}
// “请求的API参数”+“时间戳”+“盐”进行MD5算法加密
String sign = DigestUtils.md5DigestAsHex(urlSign.append(timeStamp).append(salt).toString().getBytes());

// signature是客户端从Header传过来的值
if (signature.equals(sign)) {
return true;
} else {
return false;
}

“`

关于流量的问题

在高并发的情况下,可能会出现流量过载的情况,在这里,可能会发生服务雪崩,所以在这种情况下,使用熔断器,实现接口的限流。

限流算法

一共有三种相应的限流的算法

计数器

计数器方式(传统计数器缺点:临界问题 可能违背定义固定速率原则)理论 | 优雅的构建一个健壮的API接口插图

令牌桶

理论 | 优雅的构建一个健壮的API接口插图1

漏桶算法

理论 | 优雅的构建一个健壮的API接口插图2

如上图所示,我们假设系统是一个漏桶,当请求到达时,就是往漏桶里“加水”,而当请求被处理掉,就是水从漏桶的底部漏出。水漏出的速度是固定的,当“加水”太快,桶就会溢出,也就是“拒绝请求”。从而使得桶里的水的体积不可能超出桶的容量。

接口的原则

这里设计接口需要遵守以下几个原则。

单一职责原则

一个接口只能负责单一的职责。

控制资源的使用

代码,控制CPU,内存等接口,实现服务器资源的不过载。

最后

最后一点,需要满足水平扩展,在大流量来的时候,只需要实现水平扩展,就可以承压下流量的来袭。