[转帖]Redis中的Lua脚本

redis,lua,脚本 · 浏览次数 : 0

小编点评

**Redis Lua脚本的知识点** **1. 使用 Lua 操作 Redis** - 直接发送完整的 Lua脚本即可执行操作。 - 脚本参数使用占位符(KEYS和ARGV)。 **2. 利用 Lua 操作 Redis** - 使用 `redis.call()` 或 `redis.pcall()` 函数调用脚本。 - `redis.call()` 引发 Lua 错误,返回错误码。 - `redis.pcall()` 捕获异常并返回 Lua 异常信息。 **3. Lua脚本的原子性** - 脚本在 Redis 中是原子性的,不会被其他脚本或命令中断。 **4. 关于 EVALSHA** - EVAL 命令迫使反复发送脚本,但 Redis 使用缓存机制。 - 使用 `EVALSHA` 命令可以避免脚本重复执行。 **5. 常用 SCRIPT 命令** - `SCRIPT FLUSH` 清除所有执行过的脚本缓存。 - `SCRIPT EXISTS` 验证脚本缓存中是否存在相应脚本。 - `SCRIPT LOAD` 注册脚本并缓存。 - `SCRIPT KILL` 打断正在执行的脚本。 **6. 脚本本地化** - 可以本地编写 Lua脚本并通过客户端调用 `redis-cli` 命令执行。

正文

最近琢磨分布式锁时接触到的知识点,简单记一下。

1. Redis中的Lua

 Redis支持Lua,代码直接发送完整脚本即可。基本语法(redis客户端可以直接执行):

> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"

    注:{KEYS[1],KEYS[2],ARGV[1],ARGV[2]}都是一系列的 key -value 的占位符(KEYSARGV都是全局变量),根据实际情况编写,可以是很多这样形式变量。后面的2 key1 first key2 second,首位数字2表示的是 Key 的数量,后面的变量和前面的占位符意义对应(key1对应于KEYS[1],执行时key1自动注入)

    2. 利用Lua操作Redis

     上述执行借用redis的客户端执行lua脚本,要真正对redis进行读写操作,需要调用redis.call()函数或者redis.pcall()函数,这两个函数比较相似,但如果发生错误时,redis.call()函数将引发一个Lua错误,这又将迫使EVAL向命令调用者返回错误;而redis.pcall()则会捕获异常并返回Lua异常的某个信息码。使用示例如下:

    # 同样的最后的 0 表示的全局变量 KEYS 中有0个元素 key
    eval "return redis.call('set','foo','bar')" 0
    

      向redis中写入一个 key 为foo,value为bar的对象。但eval命令将会验证这条命令的语法,具体的验证方式为:将脚本中所有的key替换为全局变量数据KEYS,所以最终验证语法的脚本为eval "return redis.call('set',KEYS[1],'bar')" 1 foo

      上述2种方式调用出错的情况
      call和pcall调用出错的情况

      当使用redis.call()函数或者redis.pcall()函数操作redis时,Redis返回的值对象将会被转换成Lua语言中的数据类再返回。相似了当执行redis.call()函数或者redis.pcall()函数时,Lua变量的数据类型将会被转成Redis中支持的数据类型(string,list,set,hash,Sorted Set)。

      3. Lua脚本的原子性

       Lua脚本在redis中的操作是原子性,redsi使用同一个Lua解释器执行脚本中的所有命令,Redis本身也保证了这个脚本执行的原子性:当该脚本在执行时间区间内,不会有其他的脚本或者命令执行。所以使用Lua执行一个慢脚本是一个很扯的事情(除非你很清楚这样的慢脚本是十分有必要的),一般情况下,因为脚本的开销非常低会很快。

      4. 关于 EVALSHA

      EVAL命令会迫使我们反复发送脚本,但Redis不需要每次都重新编译脚本(因为自己内部的缓存机制),但每次发送脚本需要耗费额外的带宽在很多场景中并不是一种友好的方式。另一方面,使用特殊命令或者通过redis.conf来定义命令也会有一些问题:

      • 不同的实例可能有该命令的不同实现;
      • 如果需要确保所有的实例都包含所给的命令,那部署将会很困难,尤其是在分布式环境中;
      • 读取应用程序代码后,由于应用程序将调用定义在服务器端的命令,因此完整的语义可能不清楚;

      为了解决上述的问题,Redis实现了EVALSHA命令,EVALSHA命令和EVAL命令很相似,但EVALSHA没有以脚本本身作为第一个参数,而是将该脚本的SHA1摘要作为第一个参数,具体行为:

      1. 如果服务端仍然记得和 SHA1 摘要匹配的脚本,那直接执行脚本;
      2. 如果忘了和 SHA1 摘要匹配的脚本,那将会抛出一个异常告诉客户端,使用EVAL命令来执行,不要使用EVALSHA

      所以客户端最好的方式还是使用EVALSHA命令来执行脚本(即使客户端使用EVAL,脚本实际已经被服务端看到,如果返回NOSCRIPT的错误就会在使用EVAL命令)。

      脚本缓存机制:已执行的脚本会永远存在于所执行的Redis实例的脚本缓存中。这就意味着如果一个EVAL在Redis实例中执行,则后续所有的EVALSHA命令都会调用成功。显式的调用SCRIPT FLUSH将会刷新脚本缓存,也会清除到目前为止所有执行过的脚本(这也是清除脚本缓存的唯一方式)。

      5. 常用SCRIPT 命令

       Redis中脚本本身操作的命令有:

      • SCRIPT FLUSH:清除Redis中到目前为止所有执行过的脚本缓存;
      • SCRIPT EXISTS sha1 sha2 ... shaN:验证脚本是否存在缓存中,返回会对应个数的0(缓存中不存在)和1(缓存中存在);
      • SCRIPT LOAD script:将脚本注册进入服务端的存储而不需要执行,确保EVALSHA可以找到对应的脚本正常执行,返回该脚本的SHA1加密值;
      • SCRIPT KILL:打断正在执行的脚本;
      # 注册脚本
      script load "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}"
      # 返回sha1值
      "a42059b356c875f0717db19a51f6aaca9ae659ea"
      

        6. 脚本本地化

         可以本地编写Lua脚本,直接通过客户端调用redis-cli --eval xxx.lua执行执行xxx.lua脚本。如实际Linux中操作经常用到redis-cli -h hostname -p port -a password SCRIPT LOAD "$(cat lua_script_file_location)"类似的命令将指定脚本缓存到Redis的服务端,已便下次直接通过返回的摘要直接执行脚本。
        t lua_script_file_location)"`类似的命令将指定脚本缓存到Redis的服务端,已便下次直接通过返回的摘要直接执行脚本。

        与[转帖]Redis中的Lua脚本相似的内容:

        [转帖]Redis中的Lua脚本

        最近琢磨分布式锁时接触到的知识点,简单记一下。 文章目录 1. Redis中的Lua2. 利用Lua操作Redis3. Lua脚本的原子性4. 关于 EVALSHA5. 常用`SCRIPT` 命令6. 脚本本地化 1. Redis中的Lua Redis支持Lua,代码直接发送完整脚本即可。基本语法(

        [转帖]【Redis】Redis中使用Lua脚本

        Lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。 Lua具体语法参考:https://www.runoob.com/lua/lua-tutorial.html 脚本的原子性 Redis使用单个Lua解释器去运

        [转帖]滥用Lua导致Redis内存无法被限制

        https://axlgrep.github.io/tech/redis-memory-control.html 问题描述 最近发现线上某个Redis实例内存占用达到了17.21G, 但是该实例中实际的用户数据并不是很多(大概200Mb的样子), 此外mem_fragmentation_ratio达

        [转帖]redis中的bigkey问题

        https://cdn.modb.pro/db/459810 什么是bigkey bigkey就是redis key/value体系中的大value问题。我们知道redis的底层数据存储结构中,有多种数据结构的实现。 String: 简单动态字符串 List: 双向链表、压缩列表 Hash: 哈希表

        [转帖]Redis key 乱码问题(springboot)

        保存到redis中的key 前半段会出现乱码问题 原来配置: @Configuration@EnableCachingpublic class RedisCacheConfig { @Bean public CacheManager cacheManager(RedisTemplate

        [转帖]Redis持久化-RDB和AOF

        持久化的功能: Redis是内存数据库, 数据都是存储在内存中, 为了避免进程退出导致数据的永久丢失, 需要定期将Redis中的数据以某种形式(数据或命令) 从内存保存到硬盘。 当下次Redis重启时, 利用持久化文件实现数据恢复。 除此之外, 为了进行灾难备份, 可以将持久化文件拷贝到一个远程位置

        [转帖]Redis系列(十七)、Redis中的内存淘汰策略和过期删除策略

        我们知道Redis是分布式内存数据库,基于内存运行,可是有没有想过比较好的服务器内存也不过几百G,能存多少数据呢,当内存占用满了之后该怎么办呢?Redis的内存是否可以设置限制? 过期的key是怎么从内存中删除的?不要怕,本篇我们一起来看一下Redis的内存淘汰策略是如何释放内存的,以及过期的key

        [转帖]Redis故障检查:识别慢查询操作

        https://weibo.com/ttarticle/p/show?id=2309404650615585505652 使用SLOWLOG命令查看Redis中的慢查询操作。 ​​前几篇日志总结了下对Redis部署时的一些配置,Redis启动后,面对各种请求,数据持久化到硬盘,很可能会出现内存不足等

        [转帖]Redis 禁用 危险命令

        一、Redis 危险命令 keys * :虽然其模糊匹配功能使用非常方便也很强大,在小数据量情况下使用没什么问题,数据量大会导致 Redis 锁住及 CPU 飙升,在生产环境建议禁用或者重命名!flushdb :删除 Redis 中当前所在数据库中的所有记录,并且此命令从不会执行失败flushall

        [转帖]Redis Scan 原理解析与踩坑

        https://www.cnblogs.com/jelly12345/p/16424080.html 1. 概述由于 Redis 是单线程在处理用户的命令,而 Keys 命令会一次性遍历所有 Key,于是在 命令执行过程中,无法执行其他命令。这就导致如果 Redis 中的 key 比较多,那么 Ke