list redis 怎样做排行_Redis的Sorted-Sets排行榜功能实现

Redis的ZSet排行榜功能实现

1. 功能需求

类似给用户n张图片, 用户左滑不喜欢右滑喜欢。所以每个用户就会有一些喜欢的图片集合和不喜欢的图片集合。现在我们要做一个将按照一个算法将喜欢的排到前面。算法 ctr = (喜欢数+20)/ (喜欢数+不喜欢数+20),所有的内容按照这个算法的结果进行排行榜排序。

2. Redis sorts sets简介

Sorted-Sets和Sets类型极为相似,它们都是字符串的集合,都不允许重复的成员出现在一个Set中。它们之间的主要差别是Sorted-Sets中的每一个成员都会有一个分数(score)与之关联,Redis正是通过分数来为集合中的成员进行从小到大的排序。然而需要额外指出的是,尽管Sorted-Sets中的成员必须是唯一的,但是分数(score)却是可以重复的。

Sorted Sets是通过Skip List(跳跃表)和hash Table(哈希表)的双端口数据结构实现的,因此每次添加元素时,Redis都会执行O(log(N))操作。所以当我们要求排序的时候,Redis根本不需要做任何工作了,早已经全部排好序了。元素的分数可以随时更新。

3. 代码实现

本文主要通过redisTemplate来操作redis,当然也可以使用redis-client,看个人喜好。

首先写两个要用到的两个方法, 一个批量插入数据,一个获取排行榜Top n。

/**

* @Description: 批量添加zset数据

* @author mazhq

*/

public Long setBatchZSet(String key, Set> typedTuples) {

try {

return redisTemplate.opsForZSet().add(key, typedTuples);

} catch (Exception e) {

logger.error("redis setZSet failed, key = " + key + "| error:" + e.getMessage(), e);

return 0L;

}

}

/**

* @Description: 获取排行前面的数据

* @author mazhq

*/

public List getTopRankZSet(String key, int topCount) {

try {

Set range = redisTemplate.opsForZSet().reverseRange(key, 0, topCount);

return Arrays.asList(range.toArray());

} catch (Exception e) {

logger.error("redis getTopRankZSet failed, key = " + key + "| error:" + e.getMessage(), e);

return new ArrayList<>();

}

}

插入排行榜数据

/**

* @Description: 批量添加图片排行榜数据

* @author mazhq

*/

public void batchAddImageData(){

//获取喜欢和不喜欢的map数据 key是图片ID

Map map = userBehaviorRecordManager.getQuickImageStatistic();

//获取所有图片列表

List imageConfigBeanList = quickImageConfigManager.getRealAllList();

Set> tuples = new HashSet<>();

for (ImageConfigBean imageConfigResp : imageConfigBeanList) {

String likeKey = imageConfigResp.getGuid() + QuickConstant.LIKE;

String unLikeKey = imageConfigResp.getGuid() + QuickConstant.UNLIKE;

//ctr算法 以1000为统计精确维度 即精确到小数点后三位

double ctr = 1000d;

if (map.containsKey(likeKey) && map.containsKey(unLikeKey)) {

double total = (map.get(likeKey)).doubleValue() + map.get(unLikeKey).doubleValue() + 20d;

double ctrStatistic = (map.get(likeKey).doubleValue() + 20d) / total;

ctr = (double) Math.round(ctrStatistic * 1000);

}else if(!map.containsKey(likeKey) && map.containsKey(unLikeKey)){

double total = map.get(unLikeKey).doubleValue() + 20d;

double ctrStatistic = 20d / total;

ctr = (double) Math.round(ctrStatistic * 1000);

}

DefaultTypedTuple tuple = new DefaultTypedTuple<>(imageConfigResp.getGuid() + "", ctr);

tuples.add(tuple);

}

redisClient.setBatchZSet(RedisKeysManager.getMiniProgramSlideRankingKey(), tuples);

}

获取Top50排行榜数

/**

* @Description: 获取排行榜top50条记录

* @author mazhq

*/

@RequestMapping("/getTop50")

public String getTop50() {

List stringList = redisClient.getTopRankWithScoresZSet(RedisKeysManager.getMiniProgramSlideRankingKey(), 50);

return JSONObject.toJSONString(stringList);

}

其它集合操作方法

//单个增加集合内容

public boolean setSortedSet(String key, double score, Object value) {

try {

return redisTemplate.opsForZSet().add(key, value, score);

} catch (Exception e) {

logger.error("redis setSortedSet failed, key = " + key + "| error:" + e.getMessage(), e);

return false;

}

}

//单个增加分数

public double incrementScore(String key, double score, Object value) {

try {

return redisTemplate.opsForZSet().incrementScore(key, value, score);

} catch (Exception e) {

logger.error("redis incrementScore failed, key = " + key + "| error:" + e.getMessage(), e);

return 0.0;

}

}

//单个删除

public boolean delSortedSet(String key, Object... values) {

try {

long count = redisTemplate.opsForZSet().remove(key, values);

return count > 0;

} catch (Exception e) {

logger.error("redis delSortedSet failed, key = " + key + "| error:" + e.getMessage(), e);

return false;

}

}

4. 总结

新增or更新

//单个新增or更新

Boolean add(K key, V value, double score);

//批量新增or更新

Long add(K key, Set> tuples);

//使用加法操作分数

Double incrementScore(K key, V value, double delta);

删除

//通过key/value删除

Long remove(K key, Object... values);

//通过排名区间删除

Long removeRange(K key, long start, long end);

//通过分数区间删除

Long removeRangeByScore(K key, double min, double max);

查寻

//通过排名区间获取列表值集合

Set range(K key, long start, long end);

//通过排名区间获取列表值和分数集合

Set> rangeWithScores(K key, long start, long end);

//通过分数区间获取列表值集合

Set rangeByScore(K key, double min, double max);

//通过分数区间获取列表值和分数集合

Set> rangeByScoreWithScores(K key, double min, double max);

//通过Range对象删选再获取集合排行

Set rangeByLex(K key, Range range);

//通过Range对象删选再获取limit数量的集合排行

Set rangeByLex(K key, Range range, Limit limit);

//获取个人排行

Long rank(K key, Object o);

//获取个人分数

Double score(K key, Object o);

统计

//统计分数区间的人数

Long count(K key, double min, double max);

//统计集合基数

Long zCard(K key);

基本整理了排行榜用到的所有方法,排行榜有这一篇文章够用了。同时大家注意当redis缓存被清空,如何重新计算排行榜相关数据,或者安排定时排行榜数据定时落地逻辑。

避免redis缓存出现问题导致系统瘫痪。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值