用redis实现排行榜(并列排行)
(2012-07-05 17:03:51)
标签:
redisranksamescoreit |
分类: web技术相关 |
redis中的sorted set数据结构,几乎就是为这个场景而生的。
public function
setScoreRank($appId, $userId, $exp)
{
$redisHandler =
$this->getGlobalRedis();
// 存储用户id,用户积分映射 hset
$memberKey
= parent::RANK_MEMBER_KEY . ':' . $appId;
// 存储用户积分,用户数映射 hset
$scoreStatKey = parent::SCORE_STAT_KEY . ':' .
$appId;
// 存储用户积分排行 zset
$scoreRankKey = parent::RANK_SCORE_KEY . ':' .
$appId;
$oldExp
= $redisHandler->hGet($memberKey,
$userId);
if ($oldExp === false)
{
$redisHandler->multi(Redis::PIPELINE)
->hSet($memberKey, $userId, $exp)
->hIncrBy($scoreStatKey, $exp, 1)
->exec();
}
else
{
//
旧积分的对应的统计减1
//
新积分对应的统计+1
$redisHandler->multi(Redis::PIPELINE)
->hSet($memberKey, $userId, $exp)
->hIncrBy($scoreStatKey, $exp, 1)
->hIncrBy($scoreStatKey, $oldExp, -1)
->exec();
}
$oldExpStat =
$redisHandler->hGet($scoreStatKey, $oldExp);
// 旧的积分在排行榜中存在,则更新新旧积分排行。不存在的话,需要删除旧的
if ($oldExpStat)
{
$redisHandler->multi(Redis::PIPELINE)
->zAdd($scoreRankKey, $exp, $exp)
->zAdd($scoreRankKey, $oldExp, $oldExp)
->exec();
}
else
{
$redisHandler->multi(Redis::PIPELINE)
->zAdd($scoreRankKey, $exp, $exp)
->zRem($scoreRankKey, $oldExp)
->exec();
}
}
public function
getRank($appId, $userId)
{
$redisHandler =
$this->getGlobalRedis();
$rankMemberKey = parent::RANK_MEMBER_KEY . ':' .
$appId;
$rankKey
= parent::RANK_SCORE_KEY . ':' . $appId;
$userExp
=
$redisHandler->hGet($rankMemberKey, $value);
return
$redisHandler->zRevrank($rankKey, $userExp);
}
假设一个用户积分排行的场景。
有a,b,c三个用户,他们的积分分别是1,2,3,那么通过如下操作,就可以创建一个排行:
redis.zadd my_rank 1 a
redis.zadd my_rank 2
b
redis.zadd my_rank 3
c
通过 redis.zrank my_rank a 就可以取到 a 在排行中排第几。
但是,如果b用户的积分也是1呢,即a,b,c三个用户,的积分分别是1,1,3。
这个时候,用上面的方法,会发现a还是排第一,b依旧排第二。
因为它原生不支持并列排行,zadd时,当field的score相同时,会以添加时间加权。最先添进去的,就排在最前面,因为先添加了a,后添加了b,所以a在b前。
那如何实现并列排行呢,直接上代码:
严格来说,这个应该叫积分排行。
用到了3个key:RANK_MEMBER_KEY(用户-积分的映射,Hashes),SCORE_STAT_KEY(积分
- 拥有该积分的人数映射,Hashes),RANK_SCORE_KEY(积分排行 Sorted
Sets)
先通过RANK_MEMBER_KEY取到用户的积分,然后在RANK_SCORE_KEY去取积分对应的排行。
SCORE_STAT_KEY这个key用作维护RANK_SCORE_KEY。