前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis的setnx简单解决请求重复提交、请求并发问题

Redis的setnx简单解决请求重复提交、请求并发问题

原创
作者头像
bug专8
修改2021-01-20 15:39:09
4.2K0
修改2021-01-20 15:39:09
举报

SETNX简介

SETNX key value

只在键 key 不存在的情况下, 将键 key 的值设置为 value

若键 key 已经存在, 则 SETNX 命令不做任何动作。

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

返回值

命令在设置成功时返回 1 , 设置失败时返回 0

代码示例

代码语言:javascript
复制
redis> EXISTS job                # job 不存在
(integer) 0

redis> SETNX job "programmer"    # job 设置成功
(integer) 1

redis> SETNX job "code-farmer"   # 尝试覆盖 job ,失败
(integer) 0

redis> GET job                   # 没有被覆盖
"programmer"

请求重复提交问题

1、问题描述:

当同一个请求在短时间内重复提交时,容易导致系统不稳定、数据库连接池占用大。例如,一个下载数据的请求在执行过程中,由于下载的数据量大、耗时较长。当客户端通过刷新或者再次点击下载操作触发下载请求时,就会导致请求重复提交。

2、解决方案:

使用redis将同一个请求的关键信息作为key存在redis中,并设置key的有效时间,当请求执行完成后主动销毁这个key。如果前一次的请求还在执行过程中,后面的重复请求在执行时,先通过setnx检查key是否存在(前一个请求是否执行完毕)。如果key存在(前一次请求还没有执行完毕),则返回key的剩余有效时间。如果key不存在(前一次已经执行完毕),则新建key重新执行请求。

代码语言:javascript
复制
	//通过redis处理数据重复下载问题
	@RequestMapping(value="/common",method=RequestMethod.POST)
	@ResponseBody
	public ResultData downCommon(String beamcodes) {
		//请求的关键信息key
		Integer bfid = getLoginBfId();
		String key = SystemConfigVal.REDIS_DEFAULT_KEY + ZL_CACHE_PREFIX + bfid + "&" + beamcodes;
		String value = getLoginBfId() + "&" + beamcodes;
		//key的有效时间60s
		Long seconds = 60L;
		//通过setnx检查key是否存在,不存在时才执行下载请求
		Long result = redisUtil.setnx(key,seconds.intValue(),value);
		if(result == 1){
			//执行数据下载,数据下载成功后删除key
			tensionDownService.downTensionData(bfid,beamcodes,key);
		}
		//返回key的有效时间
		seconds = redisUtil.ttl(key);
		return toSuccess(seconds==null?0L:seconds);
	}

请求并发问题

1、问题描述:

当多个请求并发执行时,容易导致系统不稳定、数据异常。例如,在执行下载数据请求时,需要先获取token口令,每次下载都需要获取最新的token。当下载数据请求并发时,多次请求获取的token不一致,导致数据下载的token认证失败。

2、解决方案:

使用redis将获取的最新token存在redis中,并设置key的有效时间。当进行数据下载时,先检查redis中的token是否存在,优先使用redis中的token,如果redis中token不存在或者已经失效,则重新获取最新的token并同步存进redis中。如果多个请求并发下载数据时,并且redis中不存在token,通过setnx设置最新的token。第一个setnx成功的请求将token更新至redis中,其他未setnx成功的请求则获取redis中的最新token(并发时,由于redis中token创建和获取有时间差,未setnx成功的请求需要多次才能获取到redis中的token)。

代码语言:javascript
复制
	//通过redis处理数据并发下载问题
	@Override
	public void downTensionData(Integer bfid, String beamcodes, String key) {
		try{
			String token = null;
			//请求的关键信息key
			String tokenKey = SystemConfigVal.REDIS_DEFAULT_KEY + ZL_CACHE_PREFIX + bfid;
			//并发请求时通过setnx抢占key,抢占成功的请求更新token,抢占失败的请求获取最新的token
			Long result = redisUtil.setnx(tokenKey,60*30,"");
			if(result == 1){
				//当redis不存在token时,将获取到最新token更新至redis中
				token = tensionAuthService.getToken(bfid);
				redisUtil.set(tokenKey,60*30,token);
			}else if(result == 0){
				//当redis存在token时,获取token,直至获取到到有效token
				for (int i = 0; i < 60; i++) {
					Thread.sleep(1000);
					token = redisUtil.getString(tokenKey);
					if(!StringUtils.isEmpty(token)){
						break;
					}
				}
			}
			if(token!=null && !"".equals(token.trim())) {
				//执行数据下载
				Map<String, Object> authUser = tensionAuthService.getAuthUser(bfid);
				tensionDownService.downloadTensionCommon(token,String.valueOf(authUser.get("account")),bfid, beamcodes);
			}
		}catch (Exception e){
			log.error(e.getMessage());
		}finally {
			redisUtil.delete(key);
		}
	}

简单的RedisUtil工具类

代码语言:javascript
复制
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.SerializationUtils;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;

import javax.annotation.PostConstruct;

