热门标签 | 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



推荐阅读
  • Java实战之电影在线观看系统的实现
    本文介绍了Java实战之电影在线观看系统的实现过程。首先对项目进行了简述,然后展示了系统的效果图。接着介绍了系统的核心代码,包括后台用户管理控制器、电影管理控制器和前台电影控制器。最后对项目的环境配置和使用的技术进行了说明,包括JSP、Spring、SpringMVC、MyBatis、html、css、JavaScript、JQuery、Ajax、layui和maven等。 ... [详细]
  • Spring学习(4):Spring管理对象之间的关联关系
    本文是关于Spring学习的第四篇文章,讲述了Spring框架中管理对象之间的关联关系。文章介绍了MessageService类和MessagePrinter类的实现,并解释了它们之间的关联关系。通过学习本文,读者可以了解Spring框架中对象之间的关联关系的概念和实现方式。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • 【MicroServices】【Arduino】装修甲醛检测,ArduinoDart甲醛、PM2.5、温湿度、光照传感器等,数据记录于SD卡,Python数据显示,UI5前台,微服务后台……
    这篇文章介绍了一个基于Arduino的装修甲醛检测项目,使用了ArduinoDart甲醛、PM2.5、温湿度、光照传感器等硬件,并将数据记录于SD卡,使用Python进行数据显示,使用UI5进行前台设计,使用微服务进行后台开发。该项目还在不断更新中,有兴趣的可以关注作者的博客和GitHub。 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • Imtryingtofigureoutawaytogeneratetorrentfilesfromabucket,usingtheAWSSDKforGo.我正 ... [详细]
  • React项目中运用React技巧解决实际问题的总结
    本文总结了在React项目中如何运用React技巧解决一些实际问题,包括取消请求和页面卸载的关联,利用useEffect和AbortController等技术实现请求的取消。文章中的代码是简化后的例子,但思想是相通的。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 开发笔记:spring boot项目打成war包部署到服务器的步骤与注意事项
    本文介绍了将spring boot项目打成war包并部署到服务器的步骤与注意事项。通过本文的学习,读者可以了解到如何将spring boot项目打包成war包,并成功地部署到服务器上。 ... [详细]
  • SpringBoot简单日志配置
     在生产环境中,只打印error级别的错误,在测试环境中,可以调成debugapplication.properties文件##默认使用logbacklogging.level.r ... [详细]
  • 本文介绍了一个Magento模块,其主要功能是实现前台用户利用表单给管理员发送邮件。通过阅读该模块的代码,可以了解到一些有关Magento的细节,例如如何获取系统标签id、如何使用Magento默认的提示信息以及如何使用smtp服务等。文章还提到了安装SMTP Pro插件的方法,并给出了前台页面的代码示例。 ... [详细]
  • ps:写的第一个,不足之处,欢迎拍砖---只是想用自己的方法一步步去实现一些框架看似高大上的小功能(比如说模型中的toArraytoJsonsetAtt ... [详细]
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社区 版权所有