PHP封装Redis Lua脚本

class RedisLuaScript {

    protected $_redis;

    /**
     * 是否支持lua脚本
     * @var bool
     */
    protected $_support_lua_script = true;

    /**
     * 脚本列表
     * @var array
     */
    protected $_func_table = [
        // 先获取榜首信息再加积分
        'zincrby_get_top' => [
            'sha1' => '66448d0c16571ae1e5039d243174382377435aa5',
            'script' => "local top1 = redis.call('zRevRange',KEYS[1],0,0,'WITHSCORES'); local score = redis.call('zincrby',KEYS[1],ARGV[1],ARGV[2]); return {top1, score}"
        ],
    ];

    /**
     * redis geo连接
     * @date 2021/9/29
     */
    public function connect()
    {
        try {
            $this->_redis = new \Redis();

            $this->_redis->pconnect('127.0.0.1', 6379);

            if ($this->_redis->ping() != '+PONG') {
                throw new \Exception(__METHOD__ . ' PONG 失败');
            }
        }catch (Exception $e){
            echo "redis链接失败 ". $e->getMessage();
            exit();
        }
    }

    /**
     * 拓展zincrby_get_top指令,可以原子性的加积分前先获取榜首信息
     *
     * @date 2021/9/27
     * @param $key
     * @param $increment
     * @param $member
     * @return array|bool
     * @throws \Exception
     */
    public function zincrby_get_top($key, $increment, $member)
    {
        $this->connect(); //链接redis
        $redis_cmd = __FUNCTION__;
        try {
            if ($this->_support_lua_script) {
                $result = $this->eval_script($redis_cmd, $key, [$increment,$member]);
                /**
                 * 因为zRevRange 返回的是 [key=>value]
                 * lua返回的zRevRange是原始的redis结构 [0=>key,1=>value]
                 * 为了跟php的zRevRange返回值保持一致所以在此重新组装top1
                 */
//                $top1 = $result[0];
//                $len = count($top1);
//                if($len >1){
//                    for ($i=0;$i<$len;$i+=2){
//                        $val[$top1[$i]] = $top1[$i+1];
//                    }
//                    $result[0] = $val;
//                }
                //简化写法
                if(!empty($result[0])){
                    $result[0] = [$result[0][0] => $result[0][1]];
                }
            }
            else {
                $top1= $this->_redis->zRevRange($key, 0, 0, true);
                $score = $this->_redis->zincrby($key, $increment, $member);
                $result = [$top1,$score];
            }
        } catch (\Exception $e) {
            $result = false;
        }
        return $result;
    }

    /**
     * @param string $script_Key
     * @param array|string $keys 键名
     * @param array|string $args 参数
     *
     * @return bool|mixed
     * @throws \Exception
     *
     * @example
     * obj->eval_script('incr', 'test', 100); // incr, key, 过期时间
     * 增量计数器,超过最大值就重置为0
     * obj->eval_script('incr_reset', 'test', [max_counter, 100]); // incr_reset, key, [最大值, 过期时间]
     * 增量计数器,如果当前值没有大于限定值,才可以加一并返回[1, 累加后的值],否则返回[0, 当前值]
     * obj->eval_script('incr_max', 'test', [max_counter, 100]); // incr_max, key, [最大值, 过期时间]
     *
     * @warning TODO 不能在multi中使用,否则脚本无法执行load,导致命令无效
     * @warning TODO 只在单机节点和客户端实现集群调用策略上验证过,云redis暂未测试过
     */
    public function eval_script($script_Key, $keys, $args=[]) {
        if(!isset($this->_func_table[$script_Key]) || empty($this->_func_table[$script_Key]['script'])) {
            // 开发中解决错误
            throw new \Exception(__METHOD__ . ' 请先配置'.$script_Key.'脚本');
        }
        if(empty($this->_func_table[$script_Key]['sha1'])) {
            $this->_func_table[$script_Key]['sha1'] = sha1($this->_func_table[$script_Key]['script']);
        }
        $this->connect();
        $sha1 = $this->_func_table[$script_Key]['sha1'];

        try {
            if(!is_array($keys)) {
                $keys = [$keys];
            }
            else {
                // 不需要键名索引,用数字重新建立索引
                $keys = array_values($keys);
            }
            if(!is_array($args)) {
                $args = [$args];
            }
            else {
                // 不需要键名索引,用数字重新建立索引
                $args = array_values($args);
            }
            $key_count = count($keys);
            $args = array_merge($keys, $args);
            for($i =0; $i < 2; $i++) {
                $result = $this->_redis->evalSha($sha1, $args, $key_count);
                if($result === false && $i === 0) {
                    $errorMsg = $this->_redis->getLastError();
                    $this->_redis->clearLastError();
                    // 该脚本不存在该节点上,需要执行load
                    if(stripos($errorMsg, 'NOSCRIPT') !== false) {
                        $loadParams = [
                            'load',
                            $this->_func_table[$script_Key]['script']
                        ];
                        // load脚本
                        $serverSha1 = $this->_redis->script(...$loadParams);
                        // 在开发阶段解决这个错误
                        if($serverSha1 !== $sha1) {
                            throw new \Exception($script_Key.'脚本的sha1与服务端返回的sha1不一致'.$sha1.'=='.$serverSha1, 999);
                        }
                        continue;
                    }
                }
                break;
            }
        } catch (\Exception $e) {
            // 如果是开发阶段能解决的错误,就抛出去
            if($e->getCode() === 999) {
                throw new \Exception($e->getMessage(), $e->getCode());
            }
            $result = false;
        }
        return $result;
    }

}

$redis = new RedisLuaScript();

$res = $redis->zincrby_get_top('ranking_girl_list',10,123);
var_dump($res);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值