一、前言

  运行环境window,redis版本3.2.1。此处暂不对Lua进行详细讲解,只从Redis的方面讲解。

二、Redis的Lua脚本

  在Redis的2.6版本推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行,在Lua脚本中也可以调用大部分的Redis命令。使用脚本有以下三个好处:

  (1) 减少网络开销:有些时候需要多次请求Redis获取处理数据,而使用脚本功能就可以只使用一次请求完成相同操作,减少了网络往返时延。

  (2) 原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。也就是说在编写脚本的过程中无须担心会出现竞态条件,也就是无须使用事务。事务可以完成的所有功能,都可以用脚本来完成。

  (3) 复用:客户端发送的脚本会永久存储在Redis中,这就意味着其他客户端(可以是其他语言开发的项目)可以复用这一脚本而不需要使用代码完成同样的逻辑。

三、Redis调用Lua

  1、EVAL命令

  编写完脚本后最重要的就是在程序中执行脚本。Redis提供了EVAL命令可以使开发者像调用其他Redis内置命令一样调用脚本。EVAL的命令格式如下:

127.0.0.1:> eval script numkeys key [key ...] arg [arg ...]  

  script:脚本内容。numkeys:key参数的数量。key和arg:这两个参数向脚本传递数据,它们的值可以在脚本中分别使用KEYS[index]和ARGV[index]两个表类型的全局变量访问,numkeys为key的数量和其索引的最大值,argv的索引为key和argv数量总和减去numkeys,它们的索引都是从1开始,超出则返回nil。如下:

C:\Users\Xu>redis-cli
127.0.0.1:> eval 'return ARGV[3]' key1 key2 value1 value2 value3
"value3"
127.0.0.1:> eval 'return KEYS[2]' key1 key2 value1 value2 value3
"key2"
127.0.0.1:> eval 'return KEYS[3]' key1 key2 value1 value2 value3
(nil)

  其中要读写的键名应该为key参数,其他数据都作为arg参数。

  除了上面直接写lua脚本,还可以读取lua脚本文件来执行脚本,命令如下:

C:\Users\Xu>redis-cli --eval lua_file_path key1 key2 , arg1 arg2 arg3

  注意不需要numkeys,逗号前后必须有空格,否则会被认为一个连起来的字符串。

//lua文件内容
return ARGV[]
//执行命令
C:\Users\Xu>redis-cli.exe --eval e:\redis\a.lua key1 , value1 value2
"value2"
C:\Users\Xu>redis-cli.exe --eval e:\redis\a.lua key1 , value1 value2,value3
"value2,value3"

  2、EVALSHA命令

  考虑到在脚本比较长的时候,如果每次调用脚本都需要将整个脚本传给Redis会占用较多的带宽。所以,Redis提供了EVALSHA命令允许开发者通过脚本内容的SHA1摘要来执行脚本,该命令的用法和EVAL一样,不过就是将脚本内容的script替换为它的SHA1摘要。

  Redis在执行EVAL命令时会计算脚本的SHA1摘要并记录在脚本缓存中,如果执行EVALSHA命令时没有从脚本缓存中找到相应的摘要,则返回错误。

127.0.0.1:> evalsha c349a436bd639369c62c971941fc5f7a80626ee6  key1 value1
(integer)
127.0.0.1:> evalsha c349a436bd639369c62c971941fc5f7a80626ee61 key1 value1
(error) NOSCRIPT No matching script. Please use EVAL.

  在程序中使用EVALSHA的流程如下:

  (1) 先计算脚本SHA1摘要,并使用EVALSHA执行。

  (2) 获得返回值,如果返回错误则使用EVAL重新执行脚本。

  3、SCRIPT LOAD命令

  如果只是想将脚本加入到脚本缓存中而不执行则则可以用SCRIPT LOAD命令,返回值时脚本的SHA1摘要。

127.0.0.1:> script load 'return 666'
"c349a436bd639369c62c971941fc5f7a80626ee6"

  4、SCRIPT EXISTS命令

  SCRIPT EXISTS命令可以同时查找一个或者多个脚本的SHA1摘要是否已经本缓存,1为存在0为不存在。

127.0.0.1:> script exists c349a436bd639369c62c971941fc5f7a80626ee6 123ls436bd639369c62c971941fc5f7a80626ee6
) (integer)
) (integer)

  5、SCRIPT FLUSH命令

  Redis将脚本的SHA1摘要加入到脚本缓存后会永久保存,不会删除,但是可以用SCRIPT FLUSH删除所有脚本缓存。

127.0.0.1:> script flush
OK
(.51s)

  6、SCRIPT KILL 和 SHUTDOWN NOSAVE

  由于Redis的脚本是原子性的,脚本执行期间不会执行其他命令。为了防止某个脚本执行时间过长导致Redis无法提供服务(比如死循环),Redis提供了lua-time-limit参数限制脚本最长运行时间,默认是5秒。再脚本执行期间,执行其他命令会返回“BUSY”错误,如下:

(A)127.0.0.1:> eval 'while true do end' 0
(B)127.0.0.1:> get foo
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.

  此时Redis只会接受并执行两个命令:SCRIPT KILL 和 SHUTDOWN NOSVAE。

  通过SCRIPT KILL 可以终止当前脚本的运行,脚本停止并返回错误:

(B)127.0.0.1:> script kill
OK
(B)127.0.0.1:> get foo
(nil) (A)127.0.0.1:> eval 'while true do end'
(error) ERR Error running script (call to f_694a5fe1ddb97a4c6a1bf299d9537c7d3d0f84e7): @user_script:: Script killed by user with SCRIPT KILL...
(.99s)

  如果当前执行的脚本对Redis的数据进行了修改,则SCRIPT KILL不会终止脚本的运行,因为这样违背了原子性。那么需要通过SHUTDOWN NOSAVE来强制终止Redis将原先脚本的修改操作返回,不进行持久化操作,这意味着所有发送在上一次的快照后的数据库修改都会丢失。

四、Redis获取脚本中的返回值

  很多情况下,都需要脚本通过return返回值,如果没有执行return则默认返回nil。因为我们可以像调用其他Redis内置命令一样调用我们自己写的脚本,所以同样Redis会自动将脚本返回值的Lua数据类型转化成Redis的返回值类型。具体的转换规则如下:

  (1) Lua的数字类型,Redis为整数类型。

127.0.0.1:> eval 'return 1.1'
(integer)

  (2) Lua的字符串类型,Redis也是字符串类型

  (3) Lua的表类型(数组形式),Redis会返回多行字符串

127.0.0.1:> eval 'return {0,1}'
) (integer)
) (integer)

  (4) Lua表类型(只有一个ok字段存储状态信息),Redis为成功状态回复

127.0.0.1:> eval 'return {ok="this is ok"}'
this is ok

  (5)Lua表类型(只有一个err字段存储状态信息),Redis为错误状态回复

127.0.0.1:> eval 'return {err="so bad"}'
(error) so bad

  (6)Lua的bool类型中true为Redis的1,false为nil

127.0.0.1:> eval 'return true'
(integer)
127.0.0.1:> eval 'return false'
(nil)

五、沙盒与随机数

  Redis脚本禁止使用Lua标准库中与文件或系统调用相关的函数,在脚本中只允许对Redis的数据进行处理。并且Redis还通过禁用脚本的全局变量的方式保证每个脚本都是相对隔离的,不会互相干扰。

  使用沙盒不仅是为了保证服务器的安全性,而且还确保了脚本的执行结果只有和脚本本身和执行时传递的参数有关,不依赖外界条件(如系统时间、系统中某个文件的内容、其他脚本执行结果登)。这是因为在执行复制和AOF持久话操作时记录的脚本的内容而不是脚本调用的命令,所以必须保证在脚本内容和参数一样的前提下脚本的执行结果必须一样。

  对于随机数,Redis替换了math.random和math。randomseed函数使得每次执行脚本时生成的随机数列都相同,如果希望获得不同的随机数序列,最简单的方法时由程序生成随机数并通过参数传递给脚本,或者采用更灵活的方法,即在程序中生成随机数传给脚本作为随机数种子。

六、在net core中使用脚本

  很简单,直接上代码,这里举例最基本的,还有很多的重写方法大家可以自己试试。最简单的使用eval。

var script = " return KEYS[1];";
var keys = new RedisKey[]{ "key1","key2"};
var values = new RedisValue[] { "value1", "value2" };
return await redisConnection.GetDatabase().ScriptEvaluateAsync(script, keys, values);

  缓存脚本,并使用。

var bytes = await redisConnection.GetServer(Config.Get("ConnectionStrings:Redis:ConnectionString")).ScriptLoadAsync("return 1");
var result = await redisConnection.GetDatabase().ScriptEvaluateAsync(bytes, null, null);

  脚本是否已缓存。

bool exist = await redisConnection.GetServer(Config.Get("ConnectionStrings:Redis:ConnectionString")).ScriptExistsAsync("return 1");

  删除所有脚本缓存,这个操作需要连接的ConfigurationOptions配置中AllowAdmin = true,没有会报错哦。

redisConnection.GetServer(Config.Get("ConnectionStrings:Redis:ConnectionString")).ScriptFlush();

  还有LuaScript和LoadedLuaScript两个类可以对脚本进行更多复杂的脚本,LuaScript将@myVar形式的脚本中的变量重写为redis所需的合适的ARGV[someIndex]。如果传递的参数是RedisKey类型,它将作为KEYS集合的一部分自动发送。如下。

var lua = LuaScript.Prepare("return @key");
var result = redisConnection.GetDatabase().ScriptEvaluate(lua,new {key= (RedisKey)"key1",value = "value1" });

  

