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

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

liebian365 2024-10-29 15:53 24 浏览 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

相关推荐

4万多吨豪华游轮遇险 竟是因为这个原因……

(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...

“菜鸟黑客”必用兵器之“渗透测试篇二”

"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...

科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白

作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...

麦子陪你做作业(二):KEGG通路数据库的正确打开姿势

作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...

知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势

智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...

每日新闻播报(September 14)_每日新闻播报英文

AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...

香港新巴城巴开放实时到站数据 供科技界研发使用

中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...

5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper

本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...

Qt动画效果展示_qt显示图片

今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...

如何从0到1设计实现一门自己的脚本语言

作者:dong...

三年级语文上册 仿写句子 需要的直接下载打印吧

描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...

C++|那些一看就很简洁、优雅、经典的小代码段

目录0等概率随机洗牌:1大小写转换2字符串复制...

二年级上册语文必考句子仿写,家长打印,孩子照着练

二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...

一年级语文上 句子专项练习(可打印)

...

亲自上阵!C++ 大佬深度“剧透”:C++26 将如何在代码生成上对抗 Rust?

...

取消回复欢迎 发表评论: