招聘网站建设,申请域名备案,分类信息网站怎么做流量,建筑网站建设赏析目录
1. 幂等性简介
2. 后端如何解决幂等性问题
2.1 数据库层面
- 2.1.1 防重表
- 2.1.2 数据库悲观锁(不建议,容易出现死锁情况)
- 2.1.3 数据库乐观锁
- 2.1.4 乐观锁CAS算法原理
2.2 锁层面
2.3 幂等性token层面
- 2.3.1 简介文字描述: …目录
1. 幂等性简介
2. 后端如何解决幂等性问题
2.1 数据库层面
- 2.1.1 防重表
- 2.1.2 数据库悲观锁(不建议,容易出现死锁情况)
- 2.1.3 数据库乐观锁
- 2.1.4 乐观锁CAS算法原理
2.2 锁层面
2.3 幂等性token层面
- 2.3.1 简介文字描述:
- 2.3.2 简介图示:
编辑
- 2.3.3 创建注解
- 2.3.4 创建请求拦截器
-----方案一: 使用incr保证原子性
-----方案一代码: 拦截器
方案二: 使用分布式锁, 这篇文章暂不介绍
- 2.3.5 获取幂等token代码
- 2.3.6 接口测试
- 2.3.7 使用测试工具进行测试 - 2.3.8 幂等校验token的优,缺点
----- 2.3.8.1 使用的优点:
----- 2.3.8.2 使用的缺点:
3. 前端如何操作来避免幂等问题
- 3.1前端防重
- 3.2 PRG模式 1. 幂等性简介 分布式或微服务思想实现系统架构设计中, 服务相互调用可能存在服务调用延迟或失败情况。服务端可能会进行多次点击提交。如果这样请求多次的话那最终处理的数据结果就一定要保证统一如 订单创建,支付扣款,库存扣减,物流发货等。此时就需要通过保证业务幂等性方案来完成。 2. 后端如何解决幂等性问题
2.1 数据库层面
- 2.1.1 防重表 创建唯一索引: 多用于详情信息, 个人账户等业务,如果并发情况下出现多条,数据库报异常 创建唯一组合索引: 可以创建组合索引来保证唯一性 - 2.1.2 数据库悲观锁(不建议,容易出现死锁情况)
加入悲观锁, for update, 有主键索引/明确索引就是行锁, 不然就是表锁
文章介绍跳转: - mysql for update 详细介绍
- 2.1.3 数据库乐观锁
占用数据库资源, 需要有版本号, 先查询版本号作为老的版本(oldVersion)存起来, 进行修改操作, 然后把这个值也进行修改1 条件是version oldVersion, 如果version不正确 修改失败
version 5 100个线程同时取到5 其中一个修改成功version6, 则其他99个失败
- 2.1.4 乐观锁CAS算法原理
简介: compare and swap:比较与交换 , 核心即为 冲突检测和数据更新
文章介绍跳转: -
2.2 锁层面
锁系列文章跳转(待定) -
2.3 幂等性token层面
- 2.3.1 简介文字描述: 1. 先制作幂等性token生成器, 创建token 2. 创建注解, 写入默认值 表示是否开启校验 3. 写拦截器 判断接口上是否有注解 如果没有,放行 4. 有注解的, 判断请求头中是否存在幂等token, 不存在 拦截请求 5. 存在, 判断幂等token真实性(是否过期), 不存在或incr数值异常 删除幂等token 拦截请求 6. 一切正常,删除token,放行请求, 进入controller
- 2.3.2 简介图示: - 2.3.3 创建注解
package *;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 幂等性校验* 需要多线程测试后* 查出是否需要优化* author pzy* version 0.1.1 测试版本*/
Target({ElementType.TYPE,ElementType.METHOD})
Retention(RetentionPolicy.RUNTIME)
public interface ApiIdempotentAnn {boolean value() default true;
}- 2.3.4 创建请求拦截器
-----方案一: 使用incr保证原子性 简介: Redis 的 INCR 命令可以将存储在指定键中的数字值增加 1。INCR 命令是一个原子性操作, 意味着它在执行过程中不会被中断。 例如,假设您有一个名为 counter 的键,存储的值为 0。如果您使用 INCR 命令将其 增加 1,那么这个键的值就会变为 1。如果再次使用 INCR 命令将其增加 1,那么这个键的值 就会变为 2。 由于INCR 命令是原子性的,因此可以在并发环境下使用 -----方案一代码: 拦截器
package *;import *;
import *;
import *;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.reflect.Method;import static com.aisce.common.constant.RedisKeyConstants.IDEMPOTENT_TOKEN;/*** 幂等性校验拦截器(用哪个工程粘过去即可)* 使用方式:* p* p* 后端操作: ApiIdempotentAnn注解 添加到接口位置即可 其他无需操作* p* 前端操作: 在请求头中添加 idempotent参数* 示例: idempotent: fcc8b373-b867-4d6c-9915-e3bd668db7b6** author pzy* version 0.1.1 初期版本*/
Slf4j
Component
Order(2)
public class ApiIdempotentInterceptor extends HandlerInterceptorAdapter {Autowiredprivate StringRedisTemplate stringRedisTemplate;/*** 前置拦截器* 在方法被调用前执行。在该方法中可以做类似校验的功能。如果返回true则继续调用下一个拦截器。如果返回false则中断执行* 也就是说我们想调用的方法 不会被执行但是你可以修改response为你想要的响应。*/SuppressWarnings(value {all})Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info( 执行链2 执行);//如果handler不是和HandlerMethod类型则返回trueif (!(handler instanceof HandlerMethod)) {return true;}//转化类型final HandlerMethod handlerMethod (HandlerMethod) handler;//获取方法类final Method method handlerMethod.getMethod();// 判断当前method中是否有这个注解boolean methodAnn method.isAnnotationPresent(ApiIdempotentAnn.class);//如果有幂等性注解if (methodAnn method.getAnnotation(ApiIdempotentAnn.class).value()) {// 需要实现接口幂等性//检查token//1.获取请求的接口方法boolean result checkToken(request);//如果token有值说明是第一次调用if (result) {//则放行return super.preHandle(request, response, handler);} else {//如果token没有值则表示不是第一次调用是重复调用response.setContentType(application/json; charsetutf-8);PrintWriter writer response.getWriter();writer.print(new ObjectMapper().writeValueAsString(ResultResponse.error(重复调用,已拦截!)));writer.close();response.flushBuffer();log.error(String.format( %s 重复调用%s,已拦截!, request.getRemoteAddr(), request.getRequestURI()));return false;}}//否则没有该自定义幂等性注解则放行return super.preHandle(request, response, handler);}/*** 幂等token的校验方法*/private boolean checkToken(HttpServletRequest request) {//从请求头对象中获取 幂等性校验tokenidempotentString idempotentToken request.getHeader(idempotent);//如果不存在则返回false说明是重复调用if (StringUtils.isBlank(idempotentToken)) {return false;}//格式化幂等校验tokenidempotentToken String.format(IDEMPOTENT_TOKEN, idempotentToken);if (Boolean.FALSE.equals(stringRedisTemplate.hasKey(idempotentToken))) {return false;}//保证原子性方式一 incrLong num stringRedisTemplate.opsForValue().increment(idempotentToken);if (num ! null num.intValue() ! 2) {//只要逻辑不对 删除key, 阻止请求delIdempotentToken(idempotentToken);return Boolean.FALSE;}//否则就是存在存在则把redis里删除token//return Boolean.TRUE;return delIdempotentToken(idempotentToken);}/*** 删除幂等校验token** param idempotentToken* return*/private Boolean delIdempotentToken(String idempotentToken) {return Boolean.TRUE.equals(stringRedisTemplate.delete(idempotentToken));}}
方案二: 使用分布式锁, 这篇文章暂不介绍
下篇文章预告: 分布式锁基本使用方式,以及配合注解实现快速加锁
- 2.3.5 获取幂等token代码 /*** 前端获取token,然后把该token放入请求的header中** return*/ApiOperation(获取幂等token)GetMapping(/getIdempotentToken)public ResultResponse getIdempotentToken() {log.info( 获取幂等token );String idempotent UUID.randomUUID().toString();stringRedisTemplate.opsForValue().set(String.format(IDEMPOTENT_TOKEN,idempotent),1, Duration.ofSeconds(60*2));//2分钟 不随机数了 两分钟幂等失效拦截return ResultResponse.ok().setData(idempotent);}
- 2.3.6 接口测试 ps: 使用方式: AxPost可以换成任意实体类 ResultResponse 随便的一个统一返回类型,没有写String 类型即可 ApiIdempotentAnnPostMapping(/testCheck)public ResultResponse testCheck(RequestBody AxPost axPosts) {log.info(专业测试幂等性接口 );System.out.println(axPosts);return ResultResponse.ok(ok);}
- 2.3.7 使用测试工具进行测试
测试工具介绍: Postman,ApiPost, Idea httpclient tools,ApiFox后端测试方式 - 2.3.8 幂等校验token的优,缺点
----- 2.3.8.1 使用的优点: 通过token机制来保证幂等是常见的解决方案同时也适合绝大部分场景,使用方便, 业务执行出现异常时客户端需重新获取令牌并发起访问即可。推荐使用先删除token方案。 ----- 2.3.8.2 使用的缺点: 每次的业务请求, 都会产生一个额外的请求去获取token,但出现失败次数一般不会很多, 算是一种资源的浪费,虽然redis性能很好. 3. 前端如何操作来避免幂等问题
- 3.1前端防重 通过前端防重保证幂等是最简单的实现方式JS代码即可完成设置。可靠性并不好可以通过工具跳过页面仍能重复提交。主要适用于表单重复提交或按钮重复点击。 - 3.2 PRG模式 PRG模式即POST-REDIRECT-GET。当用户进行表单提交时会重定向到另外一个提交成功页面而不是停留在原先的表单页面。这样就避免了用户刷新导致重复提交。防止通过浏览器按钮前进/后退导致表单重复提交。