热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

采用redistoken,分布式锁的接口幂等性实现

2019独角兽企业重金招聘Python工程师标准每一次进行幂等校验之前先获取token,因为token的时效性只有1次,我们每次获得的token在

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

每一次进行幂等校验之前先获取token,因为token的时效性只有1次,我们每次获得的token在幂等操作后就无效了,所以一个token不需要长期保存在redis中。

@RestController
public class TokenController {@Autowired
private RedisService redisService;
@GetMapping("/users-anon/gettoken")public Map getToken(@RequestParam("url") String url) {Map,String> tokenMap = new HashMap();
String tokenValue = UUID.randomUUID().toString();
tokenMap.put(url + tokenValue, tokenValue);
redisService.set(url + tokenValue, tokenValue);
return tokenMap;
}
}

获取token后,访问该url的接口,此时我们使用拦截器进行拦截(/add/**可表示为所有有新增操作的接口)

@SpringBootConfiguration
public class TokenInterceptorConfig extends WebMvcConfigurerAdapter {@Autowired
private TokenInterceptor tokenInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(tokenInterceptor).addPathPatterns("/add/**");
}
}

拦截器的具体内容为

@Slf4j
@Component
public class TokenInterceptor implements HandlerInterceptor {@Autowired
private RedisService redisService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String tokenName = request.getRequestURI() + request.getParameter("token_value");
String tokenValue = request.getParameter("token_value");
if (tokenValue != null && !tokenValue.equals("")) {log.info("tokenName:{},tokenValue:{}",tokenName,tokenValue);
return handleToken(request,response,handler);
}return false;
}@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {if (redisService.exists(request.getParameter("token_value"))) {RedisTool.releaseDistributedLock(redisService, request.getParameter("token_value"), request.getParameter("token_value"));
}}@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {}/**
* 分布式锁处理
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
private boolean handleToken(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//当大量高并发下所有带token参数的请求进来时,进行分布式锁定,允许某一台服务器的一个线程进入,锁定时间3分钟
if (RedisTool.tryGetDistributedLock(redisService,request.getParameter("token_value"),request.getParameter("token_value"),180)) {if (redisService.exists(request.getRequestURI() + request.getParameter("token_value"))) {//当请求的url与token与redis中的存储相同时
if (redisService.get(request.getRequestURI() + request.getParameter("token_value")).equals(request.getParameter("token_value"))) {//放行的该线程删除redis中存储的token
redisService.del(request.getRequestURI() + request.getParameter("token_value"));
//放行
return true;
}}//当请求的url与token与redis中的存储不相同时,解除锁定
RedisTool.releaseDistributedLock(redisService,request.getParameter("token_value"),request.getParameter("token_value"));
//进行拦截
return false;
}return false;
}

直到后续controller操作执行完毕后释放分布式锁,见postHandle

分布式锁的具体实现为

package com.cloud.user.config.redis;

/**
* Created by Administrator on 2018-08-05.
*/
public class RedisTool {private static final String LOCK_SUCCESS = "OK";
private static final Long RELEASE_SUCCESS = 1L;
/**
* 尝试获取分布式锁
* @param lockKey
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(RedisService redisService, String lockKey, String requestId, int expireTime) {String result = redisService.set(lockKey, requestId, expireTime);

if (LOCK_SUCCESS.equals(result)) {return true;
}return false;
}/**
* 释放分布式锁
* @param lockKey
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(RedisService redisService, String lockKey, String requestId) {Object result = redisService.eval(lockKey,requestId);

if (RELEASE_SUCCESS.equals(result)) {return true;
}return false;

}
}

在RedisServiceImpl实现类中,以上set跟eval的具体实现为

private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
@Autowired
private JedisPool jedisPool;public T execute(RedisFunction fun) {Jedis jedis = null;try {jedis = jedisPool.getResource();return (T)fun.callback(jedis);}catch (Exception e) {logger.error(e.getMessage());return null;}finally {if (jedis != null) {jedis.close();}}
}

@Override
public String set(String lockKey, String requestId, int expireTime) {return execute(new RedisFunction, Jedis>() {@Override
public String callback(Jedis jedis) {return jedis.set(lockKey,requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,expireTime);
}});
}

@Override
public Object eval(String lockKey, String requestId) {return execute(new RedisFunction, Jedis>() {@Override
public Object callback(Jedis jedis) {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
return jedis.eval(script, Collections.singletonList(lockKey),Collections.singletonList(requestId));
}});
}

public interface RedisFunction {Object callback(E jedis);
}

最后解释一下为什么在分布式锁中不直接使用setnx+expire来设置分布式锁,而使用set(key,value,"NX","PX",过期时间)来做分布式锁,因为你鬼知道它在执行setnx的时候是不是刚好系统崩溃了,这样expire就未执行,结果大家都清楚这个分布式锁就永远锁定了。


转:https://my.oschina.net/u/3768341/blog/1922950



推荐阅读
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • Java实战之电影在线观看系统的实现
    本文介绍了Java实战之电影在线观看系统的实现过程。首先对项目进行了简述,然后展示了系统的效果图。接着介绍了系统的核心代码,包括后台用户管理控制器、电影管理控制器和前台电影控制器。最后对项目的环境配置和使用的技术进行了说明,包括JSP、Spring、SpringMVC、MyBatis、html、css、JavaScript、JQuery、Ajax、layui和maven等。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • 在springmvc框架中,前台ajax调用方法,对图片批量下载,如何弹出提示保存位置选框?Controller方法 ... [详细]
  • Spring学习(4):Spring管理对象之间的关联关系
    本文是关于Spring学习的第四篇文章,讲述了Spring框架中管理对象之间的关联关系。文章介绍了MessageService类和MessagePrinter类的实现,并解释了它们之间的关联关系。通过学习本文,读者可以了解Spring框架中对象之间的关联关系的概念和实现方式。 ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • IOS开发之短信发送与拨打电话的方法详解
    本文详细介绍了在IOS开发中实现短信发送和拨打电话的两种方式,一种是使用系统底层发送,虽然无法自定义短信内容和返回原应用,但是简单方便;另一种是使用第三方框架发送,需要导入MessageUI头文件,并遵守MFMessageComposeViewControllerDelegate协议,可以实现自定义短信内容和返回原应用的功能。 ... [详细]
  • 本文介绍了一个适用于PHP应用快速接入TRX和TRC20数字资产的开发包,该开发包支持使用自有Tron区块链节点的应用场景,也支持基于Tron官方公共API服务的轻量级部署场景。提供的功能包括生成地址、验证地址、查询余额、交易转账、查询最新区块和查询交易信息等。详细信息可参考tron-php的Github地址:https://github.com/Fenguoz/tron-php。 ... [详细]
  • Java SE从入门到放弃(三)的逻辑运算符详解
    本文详细介绍了Java SE中的逻辑运算符,包括逻辑运算符的操作和运算结果,以及与运算符的不同之处。通过代码演示,展示了逻辑运算符的使用方法和注意事项。文章以Java SE从入门到放弃(三)为背景,对逻辑运算符进行了深入的解析。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
author-avatar
2012牛人
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有