天天看点

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

在处理实时数据的过程中需要缓存的参与,由于在更新实时数据时并发处理的特点,因此在更新实时数据时经常产生新老数据相互覆盖的情况,针对这个情况调查了Redis事务和Lua脚本后,发现Redis事务并不能很好的满足该场景的业务需要,必须借助Lua脚本执行原子化的操作才能在理论上解决数据更新的准确性问题。

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

在处理实时数据的过程中,经常使用Redis存取数据执行CAS(check and set)操作。一般做法是先从Redis中获取到目标数据,然后根据数据的特征指标判断是应该更新还是放弃更新。在实时数据流量较小时这个办法简单粗暴的解决了数据更新的逻辑问题,但是面对上传频率较高的场景或者在更新实时数据时同步更新相关数据的汇总值时就会经常面临更新时新数据被老数据覆盖的问题,而且问题的出现具有随机性,无法有效解决数据的缓存准确性的要求。

此过程中的调用示意图:

由上图可见在获取时间戳到发送更新指令之前由于不是原子操作,因此存在数据被更新的可能。在解决这个问题的时候不禁会想:“如果这个场景发生在数据库中会怎样?”

如果在数据库中,可以使用语句中的where条件来限制SQL语句的执行,做到按条件执行的目的。

上述思想可以用伪代码表达为:

这是个典型的先读后写的操作,该语句在数据库中以锁的方式保证了处理串行化和操作的原子性。

那么,问题是:Redis事务中能不能做到?

Redis的事务由四个关键命令构成:MULTI、EXEC、DISCARD和WATCH构成。

名称

作用

MULTI

声明开启事务通道

EXEC

开始批量执行

DISCARD

放弃执行之前发生的命令

WATCH

监听某个KEY的变化

Redis事务的基本执行方式是:

使用<code>WATCH</code>声明监听某个KEY值的变化

发送<code>MULTI</code>命令

发送事务中需要执行的指令

发送<code>EXEC指令</code>,如果监听到数据在watch后发生变化则放弃提交

Redis事务的执行示意图:

Redis的事务跟数据库的事务有极大不同,其事务实际是由WATCH监听KEY值的变化加批量执行来完成的,而且事务执行过程中无法与客户端进行交互的。这个事务的实现方式就限制了其所能满足的业务场景,比如本文中遇到的时间戳+实时数据的更新场景中,要求时时刻刻都将最新的数据更新到Redis中,而不是在更新时发现有其他client抢先更新了目标KEY之后就放弃当前比较新的时间戳的更新权。

那么,如何才能满足将最新的数据更新到Redis中这个业务需求呢?方法也是有的,那就是使用Lua脚本

Redis 2.6+都集成了Lua脚本。通过内嵌对于Lua的支持,Redis解决了长久以来不能高效处理CAS的缺点。在Redis中执行Lua脚本主要涉及到两个关键命令:<code>EVAL</code>和<code>EVALSHA</code>,另外还有个辅助的命令<code>SCRIPT EXISTS sha1 sha2 ... shaN</code>可以用于查询脚本是否已经缓存。

EVAL

执行某个客户端传入的脚本

EVALSHA

执行某个已经在Redis Server中缓存的脚本

使用Lua脚本执行CAS操作的基本步骤:

客户端发送<code>EVAL 脚本 参数...</code>命令

服务端执行脚本

获取用于进行判断的键值

判断是否应该更新

执行/放弃更新

返回脚本执行结果

其中第2步是完全在服务器端执行,根据Redis官网的描述:

在Redis Server中执行Lua脚本是一个原子性的操作,时间戳较旧的数据会自动放弃更新缓存数据,因此就可以保证存入缓存中的数据永远是最新的,因此也就解决了数据并发更新时老数据被新数据覆盖的问题。

Lua脚本内部逻辑可以用伪代码描述为:

使用Lua脚本的调用示意图:

Redis中进行原子化操作有两个方法:Redis事务或Lua脚本。使用Redis事务只能满足数据在未发生变化进行更新而发生变化就放弃更新的场景。对于实时数据的处理场景来说,Redis的事务无法满足根据时间戳进行业务处理的需要。由于Redis执行Lua脚本时是原子化的并且脚本内部可以编写读写判断逻辑,因此可以借助Lua脚本完成实时数据更新的业务需要。

虽然使用Lua脚本可以较好的满足业务需要,但是在使用Redis脚本时也有一定的注意事项,Lua脚本中不要编写太复杂的操作,应该以尽量简单的逻辑完成整个操作过程,避免因为脚本的执行产生阻塞效应。

本文链接:http://www.cnblogs.com/zhu-wj/p/7777762.html

Lua 脚本

EVAL script numkeys key [key ...] arg [arg ...]

Redis Transactions

继续阅读