Redis之Lua脚本:原子性的执行exsits&incr

4 篇文章 0 订阅
3 篇文章 0 订阅

Redis提供的命令虽然不少, 但是严格的说计算能力还是比较有限的. 好在Redis2.6版本后引入Lua脚本, 大大增强了这方面的计算能力. 最重要的是执行lua脚本还具备原子性, 所以在对一致性要求高的环境下, lua脚本或许是个不错的选择.本文通过具体的场景来简介下lua脚本的使用.这里,我们基于Springboot框架的RedisTemplate来操作redis.

问题抽象:

  • 使用redis计数, 计数器的key会在生命周期是15分钟;
  • 每次优先查询redis中的计数器,当计数值不存在时,从数据库中计算,并重新设置redis值;
  • 每次产品有更新时, 将redis的计数器自增1个步长;

下面代码片段,帮助你理解上述的场景:

@Autowired
private DemoDao demoDao;

@Autowired
private StringRedisTemplate redisTemplate;
	
// snippet 1;
public long getCounter(String countKey) {
	Long count = redisTemplate.opsForValue().get(countKey);
	if (null == count || count <= 0L) {
		count = demoDao.countByXXAndYY(XX, YY);
		if (count > 0) {
			redisTemplate.opsForValue().set(countKey, String.valueOf(count), 15, TimeUnit.MINUTES);
		}
	}
	return count;
}

// snippet 2;
public void update() {
	...
	demoDao.updateByXXAndYY(XX, YY);
	redisTemplate.opsForValue().increment(countKey, 1L);
	...
}

上述代码有问题吗?问题在哪儿? 不妨看下面的操作:

127.0.0.1:6379> get incr_key
(nil)
127.0.0.1:6379> incr incr_key
(integer) 1
127.0.0.1:6379> get incr_key
"1"

如果在第15分钟, 缓存失效了, key被删除, 但这是当产品更新时,又执行了increment操作;由于redis在执行"incr"命令时, 如果key不存在,那就创建并自增;那么在接下来的15分钟内,你会发现缓存中值,不可信了。

寻求解决方法
要解决上面的问题,一种可行的方法是,在在incr操作时,判断下key是否存在,仅在存在的情况下,才执行increment; 但问题是 exist和incr是两个指令,既不能保证他们都执行成功,更不能保证他们的原子性。
这时lua脚本就可以展现它的优越性了。判断+更新的lua脚本如下:

static {
	incrScript = "if (redis.call('exists', KEYS[1])) == 1 then \n"
			+ "return redis.call('incr', KEYS[1]) \n"
			+ "else return 0 end";
}

需要注意的是,Lua中只有nil和false才是假,其余值,包括空字符串和0,都被认为是真值,所有if语句的判断不能简单的写为"if redis.call(‘exists’, KEYS[1]) then …"
那么redisTemplate如何执行它了?

private Long incrementOnExist(String key) {
	DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
	redisScript.setScriptText(incrScript);
	redisScript.setResultType(Long.class);
	Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), null);
	log.info("increment key:{} result:{}", key, result);
	return result;
}

当然,上面是默认使用Lettuce驱动的connection.如果不是使用上述驱动,需要拿到redis所使用的链接,然后执行。

Long result = redisTemplate.execute(new RedisCallback<Long>() {
	@Override
	public Long doInRedis(RedisConnection redisConnection) throws DataAccessException {
		Object connection = redisConnection.getNativeConnection();
		//集群模式
		if (connection instanceof JedisCluster) {
			return (Long) ((JedisCluster) connection).eval(incrScript, Collections.singletonList(key), Collections.EMPTY_LIST);
		}
		//单机模式
		else if (connection instanceof Jedis) {
			return (Long) ((Jedis) connection).eval(incrScript, Collections.singletonList(key), Collections.EMPTY_LIST);
		}
		return null;
	}
});
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis中,Lua脚本可以通过使用EVAL命令来实现原子性操作。通过将多个Redis命令组合在一个Lua脚本执行,可以确保这些命令在同一时间内被连续地执行,从而保证原子性。引用 在Lua脚本中,可以使用redis.call()和redis.pcall()这两个不同的函数来调用Redis命令。redis.call()函数用于执行普通的Redis命令,而redis.pcall()函数则用于执行Redis命令并处理错误。通过使用这两个函数,可以在Lua脚本执行多个Redis命令,并保证它们的原子性。引用 另外,Redis执行Lua脚本时采用了单线程的方式,这意味着同一时间内只能执行一个Lua脚本,不会被其他脚本Redis命令打断。因此,在执行Lua脚本期间,不会发生并发问题,从而保证了原子性。引用 综上所述,通过将多个Redis命令组合在Lua脚本中,并使用redis.call()和redis.pcall()函数来执行这些命令,结合Redis的单线程执行机制,可以保证Lua脚本原子性操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Redis Lua脚本实现原子性操作](https://blog.csdn.net/kuishao1314aa/article/details/120367618)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值