使用Lua脚本创建用户token以及token续期(附带lua实现秒杀抢占库存)

问题描述

项目中很多地方使用Lua脚本,Lua脚本的优势之一是可以保证操作的原子性,就是在执行多个redis操作时,可以保证同时成功或者失败。
Lua脚本在很多场景都可以使用,例如用户登录、秒杀扣减库存,凡是同时执行多个redis操作又需要保证原子性的场景都是可以使用的。那么怎么使用Lua脚本呢?
下面通过保存用户token和token续期两个场景,最直观得来学习lua脚本的应用

技术栈和版本

项目中通过spring-data-redis来调用Lua脚本,maven配置如下

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>2.1.9.RELEASE</version>
        </dependency>

TokenDTO

package com.penn.front.sys.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;

/**
 * @author xiaopeng.zhang
 * @since 2020-07-21 10:20
 * token
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TokenDTO implements Serializable {
	/**
	 * 用户ID
	 */
	private Long userId;
	/**
	 * 用户token
	 */
	private String token;
	/**
	 * 过期时间
	 */
	private Date expireTime;
}

保存Token
因为这个项目需要区分用户在多平台登录,所有这里有platform参数字段,这段代码是在登录成功后调用,逻辑主要是这样

  • DefaultRedisScript 指定执行lua脚本类
  • ResourceScriptSource 指定lua文件
  • List keys 定义key
  • Object[] argv 定义参数
  • redisTemplate.execute 执行脚本
	/**
	 * 新增缓存
	 * @param tokenDTO
	 */
	public void set(String platform, TokenDTO tokenDTO) {
		// 获取当前登录平台 01-app, 02-pc

		// 定义lua脚本
		DefaultRedisScript redisScript = new DefaultRedisScript<>();
		redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/createToken.lua")));

		// 定义lua脚本key参数
		List<String> keys = Lists.newArrayList();
		keys.add(RedisKeys.getUserTokenKey(tokenDTO.getToken()));
		keys.add(RedisKeys.getUserTokenRelationKey(tokenDTO.getUserId(), platform));

		// 定义lua脚本的argv参数
		Object[] argv = {
				tokenDTO.getUserId(),
				tokenDTO.getToken(),
				DateUtil.format(tokenDTO.getExpireTime(), "yyyyMMddHHmmss"),
				ApiConstant.EXPIRE.intValue()
		};

		// 执行lua脚本
		redisTemplate.execute(redisScript, keys, argv);
	}

createToken.lua脚本
我们看脚步里面其实有多个call操作,这就是跟分别执行redis的区别

--
-- 创建token和用户的关系
-- User: penn
-- Date: 2020/7/24
-- Time: 20:40
-- To change this template use File | Settings | File Templates.
--

local token_key = KEYS[1];
local user_token_relation_key = KEYS[2];
local v_userId = ARGV[1];
local v_token = ARGV[2];
local v_expireTime = ARGV[3];
local expire = ARGV[4];

local del_key_prefix = 'sys:security:user:token:'
local token = redis.call('HGET', user_token_relation_key, 'token')
local del_key = ''

if type(token) == 'string'
then
    del_key = del_key_prefix .. string.gsub(token,'\"','')
    redis.call('del', del_key)
end

redis.call('HMSET', token_key, 'userId', v_userId, 'token', v_token, 'expireTime', v_expireTime);
redis.call('EXPIRE', token_key, expire);

redis.call('HMSET', user_token_relation_key, 'token', v_token);
redis.call('EXPIRE', user_token_relation_key, expire);

附带一个token需要的实现
token续期

	/**
	 * 刷新缓存有效期
	 * @param userId
	 * @param token
	 */
	public void expire(String platform, Long userId, String token, String expireTime) {

		// 定义lua脚本
		DefaultRedisScript redisScript = new DefaultRedisScript<>();
		redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/expireToken.lua")));

		// 定义lua脚本key参数
		List<String> keys = Lists.newArrayList();
		keys.add(RedisKeys.getUserKey(userId));
		keys.add(RedisKeys.getUserTokenKey(token));
		keys.add(RedisKeys.getUserTokenRelationKey(userId, platform));

		// 定义lua脚本的argv参数
		Object[] argv = {
				expireTime,
				ApiConstant.EXPIRE.intValue()
		};

		// 执行lua脚本
		redisTemplate.execute(redisScript, keys, argv);
	}

expireToken.lua

--
-- 创建token和用户的关系
-- User: penn
-- Date: 2022/7/24
-- Time: 20:40
-- To change this template use File | Settings | File Templates.
--

local user = KEYS[1];
local token_key = KEYS[2];
local user_token_relation_key = KEYS[3];
local v_expireTime = ARGV[1];
local expire = ARGV[2];

redis.call('EXPIRE', user, expire);

redis.call('HMSET', token_key, 'expireTime', v_expireTime);
redis.call('EXPIRE', token_key, expire);

redis.call('EXPIRE', user_token_relation_key, expire);


Lua实现秒杀抢占库存
参考地址

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xinqing5130

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值