百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

Redis系列五:Redis+Lua高并发场景的数据一致性

liebian365 2024-10-29 15:53 28 浏览 0 评论

本文目标

  1. 学习lua基本语法
  2. 能够采用redis+lua

lua 基本语法

Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

Lua 是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组于 1993 年开发的,该小组成员有:Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo。

设计目的

其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

Lua 特性

  1. 轻量级: 它用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里。
  2. 可扩展: Lua提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样。
  3. 支持面向过程(procedure-oriented)编程和函数式编程(functional programming);
  4. 自动内存管理;只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象;
  5. 语言内置模式匹配;闭包(closure);函数也可以看做一个值;提供多线程(协同进程,并非操作系统所支持的线程)支持;
  6. 通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。

基本语法

-- 行注释
--[[
块注释
--]]
--全局
num = 3

-- 变量
a1 = 5
local a2 = 6

function fun1()
    a3 = 7
    local a4 = 8 --局部变量
end
print("1.----变量分为:全局变量和局部变量")
print(a1, a2, a3, a4)

print("2.----循环与控制语句")
b1 = 1
while (b1 < num) do
    b1 = b1 + 1 -- 没有 【+= ++】 语法
    print("while循环", b1)
end
--[[
    数值 for 循环
for var=exp1, exp2, exp3 do end
var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次"执行体"。exp3 是可选的,如果不指定,默认为 1。
--]]
for i = 1, 4, 2 do
    print("for数值-循环", i)
end

--[[
    泛型循环
    i 是数组索引值,v 是对应索引的数组元素值。ipairs 是 Lua 提供的一个迭代器函数,用来迭代数组。
--]]
cArray = {
    "v1",
    "v2",
    "v3"
}
for i, v in ipairs(cArray) do
    print("for泛型-index: ", i, "value: ", v)
end

--[[
repeat...until循环
repeat...until 循环和 C 语言里面的 do...while() 作用是一样的。
--]]
d1 = 0
repeat
    d1 = d1 + 1
    print("repeat-", d1)
until (d1 > num)

--[[
    if 语句
    Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table。
    !boolean 类型只有两个可选值:true(真) 和 false(假),Lua 把 false 和 nil 看作是 false,其他的都为 true,数字 0 也是 true:
--]]
if false or nil then
    print("至少有一个是 true")
else
    print("false 和 nil 都为 false")
end

if 0 then
    print("数字 0 是 true")
else
    print("数字 0 为 false")
end

--[[
运算符

1. +加、 -减、 *乘、 /除、 %取余、 ^乘幂、 -负数
2. ==等于、【~=】不等于、>、<、>=、<=
3. and、or、【not】 
--]]
print("---------分割线---------")

e1 = true
e2 = true

if (e1 and e2) then
    print("e1 and e2 - 条件为 true")
end

if (e1 or e2) then
    print("e1 or e2 - 条件为 true")
end

print("---------分割线---------")

-- 修改 a 和 b 的值
e1 = false
e2 = true

if (e1 and e2) then
    print("e1 and e2 - 条件为 true")
else
    print("e1 and e2 - 条件为 false")
end

if (not (e1 and e2)) then
    print("not( e1 and e2) - 条件为 true")
else
    print("not( e1 and e2) - 条件为 false")
end

print("---------函数---------")
myprint = function(params)
    print("函数 ##", params, "##")
end

function add(num1, num2, functionPrint)
    sum = num1 + num2
    functionPrint(sum)
end
add(1, 3, myprint)

function maximun(array)
    local index = 1
    local value = array[index]
    for i, v in ipairs(array) do
        if v > value then
            index = i
            value = v
        end
    end
    return index, value
end
-- !Lua的下标不是从0开始的,是从1开始的。
print(maximun({800, 19, 1, 4, 8, 102}))

-- 可变参数 三点 ... 表示函数有可变的参数
function add(...)
    local sum = 0
    for i,v in ipairs{...} do 
        sum = sum + v
    end
    -- select("#",...) 来获取可变参数的数量
    -- .. 字符串拼接
    print("总共传入 " .. select("#",...) .. " 个数")
    return sum
end
print(add(1,2,3,4,5))
-- 斐波那契数列
function fib(n)
    if n<2 then return 1 end
    return fib(n-2) + fib(n+1)
end

--[[
    闭包
--]]
function newCounter()
    local i=0
    return function()
        i= i+1
        return i
    end
end
newCounter = newCounter()
newCounter()
newCounter()

--[[
    函数返回值,多个
--]]
function getUserInfo(id)
    print(id)
    return "haoel", 37, "haoel@hotmail.com", "https://coolshell.cn"
end
-- 似乎必须直接解构!!!
name, age, email, website, bGay = getUserInfo()
userInfo = getUserInfo()
-- haoel   37      haoel@hotmail.com       https://coolshell.cn    nil
print(name, age, email, website, bGay)
-- haoel
print(userInfo)

--[[
    函数返回值,多个
--]]
print("---------函数---------")
print("---------table 类型---------")

