Redis集成Lua脚本实现

8 篇文章 0 订阅
3 篇文章 0 订阅
  作者:zhanhailiang 日期:2014-12-02

相关依赖

集成Lua脚本操作Redis的优势

能够上Redis更快!很多Redis应用的使用方式是read-compute-write模式,这使得一次简单的数据计算都需要客户端与服务端进行两次通信,而如果把中间的compute过程转移到服务端执行,则可以成倍地减少round-trip时间。
充分利用CPU!Redis的绝大多数应用场景都是IO密集型,即使是到达CPU100%极限(Redis无法使用多核)的,CPU的使用也大多是在网络协议栈的处理上,但如果使用服务端执行的脚本,则可以充分将Redis Server的CPU利用起来。
但最根本的原因在于:这样我们可以只在Redis中实现最基本的能够满足99%用户需求的功能,把其它独特应用场景下的1%的功能留给自定义的服务端执行脚本来实现。对于害怕Redis引入服务端脚本后变得冗余庞大的同学,看到这里可以舒一口气了,因为这就是为了防止满足无休止的需求而提出的终极解决方案。

原文请见:Redis新分支,进行服务端lua脚本支持的开发

集成Lua脚本的弊端(待考证)

  1. 使用twemproxy作为redis svr代理时由于twemproxy对不完全支持eval,evalSha,可能导致部分兼容问题;
  2. Lua可能会给服务器带来额外开销;

应用原理

Redis实现与Lua脚本的通信的方案是通过客户端将lua脚本作为命令传给服务端,服务端读到脚本,调用解释器进行解释后进行执行并返回来实现。

其提供两个接口实现:

[root@~/wade/lua/historyBrowsing]# redis-cli 
127.0.0.1:6379> help eval
 
  EVAL script numkeys key [key ...] arg [arg ...]
  summary: Execute a Lua script server side
  since: 2.6.0
  group: scripting
 
127.0.0.1:6379> help evalsha
 
  EVALSHA sha1 numkeys key [key ...] arg [arg ...]
  summary: Execute a Lua script server side
  since: 2.6.0
  group: scripting

接下来通过一个简单的Demo实现来解释下其应用场景:

在redis服务器上存储了一个key为name2的数据,其存储结构为zset,此时我们可以通过如下方法访问:

127.0.0.1:6379> eval 'return redis.call("zrange", "name2", 0 , -1);' 0
1) "1"

当然以上示例只是为了说明eval的用法,实际操作时直接redis-client zrang name2 0 -1即可。

但是如果我要求可以动态读取key来获取对应的值,此时就需要使用lua脚本来实现对应的功能。

以下托管于github.com的代码实现了浏览历史添加功能,有兴趣的同学可以学习下,不必在意其中具体的业务逻辑,只需关心redis是如何与lua通信的即可:

<?php
// vim: set expandtab cindent tabstop=4 shiftwidth=4 fdm=marker:
 
/**
 * @version  1.0
 * @author   wade
 * @date     2014-12-01 22:56:38
 * @desc     redis集成lua脚本功能实现浏览历史功能
 */
$redis = new Redis();
$redis->connect('127.0.0.1', '6379');
// $redis->set('test', 'Hello World');
// echo $redis->get('test');
$luaScript = file_get_contents('./addItemToBrowsingHistory.lua');
$ret = $redis->eval($luaScript, array(4028, 'brand_browsing_10010', 1417498107, 'brand_expired_10010', 1417498107, 1417498107, 3, 1417457308));
var_export($ret);
$err = $redis->getLastError(); 
var_export($err);
-- $productId, $browsingKey, $browsingTime, $expiredKey, $expiredTime, $delTime, $maxHistoryLength, $now
-- ARGV[1], ARGV[2], ARGV[3], ARGV[4], , ARGV[5], ARGV[6], ARGV[7], ARGV[8]
-- 4011, brand_browsing_10010, 1417498107, brand_expired_10010, 1417498107, 1417498107, 3, 1417440107
 
local productId = ARGV[1];
local browsingKey = ARGV[2];
local browsingTime = ARGV[3];
local expiredKey = ARGV[4];
local expiredTime = ARGV[5];
local delTime = ARGV[6];
local maxHistoryLength = ARGV[7];
local now = ARGV[8];
local num;
local tProductId;
local index;
 
redis.call('zAdd', browsingKey, browsingTime, productId);
redis.call('expire', browsingKey, delTime);
 
redis.call('zAdd', expiredKey, expiredTime, productId);
redis.call('expire', expiredKey, delTime);
 
local count = redis.call('ZCARD', expiredKey);
local balance = count - maxHistoryLength;
if balance > 0 then
    -- 获取过期列表
    -- NMB by score
    local expiredList = redis.call('zRangeByScore', expiredKey, 0, now);
    if expiredList ~= false and #expiredList ~= 0 then
        -- 有商品过期 删除
        num = redis.call('zDeleteRangeByScore', expiredKey, 0, now);
        balance = balance - num;
        for tProductId in ipairs(expiredList) do
            redis.call('ZREM', browsingKey, tProductId);
        end
    end
 
    if balance > 0 then
        -- 否则判断浏览历史列表
        -- 删了浏览历史最后一下
        local expiredList2 = redis.call('zRange', browsingKey, 0, balance - 1);
        num = redis.call('zRemRangeByRank', browsingKey, 0, balance - 1);
 
        -- NMB by index
        for index=1, #expiredList2 do
            redis.call('ZREM', expiredKey, expiredList2[index]);
        end
    end
end
 
return 0;

源码请见:https://github.com/billfeller/historyBrowsing

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值