AI实践哪家强?来 AICon, 解锁技术前沿,探寻产业新机! 了解详情
写点什么

Redis 进阶应用:Redis+Lua 脚本实现复合操作

  • 2020-02-07
  • 本文字数:2370 字

    阅读完需:约 8 分钟

Redis进阶应用:Redis+Lua脚本实现复合操作

一、引言

Redis 是高性能的 key-value 数据库,在很大程度克服了 memcached 这类 key/value 存储的不足,在部分场景下,是对关系数据库的良好补充。得益于超高性能和丰富的数据结构,Redis 已成为当前架构设计中的首选 key-value 存储系统。


虽然 Redis 官网上提供了 200 多个命令,但做程序设计时还是避免不了为了实现一小步业务逻辑而多次调用 Redis 的情况。


以 compare and set 场景为例。如果使用 Redis 原生命令,需要从 Redis 中获取这个 key,然后提取其中的值进行比对:如果相等就不做处理;如果不相等或者 key 不存在则将 key 设置成目标值。仅仅一个单点的 compare and set 操作就需要与 Redis 通讯两次。


此外,这种分散操作无法利用 Redis 的原子特性,占用多次网络 IO。


今天我们就来探讨一下如何优雅地应对上述场景。

二、Redis 与 Lua

在介绍 Lua 之前,我们需要先对这个语言有个初步了解。Lua 是一个小巧的脚本语言,几乎可以运行在所有操作系统和平台上。我们一般不会用 Lua 处理特别复杂的事务,因此只需了解一些 lua 的基本语法即可。


Redis 问世之后,其开发者也意识到了开篇提到的问题,因此 Redis 从 2.6 版本开始支持 Lua 脚本。新版本的 Redis 还支持 Lua Script debug,感兴趣的小伙伴可以去官网的 Documentation 中找到对应介绍和 QuickStart。


有了 Lua 脚本之后,使用 Redis 程序时便能够在以下方面实现显著提升:


  • 减少网络开销:本来 N 次网络请求的操作,可以用一个请求完成。原先 N 次请求的逻辑放在 Redis 服务器上完成,减少了网络往返时延;

  • 原子操作:Redis 会将整个脚本作为一个整体执行,中间不会被其他命令插入。这是一个重要特性,一定要拿小本本记好。至于为什么是一个原子操作,我们以后再分析;

  • 复用:客户端发送的脚本会永久存储在 Redis 中。这样其他客户端就可以复用这一脚本,而不需要使用代码完成同样的逻辑。


所以现在流传一句话:要想学好 Redis,必会 Lua Script。

三、通过 Lua 脚本实现 compare and set

接下来我们就实现一个简单的 compare and set,并通过这个例子感受一下 Lua 脚本给 Redis 使用带来的全新体验。


首先看一下如何让 Redis 执行 Lua 脚本。

3.1 Redis 的 EVAL

Redis 127.0.0.1:6379> EVAL script  numkeys key [key ...] arg [arg ...]
复制代码


  • script: 参数是一段 Lua 5.1 脚本程序。脚本不必(也不应该)定义为一个 Lua 函数。

  • numkeys: 用于指定键名参数的个数。

  • key [key …]: 从 EVAL 的第三个参数开始算起,表示在脚本中所用到的 Redis 键(key)。在 Lua 中,这些键名参数可以通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] ,KEYS[2],依次类推)。

  • arg [arg …]: 附加参数,在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。


这里借用一下官网的例子。


1565063153728030309.jpeg


上述脚本直接返回了入参。


  • eval 为 Redis 关键字;

  • 第一个引号中的内容就是 Lua 脚本;

  • 2 为参数个数;

  • key1 和 key2 是 KEYS[1]、KEYS[2]的入参;

  • first 和 second 是 ARGV[1],ARGV[2]的入参。


大家可以简单地将 KEYS[1],KEYS[2], ARGV[1],ARGV[2]理解为占位符。

3.2 执行脚本文件和缓存脚本

如果只能在命令行中写脚本执行,遇到复杂的脚本程序岂不是会抓狂?


下面我们来看一下,如何让 Redis 执行 Lua 脚本文件,同时也验证一下 lua 脚本的复用特性(以后我们再也不需要定期批量删除某些符合特定规则的 key 了)。


Redis 127.0.0.1:6379> SCRIPT LOAD  scriptRedis 127.0.0.1:6379> EVALSHA sha1  numkeys key [key ...] arg [arg ...]
复制代码


Redis 提供了一个 SCRIPTLOAD 命令,命令后面的 script 即为 Lua 脚本。命令将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。执行命令后,Redis 会返回一个 SHA1 串,第二个 EVALSHA 命令即可执行。


需要注意的是,脚本可以在缓存中保留无限长的时间,直到执行完 SCRIPT FLUSH。我们来看一下效果。


1565063162744000455.jpeg


Redis 还支持直接执行 Lua 脚本文件。首先编写并存储一个 Lua 脚本。


1565063169363042305.jpeg


然后调用 Redis-cli –eval 命令


1565063176794018397.jpeg


Redis-cli –eval 命令语法基本与原 eval 语法相同。

3.3 使用 Lua 脚本实现 compare and set

compareand set 的实现逻辑是这样的:首先获取 Redis 中指定 key 的 value,然后与给定值进行比较:如果相等,则将 key 设定为目标值并返回一个标识符;如果不相等,则不作任何操作并返回一个标识符。