--[[
    table 类型
--]]

mytable = {}
-- table 里面值的设置和获取
mytable[1] = "元素1"
mytable["er"] = "元素2"
print(mytable[1])

-- 数组,lua里面的元素是从 1 开始的
array = {10,20,30,40,50}
--[[
    等价于
    array = {[1]=10, [2]=20, [3]=30, [4]=40, [5]=50}
--]]

-- 数组里面值得获取
print(array[1], array[2], array[3])
-- 字典
dictionary = {
    key1 = "value1",
    key2 = "value2",
    key3 = "value3"
}

-- 字典里面值得获取
print(dictionary.key1, dictionary.key2, dictionary.key3)
print("---------table 类型---------")

Redis + Lua

在 Redis 中,执行 Lua 语言是原子性的,有助于 Redis 对并发数据一致性的支持。

为什么要用lua

  1. 减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成。使用脚本,减少了网络往返时延。
  2. 原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他进程或者进程的命令插入。(最重要)
  3. 复用:客户端发送的脚本会永久存储在Redis中,意味着其他客户端可以复用这一脚本而不需要使用代码完成同样的逻辑。

两种方法运行脚本

  1. 直接输入一些 Lua 语言的程序代码;简单的脚本可以直接采用这种
  2. 将 Lua 语言编写成文件。有一定逻辑的采用这种

基本命令

eval lua-script key-num [key1 key2 key3 ...] [value1 value2 value3 ...]

其中:

  • eval 代表执行 Lua 语言的命令。
  • Lua-script 代表 Lua 语言脚本。
  • key-num 整数代表参数中有多少个 key,需要注意的是 Redis 中 key 是从 1 开始的,如果没有 key 的参数,那么写 0。
  • [key1 key2 key3…] 是 key 作为参数传递给 Lua 语言,也可以不填它是 key 的参数,但是需要和 key-num 的个数对应起来。
  • [value1 value2 value3…] 这些参数传递给 Lua 语言,它们是可填可不填的。

实例一 嵌入脚本

基本用法:set、get

# set
127.0.0.1:6379> EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 lua-key lua-value
OK
# get
127.0.0.1:6379> EVAL "return redis.call('get', KEYS[1])" 1 lua-key
"lua-value"

127.0.0.1:6379> lpush person a b c
(integer) 3
# 多参数
127.0.0.1:6379> EVAL "return redis.call('lrange', KEYS[1], ARGV[1], ARGV[2])" 1 person 0 -1
1) "c"
2) "b"
3) "a"

字段说明

  1. KEYS[1]: 需要大写,对应的是1之后的lua-key, 【占位符】
  2. 1: 代表之后key的个数,多余的舍弃
  3. ARGV[1]: 需要大写,对应的是value的第一个

注意事项

  1. KEYS、ARGV:需要大写
  2. 内部需要用单引号

node代码

// 嵌入脚本
async function fun1() {
    const argv = ['lua-key', 'lua-value'];
    const set = await redis.", 1, argv);
    const get = await redis.", 1, argv[0]);
    console.log("简单:", set, get);
    // 同时传入多个key需要借助lua中的循环
    // const list = await redis.", 1, 'list', 1,2,3);
    await redis.", 1, 'list', '1');
    const listGet = await redis.", 1, 'list', 0, -1);
    console.log("队列:", listGet)
}
const evalScript = `return redis.call('SET', KEYS[1], ARGV[2])`;
// 缓存脚本
async function fun2() {
    // 1. 缓存脚本获取 sha1 值
    const sha1 = await redis.script("load", evalScript);
    console.log(sha1); // 6bce4ade07396ba3eb2d98e461167563a868c661
    // 2. 通过 evalsha 执行脚本
    await redis.evalsha(sha1, 2, 'name1', 'name2', 'val1', 'val2');
    // 3. 获取数据
    const result = await redis.get("name1");
    console.log(result); // "val2"
}

实例二 脚本文件 -- 频次限制

// 脚本文件
async function fun3() {
    const luaScript = fs.readFileSync('./limit.lua');
    const key = 'rate:limit';
    // @ts-ignore
    const limit = await redis.;
    console.log('limit', limit);
}
--[[
Author: simuty
Date: 2020-11-26 11:17:33
LastEditTime: 2020-11-26 13:50:03
LastEditors: Please set LastEditors
Description:

limit.lua

!10秒内只能访问3次。 后续该脚本可以在nginx或者程序运行脚本中直接使用,判断返回是否为0,就0就不让其继续访问。
!以上,如果不使用redis+lua,那高并发下incr和expire就会出现原子性破坏,造成expire执行多次浪费

--]]

local times = redis.call("incr", KEYS[1])

if times == 1 then
    redis.call("expire", KEYS[1], ARGV[1])
else
    if times > tonumber(ARGV[2]) then
        return 0
    end
end

return 1
?  6Lua git:(main) ? ts-node index.ts
limit 1
limit 1
limit 1
limit 0
limit 0

