当前位置:首页 » 网页前端 » redis中lua脚本的使用
扩展阅读
webinf下怎么引入js 2023-08-31 21:54:13
堡垒机怎么打开web 2023-08-31 21:54:11

redis中lua脚本的使用

发布时间: 2022-08-16 17:45:02

Ⅰ 如何在 Redis 实现 Lua 脚本事务

在 1998-2003 年间,如果你想运行一个正规的数据库驱动的网站/服务,但又没有足够的资金购买微软或 Oracle 的数据库,你可以选择 MySQL 或 Postgres 。很多人都选择了 MySQL,因为它速度较快——主要是因为 MyISAM 存储引擎没有提供事务功能以此来换取性能,但速度确实很快。另一些人转向 Postgres,因为虽然在相同硬件上其性能明显低于 MySQL,但 Postgres 不会丢失数据(说实话,MySQL 数据丢失的情况非常少见,但丢了可不是闹着玩的)。
就这样凑合着过了很久;MySQL 将其默认的存储引擎从 MyISAM 过渡到了 InnoDB (其实很早

Ⅱ 使用redis实现的分布式锁原理是什么

一、写在前面

现在面试,一般都会聊聊分布式系统这块的东西。通常面试官都会从服务框架(Spring Cloud、Dubbo)聊起,一路聊到分布式事务、分布式锁、ZooKeeper等知识。

所以咱们这篇文章就来聊聊分布式锁这块知识,具体的来看看Redis分布式锁的实现原理。

说实话,如果在公司里落地生产环境用分布式锁的时候,一定是会用开源类库的,比如Redis分布式锁,一般就是用Redisson框架就好了,非常的简便易用。

大家如果有兴趣,可以去看看Redisson的官网,看看如何在项目中引入Redisson的依赖,然后基于Redis实现分布式锁的加锁与释放锁。

下面给大家看一段简单的使用代码片段,先直观的感受一下:

大家看到了吧,那个myLock的hash数据结构中的那个客户端ID,就对应着加锁的次数

(5)释放锁机制

如果执行lock.unlock(),就可以释放分布式锁,此时的业务逻辑也是非常简单的。

其实说白了,就是每次都对myLock数据结构中的那个加锁次数减1。

如果发现加锁次数是0了,说明这个客户端已经不再持有锁了,此时就会用:

“del myLock”命令,从redis里删除这个key。

然后呢,另外的客户端2就可以尝试完成加锁了。

这就是所谓的分布式锁的开源Redisson框架的实现机制。

一般我们在生产系统中,可以用Redisson框架提供的这个类库来基于redis进行分布式锁的加锁与释放锁。

(6)上述Redis分布式锁的缺点

其实上面那种方案最大的问题,就是如果你对某个redis master实例,写入了myLock这种锁key的value,此时会异步复制给对应的master slave实例。

但是这个过程中一旦发生redis master宕机,主备切换,redis slave变为了redis master。

接着就会导致,客户端2来尝试加锁的时候,在新的redis master上完成了加锁,而客户端1也以为自己成功加了锁。

此时就会导致多个客户端对一个分布式锁完成了加锁。

这时系统在业务语义上一定会出现问题,导致各种脏数据的产生。

所以这个就是redis cluster,或者是redis master-slave架构的主从异步复制导致的redis分布式锁的最大缺陷:在redis master实例宕机的时候,可能导致多个客户端同时完成加锁。

Ⅲ redis有脚本语言吗

有,lua脚本语言

Redis脚本

使用脚本的好处:

  • 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延

  • 原子操作。redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。

  • 复用。客户端发送的脚步会永久存在redis中,这样,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑。

  • 调用Lua脚本的语法:

    $ redis-cli --eval path/to/redis.lua KEYS[1] KEYS[2] , ARGV[1] ARGV[2] ...

  • --eval,告诉redis-cli读取并运行后面的lua脚本

  • path/to/redis.lua,是lua脚本的位置

  • KEYS[1] KEYS[2],是要操作的键,可以指定多个,在lua脚本中通过KEYS[1], KEYS[2]获取

  • ARGV[1] ARGV[2],参数,在lua脚本中通过ARGV[1], ARGV[2]获取。

  • 注意:

    KEYS和ARGV中间的 ',' 两边的空格,不能省略。

    redis支持大部分Lua标准库

    库名

    说明

    Base 提供一些基础函数

    String 提供用于字符串操作的函数

    Table 提供用于表操作的函数

    Math 提供数学计算函数

    Debug 提供用于调试的函数

    在脚本中调用redis命令

    在脚本中可以使用redis.call函数调用Redis命令

  • redis.call('set', 'foo', 'bar')local value=redis.call('get', 'foo') --value的值为bar

  • redis.call函数的返回值就是Redis命令的执行结果

    Redis命令的返回值有5种类型,redis.call函数会将这5种类型的回复转换成对应的Lua的数据类型,具体的对应规则如下(空结果比较特殊,其对应Lua的false)

    redis返回值类型和Lua数据类型转换规则

    redis返回值类型

    Lua数据类型

    整数回复 数字类型

    字符串回复 字符串类型

    多行字符串回复 table类型(数组形式)

    状态回复 table类型(只有一个ok字段存储状态信息)

    错误回复 table类型(只有一个err字段存储错误信息)

    redis还提供了redis.pcall函数,功能与redis.call相同,唯一的区别是当命令执行出错时,redis.pcall会记录错误并继续执行,而redis.call会直接返回错误,不会继续执行。

    在脚本中可以使用return语句将值返回给客户端,如果没有执行return语句则默认返回nil

    Lua数据类型和redis返回值类型转换规则

    Lua数据类型

    redis返回值类型

    数字类型 整数回复(Lua的数字类型会被自动转换成整数)

    字符串类型 字符串回复

    table类型(数组形式) 多行字符串回复

    table类型(只有一个ok字段存储状态信息) 状态回复

    table类型(只有一个err字段存储错误信息) 错误回复

    脚本相关命令

  • EVAL "lua-script" [key ...] [arg ...]

    通过key和arg这两类参数向脚本传递数据,它们的值在脚本中分别使用KEYS和ARGV两个表类型的全局变量访问

    注意: EVAL命令依据参数key-number来将其后面的所有参数分别存入脚本中KEYS和ARGV两个table类型的全局变量。当脚本不需要任何参数时,也不能省略这个参数(设为0)

    redis>EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 foo bar
    OK
    redis>GET foo"bar"
  • EVALSHA命令

    在脚本比较长的情况下,如果每次调用脚本都需要将整个脚本传给Redis会占用较多的带宽。为了解决这个问题,Redis提供了EVALSHA命令,允许开发者通过脚本内容的SHA1摘要来执行脚本,该命令的用法和EVAL一样,只不过是将脚本内容替换成脚本内容的SHA1摘要。

    Redis在执行EVAL命令时会计算脚本的SHA1摘要并记录在脚本缓存中,执行EVALSHA命令时Redis会根据提供的摘要从脚本缓存中查找对应的脚本内容,如果找到了则执行脚本,否则会返回错误:"NOSCRIPT No matching script. Please use EVAL."

    在程序中使用EVALSHA命令的一般流程如下。

    虽然这一流程略显麻烦,但值得庆幸的是很多编程语言的Redis客户端都会代替开发者完成这一流程。执行EVAL命令时,先尝试执行EVALSHA命令,如果失败了才会执行EVAL命令。

  • 先计算脚本的SHA1摘要,并使用EVALSHA命令执行脚本。

  • 获得返回值,如果返回“NOSCRIPT”错误则使用EVAL命令重新执行脚本。

  • SCRIPTLOAD "lua-script"

    将脚本加入缓存,但不执行. 返回:脚本的SHA1摘要

  • SCRIPT EXISTS lua-script-sha1

    判断脚本是否已被缓存

  • SCRIPT FLUSH

    清空脚本缓存 redis将脚本的SHA1摘要加入到脚本缓存后会永久保留,不会删除,但可以手动使用SCRIPT FLUSH命令情况脚本缓存。

  • SCRIPT KILL

    强制终止当前脚本的执行。 但是,如果当前执行的脚步对redis的数据进行了写操作,则SCRIPT KILL命令不会终止脚本的运行,以防止脚本只执行了一部分。脚本中的所有命令,要么都执行,要么都不执行。

  • Redis的脚本执行是原子的,即脚本执行期间Redis不会执行其他命令。所有的命令都必须等待脚本执行完成后才能执行。为了防止某个脚本执行时间过长导致Redis无法提供服务(比如陷入死循环),Redis提供了lua-time-limit参数限制脚本的最长运行时间,默认为5秒钟。当脚本运行时间超过这一限制后,Redis将开始接受其他命令但不会执行(以确保脚本的原子性,因为此时脚本并没有被终止),而是会返回“BUSY”错误

Ⅳ redis lua脚本有什么用

主要用途是: (1)描述界面:WOW和剑网三的界面都是用LUA写的; (2)沟通引擎:游戏图形引擎提供了一些接口库,可以在LUA中调用; (3)服务器端:有些游戏,例如剑网三,在服务器端也会大量使用LUA。

Ⅳ redis执行lua脚本的时候会不会删除过期键

生命周期为一天,现在执行lua脚本,逻辑是判断a是否存在,存在则往其中添加其它的元素

Ⅵ hash类型的redis怎样实现联合查询

Redis不仅仅是一个简单的key-value内存数据库,Redis官网对自身的定义是“数据结构服务器”。通过用心设计各种数据结构类型的数据存储,可以实现部分的数据查询功能。因为在Redis的设计中,key是一切,对于Redis是可见的,而value对于Redis来说就是一个字节数组,Redis并不知道你的value中存储的是什么,所以要想实现比如
‘select * from users where user.location="shanghai"’

这样的查询,在Redis是没办法通过value进行比较得出结果的。但是可以通过不同的数据结构类型来做到这一点。比如如下的数据定义

users:1 {name:Jack,age:28,location:shanghai}
users:2 {name:Frank,age:30,location:beijing}
users:location:shanghai [1]
其中users:1 users:2 分别定义了两个用户信息,通过Redis中的hash数据结构,而users:location:shanghai 记录了所有上海的用户id,通过集合数据结构实现。这样通过两次简单的Redis命令调用就可以实现我们上面的查询。

Jedis jedis = jedisPool.getResource();
Set<String> shanghaiIDs = jedis.smembers("users:location:shanghai");
//遍历该set
//...
//通过hgetall获取对应的user信息
jedis.hgetAll("users:" + shanghaiIDs[0]);
通过诸如以上的设计,可以实现简单的条件查询。但是这样的问题也很多,首先需要多维护一个ID索引的集合,其次对于一些复杂查询无能为力(当然也不能期望Redis实现像关系数据库那样的查询,Redis不是干这的)。

但是Redis2.6集成了Lua脚本,可以通过eval命令,直接在RedisServer环境中执行Lua脚本,并且可以在Lua脚本中调用Redis命令。其实,就是说可以让你用Lua这种脚本语言,对Redis中存储的key value进行操作,这个意义就大了,甚至可以将你们系统所需的各种业务写成一个个lua脚本,提前加载进入Redis,然后对于请求的响应,只需要调用一个个lua脚本就行。当然这样说有点夸张,但是意思就是这样的。

比如,现在我们要实现一个‘所有age大于28岁的user’这样一个查询,那么通过以下的Lua脚本就可以实现

public static final String SCRIPT =
"local resultKeys={};"
+ "for k,v in ipairs(KEYS) do "
+ " local tmp = redis.call('hget', v, 'age');"
+ " if tmp > ARGV[1] then "
+ " table.insert(resultKeys,v);"
+ " end;"
+ "end;"
+ "return resultKeys;";

执行脚本代码
Jedis jedis = jedisPool.getResource();
jedis.auth(auth);
List<String> keys = Arrays.asList(allUserKeys);

List<String> args = new ArrayList<>();
args.add("28");

List<String> resultKeys = (List<String>)jedis.evalsha(funcKey, keys, args);
return resultKeys;
注意,以上的代码中使用的是evalsha命令,该命令参数的不是直接Lua脚本字符串,而是提前已经加载到Redis中的函数的一个SHA索引,通过以下的代码将系统中所有需要执行的函数提前加载到Redis中,我们的系统维护一个函数哈希表,后续需要实现什么功能,就从函数表中获取对应功能的SHA索引,通过evalsha调用就行。
String shaFuncKey = jedis.scriptLoad(SCRIPT);//加载脚本,获取sha索引
funcTable.put(funcName_age, shaFuncKey);//添加到函数表中
通过以上的方法,便可以使较为复杂的查询放到Redis中去执行,提高效率。

Ⅶ redis加载lua脚本,怎么获取数据

需要用lua写redis的操作函数,先连接redis数据库,然后执行命令,来获取数据。

Ⅷ 为什么在 Redis 实现 Lua 脚本事务

从很多方面来看,Redis 很像当初采用 InnoDB 前的 MySQL。而 Redis 采用了一种很合理的方式来保证数据完整性(复制,AOF 等),并且从 Redis2.6 开始引入的 Lua 脚本在功能与易用性方面为 Redis 的成长提供了很大助力。

相对来说,Lua 脚本与其他数据库中的存储过程很相似,但脚本的执行有些许不同。在本文中最重要的一点就是一旦将脚本写入数据库,它会一直执行直到以下任一种情况出现:

1. 完成所有工作,所有写操作处理完成后脚本会自动退出。

2. 脚本运行时出错并中途退出,所有以前执行的写操作都已发生,但不会再有其他写操作。

3. Redis 通过 SHUTDOWN NOSAVE 关闭时(不保存)。

4. 你附加了调试器来“使”脚本完成 #1 与 #2 (或其他手段来保证不会丢失数据)。

对于使用数据库开发软件的人,我想你也认同只有情景 #1 是最理想的。情景 #2,#3,#4 都会导致数据异常(#2 与
#4)和/或数据丢失(#3 和 #4)。如果你很重视数据,你应该尽可能地阻止数据异常与丢失。这不是哲学,而是工作(This is not
philosophy, this is doing your job)。但很遗憾目前的 Redis 也帮不了你多少。所以我决定改变这种情况。

Ⅸ Redis中嵌套结构的替代方案

基本上有两种策略:

  • 可以序列化复杂对象并将其存储为字符串。我们建议使用json或msgpack作为序列化格式。这很容易从大多数客户端语言进行操作。如果需要服务器端访问,则服务器端的Lua脚本可以轻松地对此类对象进行编码/解码,因为Redis是使用针对Lua的msgpack和json支持进行编译的。

  • 可以将对象拆分为不同的键。除了存储user:id和该ID的复杂数据结构之外,还可以存储几个键,如user:id,user:id:address_list,user:id:document_lists等…如果需要原子性,则对MULTI / EXEC块可用于保证数据一致性并汇总往返次数。

    Redis不是面向文档的数据库。如果确实有很多复杂的文档,那么MongoDB,ArangoDB,CouchDB,Couchbase等解决方案可能会更好地提供服务