/**
 * @author jiangdongping
 * @version v1.0
 * @ClassName: RedisUtils
 * @date 2021/1/6  9:21
 */
@Component
public class RedisUtil {
	private Log log = LogFactory.getLog(RedisUtil.class);

	@Autowired
	private ShardedJedisPool shardedJedisPool;

	private static boolean isEnabled = true;

	/**
	 * @param shardedJedisPool the shardedJedisPool to set
	 */
	public void setShardedJedisPool(ShardedJedisPool shardedJedisPool) {
		this.shardedJedisPool = shardedJedisPool;
	}

	@PostConstruct
	private void checkConnect() {
		try {
			log.info("系统启动,检查Redis服务是否启动!");
			ShardedJedis jedis = shardedJedisPool.getResource();
			isEnabled = jedis!=null?true:false;
			log.info("Redis服务已启动!");
		}catch (Exception e) {
			log.error("redis 连接失败:"+e.getMessage());
			isEnabled = false;
		}
	}

	public static boolean isEnable() {
		return isEnabled;
	}

	public boolean valiEnable() {
		//如果启用状态为false,主动尝试一次连接,更新状态
		if(!isEnabled) {
			try {
				log.info("系统启动,检查Redis服务是否启动!");
				ShardedJedis jedis = shardedJedisPool.getResource();
				isEnabled = (jedis!=null?true:false);
				log.info("Redis服务已启动!");
			}catch (Exception e) {
				log.error("redis 连接失败:"+e.getMessage());
				isEnabled = false;
			}
		}

		return isEnabled;
	}
	public String set(String key, int seconds, String value) {
		if(!valiEnable()) {
			return null;
		}
		try {
			ShardedJedis jedis = shardedJedisPool.getResource();
			String result = jedis.setex(SerializationUtils.serialize(key), seconds,SerializationUtils.serialize(value));
			jedis.close();
			return result;
		} catch (Exception e) {
			e.printStackTrace();
			isEnabled = false;
			log.error("redis 连接失败:"+e.getMessage());
		}
		return null;
	}

	public Long setnx(String key, int seconds, String value) {
		if(!valiEnable()) {
			return null;
		}
		try {
			ShardedJedis jedis = shardedJedisPool.getResource();
			Long result = jedis.setnx(SerializationUtils.serialize(key),SerializationUtils.serialize(value));
			if(result == 1){
                jedis.expire(SerializationUtils.serialize(key),seconds);
            }
			jedis.close();
			return result;
		} catch (Exception e) {
			e.printStackTrace();
			isEnabled = false;
			log.error("redis 连接失败:"+e.getMessage());
		}
		return null;
	}

	public String getString(String key) {
		if(!valiEnable()) {
			return null;
		}
		if(!exists(key)) {
			log.info("该值:"+key +" 在缓存中不存在!");
			return null;
		}
		try {
			ShardedJedis jedis = shardedJedisPool.getResource();
			byte[] rb = jedis.get(SerializationUtils.serialize(key));
			jedis.close();
			return SerializationUtils.deserialize(rb).toString();
		} catch (Exception e) {
			isEnabled = false;
			log.error("redis 连接失败:"+e.getMessage());
			return null;
		}
	}

	public boolean exists(String key) {
		if(!valiEnable()) {
			return false;
		}
		try {
			ShardedJedis jedis = shardedJedisPool.getResource();
			boolean result = jedis.exists(SerializationUtils.serialize(key));
			jedis.close();
			return result;
		} catch (Exception e) {
			e.printStackTrace();
			isEnabled = false;
			log.error("redis 连接失败:"+e.getMessage());
			return false;
		}
	}

	public void delete(String key) {
		if(!valiEnable()) {
			return;
		}
		if(!exists(key)) {
			log.info("该值:"+key +" 在缓存中不存在!");
			return;
		}
		try {
			ShardedJedis jedis = shardedJedisPool.getResource();
			jedis.del(SerializationUtils.serialize(key));
			jedis.close();
		} catch (Exception e) {
			e.printStackTrace();
			isEnabled = false;
			log.error("redis 连接失败:"+e.getMessage());
		}
	}

	public Long ttl(String key) {
		if(!valiEnable()) {
			return null;
		}
		if(!exists(key)) {
			log.info("该值:"+key +" 在缓存中不存在!");
			return null;
		}
		try {
			ShardedJedis jedis = shardedJedisPool.getResource();
			Long seconds = jedis.ttl(SerializationUtils.serialize(key));
			jedis.close();
			return seconds;
		} catch (Exception e) {
			isEnabled = false;
			log.error("redis 连接失败:"+e.getMessage());
			return null;
		}
	}
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SETNX简介
    • SETNX key value
      • 返回值
        • 代码示例
        • 请求重复提交问题
          • 1、问题描述:
            • 2、解决方案:
            • 请求并发问题
              • 1、问题描述:
                • 2、解决方案:
                • 简单的RedisUtil工具类
                相关产品与服务
                云数据库 Redis
                腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档