redis zset做排行榜

  • 引子

直播运营活动中经常会有这样的需求,根据用户送礼情况做排名。这个排行榜具有以下特点:

  1. 用户每次请求会返回用户的排名
  2. 送礼金额越多粉丝排名越靠前
  3. 相同金额送礼越早越靠前
  4. 排行榜会随着粉丝送礼变化而不断变化
  • 排行榜的实现方式
表结构
CREATE TABLE `user` (
  `id` int(10) NOT NULL COMMENT '编号',
  `uid` varchar(32) NOT NULL COMMENT '用户',
  `coin` int(10) NOT NULL COMMENT '用户送出金额',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uid` (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';
1. sql查询
EXPLAIN SELECT
    *
FROM
    (
        SELECT
            @rank := @rank + 1 AS rank,
            s.uid AS uid,
            s.coin AS coin
        FROM
            `user` s,
            (SELECT @rank := 0) r
        ORDER BY
            coin DESC,
            create_time
    ) q
WHERE
    q.uid = 'xiaoming';

根据 select @rank 与 user表结合起来作为一张有排名的新表,然后再从中找出某个用户的排名。这种方法的优点是简单,每次用户来请求,只要用这个SQL查一下即可;缺点是这个算是比较复杂的SQL,查起来太慢,每次都要全表查询,试了几次都要0.5s左右。用explain分析如下:


explain
2. user中添加rank字段

在user中添加rank字段,写个计划任务每隔2分钟全表扫描,然后更新rank名次字段。这个方法最残暴,但是也是最不可取的。首先会产生延迟,因为2分钟才更新一次名次,其次,每次都要更新全部数据,给数据库很大的压力,最后,计划任务更新数据的时候,用户送礼也在更新数据,稍微不注意就会出现脏读的情况。

3. 用Redis的zset数据结构
  • ZSet实现排行榜

zset的相关api (PipelineCluster/Jedis)
  1. 插入或者更新数据
    Long zadd(final String key, final double score, final String member)
    key : 排行榜的名字
    memeber : 用户
    score : 用户的分数
  2. 获取用户分数
    Double zscore(String key, final String member)
  3. 获取用户的排名
    Long zrevrank(final String key, final String member):(score从大到小,从0开始,所以需要加1)
    Long zrank(final String key, final String member):(score从小到大,从0开始,所以需要加1)
  4. 获取某个范围内的用户排名
    Set<Tuple> zrevrangeWithScoresBytes(String key, final long start, final long end) (从大到小)
    Set<Tuple> zrangeWithScoresBytes(String key, final long start, final long end) (从小到大)
    start : 开始排名
    end : 结束排名
    Tuple :
public class Tuple implements Comparable<Tuple> {
    // 用户
    private byte[] element;
    //分数
    private Double score;
}

比如我们想查1-10的排名,我们可以zrevrangeWithScoresBytes(key, 0, 9)

排行榜的实现
  1. 简单
    简单的排行榜就是每次用户信息更新后,把用户uid和用户coin都更新到zset中,这个的好处是比较简单,有一点不好的就是他不能实现先到先得,即先相同金额送礼越早越靠前。
  2. 较复杂(可实现先到先得)
  • 较复杂的zset和简单的不同的是score存的不仅仅是用户的coin,而是用户coin 和时间戳(秒)ts的组合。为了实现先到先得的zset,可设置存进去的score = (coin * 10000000000(十次方)) + (100000000000(十一次方) - ts)
  • 表面上好像是解决了先到先得这个难题,但是实际上这样子还不是最优解,因为存进去的score长度是有限的,据我所测,好像是18位数左右,除掉时间戳10位以后,只能存8位的coin了。这很明显还不够。那该怎么办呢?
  • 我们缩短一下coin或者ts的长度不就OK了吗?首先coin是改不了的,因为这是核心数据,所以能够下手的就只有ts了。ts这个时间戳,其实包括了年月日分时秒,某一段相近时间内,他们的ts前几位都是相同的。比如2018-08-01 00:00:00 的时间戳为1533052800, 2018-09-01 00:00:00 的时间戳为1535731200,相隔一个月的两个时间,他们的前三位都是相同的,所以我们只需要取后面7位参与计算即可。取多少位取决于我们的活动要举办多久。我们根据开始时间和结束时间的时间戳,取出不同部分参与计算。
  • 如果ts被我们压缩到了3位,也就是说我们的coin可以增加三位 11位的coin差不多人民币亿元起,我们欢迎砸钱超过10亿的土豪让我们的程序出现bug。
  • 以下是对coin转score的封装:
    /**
     * 将coin加密成可以存在zset的值,实际上就是 coin * 10000000 + now % 10000000
     * @param coin
     * @return
     */
    public static Double encrypt(Long coin){
        Long value = coin * KEY + (KEY - DateUtil.getInt() % KEY);
        return value.doubleValue();
    }

    /**
     * 将zset的值转成long型的coin
      * @param value
     * @return
     */
    public static Long decrypt(Double value){
        Double coin = value / KEY;
        return coin.longValue();
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容