參考:
https://www.jb51.net/article/212010.htm
https://www.jb51.net/article/179946.htm
https://www.jianshu.com/p/76bc0e963172
https://www.letianbiji.com/redis/redis-lua.html
https://www.jb51.net/article/148833.htm
Redis從2.6版本開始引入對Lua腳本的支持,通過在服務器中嵌入Lua環境,Redis客戶端可以使用Lua腳本,直接在服務端原子的執行多個Redis命令。
其中,使用EVAL命令可以直接對輸入的腳本進行求值:
1
2
|
redis>EVAL
"return 'hello world'"
0
"hello world"
|
使用腳本的好處如下:
1.減少網絡開銷:本來5次網絡請求的操作,可以用一個請求完成,原先5次請求的邏輯放在redis服務器上完成。使用腳本,減少了網絡往返時延。
2.原子操作:Redis會將整個腳本作為一個整體執行,中間不會被其他命令插入。
3.復用:客戶端發送的腳本會永久存儲在Redis中,意味着其他客戶端可以復用這一腳本而不需要使用代碼完成同樣的邏輯。
第一步:redis配置
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@EnableCaching //引入緩存
public class RedisConfig {
/**
* RedisTemplate配置
* @param redisConnectionFactory
* @return
*/
@Primary
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 設置序列化
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(
Object.class);
ObjectMapper om = new ObjectMapper()
.registerModule(new ParameterNamesModule())
.registerModule(new Jdk8Module())
.registerModule(new JavaTimeModule()); // new module, NOT JSR310Module;;
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置redisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
RedisSerializer<?> stringSerializer = new StringRedisSerializer();
// key序列化
redisTemplate.setKeySerializer(stringSerializer);
// value序列化
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// Hash key序列化
redisTemplate.setHashKeySerializer(stringSerializer);
// Hash value序列化
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
第二步:寫lua腳本
redis_lock4.lua的內容:
if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then
return redis.call('expire',KEYS[1],ARGV[2])
else
return -1000
end
解釋 :setnx命令, 設置 KEYS[1]的值為ARGV[1], 並設置KEYS[1]的過期時間為ARGV[2]。
如設置prize_stock的值為100,並設置prize_stock的過期時間為200秒。
第三步:代碼
Long result = null;
try {
//調用lua腳本並執行
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setResultType(Long.class);//返回類型是Long
//lua文件存放在resources目錄下的redis文件夾內
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/redis_lock4.lua")));
result = redisTemplate.execute(redisScript, Arrays.asList("prize_stock"), 100, 300);
System.out.println("lock==" + result);
} catch (Exception e) {
e.printStackTrace();
}
解釋:
Arrays.asList("prize_stock") 為KEYS, lua腳本對應 KEYS[1]
100, 300為參數,lua腳本對應ARGV[1] 和 ARGV[2]。
---------------------------------------------------------
僅記錄,沒用:
加鎖:
if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then
return redis.call('expire',KEYS[1],ARGV[2])
else
return -1000
end
解鎖:
if redis.call("exists",KEYS[1]) == 0 then
return 100
end
if redis.call('get',KEYS[1]) == ARGV[1] then
return redis.call('del',KEYS[1])
else
return 200
end
扣庫存:
if (redis.call('exists', KEYS[1]) == 1) then
local stock = tonumber(redis.call('get', KEYS[1]))
local num = tonumber(ARGV[1])
if (stock >= num) then
return redis.call('incrby', KEYS[1], 0 - num)
end
end
return -100