Redis进阶lua脚本以及Redis + Lua实现分布式锁 - Go语言中文社区

Redis进阶lua脚本以及Redis + Lua实现分布式锁


一、Redis中使用Lua脚本

为什么Redis需要整合Lua脚本?- lua脚本可以保持原子性的方式操作redis服务,redis在执行lua脚本时,不会执行其他客户端的任何命令

什么是多线程模型服务?什么是单线程模型服务?

多线程模型服务(Tomcat),单线程模型服务(Redis)

redis既然是单线程模型,是否意味着redis的操作就是线程安全的?- 不是
在这里插入图片描述
注意:Redis中的lua脚本一定不要执行耗时操作,一旦执行耗时操作,就会阻塞redis的主线程,其他的客户端命令就无法得到执行。因此redis默认有一个lua超时机制,一旦操作5S,这lua脚本就会终止执行。
redis如何操作Lua脚本

 命令格式: eval   script  number  key....  arg.....
        eval  - 执行lua脚本的关键字
        script - 需要执行的lua脚本
        number - lua脚本中key变量的数量    
        key.... - 多个key参数,用于传递到脚本中执行
        arg..... - 多个普通参数,用于传递到脚本中执行

    在lua脚本中执行redis命令:redis.call('','',''.....), 比如需要执行
    set name xiaoming -> redis.call('set', 'name', 'xiaoming')

    案例:
        eval "return 'Hello Lua!'" 0
        eval "redis.call('set', 'name', 'xiaohei')" 0
        eval "return redis.call('get', 'name')" 0
        eval "redis.call('hset', 'person', 'name', 'xiaoming') redis.call('hset', 'person', 'age', '18')" 0
        eval "redis.call('lpush', 'books', 'java', 'php', 'js')" 0

    带参数的案例:
        eval "redis.call('set', KEYS[1], ARGV[1])" 1 name xiaoming
        eval "local score = tonumber(ARGV[1]) if score >= 90 then redis.call('set', 'pingfen', 'A') else redis.call('set', 'pingfen', 'B') end" 0 95

redis如何缓存lua脚本

    命令格式:
        缓存 - script load "待缓存的lua脚本"
        执行 - evalsha "lua的sha签名" number key... arg....

SpringBoot如何操作lua脚本

     //执行lua脚本
DefaultRedisScript defaultRedisScript = new DefaultRedisScript("return redis.call('get',  KEYS[1])", String.class);
Object result = redisTemplate.execute(defaultRedisScript, Collections.singletonList("name"));
System.out.println("执行lua脚本:" + result);


//通过获取原始连接的方式操作lua脚本
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.eval();
connection.evalSha();
connection.scriptLoad();

二、Redis + Lua实现分布式锁

什么是分布式锁?

在分布式集群环境下synchronized关键字,并不能很好的保证集群中每台机器业务的同步,所以这个时候就需要用到分布式锁来解决分布式集群环境下各台机器的业务线程同步的问题。

redis如何实现分布式锁?

通过setnx命令实现。

redis实现分布式锁的lua脚本:

       --使用lua脚本添加分布式锁
       --需要的变量
       local lockName = KEYS[1]
       local lockValue = ARGV[1]
       local lockTimeOut = ARGV[2]
       
       --设置分布式锁
       local result = redis.call('setnx', lockName, lockValue)
       
       --判断是否获得分布式锁
       if result == 1 then 
       --获得分布式锁,添加超时时间
       redis.call('expire', lockName, lockTimeOut)
       --返回1表示获得分布式锁     
       return "1"
         else
       --没有获得分布式锁
       return "0"
       end

redis实现分布式删除的lua脚本:

--删除分布式锁的lua脚本
--获得变量
local lockName = KEYS[1]
local uuid = ARGV[1]


--获得锁的value
local lockValue = redis.call('get', lockName)


--判断锁的value和添加锁时的uuid是否一致
if lockValue == uuid then
--说明锁时当前线程添加
redis.call('del', lockName);
--返回成功
return '1'
end


--说明说不是当前线程添加
return '0'

redis+lua实现分布式锁的工具方法:

package com.qf.util;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;


import java.util.Collections;
import java.util.UUID;


/**
分布式锁的操作工具
*/
@Component
public class LockUtil {


@Autowired
private StringRedisTemplate redisTemplate;


//添加分布式锁的lua脚本
private String lockLua = "--使用lua脚本添加分布式锁n" +
        "--需要的变量n" +
        "local lockName = KEYS[1]n" +
        "local lockValue = ARGV[1]n" +
        "local lockTimeOut = ARGV[2]n" +
        "n" +
        "--设置分布式锁n" +
        "local result = redis.call('setnx', lockName, lockValue)n" +
        "n" +
        "--判断是否获得分布式锁n" +
        "if result == 1 thenn" +
        " t--获得分布式锁,添加超时时间n" +
        "tredis.call('expire', lockName, lockTimeOut)n" +
        "t--返回1表示获得分布式锁tn" +
        "treturn '1'n" +
        "elsen" +
        "t--没有获得分布式锁n" +
        "treturn '0'n" +
        "endn";


//删除分布式锁的lua脚本
private String lockDelLua = "--删除分布式锁的lua脚本n" +
        "--获得变量n" +
        "local lockName = KEYS[1]n" +
        "local uuid = ARGV[1]n" +
        "n" +
        "--获得锁的valuen" +
        "local lockValue = redis.call('get', lockName)n" +
        "n" +
        "--判断锁的value和添加锁时的uuid是否一致n" +
        "if lockValue == uuid thenn" +
        "t--说明锁时当前线程添加n" +
        "tredis.call('del', lockName);n" +
        "t--返回成功n" +
        "treturn '1'n" +
        "endn" +
        "n" +
        "--说明说不是当前线程添加n" +
        "return '0'";


private ThreadLocal<String> threadLocal = new ThreadLocal<>();


/**
 * 添加分布式锁
 * @param lockName
 * @param timeoutSecond
 * @return
 */
public boolean lock(String lockName, Integer timeoutSecond){
    //锁的value
    String uuid = UUID.randomUUID().toString();
    threadLocal.set(uuid);


    //调用lua脚本添加分布式锁
    String luaResult = (String) redisTemplate.execute(
            new DefaultRedisScript(lockLua, String.class),
            Collections.singletonList(lockName),
            uuid, timeoutSecond + "");


    return Integer.parseInt(luaResult) == 1;
}


/**
 * 解除分布式锁
 * @return
 */
public boolean unlock(String lockName){


    String uuid = threadLocal.get();


    //调用lua脚本删除分布式锁
    String result = (String) redisTemplate.execute(
            new DefaultRedisScript(lockDelLua, String.class),
            Collections.singletonList(lockName),
            uuid);


    return  Integer.parseInt(result) == 1;
}
}
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_45384029/article/details/103261588
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-05-21 21:05:59
  • 阅读 ( 963 )
  • 分类:Redis

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