.Net Core使用分布式缓存Redis:Lua脚本的更多相关文章

  1. ASP.Net Core使用分布式缓存Redis从入门到实战演练

    一.课程介绍 人生苦短,我用.NET Core!缓存在很多情况下需要用到,合理利用缓存可以一方面可以提高程序的响应速度,同时可以减少对特定资源访问的压力.  所以经常要用到且不会频繁改变且被用户共享的 ...

  2. .Net Core使用分布式缓存Redis:基础

    一.前言 Redis的介绍网上很多不再赘述.本次环境为net core 2.2,使用的StackExchange.Redis来操作Redis. 二.引用Microsoft.Extensions.Cac ...

  3. .Net Core使用分布式缓存Redis:数据结构

    一.前言 本篇主要使用StackExchangeRedis在.Net Core中使用Redis,使用基础见:点击此处. 二.五种基础数据结构 1.字符串类型String 字符串类型是Redis中最基本 ...

  4. 一个技术汪的开源梦 —— 公共组件缓存之分布式缓存 Redis 实现篇

    Redis 安装 & 配置 本测试环境将在 CentOS 7 x64 上安装最新版本的 Redis. 1. 运行以下命令安装 Redis $ wget http://download.redi ...

  5. 【开源项目系列】如何基于 Spring Cache 实现多级缓存(同时整合本地缓存 Ehcache 和分布式缓存 Redis)

    一.缓存 当系统的并发量上来了,如果我们频繁地去访问数据库,那么会使数据库的压力不断增大,在高峰时甚至可以出现数据库崩溃的现象.所以一般我们会使用缓存来解决这个数据库并发访问问题,用户访问进来,会先从 ...

  6. Redis Lua脚本完全入门

    1. 前言 Redis是高性能的KV内存数据库,除了做缓存中间件的基本作用外还有很多用途,比如胖哥以前分享的Redis GEO地理位置信息计算.Redis提供了丰富的命令来供我们使用以实现一些计算.R ...

  7. Redis Lua 脚本使用

    本文转载自Redis Lua 脚本使用 Lua 简介 Lua语言提供了如下几种数据类型:booleans(布尔).numbers(数值).strings(字符串).tables(表格). 下面是一些 ...

  8. 企业项目开发--分布式缓存Redis

    第九章 企业项目开发--分布式缓存Redis(1) 注意:本章代码将会建立在上一章的代码基础上,上一章链接<第八章 企业项目开发--分布式缓存memcached> 1.为什么用Redis ...

  9. Redis Lua脚本调试

    从版本3.2开始,Redis包含一个完整的Lua调试器,可以用来使编写复杂Redis脚本的任务更加简单. 由于Redis 3.2仍处于测试阶段,请unstable从Github 下载Redis 的分支 ...

随机推荐

  1. 2018-8-10-win10-uwp-自定义控件初始化

    title author date CreateTime categories win10 uwp 自定义控件初始化 lindexi 2018-08-10 19:16:50 +0800 2018-2- ...

  2. 前端开发之BOM和DOM

    BOM BOM:是指浏览器对象模型,它使JavaScript可以和浏览器进行交互. 1,navigator对象:浏览器对象,通过这个对象可以判定用户所使用的浏览器,包含了浏览器相关信息. naviga ...

  3. 符合阿里巴巴代码规范的checkstyle检测文件

    一.安装与简介 eclipse和idea都有对应的插件,找到插件安装界面.搜索checkstyle,点击安装后,重启IDE即可.(网上有很多安装教程,就不重复制造轮子了) 二.导入配置文件 在chec ...

  4. git 回滚到某个版本

    首先使用git log 显示最近的代码提交记录 commit后面的内容,就是回滚的记录名 增加了加载条显示,提高用户体验 commit 47f45668e72e4deeccae85e9767c250d ...

  5. WPF 解决弹出模态窗口关闭后,主窗口不在最前

    本文告诉大家如何解决这个问题,在 WPF 的软件,弹出一个模态窗口.使用另一个窗口在模态窗口前面.从任务栏打开模态窗口.关闭模态窗口.这时发现,主窗口会在刚才使用的另一个窗口下面 这是 Windows ...

  6. 搭建zookeeper出现错误:starting zookeeper... already running process 2853

    今天搭建zookeeper时碰到了starting zookeeper already running process 2853这样一个错误. 上网上查了几个相似的问题都是要删除zookeeper_s ...

  7. 【elasticsearch】数据早8小时Or晚8小时,你知道为什么吗,附解决方案

    前言 这篇文章,不会解释什么是本初子午线,只想以做实验的方式来理解数据差8小时的问题.下面就先说结论,再来谈原理. 解决方案 想必大家都很清楚:中国标准时间= UTC + 8小时. 那么所有和时区有关 ...

  8. ELK学习实验004:Elasticsearch的简单介绍和操作

    一 集群节点 Elstaicsearch的集群是由多个节点组成都,通过cluster.name设置集权名称,比能切用与区分其他的集群,每个节点通过node.name指定节点 在Elasticsearc ...

  9. hexo+next 详细搭建

    安装node node下载地址:http://nodejs.cn/download/ 具体安装方法,这里不做详写 安装完成可以通过node -v 查看安装是否生效和node的版本 我这里使用的是v10 ...

  10. vs2017编译installer项目提示缺少visualstudio2010shell(Integrated)-CHN

    这个问题折腾我一天,欲仙欲死啊~~~~~~! 国内的网站各种百度,都不行. 还是Google大神比较强,在stackoverflow.com找到了解决方法,说来也简单,难者不会,会者不难! ----- ...