从 2.6版本 起, Redis 开始支持 Lua 脚本 让开发者自己扩展 Redis …

案例-实现访问频率限制: 实现访问者 $ip 在一定的时间 $time 内只能访问 $limit 次.
  • 非脚本实现
private boolean accessLimit(String ip, int limit, int time, Jedis jedis) {boolean result = true; String key = "rate.limit:" + ip; if (jedis.exists(key)) { long afterValue = jedis.incr(key); if (afterValue > limit) { result = false; } } else { Transaction transaction = jedis.multi(); transaction.incr(key); transaction.expire(key, time); transaction.exec(); }  return result; }
  • 以上代码有两点缺陷

    1. 可能会出现竞态条件: 解决方法是用 WATCH 监控 rate.limit:$IP 的变动, 但较为麻烦;
    2. 以上代码在不使用 pipeline 的情况下最多需要向Redis请求5条指令, 传输过多.

  • Lua脚本实现 
    Redis 允许将 Lua 脚本传到 Redis 服务器中执行, 脚本内可以调用大部分 Redis 命令, 且 Redis 保证脚本的原子性:

    • 首先需要准备Lua代码: script.lua
--
-- Created by IntelliJ IDEA.
-- User: jifang
-- Date: 16/8/24
-- Time: 下午6:11 -- local key = "rate.limit:" .. KEYS[1] local limit = tonumber(ARGV[1]) local expire_time = ARGV[2] local is_exists = redis.call("EXISTS", key) if is_exists == 1 then if redis.call("INCR", key) > limit then return 0 else return 1 end else redis.call("SET", key, 1) redis.call("EXPIRE", key, expire_time) return 1 end
  • Java
private boolean accessLimit(String ip, int limit, int timeout, Jedis connection) throws IOException { List<String> keys = Collections.singletonList(ip); List<String> argv = Arrays.asList(String.valueOf(limit), String.valueOf(timeout)); return 1 == (long) connection.eval(loadScriptString("script.lua"), keys, argv); } // 加载Lua代码 private String loadScriptString(String fileName) throws IOException { Reader reader = new InputStreamReader(Client.class.getClassLoader().getResourceAsStream(fileName)); return CharStreams.toString(reader); }

  • Lua 嵌入 Redis 优势:

    1. 减少网络开销: 不使用 Lua 的代码需要向 Redis 发送多次请求, 而脚本只需一次即可, 减少网络传输;
    2. 原子操作: Redis 将整个脚本作为一个原子执行, 无需担心并发, 也就无需事务;
    3. 复用: 脚本会永久保存 Redis 中, 其他客户端可继续使用.

Lua模型

Lua是一种 便于嵌入应用程序 的脚本语言, 具备了作为通用脚本语言的所有功能. 其高速虚拟机实现非常有名(Lua的垃圾回收很有讲究- 增量垃圾回收 ), 在很多虚拟机系性能评分中都取得了优异的成绩. Home lua.org. 

嵌入式为方针设计的Lua, 在默认状态下简洁得吓人. 除了基本的数据类型外, 其他一概没有. 标注库也就 CoroutineStringTableMath、 I/OOS, 再加上Modules包加载而已. 参考: Lua 5.1 Reference Manual - Standard Libraries(中文版: Lua 5.1 参考手册).

注: 本文仅介绍 Lua 与众不同的设计模型(对比 Java/C/C++JavaScriptPython 与 Go), 语言细节可参考文内和附录推荐的文章以及Lua之父Roberto Ierusalimschy的<Programming in Lua>(中文版: <LUA程序设计(第2版)>)


Base

1. 数据类型

  • 作为通用脚本语言, Lua的数据类型如下:

    • 数值型: 
      全部为浮点数型, 没有整型; 
      只有 nil 和 false 作为布尔值的 false , 数字 0 和空串(‘’/‘\0’)都是 true;
    • 字符串
    • 用户自定义类型
    • 函数(function)
    • 表(table)

变量如果没有特殊说明为全局变量(那怕是语句块 or 函数内), 局部变量前需加local关键字.


2. 关键字


3. 操作符

  • Tips:

    • 数学操作符的操作数如果是字符串会自动转换成数字;
    • 连接 .. 自动将数值转换成字符串;
    • 比较操作符的结果一定是布尔类型, 且会严格判断数据类型('1' != 1);

