基于Lua脚本解决实时数据处理流程中的关键问题

摘要
在处理实时数据的过程中需要缓存的参与,由于在更新实时数据时并发处理的特点,因此在更新实时数据时经常产生新老数据相互覆盖的情况,针对这个情况调查了Redis事务和Lua脚本后,发现Redis事务并不能很好的满足该场景的业务需要,必须借助Lua脚本执行原子化的操作才能在理论上解决数据更新的准确性问题。
实时数据处理过程中遇到的问题
在处理实时数据的过程中,经常使用Redis存取数据执行CAS(check and set)操作。一般做法是先从Redis中获取到目标数据,然后根据数据的特征指标判断是应该更新还是放弃更新。在实时数据流量较小时这个办法简单粗暴的解决了数据更新的逻辑问题,但是面对上传频率较高的场景或者在更新实时数据时同步更新相关数据的汇总值时就会经常面临更新时新数据被老数据覆盖的问题,而且问题的出现具有随机性,无法有效解决数据的缓存准确性的要求。
此过程中的调用示意图:
发送命令请求,获取时间戳
Caller ----------------------------------------> Redis
发送时间戳给客户端
Caller <---------------------------------------- Redis
发送更新指令
Caller ----------------------------------------> Redis
返回执行结果
Caller <---------------------------------------- Redis
由上图可见在获取时间戳到发送更新指令之前由于不是原子操作,因此存在数据被更新的可能。在解决这个问题的时候不禁会想:“如果这个场景发生在数据库中会怎样?”
如果在数据库中,可以使用语句中的where条件来限制SQL语句的执行,做到按条件执行的目的。
上述思想可以用伪代码表达为:
UPDATE 终端状态 set status='XXXXX' where code='XXXX' and 时间戳>timestamp
这是个典型的先读后写的操作,该语句在数据库中以锁的方式保证了处理串行化和操作的原子性。
那么,问题是:Redis事务中能不能做到?
Redis事务
Redis的事务由四个关键命令构成:MULTI、EXEC、DISCARD和WATCH构成。
| 名称 | 作用 |
|---|---|
| MULTI | 声明开启事务通道 |
| EXEC | 开始批量执行 |
| DISCARD | 放弃执行之前发生的命令 |
| WATCH | 监听某个KEY的变化 |
Redis事务的基本执行方式是:
- 使用
WATCH声明监听某个KEY值的变化 - 发送
MULTI命令 - 发送事务中需要执行的指令
- 发送
EXEC指令,如果监听到数据在watch后发生变化则放弃提交
Redis事务的执行示意图:
发送命令请求,获取时间戳
Caller ----------------------------------------> Redis
发送时间戳给客户端
Caller <---------------------------------------- Redis
发送WATCH指令
Caller ----------------------------------------> Redis
发送MULTI指令
Caller ----------------------------------------> Redis
发送更新指令
Caller ----------------------------------------> Redis
发送EXEC指令
Caller ----------------------------------------> Redis
返回执行结果
Caller <---------------------------------------- Redis
Redis的事务跟数据库的事务有极大不同,其事务实际是由WATCH监听KEY值的变化加批量执行来完成的,而且事务执行过程中无法与客户端进行交互的。这个事务的实现方式就限制了其所能满足的业务场景,比如本文中遇到的时间戳+实时数据的更新场景中,要求时时刻刻都将最新的数据更新到Redis中,而不是在更新时发现有其他client抢先更新了目标KEY之后就放弃当前比较新的时间戳的更新权。
那么,如何才能满足将最新的数据更新到Redis中这个业务需求呢?方法也是有的,那就是使用Lua脚本
Redis+Lua脚本
Redis 2.6+都集成了Lua脚本。通过内嵌对于Lua的支持,Redis解决了长久以来不能高效处理CAS的缺点。在Redis中执行Lua脚本主要涉及到两个关键命令:EVAL和EVALSHA,另外还有个辅助的命令SCRIPT EXISTS sha1 sha2 ... shaN可以用于查询脚本是否已经缓存。
| 名称 | 作用 |
|---|---|
| EVAL | 执行某个客户端传入的脚本 |
| EVALSHA | 执行某个已经在Redis Server中缓存的脚本 |
使用Lua脚本执行CAS操作的基本步骤:
- 客户端发送
EVAL 脚本 参数...命令 - 服务端执行脚本
- 获取用于进行判断的键值
- 判断是否应该更新
- 执行/放弃更新
- 返回脚本执行结果
其中第2步是完全在服务器端执行,根据Redis官网的描述:
Redis uses the same Lua interpreter to run all the commands. Also Redis guarantees that a script is executed in an atomic way: no other script or Redis command will be executed while a script is being executed.
在Redis Server中执行Lua脚本是一个原子性的操作,时间戳较旧的数据会自动放弃更新缓存数据,因此就可以保证存入缓存中的数据永远是最新的,因此也就解决了数据并发更新时老数据被新数据覆盖的问题。
Lua脚本内部逻辑可以用伪代码描述为:
timestamp=Redis.call('获取时间戳的指令')
if (timestamp==nil)
then
Redis.call('执行更新')
elseif (时间戳>timestamp)
then
Redis.call('执行更新')
end
发送脚本
Caller ----------------------------------------> Redis
为脚本创建 Lua 函数
Redis ----------------------------------------> Lua
绑定超时处理钩子
Redis ----------------------------------------> Lua
执行脚本函数
Redis ----------------------------------------> Lua
返回函数执行结果(一个 Lua 值)
Redis <---------------------------------------- Lua
将 Lua 值转换为 Redis 回复
并将结果返回给客户端
Caller <---------------------------------------- Redis
总结
Redis中进行原子化操作有两个方法:Redis事务或Lua脚本。使用Redis事务只能满足数据在未发生变化进行更新而发生变化就放弃更新的场景。对于实时数据的处理场景来说,Redis的事务无法满足根据时间戳进行业务处理的需要。由于Redis执行Lua脚本时是原子化的并且脚本内部可以编写读写判断逻辑,因此可以借助Lua脚本完成实时数据更新的业务需要。
虽然使用Lua脚本可以较好的满足业务需要,但是在使用Redis脚本时也有一定的注意事项,Lua脚本中不要编写太复杂的操作,应该以尽量简单的逻辑完成整个操作过程,避免因为脚本的执行产生阻塞效应。
本文链接:http://www.cnblogs.com/zhu-wj/p/7777762.html
参考资料
基于Lua脚本解决实时数据处理流程中的关键问题的更多相关文章
- ESP8266使用详解--基于Lua脚本语言
这些天,,,,今天终于看到了希望,,,天道酬勤 先说实现的功能...让ESP8266连接无线网,然后让它建立服务器,,我的客户端连接上以后,发给客户端发数据模块打印到串口,,往ESP8266串口里发数 ...
- 基于bat脚本的前端发布流程的优化
背景介绍 前面在基于bat脚本的前端发布流程设计与实现中,我已经介绍了设计与实现,这一篇主要是针对其的一个优化折腾(分两步走,第一步先搞出来,第二步再想着怎么去优化它),我主要做了以下几件事. &qu ...
- 基于bat脚本的前端发布流程设计与实现
写在前面 本文大致向读者介绍了楼下几点知识,希望在编写bat脚本时候能够帮到读者,如果能够有所启迪,那就更好了. bat脚本的相关知识和案例编写 用windows自带的命令压缩文件 windows和l ...
- 七,ESP8266-UDP(基于Lua脚本语言)
https://www.cnblogs.com/yangfengwu/p/7533302.html 那天朋友问我为什么有UDP Sever 和 UDP Client ,,我说:每个人想的不一样,设 ...
- 二,ESP8266 GPIO和SPI和定时器和串口(基于Lua脚本语言)
https://www.cnblogs.com/yangfengwu/p/7514336.html 我们写lua用这个软件 如果点击的时候提示安装,,安装就行,,如果没有提示呢可以,按照下面链接的提示 ...
- 一,ESP8266下载和刷固件(基于Lua脚本语言)
用自己的小板测试...... 安排上呢 一, ESP8266下载和刷固件(Lua开发----体验一下lua开发的魅力所在) 二, 控制一个灯亮灭 三, TCP服务器 四, TCP客户端 五, UDP ...
- 九,ESP8266 判断是断电上电(强制硬件复位)之后运行的内部程序还是内部软件复位之后运行的程序(基于Lua脚本语言)
现在我有一个需求,WIFI模块控制一个继电器,我要做的是如果内部程序跑乱了,造成了内部程序复位重启,那么控制继电器的状态不能改变 如果是设备断电了,然后又来电了,我需要的是继电器一定要是断开才好.不能 ...
- 八,ESP8266 文件保存数据(基于Lua脚本语言)
https://www.cnblogs.com/yangfengwu/p/7533845.html 应该是LUA介绍8266的最后一篇,,,,,,下回是直接用SDK,,然后再列个12345...... ...
- 六,ESP8266 TCP Client(基于Lua脚本语言)
今天不知道是不是让我姐挺失望.......很多时候都不知道自己努力的方向对不对,,以后能不能带给家人最美好的期盼...... Init.lua 没啥改变,,就改了一下加载Client.lua gpio ...
随机推荐
- CSS3的颜色渐变效果
在 animate.css寻找自己想要的动态效果,看到标题Animate.css和按钮Animate it的颜色在逐渐变化,觉得蛮有趣的,把控制变化的相关代码扒了下来,自己分析实现一波. 一开始认为使 ...
- Web 项目更改项目名
简单的记录web开发中基本的操作. 更改项目名 直接修改 找到原项目中的.project 文件,更改中项目名称.然后在同目录下找到.mymetadata 文件 并更改name.context-root ...
- 极化码之tal-vardy算法(2)
上一节我们了解了tal-vardy算法的大致原理,对所要研究的二元输入无记忆对称信道进行了介绍,并着重介绍了能够避免输出爆炸灾难的合并操作,这一节我们来关注信道弱化与强化操作. [1]<Chan ...
- Linux的硬盘使用情况、挂载、SSD挂载(查看df -h不能看到的卷)
linux上的盘和window的有区别,磁盘空间必须挂载在目录上,要不然没用 对与新增的硬盘.SSD固态硬盘.挂载到linux上的操作如下: df -h #显示目前在Linux系统上的文件系 ...
- GitHub使用(四) - 关于分支Branch
1. 什么是分支Branch? 我初步的理解为:GitHub仓库默认有一个master的分支,当我们在master分支开发过程中接到一个新的功能需求,我们就可以新建一个分支同步开发而互不影响,开发完成 ...
- ThinkPHP中:使用递归写node_merge()函数
需求描述: 现有一个节点集合 可以视为一个二维数组 array(5) { [0] => array(4) { ["id"] => string(1) "1&q ...
- Hex to Int 【十六进制转十进制】
long HexToInt(char *msgline){ long strlength,chvalue,tvalue; WORD i; chvalue=0; strlengt ...
- Android打包版本号设置
之前没有设置过打包的命名,每次打包都是默认的"app-realease.apk",之后手动修改名字来显示出它是一个新版本. 晚上学习了如何配置打包名称,很简单,修改build.gr ...
- Postgresql在线备份和恢复
1.实验环境 OS: RedHat Linux Enterprisedb 6.3 DB: postgresql 9.3 PGHOME: /opt/PostgreSQL/9.3 PGDATA: /opt ...
- C# 中函数内定义函数的委托方法
//定义委托方法Action(无返回值)Func(有返回值) //无返回值委托 Action<string> SetKeyAndValue = delegate(string key) { ...