### 基于MySQL实现
这种实现方式是利用 mysql 唯一索引的特性。示意图如下:
具体流程步骤:
- 建立一张去重表,其中某个字段需要建立唯一索引
- 客户端去请求服务端,服务端会将这次请求的一些信息插入这张去重表中
- 因为表中某个字段带有唯一索引,如果插入成功,证明表中没有这次请求的信息,则执行后续的业务逻辑
- 如果插入失败,则代表已经执行过当前请求,直接返回
### 基于Redis实现
这种实现方式是基于 SETNX 命令实现的 SETNX key value:将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则 SETNX 不做任何动作。该命令在设置成功时返回 1,设置失败时返回 0。示意图如下:
具体流程步骤:
- 客户端先请求服务端,会拿到一个能代表这次请求业务的唯一字段
- 将该字段以 SETNX 的方式存入 redis 中,并根据业务设置相应的超时时间
- 如果设置成功,证明这是第一次请求,则执行后续的业务逻辑
- 如果设置失败,则代表已经执行过当前请求,直接返回
### 基于业务参数实现
**第一阶段**:只要客户端请求有唯一的请求编号,那么就能借用Redis做这个去重:只要这个唯一请求编号在Redis存在,证明处理过,那么就认为是重复的。
**第二阶段**:但很多的场景下,请求并不会带这样的唯一编号。先考虑简单的场景,假设请求参数只有一个字段reqParam,我们可以利用以下标识去判断这个请求是否重复:**用户ID:接口名:请求参数** 。
**第三阶段**:但我们的接口通常不是这么简单,参数通常是一个JSON。假设我们把请求参数(JSON)按KEY做升序排序,排序后拼成一个字符串作为KEY值,但这可能非常的长,所以可以考虑对这个字符串求一个MD5作为参数的摘要,以这个摘要去取代reqParam的位置。
```java
String KEY = "user_opt:U="+userId + "M=" + method + "P=" + reqParamMD5;
```
**第四阶段**:上面的问题其实已经是一个很不错的解决方案了,但是实际投入使用的时候可能发现有些问题:某些请求用户短时间内重复的点击了(例如1000毫秒发送了三次请求),但绕过了上面的去重判断(不同的KEY值)。原因是这些请求参数的字段里面,**是带时间字段的**,这个字段标记用户请求的时间,服务端可以借此丢弃掉一些老的请求(例如5秒前)。
**总结**
将业务参数(Query+Body)按KEY(排除时间字段和经纬度字段)做升序排序,排序后将按Query方式逐一拼接成参数字符串,然后将这个字符串进行MD5摘要计算,然后使用以下规则进行KEY值计算:
```java
String KEY = "前缀标识:U=" + <用户唯一标识> + "M=" + <接口唯一标识> + "P=" + <业务参数MD5签名>;
```