实例三 脚本文件 -- 自增ID

local key = KEYS[1]
local id = redis.call("get", key)
if id == false then
    redis.call("set", key, 1)
    return key .. "00001"
else
    redis.call("set", key, id + 1)
    return key .. string.format("%04d", id + 1)
end

相关推荐

精品博文嵌入式6410中蓝牙的使用

BluetoothUSB适配器拥有一个BluetoothCSR芯片组,并使用USB传输器来传输HCI数据分组。因此,LinuxUSB层、BlueZUSB传输器驱动程序以及B...

win10跟这台计算机连接的前一个usb设备工作不正常怎么办?

前几天小编闲来无事就跑到网站底下查看粉丝朋友给小编我留言询问的问题,还真的就给小编看到一个问题,那就是win10跟这台计算机连接的一个usb设备运行不正常怎么办,其实这个问题的解决方法时十分简单的,接...

制作成本上千元的键盘,厉害在哪?

这是稚晖君亲自写的开源资料!下方超长超详细教程预警!!全文导航:项目简介、项目原理说明、硬件说明、软件说明项目简介瀚文智能键盘是一把我为自己设计的——多功能、模块化机械键盘。键盘使用模块化设计。左侧的...

E-Marker芯片,USB数据线的“性能中枢”?

根据线缆行业的研究数据,在2019年搭载Type-C接口的设备出货量已达到20亿台,其中80%的笔记本电脑和台式电脑采用Type-C接口,50%的智能手机和平板电脑也使用Type-C接口。我们都知道,...

ZQWL-USBCANFD二次开发通讯协议V1.04

修订历史:1.功能介绍1.1型号说明本文档适用以下型号:  ZQWL-CAN(FD)系列产品,USB通讯采用CDC类实现,可以在PC机上虚拟出一个串口,串口参数N,8,1格式,波特率可以根据需要设置(...

win10系统无法识别usb设备怎么办(win10不能识别usb)

从驱动入手,那么win10系统无法识别usb设备怎么办呢?今天就为大家分享win10系统无法识别usb设备的解决方法。1、右键选择设备管理器,如图:  2、点击更新驱动程序,如图:  3、选择浏览...

微软七月Win8.1可选补丁有内涵,含大量修复

IT之家(www.ithome.com):微软七月Win8.1可选补丁有内涵,含大量修复昨日,微软如期为Win7、Win8.1发布7月份安全更新,累计为6枚安全补丁,分别修复总计29枚安全漏洞,其中2...

如何从零开始做一个 USB 键盘?(怎么制作usb)

分两种情况:1、做一个真正的USB键盘,这种设计基本上不涉及大量的软件编码。2、做一个模拟的USB键盘,实际上可以没有按键功能,这种的需要考虑大量的软件编码,实际上是一个单片机。第一种设计:买现成的U...

电脑识别U盘失败?5个实用小技巧,让你轻松搞定USB识别难题

电脑识别U盘失败?5个实用小技巧,让你轻松搞定USB识别难题注意:有些方法会清除USB设备里的数据,请谨慎操作,如果不想丢失数据,可以先连接到其他电脑,看能否将数据复制出来,或者用一些数据恢复软件去扫...

未知usb设备设备描述符请求失败怎么解决

出现未知daousb设备设备描述符请求失du败解决办zhi法如下:1、按下Windows+R打开【运行】;2、在版本运行的权限输入框中输入:services.msc按下回车键打开【服务】;2、在服务...

读《飘》47章20(飘每章概括)

AndAhwouldn'tleaveMissEllen'sgrandchildrenfornotrashystep-patobringup,never.Here,Ah...

英翻中 消失的过去 37(消失的英文怎么说?)

翻译(三十七):消失的过去/茱迪o皮考特VanishingActs/JodiPicoult”我能做什么?“直到听到了狄利亚轻柔的声音,我才意识到她已经在厨房里站了好一会儿了。当她说话的时候,...

RabbitMQ 延迟消息实战(rabbitmq如何保证消息不被重复消费)

现实生活中有一些场景需要延迟或在特定时间发送消息,例如智能热水器需要30分钟后打开,未支付的订单或发送短信、电子邮件和推送通知下午2:00开始的促销活动。RabbitMQ本身没有直接支持延迟...

Java对象拷贝原理剖析及最佳实践(java对象拷贝方法)

作者:宁海翔1前言对象拷贝,是我们在开发过程中,绕不开的过程,既存在于Po、Dto、Do、Vo各个表现层数据的转换,也存在于系统交互如序列化、反序列化。Java对象拷贝分为深拷贝和浅拷贝,目前常用的...

如何将 Qt 3D 渲染与 Qt Quick 2D 元素结合创建太阳系行星元素?

Qt组件推荐:QtitanRibbon:遵循MicrosoftRibbonUIParadigmforQt技术的RibbonUI组件,致力于为Windows、Linux和MacOSX提...

取消回复欢迎 发表评论: