php+redis实现排行榜demo

7 篇文章 0 订阅

php+redis实现排行榜demo


本周上班因为任务分配的原因,跑回去写redis去了.本周继续复习redis,感觉对于redis的实践开始有了新的认识.核心依旧是作为缓存,而不是拿关系型数据库来用.扯远了,本周又写了一些模块,其中比较好用的是使用redis来实现排行榜,确实好用,快的多,要比传统上使用mysql,存入到数据库中(当然如果能有效利用缓存也能吧速度提升一个数量级,但是还是不如redisdemo稳定),再利用mysql引擎排序来快的多,缺点是需要独立一个zset来保存排行榜.

有序集合和跳跃表

提到排行榜的数据结构,最开始毫无疑问我是想到当初写的大根堆,这样的二叉树实现的数据结构的堆排序,从而实现排行榜.不过在redis中使用了更为简单的跳跃表 (当然在工程上通常会更复杂一点,这一类算法,通常会在较小的集合上用另一种简单的数据结构代替复杂数据结构)来实现有序集合.通过有序集合(zset)我们可以实现排行榜这样高效排序.

功能实现和分析

通过有序集合保存对应的分数和节点,这是一个非常简单的数据结构.redis的zset提供了众多的接口函数来实现对应的功能.

在这里我有两个实现思路.
- 一个是对于需要所有节点都在排行榜里面的,会将所有的节点都放入zset中
- 只需要特定数量的排行榜.比如我只需要前20件热销商品(或者说这一类需求最集中),这样我只维护一个大小为20的zset,对于最新的分数,会尝试重新插入现有的集合中,并除去最低或者最高的分数.理论上这个算法,在总分数的数量极大之后会更快.

当然在这里我先实现的第一种思路,这种思路比较简单,所有的插入更新都直接使用zset更新.如果以后有新的需求我会尝试使用第二个思路.

在这里只实现两个功能,
- 能够查询每个节点的分数和名次 通过zRevRange函数获取;
- 能够按名次查询排名前N名的节点通过zRevRank函数获取;

代码

这里使用phpredis扩展来实现redis的链接

<?php
namespace Leaderboard;

/**
 * 使用rediszset的的商品排行榜
 * @author yiwang
 *        
 */
class RedisLeaderboard
{

    /**
     *
     * @var object redis client
     */
    private $redis;
    /**
     *
     * @var string 放置排行榜的key
     */
    private $leaderboard;

    /**
     * 构造函数
     * @param object $redis 已连接redis的phpredis的对象
     * @param string $leaderboard 字符串,排行榜的key名
     */
    public function __construct($redis = [], $leaderboard = '')
    {

        if ($redis) {
          $this->redis = $redis;
        } else {
          $this->redis = new \Redis();
          $this->redis->connect('127.0.0.1');
        }

        if ($leaderboard) {
            //这里不会检查当前的key值是否存在,是为了方便重新访问对应的排行榜
            $this->leaderboard = $leaderboard;
        } else {
            $this->leaderboard = 'leaderboard:' . mt(1, 100000);
            while (!empty($this->redis->exists($this->leaderboard))) {
                $this->leaderboard = 'leaderboard:' . mt(1, 100000);
            }
        }

    }
    /**
     * 获取当前的排行榜的key名
     * @return string
     */
    public function getLeaderboard()
    {
        return $this->leaderboard;
    }
    /**
     * 将对应的值填入到排行榜中
     * @param  $node 对应的需要填入的值(比如商品的id)
     * @param number $count 对应的分数,默认值为1
     * @return Long 1 if the element is added. 0 otherwise.
     */
    public function addLeaderboard($node, $count = 1)
    {
        return $this->redis->zAdd($this->leaderboard, $count, $node);
    }
    /**
     * 给出对应的排行榜
     * @param int $number 需要给出排行榜数目
     * @param bool $asc 排序顺序 true为按照高分为第0
     * @param bool $withscores 是否需要分数
     * @param callback $callback 用于处理排行榜的回调函数
     * @return [] 对应排行榜
     */
    public function getLeadboard($number, $asc = true, $withscores = false,$callback = null)
    {
        if ($asc) {
            $nowLeadboard =  $this->redis->zRevRange($this->leaderboard, 0, $number -1, $withscores);//按照高分数顺序排行;
        } else {
            $nowLeadboard =  $this->redis->zRange($this->leaderboard, 0, $number -1, $withscores);//按照低分数顺序排行;
        }


        if ($callback) {
            //使用回调处理
            return $callback($nowLeadboard);
        } else {
            return $nowLeadboard;
        }
    }
    /**
     * 获取给定节点的排名
     * @param string $node 对应的节点的key名
     * @param string $asc 是否按照分数大小正序排名, true的情况下分数越大,排名越高
     * @return 节点排名,根据$asc排序,true的话,第一高分为0,false的话第一低分为0
     */
    public function getNodeRank($node, $asc = true)
    {
        if ($asc) {
            //zRevRank 分数最高的排行为0,所以需要加1位
            return $this->redis->zRevRank($this->leaderboard, $node);
        } else {
            return $this->redis->zRank($this->leaderboard, $node);
        }
    }

}

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值