函数(function)

在 Lua 中, 函数是和字符串、数值和表并列的基本数据结构, 属于第一类对象first-class-object /一等公民), 可以和数值等其他类型一样赋给变量作为参数传递, 以及作为返回值接收(闭包):

  • 使用方式类似JavaScript:
-- 全局函数: 求阶乘
function fact(n) if n == 1 then return 1 else return n * fact(n - 1) end end -- 1. 赋给变量 local func = fact print("func type: " .. type(func), "fact type: " .. type(fact), "result: " .. func(4)) -- 2. 闭包 local function new_counter() local value = 0; return function() value = value + 1 return value end end local counter = new_counter() print(counter(), counter(), counter()) -- 3. 返回值类似Go/Python local random_func = function(param) return 9, 'a', true, "ƒ∂π", param end local var1, var2, var3, var4, var5 = random_func("no param is nil") print(var1, var2, var3, var4, var5) -- 4. 变数形参 local function square(...) local argv = { ... } for i = 1, #argv do argv[i] = argv[i] * argv[i] end return table.unpack(argv) end print(square(1, 2, 3))

表(table)

Lua最具特色的数据类型就是表(Table), 可以实现数组Hash对象所有功能的万能数据类型:

-- array
local array = { 1, 2, 3 } print(array[1], #array) -- hash local hash = { x = 1, y = 2, z = 3 } print(hash.x, hash['y'], hash["z"], #hash) -- array & hash array['x'] = 8 print(array.x, #array)
  • Tips:

    • 数组索引从1开始;
    • 获取数组长度操作符#其’长度’只包括以(正)整数为索引的数组元素.
    • Lua用表管理全局变量, 将其放入一个叫_G的table内:
-- pairs会遍历所有值不为nil的索引, 与此类似的ipairs只会从索引1开始递遍历到最后一个值不为nil的整数索引.
for k, v in pairs(_G) do print(k, " -> ", v, " type: " .. type(v)) end

Hash实现对象的还有JavaScript, 将数组和Hash合二为一的还有PHP.


元表

Every value in Lua can have a metatable/元表. This metatable is an ordinary Lua table that defines the behavior of the original value under certain special operations. You can change several aspects of the behavior of operations over a value by setting specific fields in its metatable. For instance, when a non-numeric value is the operand of an addition, Lua checks for a function in the field “__add” of the value’s metatable. If it finds one, Lua calls this function to perform the addition. 
The key for each event in a metatable is a string with the event name prefixed by two underscores__; the corresponding values are called metamethods. In the previous example, the key is “__add” and the metamethod is the function that performs the addition.

metatable中的键名称为事件/event, 值称为元方法/metamethod, 我们可通过getmetatable()来获取任一值的metatable, 也可通过setmetatable()来替换tablemetatable. Lua 事件一览表: 
 
对于这些操作, Lua 都将其关联到 metatable 的事件Key, 当 Lua 需要对一个值发起这些操作时, 首先会去检查其metatable中是否有对应的事件Key, 如果有则调用之以控制Lua解释器作出响应.


MetaMethods

MetaMethods主要用作一些类似C++中的运算符重载操作, 如重载+运算符:

local frac_a = { numerator = 2, denominator = 3 }
local frac_b = { numerator = 4, denominator = 8 } local operator = { __add = function(f1, f2) local ret = {} ret.numerator = f1.numerator * f2.denominator + f1.denominator * f2.numerator ret.denominator = f1.denominator * f2.denominator return ret end, __tostring = function(self) return "{ " .. self.numerator .. " ," .. self.denominator .. " }" end } setmetatable(frac_a, operator) setmetatable(frac_b, operator) local frac_res = frac_a + frac_b setmetatable(frac_res, operator) -- 使tostring()方法生效 print(tostring(frac_res))

关于更多Lua事件处理可参考文档: Metamethods.


MetaTables 与 面向对象

Lua本来就不是设计为一种面向对象语言, 因此其面向对象功能需要通过元表(metatable)这种非常怪异的方式实现, Lua并不直接支持面向对象语言中常见的类、对象和方法: 其对象通过实现, 而方法是通过函数来实现.

上面的Event一览表内我们看到有__index这个事件重载,这个东西主要是重载了find key操作, 该操作可以让Lua变得有点面向对象的感觉(类似JavaScript中的prototype). 通过Lua代码模拟:

local function gettable_event(t, key) local h if type(t) == "table" then local value = rawget(t, key) if value ~= nil then return value end h = getmetatable(t).__index if h == nil then return nil end else h = getmetatable(t).__index if h == nil then error("error") end end if type(h) == "function" then -- call the handler return (h(t, key)) else -- or repeat opration on it return h[key] end end -- 测试 obj = { 1, 2, 3 } op = { x = function() return "xx" end } setmetatable(obj, { __index = op['x'] }) print(gettable_event(obj, x))
  • 对于任何事件, Lua的处理都可以归结为以下逻辑:

    1. 如果存在规定的操作则执行它;
    2. 否则从元表中取出各事件对应的__开头的元素, 如果该元素为函数, 则调用;
    3. 如果该元素不为函数, 则用该元素代替table来执行事件所对应的处理逻辑.

这里的代码仅作模拟, 实际的行为已经嵌入Lua解释器, 执行效率要远高于这些模拟代码.


方法调用的实现

面向对象的基础是创建对象和调用方法. Lua中, 表作为对象使用, 因此创建对象没有问题, 关于调用方法, 如果表元素为函数的话, 则可直接调用:

-- 从obj取键为x的值, 将之视为function进行调用
obj.x(foo)

不过这种实现方法调用的方式, 从面向对象角度来说还有2个问题:

  • 首先: obj.x这种调用方式, 只是将表obj的属性x这个函数对象取出而已, 而在大多数面向对象语言中, 方法的实体位于类中, 而非单独的对象中. 在JavaScript等基于原型的语言中, 是以原型对象来代替类进行方法的搜索, 因此每个单独的对象也并不拥有方法实体. 在Lua中, 为了实现基于原型的方法搜索, 需要使用元表的__index事件: 
    如果我们有两个对象ab,想让b作为aprototype需要setmetatable(a, {__index = b}), 如下例: 为obj设置__index加上proto模板来创建另一个实例:
proto = {x = function()print("x") end } local obj = {} setmetatable(obj, { __index = proto }) obj.x()

proto变成了原型对象, 当obj中不存在的属性被引用时, 就会去搜索proto.

  • 其次: 通过方法搜索得到的函数对象只是单纯的函数, 而无法获得最初调用方法的表(接收器)相关信息. 于是, 过程和数据就发生了分离.JavaScript中, 关于接收器的信息可由关键字this获得, 而在Python中通过方法调用形式获得的并非单纯的函数对象, 而是一个“方法对象” –其接收器会在内部作为第一参数附在函数的调用过程中
    而Lua准备了支持方法调用的语法糖:obj:x(). 表示obj.x(obj), 也就是: 通过冒号记法调用的函数, 其接收器会被作为第一参数添加进来(obj的求值只会进行一次, 即使有副作用也只生效一次).
-- 这个语法糖对定义也有效
function proto:y(param) print(self, param) end - Tips: 用冒号记法定义的方法, 调用时最好也用冒号记法, 避免参数错乱 obj:y("parameter")

更多MetaTable介绍可参考文档Metatable与博客metatable和metamethod.


基于原型的编程

Lua虽然能够进行面向对象编程, 但用元表来实现, 仿佛把对象剖开看到五脏六腑一样.

<代码的未来>中松本行弘老师向我们展示了一个基于原型编程的Lua库, 通过该库, 即使没有深入解Lua原始机制, 也可以实现面向对象:

--
-- Author: Matz
-- Date: 16/9/24
-- Time: 下午5:13
-- -- Object为所有对象的上级 Object = {} -- 创建现有对象副本 function Object:clone() local object = {} -- 复制表元素 for k, v in pairs(self) do object[k] = v end -- 设定元表: 指定向自身`转发` setmetatable(object, { __index = self }) return object end -- 基于类的编程 function Object:new(...) local object = {} -- 设定元表: 指定向自身`转发` setmetatable(object, { __index = self }) -- 初始化 object:init(...) return object end -- 初始化实例 function Object:init(...) -- 默认不进行任何操作 end Class = Object:new()

另存为prototype.lua, 使用时只需require()引入即可:

require("prototype")-- Point类定义
Point = Class:new()
function Point:init(x, y) self.x = x self.y = y end function Point:magnitude() return math.sqrt(self.x ^ 2 + self.y ^ 2) end -- 对象定义 point = Point:new(3, 4) print(point:magnitude()) -- 继承: Point3D定义 Point3D = Point:clone() function Point3D:init(x, y, z) self.x = x self.y = y self.z = z end function Point3D:magnitude() return math.sqrt(self.x ^ 2 + self.y ^ 2 + self.z ^ 2) end p3 = Point3D:new(1, 2, 3) print(p3:magnitude()) -- 创建p3副本 ap3 = p3:clone() print(ap3.x, ap3.y, ap3.z)

Redis - Lua

在传入到Redis的Lua脚本中可使用redis.call()/redis.pcall()函数调用Reids命令:

redis.call("set", "foo", "bar") local value = redis.call("get", "foo")

redis.call()返回值就是Reids命令的执行结果, Redis回复与Lua数据类型的对应关系如下:

Reids返回值类型 Lua数据类型
整数 数值
字符串 字符串
多行字符串 表(数组)
状态回复 表(只有一个ok字段存储状态信息)
错误回复 表(只有一个err字段存储错误信息)

注: Lua 的 false 会转化为空结果.

redis-cli提供了EVALEVALSHA命令执行Lua脚本:

  • EVAL 
    EVAL script numkeys key [key ...] arg [arg ...] 
    keyarg两类参数用于向脚本传递数据, 他们的值可在脚本中使用KEYSARGV两个table访问: KEYS表示要操作的键名, ARGV表示非键名参数(并非强制).
  • EVALSHA 
    EVALSHA命令允许通过脚本的SHA1来执行(节省带宽), Redis在执行EVAL/SCRIPT LOAD后会计算脚本SHA1缓存, EVALSHA根据SHA1取出缓存脚本执行.

创建Lua环境

为了在 Redis 服务器中执行 Lua 脚本, Redis 内嵌了一个 Lua 环境, 并对该环境进行了一系列修改, 从而确保满足 Redis 的需要. 其创建步骤如下:

  • 创建基础 Lua 环境, 之后所有的修改都基于该环境进行;
  • 载入函数库到 Lua 环境, 使 Lua 脚本可以使用这些函数库进行数据操作: 如基础库(删除了loadfile()函数)、Table、String、Math、Debug等标准库, 以及CJSON、 Struct(用于Lua值与C结构体转换)、 cmsgpack等扩展库(Redis 禁用Lua标准库中与文件或系统调用相关函数, 只允许对 Redis 数据处理).
  • 创建全局表redis, 其包含了对 Redis 操作的函数, 如redis.call()、 redis.pcall() 等;
  • 替换随机函数: 为了确保相同脚本可在不同机器上产生相同结果, Redis 要求所有传入服务器的 Lua 脚本, 以及 Lua 环境中的所有函数, 都必须是无副作用的纯函数, 因此Redis使用自制函数替换了 Math 库中原有的 math.random()和 math.randomseed() .
  • 创建辅助排序函数: 对于 Lua 脚本来说, 另一个可能产生数据不一致的地方是那些带有不确定性质的命令(如: 由于set集合无序, 因此即使两个集合内元素相同, 其输出结果也并不一样), 这类命令包括SINTERSUNIONSDIFFSMEMBERSHKEYSHVALSKEYS 等. 
    Redis 会创建一个辅助排序函数__redis__compare_helper, 当执行完以上命令后, Redis会调用table.sort()__redis__compare_helper作为辅助函数对命令返回值排序.
  • 创建错误处理函数: Redis创建一个 __redis__err__handler 错误处理函数, 当调用 redis.pcall() 执行 Redis 命令出错时, 该函数将打印异常详细信息.
  • Lua全局环境保护: 确保传入脚本内不会将额外的全局变量导入到 Lua 环境内. 

    小心: Redis 并未禁止用户修改已存在的全局变量.

  • 完成Redis的lua属性与Lua环境的关联: 
     
    整个 Redis 服务器只需创建一个 Lua 环境.

Lua环境协作组件

  • Redis创建两个用于与Lua环境协作的组件: 伪客户端- 负责执行 Lua 脚本中的 Redis 命令, lua_scripts字典- 保存 Lua 脚本:

    • 伪客户端 
      执行Reids命令必须有对应的客户端状态, 因此执行 Lua 脚本内的 Redis 命令必须为 Lua 环境专门创建一个伪客户端, 由该客户端处理 Lua 内所有命令: redis.call()/redis.pcall()执行一个Redis命令步骤如下: 
    • lua_scripts字典 
      字典key为脚本 SHA1 校验和, value为 SHA1 对应脚本内容, 所有被EVALSCRIPT LOAD载入过的脚本都被记录到 lua_scripts 中, 便于实现 SCRIPT EXISTS 命令和脚本复制功能.

EVAL命令原理

EVAL命令执行分为以下三个步骤:

  1. 定义Lua函数: 
    在 Lua 环境内定义 Lua函数 : 名为f_前缀+脚本 SHA1 校验和, 体为脚本内容本身. 优势:

    • 执行脚本步骤简单, 调用函数即可;
    • 函数的局部性可保持 Lua 环境清洁, 减少垃圾回收工作量, 且避免使用全局变量;
    • 只要记住 SHA1 校验和, 即可在不知脚本内容的情况下, 直接调用 Lua 函数执行脚本(EVALSHA命令实现).
  2. 将脚本保存到lua_scripts字典;

  3. 执行脚本函数: 
    执行刚刚在定义的函数, 间接执行 Lua 脚本, 其准备和执行过程如下: 
    1). 将EVAL传入的键名和参数分别保存到KEYSARGV, 然后将这两个数组作为全局变量传入到Lua环境; 
    2). 为Lua环境装载超时处理hook(handler), 可在脚本出现运行超时时让通过SCRIPT KILL停止脚本, 或SHUTDOWN关闭Redis; 
    3). 执行脚本函数; 
    4). 移除超时hook
    5). 将执行结果保存到客户端输出缓冲区, 等待将结果返回客户端; 
    6). 对Lua环境执行垃圾回收.

对于会产生随机结果但无法排序的命令(如只产生一个元素, 如 SPOPSRANDMEMBERRANDOMKEYTIME), Redis在这类命令执行后将脚本状态置为lua_random_dirty, 此后只允许脚本调用只读命令, 不允许修改数据库值.


实践

使用Lua脚本重新构建带有过期时间的分布式锁.

案例来源: <Redis实战> 第6、11章, 构建步骤:

  • 锁申请

    • 首先尝试加锁:

      • 成功则为锁设定过期时间; 返回;
      • 失败检测锁是否添加了过期时间;
    • wait.
  • 锁释放 
    • 检查当前线程是否真的持有了该锁:

      • 持有: 则释放; 返回成功;
      • 失败: 返回失败.

非Lua实现

String acquireLockWithTimeOut(Jedis connection, String lockName, long acquireTimeOut, int lockTimeOut) {String identifier = UUID.randomUUID().toString();String key = "lock:" + lockName; long acquireTimeEnd = System.currentTimeMillis() + acquireTimeOut; while (System.currentTimeMillis() < acquireTimeEnd) { // 获取锁并设置过期时间 if (connection.setnx(key, identifier) != 0) { connection.expire(key, lockTimeOut); return identifier; } // 检查过期时间, 并在必要时对其更新 else if (connection.ttl(key) == -1) { connection.expire(key, lockTimeOut); } try { Thread.sleep(10); } catch (InterruptedException ignored) { } } return null; } boolean releaseLock(Jedis connection, String lockName, String identifier) { String key = "lock:" + lockName; connection.watch(key); // 确保当前线程还持有锁 if (identifier.equals(connection.get(key))) { Transaction transaction = connection.multi(); transaction.del(key); return transaction.exec().isEmpty(); } connection.unwatch(); return false; }

Lua脚本实现

  • Lua脚本: acquire
local key = KEYS[1]
local identifier = ARGV[1]
local lockTimeOut = ARGV[2]-- 锁定成功
if redis.call("SETNX", key, identifier) == 1 then redis.call("EXPIRE", key, lockTimeOut) return 1 elseif redis.call("TTL", key) == -1 then redis.call("EXPIRE", key, lockTimeOut) end return 0
  • Lua脚本: release
local key = KEYS[1]
local identifier = ARGV[1]if redis.call("GET", key) == identifier then redis.call("DEL", key) return 1 end return 0
  • Pre工具: 脚本执行器
/*** @author jifang* @since 16/8/25 下午3:35.*/
public class ScriptCaller { private static final ConcurrentMap<String, String> SHA_CACHE = new ConcurrentHashMap<>(); private String script; private ScriptCaller(String script) { this.script = script; } public static ScriptCaller getInstance(String script) { return new ScriptCaller(script); } public Object call(Jedis connection, List<String> keys, List<String> argv, boolean forceEval) { if (!forceEval) { String sha = SHA_CACHE.get(this.script); if (Strings.isNullOrEmpty(sha)) { // load 脚本得到 sha1 缓存 sha = connection.scriptLoad(this.script); SHA_CACHE.put(this.script, sha); } return connection.evalsha(sha, keys, argv); } return connection.eval(script, keys, argv); } }
  • Client
public class Client {private ScriptCaller acquireCaller = ScriptCaller.getInstance("local key = KEYS[1]\n" +"local identifier = ARGV[1]\n" + "local lockTimeOut = ARGV[2]\n" + "\n" + "if redis.call(\"SETNX\", key, identifier) == 1 then\n" + " redis.call(\"EXPIRE\", key, lockTimeOut)\n" + " return 1\n" + "elseif redis.call(\"TTL\", key) == -1 then\n" + " redis.call(\"EXPIRE\", key, lockTimeOut)\n" + "end\n" + "return 0" ); private ScriptCaller releaseCaller = ScriptCaller.getInstance( "local key = KEYS[1]\n" + "local identifier = ARGV[1]\n" + "\n" + "if redis.call(\"GET\", key) == identifier then\n" + " redis.call(\"DEL\", key)\n" + " return 1\n" + "end\n" + "return 0" ); @Test public void client() { Jedis jedis = new Jedis("127.0.0.1", 9736); String identifier = acquireLockWithTimeOut(jedis, "ret1", 200 * 1000, 300); System.out.println(releaseLock(jedis, "ret1", identifier)); } String acquireLockWithTimeOut(Jedis connection, String lockName, long acquireTimeOut, int lockTimeOut) { String identifier = UUID.randomUUID().toString(); List<String> keys = Collections.singletonList("lock:" + lockName); List<String> argv = Arrays.asList(identifier, String.valueOf(lockTimeOut)); long acquireTimeEnd = System.currentTimeMillis() + acquireTimeOut; boolean acquired = false; while (!acquired && (System.currentTimeMillis() < acquireTimeEnd)) { if (1 == (long) acquireCaller.call(connection, keys, argv, false)) { acquired = true; } else { try { Thread.sleep(10); } catch (InterruptedException ignored) { } } } return acquired ? identifier : null; } boolean releaseLock(Jedis connection, String lockName, String identifier) { List<String> keys = Collections.singletonList("lock:" + lockName); List<String> argv = Collections.singletonList(identifier); return 1 == (long) releaseCaller.call(connection, keys, argv, true); } }

参考 & 推荐
代码的未来
Redis入门指南
Redis实战
Redis设计与实现
云风的Blog: Lua与虚拟机
Lua简明教程- CoolShell
Lua-newbie
Lua-Users
redis.io

转载于:https://www.cnblogs.com/barrywxx/p/8563284.html

Redis结合Lua脚本实现高并发原子性操作相关推荐

  1. Spring Redis中使用Lua脚本实现高并发原子操作

    1. 前言 在上一文中我对 Lua 语言的一些简单的语法及其在 Redis 中的操作进行了介绍,但是在 Java 开发中我们还需要进一步的学习才能使这种技术落地.今天就结合Spring Data Re ...

  2. Redis 使用 Lua 脚本进行原子操作

    Redis 使用 Lua 脚本进行原子操作 Intro 之前写过一篇文章也是 Redis 使用 LUA 脚本实现分布式的 CAS 操作,可以参考:基于 Redis 实现 CAS 操作 最近使用 Red ...

  3. nx set 怎么实现的原子性_【redis进阶(1)】redis的Lua脚本控制(原子性)

    [toc] 为什么要用lua 减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成.使用脚本,减少了网络往返时延. 原子操作:Redis会将整个脚本作 ...

  4. redis之lua脚本: 原子性 调试 嵌入高级语言

    实验环境: redis: 6.0.9 redis执行lua脚本时, 出错不会回滚(rollback) 我们知道, 使用lua脚本可以在执行一串redis命令时, 实现一定原子性(lua脚本中多条指令执 ...

  5. Redis进阶-lua脚本

    文章目录 Pre 语法 jedis操作lua 好处 lua实战 注意事项 Pre Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行. 语法 从Redis2.6.0 ...

  6. PHP中使用redis执行lua脚本示例

    一.引言 redis学了一段时间了,基本的东西都没问题了.从今天开始讲写一些redis和lua脚本的相关的东西,lua这个脚本是一个好东西,可以运行在任何平台上,也可以嵌入到大多数语言当中,来扩展其功 ...

  7. Redis 中 Lua 脚本的应用和实践

    引言 前段时间组内有个投票的产品,上线前考虑欠缺,导致被刷票严重.后来,通过研究,发现可以通过 redis lua 脚本实现限流,这里将 redis lua 脚本相关的知识分享出来,讲的不到位的地方还 ...

  8. Redis学习笔记(八)redis之lua脚本学习

    redis系列文章目录 使用spring-data-redis实现incr自增 Redis 利用Hash存储节约内存 Redis学习笔记(九)redis实现时时直播列表缓存,支持分页[热点数据存储] ...

  9. Redis的Lua脚本总结

    Redis的Lua脚本总结 在redis操作中有时我们需要多条命令在执行时保证原子性,例如:使用redis做分布式锁时的加锁(判断key是否存在,不存在就set)和解锁(判断key是否存在并等于指定值 ...

  10. redis执行lua脚本

    从redis 2.6.0版本开始,redis内置了Lua解释器,并提供了eval命令来解析Lua脚本求值. 1. 语法格式 语法: eval script numkeys keys args 参数: ...

最新文章

  1. 动态链接库dll,静态链接库lib, 导入库lib
  2. 自定义本地音乐播放器
  3. dxf转nc代码软件_FastCAM激光版套料软件
  4. R语言数据转换——plyr包
  5. (筆記) 如何使用ModelSim作前仿真與後仿真? (SOC) (Quartus II) (ModelSim)
  6. Python爬虫入门教程 44-100 Charles的安装与使用-手机APP爬虫部分
  7. Android编程之SparseArrayE详解 Android编程之SparseArrayE详解
  8. wordpress博客加载缓慢解决:去除Open Sans和Lato 字体
  9. 二叉树遍历的几种常见方法
  10. JAVA集合和guava集合使用和原理解析
  11. 数据库系统概念第6版第三章答案
  12. 落实安全生产责任,施工单位双重预防体系智能化管理解决方案
  13. 【MFC】Ribbon界面开发(三)
  14. php 连接局域网打印机,如何添加局域网打印机?局域网打印机添加方法介绍
  15. Revit 2019注册机
  16. win10下设置显示文件后缀名以及显示隐藏文件
  17. Python之多张图片拼接
  18. 计算机打不开guest用户,Win10打不开guest账户怎么办?
  19. 吴恩达深度学习学习笔记——C1W2——神经网络基础——练习题
  20. 劳易测BCB G40 H47 L030 - 条码带

热门文章

  1. poj 1094 Sorting It All Out 很好的拓扑排序,让我对拓扑排序有了一个很好的写法!!!
  2. poj 1125 Stockbroker Grapevine 很好的一道floyd入门题
  3. 如何在 Mac 上的“屏幕使用时间”中设置限定通信?
  4. Mac系统功能快速切换状态栏小工具One Switch for Mac
  5. 如何在 macOS Monterey 正式发布之前备份您的 Automator 作品?
  6. 5G来临,一对一直播社交源码如何利用热门机制吸引万千用户?
  7. 数据挖掘面试题之梯度提升树
  8. 基于USB armory 制作一个USB恶意软件分析器
  9. 仅109美元 搞一套Evive物联网开发工具包回家耍
  10. Koa-router 优先级问题