当前位置:   article > 正文

防重复提交设计方案_如何设计不能重复提交时间的预约

如何设计不能重复提交时间的预约

场景描述

大型互联网项目中,由于业务特点(例如秒杀)同一时间很多的人在使用,用户连续快速点击,而且前端没有针对性处理,导致连续发送两次请求,此时如果不做好控制,那么系统将会产生很多的数据重复的问题。

解决方案

解决思路:相同的请求在同一时间只能被处理一次。

分布式锁

1.服务器A接收到请求之后,获取锁,获取成功

2.服务器A进行业务处理,订单提交成功

3.服务器B接收到相同的请求,获取锁,失败,因为锁被服务器A获取了,并且未释放

4.服务器A处理完成,释放锁 实现采用redis

流程图

20190128151646582.png

代码设计

防重复提交注解类

  1. /**
  2. * 避免重复提交注解
  3. * @Author huanglian
  4. * @Date 2019-01-24-下午4:56
  5. */
  6. @Target({ElementType.METHOD, ElementType.TYPE})
  7. @Retention(RetentionPolicy.RUNTIME)
  8. @Documented
  9. public @interface AvoidRepeatableCommit {
  10. long acquireTimeout() default 1000;
  11. long lockTimeout() default 2000;
  12. }

防重复提交切面类

  1. /**
  2. * 避免重复提交切面
  3. *
  4. * @Author huanglian
  5. * @Date 2019-01-24-下午5:09
  6. */
  7. @Component
  8. @Aspect
  9. public class AvoidRepeatableCommitAspect {
  10. private static final Logger logger = LoggerFactory.getLogger(AvoidRepeatableCommitAspect.class);
  11. @Autowired
  12. private StringRedisTemplate stringRedisTemplate;
  13. private AvoidRepeatableCommitLock avoidRepeatableCommitLock;
  14. @PostConstruct
  15. private void init() {
  16. avoidRepeatableCommitLock = new AvoidRepeatableCommitLock(stringRedisTemplate);
  17. }
  18. @Autowired
  19. private HttpServletRequest request;
  20. @Around("execution(public * com.app.sns.controller..*.*(..))")
  21. public Object process(ProceedingJoinPoint joinPoint) throws Throwable {
  22. Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
  23. AvoidRepeatableCommit avoidRepeatableCommitAnnotation = method.getAnnotation(AvoidRepeatableCommit.class);
  24. //如果注解为空,则不需要校验重复提交
  25. if (avoidRepeatableCommitAnnotation == null) {
  26. return joinPoint.proceed();
  27. }
  28. //key从小到大,对参数进行排序,目的是确保统一请求的内容加密结果一致
  29. Map<String, String[]> requestParams = request.getParameterMap();
  30. String body = requestParams.get("body")[0];
  31. //key从小到大,对参数body进行排序
  32. TreeMap treeMap = JSONObject.parseObject(body, TreeMap.class);
  33. body = JSONObject.toJSONString(treeMap);
  34. requestParams.put("body", new String[]{body});
  35. String lockName = MapUtil.getRedisKeyByParam(requestParams);
  36. logger.info("lockName ->" + lockName);
  37. long start = System.currentTimeMillis();
  38. //获取锁
  39. String identifier = avoidRepeatableCommitLock.acquireLock(lockName);
  40. //锁标识为空,则表示锁已存在,当前请求是重复提交
  41. if (identifier == null) {
  42. IBaseResp resp = new IBaseResp();
  43. return BaseRespUtils.buildFailed(resp, ErrorType.FAILED.getErrorCode(), "您操作太频繁啦");
  44. }
  45. logger.info("acquireLock time -> " + (System.currentTimeMillis() - start));
  46. try {
  47. return joinPoint.proceed();
  48. } finally {
  49. //释放锁
  50. boolean flag = avoidRepeatableCommitLock.releaseLock(lockName, identifier);
  51. logger.info("releaseLock flag ->" + flag);
  52. }
  53. }
  54. }

防重复提交锁类

  1. /**
  2. * Redis防重复提交锁
  3. *
  4. * @Author huanglian
  5. * @Date 2019-01-24-上午9:50
  6. */
  7. public class AvoidRepeatableCommitLock {
  8. private static Logger logger = LoggerFactory.getLogger(AvoidRepeatableCommitLock.class);
  9. private final long lockTimeout = 3000;
  10. private StringRedisTemplate redisTemplate;
  11. public AvoidRepeatableCommitLock(StringRedisTemplate redisTemplate) {
  12. this.redisTemplate = redisTemplate;
  13. }
  14. public String acquireLock(final String lockName) {
  15. return acquireLock(lockName, lockTimeout);
  16. }
  17. public String acquireLock(final String lockName, final long lockTimeout) {
  18. //保证释放锁的时候是同一个持有锁的人
  19. final String identifier = UUID.randomUUID().toString();
  20. final int lockExpire = (int) (lockTimeout / 1000);
  21. final String lockKey = "lock:" + lockName;
  22. final String lua = "if redis.call(\"setnx\",KEYS[1], ARGV[1])==1 then " +
  23. "redis.call(\"expire\",KEYS[1], ARGV[2]) " +
  24. "return 1 " +
  25. "else return 0 end";
  26. Long rs = redisTemplate.execute(new RedisCallback<Long>() {
  27. @Override
  28. public Long doInRedis(RedisConnection redisConnection) throws DataAccessException {
  29. Object nativeConnection = redisConnection.getNativeConnection();
  30. Long result = 0L;
  31. List<String> keys = new ArrayList<>();
  32. keys.add(lockKey);
  33. List<String> values = new ArrayList<>();
  34. values.add(identifier);
  35. values.add(lockExpire + "");
  36. // 集群模式
  37. if (nativeConnection instanceof JedisCluster) {
  38. result = (Long) ((JedisCluster) nativeConnection).eval(lua, keys, values);
  39. }
  40. // 单机模式
  41. if (nativeConnection instanceof Jedis) {
  42. result = (Long) ((Jedis) nativeConnection).eval(lua, keys, values);
  43. }
  44. return result;
  45. }
  46. });
  47. return rs == 1 ? identifier : null;
  48. }
  49. public boolean releaseLock(final String lockName, final String identifier) {
  50. logger.info(lockName + "开始释放锁:" + identifier);
  51. final String lockKey = "lock:" + lockName;
  52. final String lua = "if redis.call(\"get\",KEYS[1])==ARGV[1] then " +
  53. "return redis.call(\"del\",KEYS[1]) " +
  54. "else return 0 end";
  55. Long rs = redisTemplate.execute(new RedisCallback<Long>() {
  56. @Override
  57. public Long doInRedis(RedisConnection redisConnection) throws DataAccessException {
  58. Object nativeConnection = redisConnection.getNativeConnection();
  59. Long result = 0L;
  60. List<String> keys = new ArrayList<>();
  61. keys.add(lockKey);
  62. List<String> values = new ArrayList<>();
  63. values.add(identifier);
  64. // 集群模式
  65. if (nativeConnection instanceof JedisCluster) {
  66. result = (Long) ((JedisCluster) nativeConnection).eval(lua, keys, values);
  67. }
  68. // 单机模式
  69. if (nativeConnection instanceof Jedis) {
  70. result = (Long) ((Jedis) nativeConnection).eval(lua, keys, values);
  71. }
  72. return result;
  73. }
  74. });
  75. return rs.intValue() > 0;
  76. }
  77. }

Controller类

  1. /**
  2. * @Author huanglian
  3. * @Date 2019-01-24-上午11:38
  4. */
  5. @Controller
  6. @RequestMapping(value = "/lock/")
  7. public class LockController {
  8. @AvoidRepeatableCommit
  9. @RequestMapping(value = "/test", method = RequestMethod.POST)
  10. @ResponseBody
  11. public IBaseResp lock() throws InterruptedException {
  12. Thread.sleep(1000);
  13. return BaseRespUtils.buildSuccess(new IBaseResp());
  14. }
  15. }

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/234598
推荐阅读
相关标签
  

闽ICP备14008679号