Redis-Realize the ranking function through SortedSet (solve ranking in chronological order under the same score)

background

The 618 event needs to design a user ranking function. Considering that redis has a SortedSet data structure (implemented by a jump table + dictionary), it is more suitable to implement a ranking.

Pit

The demand scenario is,If two users have the same order quantity, the user who arrives at that order quantity first will be ranked first.
The question I first considered at the beginning was:
In SortedSet, if the scores are the same, how are they sorted?
Later, I learned that if the score is the same, it is in the lexicographic order of the member, that is, a is ranked in front of b, and 1 is ranked in front of 2.

Then can I add the timestamp to the member of the SortedSet, such as timestamp_user id. In order to make the timestamp smaller in the front, we can change the design of memebr to (Long.MAX_VALUE-timestamp)_user id.
After this, I feel that the problem has been solved.When the timestamp is smaller, the corresponding member dictionary order will be larger and ranked first.

As everyone knows, we know that the elements in SortedSet are unique (that is, the members in SortSet are unique), and in our design, the timestamp will change with the order. If you add an element to SortedSet, there will be two duplicates.
As shown in the figure:

Insert picture description here


Two pieces of data for the same user will appear.
There are two problems here:
1. When modifying the data, you need to delete the original member and add it again.
2. Because the timestamp will change, you need to record the mapping relationship between uid and member.

There are many operations for redis, although it can be achieved, but it is very circumstantial. It is not friendly to redis and is not conducive to supporting the leaderboards with large amounts of data.

solution

In order to deal with the ranking problem of users with the same score, you can putThe timestamp is taken into account in the score. Specific ideas:

1. Suppose the order quantity of user a is 40 orders. The time stamp of the last order is: 1655567999
2. Define a base time, which can be 2050 (2539180799) or 2100.
3. Add (base time-order time)/base time to the order quantity. (基准时间 - 下单时间)/ 基准时间Must be less than 1.

In the design of score:

    /**
     * 计算score,通过一个基准时间,可以是2100或2050年,减去lastOrderTime再除以基准时间,可以获得一个小于1的小数,
     * 在获取真正score的时候,只要舍去小数位即可
     * @param orderNum
     * @param lastOrderTime
     * @return
     */
    private double getOrderNum(int orderNum, long lastOrderTime) {
        return orderNum + (BASE_TIME - lastOrderTime) * 1.0 / BASE_TIME;
    }

4. When actually taking the score, just take the integer digits.

Code example

    /**
     * 更新排行榜数据
     * @param ownerUid
     * @param lastOrderTime
     * @param orderNum
     */
    private void doUpdateCommunityRankingList(Long ownerUid, long lastOrderTime, int orderNum) {
        // 插入排行榜信息,对于zset,如果已经包含member,add的时候返回就是false
        Boolean success = redisTemplate.opsForZSet().add(OBM_INVITE_WHITE_RANKING_LIST, ownerUid.toString(),
                getOrderNum(orderNum, lastOrderTime));
    }

    /**
     * 计算score,通过一个基准时间,可以是2100或2050年,减去lastOrderTime再除以基准时间,可以获得一个小于1的小数,
     * 在获取真正score的时候,只要舍去小数位即可
     * @param orderNum
     * @param lastOrderTime
     * @return
     */
    private double getOrderNum(int orderNum, long lastOrderTime) {
        return orderNum + (BASE_TIME - lastOrderTime) * 1.0 / BASE_TIME;
    }

When the value is taken out, it is rounded by strong conversion:

  long value =(long) t.getScore().doubleValue();