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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介:

每一次进行幂等校验之前先获取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,String> tokenMap = new HashMap();
    String tokenValue = UUID.randomUUID().toString();
    tokenMap.put(url + tokenValue, tokenValue);
    redisService.set(url + tokenValue, tokenValue);
    return tokenMap;
}
AI 代码解读

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

@Autowired
private TokenInterceptor tokenInterceptor;

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

}
拦截器的具体内容为
@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;
}
AI 代码解读

直到后续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;

}
AI 代码解读

}
在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();
    }
}
AI 代码解读

}
@Override
public String set(String lockKey, String requestId, int expireTime) {

return execute(new RedisFunction<String, Jedis>() {

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

});
AI 代码解读

}
@Override
public Object eval(String lockKey, String requestId) {

return execute(new RedisFunction<String, 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));
    }
});
AI 代码解读

}
public interface RedisFunction {

Object callback(E jedis);
AI 代码解读

}

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
打赏
0
0
0
0
16
分享
相关文章
高并发秒杀系统实战(Redis+Lua分布式锁防超卖与库存扣减优化)
秒杀系统面临瞬时高并发、资源竞争和数据一致性挑战。传统方案如数据库锁或应用层锁存在性能瓶颈或分布式问题,而基于Redis的分布式锁与Lua脚本原子操作成为高效解决方案。通过Redis的`SETNX`实现分布式锁,结合Lua脚本完成库存扣减,确保操作原子性并大幅提升性能(QPS从120提升至8,200)。此外,分段库存策略、多级限流及服务降级机制进一步优化系统稳定性。最佳实践包括分层防控、黄金扣减法则与容灾设计,强调根据业务特性灵活组合技术手段以应对高并发场景。
398 7
【📕分布式锁通关指南 02】基于Redis实现的分布式锁
本文介绍了从单机锁到分布式锁的演变,重点探讨了使用Redis实现分布式锁的方法。分布式锁用于控制分布式系统中多个实例对共享资源的同步访问,需满足互斥性、可重入性、锁超时防死锁和锁释放正确防误删等特性。文章通过具体示例展示了如何利用Redis的`setnx`命令实现加锁,并分析了简化版分布式锁存在的问题,如锁超时和误删。为了解决这些问题,文中提出了设置锁过期时间和在解锁前验证持有锁的线程身份的优化方案。最后指出,尽管当前设计已解决部分问题,但仍存在进一步优化的空间,将在后续章节继续探讨。
777 131
【📕分布式锁通关指南 02】基于Redis实现的分布式锁
|
2月前
|
redis分布式锁在高并发场景下的方案设计与性能提升
本文探讨了Redis分布式锁在主从架构下失效的问题及其解决方案。首先通过CAP理论分析,Redis遵循AP原则,导致锁可能失效。针对此问题,提出两种解决方案:Zookeeper分布式锁(追求CP一致性)和Redlock算法(基于多个Redis实例提升可靠性)。文章还讨论了可能遇到的“坑”,如加从节点引发超卖问题、建议Redis节点数为奇数以及持久化策略对锁的影响。最后,从性能优化角度出发,介绍了减少锁粒度和分段锁的策略,并结合实际场景(如下单重复提交、支付与取消订单冲突)展示了分布式锁的应用方法。
187 3
|
5月前
|
Springboot使用Redis实现分布式锁
通过这些步骤和示例,您可以系统地了解如何在Spring Boot中使用Redis实现分布式锁,并在实际项目中应用。希望这些内容对您的学习和工作有所帮助。
323 83
|
2月前
|
从扣减库存场景来讲讲redis分布式锁中的那些“坑”
本文从一个简单的库存扣减场景出发,深入分析了高并发下的超卖问题,并逐步优化解决方案。首先通过本地锁解决单机并发问题,但集群环境下失效;接着引入Redis分布式锁,利用SETNX命令实现加锁,但仍存在死锁、锁过期等隐患。文章详细探讨了通过设置唯一标识、续命机制等方法完善锁的可靠性,并最终引出Redisson工具,其内置的锁续命和原子性操作极大简化了分布式锁的实现。最后,作者剖析了Redisson源码,揭示其实现原理,并预告后续关于主从架构下分布式锁的应用与性能优化内容。
119 0
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
【📕分布式锁通关指南 03】通过Lua脚本保证redis操作的原子性
本文介绍了如何通过Lua脚本在Redis中实现分布式锁的原子性操作,避免并发问题。首先讲解了Lua脚本的基本概念及其在Redis中的使用方法,包括通过`eval`指令执行Lua脚本和通过`script load`指令缓存脚本。接着详细展示了如何用Lua脚本实现加锁、解锁及可重入锁的功能,确保同一线程可以多次获取锁而不发生死锁。最后,通过代码示例演示了如何在实际业务中调用这些Lua脚本,确保锁操作的原子性和安全性。
282 6
【📕分布式锁通关指南 03】通过Lua脚本保证redis操作的原子性
Redis分布式锁如何实现 ?
Redis分布式锁主要依靠一个SETNX指令实现的 , 这条命令的含义就是“SET if Not Exists”,即不存在的时候才会设置值。 只有在key不存在的情况下,将键key的值设置为value。如果key已经存在,则SETNX命令不做任何操作。 这个命令的返回值如下。 ● 命令在设置成功时返回1。 ● 命令在设置失败时返回0。 假设此时有线程A和线程B同时访问临界区代码,假设线程A首先执行了SETNX命令,并返回结果1,继续向下执行。而此时线程B再次执行SETNX命令时,返回的结果为0,则线程B不能继续向下执行。只有当线程A执行DELETE命令将设置的锁状态删除时,线程B才会成功执行S
|
5月前
|
【📕分布式锁通关指南 04】redis分布式锁的细节问题以及RedLock算法原理
本文深入探讨了基于Redis实现分布式锁时遇到的细节问题及解决方案。首先,针对锁续期问题,提出了通过独立服务、获取锁进程自己续期和异步线程三种方式,并详细介绍了如何利用Lua脚本和守护线程实现自动续期。接着,解决了锁阻塞问题,引入了带超时时间的`tryLock`机制,确保在高并发场景下不会无限等待锁。最后,作为知识扩展,讲解了RedLock算法原理及其在实际业务中的局限性。文章强调,在并发量不高的场景中手写分布式锁可行,但推荐使用更成熟的Redisson框架来实现分布式锁,以保证系统的稳定性和可靠性。
214 0
【📕分布式锁通关指南 04】redis分布式锁的细节问题以及RedLock算法原理
|
7月前
|
使用lock4j-redis-template-spring-boot-starter实现redis分布式锁
通过使用 `lock4j-redis-template-spring-boot-starter`,我们可以轻松实现 Redis 分布式锁,从而解决分布式系统中多个实例并发访问共享资源的问题。合理配置和使用分布式锁,可以有效提高系统的稳定性和数据的一致性。希望本文对你在实际项目中使用 Redis 分布式锁有所帮助。
466 5

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问