Redis統合Luaスクリプト実装

23031 ワード

    :zhanhailiang   :2014-12-02

依存関係
1.環境配置
Redisインストール構成チュートリアルおよびphpredis拡張インストールテスト

Redisインストールおよびphp拡張

Windowsにphpredisモジュールをインストールします(現在のWindow環境ではphp_redis.dllは2.1.3ですが、Linuxのredis.soバージョンは2.2.5に達しており、一部のコマンドセットのサポートレベルが異なる可能性があります)

2.Redis指令マニュアル
php-redis中国語ヘルプマニュアルchm(このマニュアルは比較的古いので、Redis Commandsを参照)

Redis Commands

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

3.Lua言語ベース
『Lua 5.1参考マニュアル』雲風訳

LUA TABLE遍歴配列の応用

4.RedisとLuaの通信原理
Lua:Redisユーザーへの入門指導

Redisの新しいブランチ、サービス側luaスクリプトサポートの開発を行う

『Redis 2.6 Luaスクリプト機能実現分析』

Luaスクリプト操作Redisの統合の利点
Redisに行けるのが早い!多くのRedisアプリケーションの使用方法はread-compute-writeモードであり、これにより、単純なデータ計算ではクライアントとサービス側が2回通信する必要があり、中間のcomputeプロセスをサービス側に移行して実行するとround-trip時間を倍増させることができる.
CPUをフル活用!RedisのほとんどのアプリケーションシーンはIO密集型であり、CPU 100%の限界(Redisではマルチコアが使用できない)に達した場合でも、CPUの使用はネットワークプロトコルスタックの処理上で行われることが多いが、サービス側で実行されるスクリプトを使用すれば、RedisサーバのCPUを十分に利用できる.
しかし、最も根本的な原因は、Redisで99%のユーザーのニーズを満たす最も基本的な機能だけを実現し、他のユニークなアプリケーションシーンの1%の機能をカスタムのサービス側実行スクリプトに残して実現できることです.Redisがサービス側スクリプトを導入して冗長になることを恐れている学生にとって、ここを見てほっとすることができます.これは、絶え間ない需要を満たすために提案された究極の解決策だからです.
译文1:Redisの新しいブランチは、サービス側luaスクリプトのサポートの開発を行う
統合Luaスクリプトの弊害(検証対象)
twemproxyをredis svrエージェントとして使用する場合、twemproxyはeval、evalShaを完全にサポートしていないため、一部の互換性の問題を引き起こす可能性があります.

Luaはサーバに追加のオーバーヘッドをもたらす可能性があります.

応用原理
RedisがLuaスクリプトとの通信を実現する方式は,クライアントによってluaスクリプトをコマンドとしてサービス側に伝達し,サービス側がスクリプトを読み,解釈器を呼び出して解釈して実行して戻ることで実現する.
2つのインタフェース実装を提供します.
[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がname 2のデータが格納されています.その格納構造はzsetです.この場合、次の方法でアクセスできます.
127.0.0.1:6379> eval 'return redis.call("zrange", "name2", 0 , -1);' 0
1) "1"

もちろん、上記の例はevalの使い方を説明するために、実際の操作時に直接redis-client zrang name 2 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