if Redis.call('get', KEYS[1]) == ARGV[1]  then     Redis.call('set', KEYS[1], ARGV[2]);     return 1else     return 0 end
复制代码


下面我们来测试一下这个脚本。


首先向 Redis 的指定 key compareAndSet:key 写入一个值 value


1565063186284056507.jpeg


在 Redis 中执行 lua 脚本


1565063195364039755.jpeg


可以看到第一次执行返回 1,说明修改成功了;再使用原参数执行时返回 0,说明没有做任何修改。我们再查询一下 compareAndSet:key 这个 key


1565063202334030640.jpeg


可以看到 compareAndSet:key 这个 key 已经被修改为 new_value 了。

四、总结

我们通过 lua 脚本实现了一个简单的 compareAndSet 操作。


下面我们通过这个例子来验证一下开篇提到的特性。


  • 减少网络开销:不使用脚本的情况下,我们实现一个 compareAndSet 至少需要与 Redis 交互两次,而现在只需要执行一次操作即可完成;

  • 原子操作:得益于 Redis 的设计,Redis 会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心出现竞态条件,无需使用事务,感兴趣的可以百度或等待以后后续文章更新;

  • 复用:可以将一系列操作封装成一个 Lua 脚本,存储在文件或 Redis 上,下次使用时直接调用即可。


读到这里,希望你已经对 Redis+Lua 有了一定的了解,并能使用脚本完成一些简单的复合操作。后续还会继续更新一些基于 Lua 脚本+java 程序实现的分布式数据结构,如延迟队列、可重入锁等,感兴趣的小伙伴可以持续关注。


本文转载自宜信技术学院。


原文链接:http://college.creditease.cn/detail/284


2020-02-07 20:381848

评论 1 条评论

发布
用户头像
一直在纠结这个原子性,看到这里明白了,我一直纠结的是回滚,因为redis. call一旦执行就不会被撤销,所以比如编程错误,例如语法类型错误,lua script脚本还是会继续执行脚本里的其他的命令
2021-01-31 23:44
回复
没有更多了
发现更多内容

前端项目上传图片,压缩,拍照图片旋转解决方案

Vue js canvas axios

Nginx如何配置Http、Https、WS、WSS?

冰河

nginx 负载均衡 反向代理 https HTTP

Apache Hue介绍

大数据技术指南

hue 5月日更

5月20日,GaussDB将有大事发生

华为云开发者联盟

数据库 云原生 华为云 GaussDB TechWave

实战排查由于系统负载引起的服务响应异常

Coder的技术之路

高并发 性能调优 线上问题

消除数据孤岛,华为云DRS让一汽红旗ERP系统数据活起来

华为云开发者联盟

数据库 GaussDB 数据孤岛 华为云DRS ERP

云厂商下一块必争之地就是它了!

Serverless Devs

Serverless 云原生

凭借师兄甩给我的通关秘籍,顺利拿到字节Offer

学Java关注我

Java 编程 架构 面试

阿里P7大佬!王者级讲解ConcurrentHashMap源码,码农:太透彻了

牛哄哄的java大师

Java ConcurrentHashMap

重磅!数字人民币接入支付宝!

CECBC

数字人民币

差点扛不住了,阿里巴巴支付宝面试 5 轮暴击,终获 Offer

Java架构师迁哥

强!上线3天获10w浏览量,京东T8纯手码Redis缓存手册,我粉了

飞飞JAva

redis

如何下载和保存YouTube上的中英双语字幕和视频

flyfk

字幕

GitHub霸屏文章!清华教授手写保姆级笔记Scala - 类,网友:太香了

牛哄哄的java大师

Java scala

来了!这份阿里P7大佬梳理的Java注解和反射精髓笔记,信息量过大

飞飞JAva

Java

云原生下的灰度体系建设

阿里巴巴云原生

容器 运维 云原生 k8s 监控

异步编程的几种方式,你知道几种?

xcbeyond

Java 异步编程 5月日更

一千座5G工厂的花苞

脑极体

python解释器+pycharm的安装

Geek_6370d5

#python学习之路

拥有一个高性能低延时数据库是什么样的体验?

华为云开发者联盟

数据库 华为云 GaussDB GaussDB(for Cassandra) 低延时

被解救的代码 - 代码即服务时代来了!

Serverless Devs

阿里云 Serverless 云原生

太赞了!美团大牛强推的Spring事务笔记,上线仅1天就获赞上万

飞飞JAva

Java 事务spring

王兴的失败观

池建强

成功 王兴 创业失败启示录

工业制造业亟需数字化转型,区块链可以发挥哪些价值?

CECBC

区块链

消息队列的两种模式

五分钟学大数据

kafka 5月日更

300条数据变更引发的血案-记某十亿级核心mongodb集群部分请求不可用故障踩坑记

杨亚洲(专注MongoDB及高性能中间件)

数据库 mongodb 架构 MySQ 分布式数据库mongodb

☕【Java技术之旅】如何彻底认识AQS的原理(上篇)

码界西柚

Java AQS JVM JUC 5月日更

从SPACE矩阵,看5G究竟是否在走向成功?

脑极体

阿里P7:每个码农都应该知道的MySQL主从复制方法,看这篇就够了

牛哄哄的java大师

Java MySQL 数据库

高德 Serverless 平台建设及实践

Serverless Devs

阿里云 Serverless 云原生

云原生的进一步具象化

阿里巴巴云原生

大数据 容器 云原生 监控 中间件

Redis进阶应用:Redis+Lua脚本实现复合操作_行业深度_李崇_InfoQ精选文章