Redian新闻
>
Redis实战 | 使用Redis 的有序集合(Sorted Set)实现排行榜功能,和Spring Boot集成

Redis实战 | 使用Redis 的有序集合(Sorted Set)实现排行榜功能,和Spring Boot集成

公众号新闻

    Redis 的有序集合(Sorted Set)是一个基于分数(score)排序的数据结构,它在 Redis 中非常重要,常用于实现排行榜、近似计数器等功能。


    Redis 的有序集合(Sorted Set)是基于跳跃表(Skip List)实现的。跳跃表是一种高效的数据结构,其插入、删除和查找操作的平均时间复杂度都是 O(log n),相对于平衡树(如红黑树)的实现要简单很多。跳跃表的结构类似于链表,每个节点除了保存元素值外,还包含一个指针数组,分别指向对应层次的下一个节点。这种多级指针的设计,使得跳表可以跨越多个节点进行快速搜索,同时保证跳表结构的高效性和简洁性。

    有序集合的底层数据结构由哈希(Hash)和跳跃表组成。在哈希中,存储了元素及其关联的评分(分数)。每个元素都有一个唯一的评分,用于确定其在跳跃表中的位置。当需要对有序集合进行操作时,Redis 首先通过哈希表找到元素及其评分,然后通过跳跃表进行相应的操作。

以下是 Redis 有序集合(Sorted Set)的一些核心操作及其对应的核心代码分析:


添加元素(ZADD):

    有序集合中的元素添加操作是通过哈希表和跳跃表协同完成的。首先,Redis 将元素值和评分存储在哈希表中。然后,根据评分在跳跃表中找到对应的位置,并将新元素插入到该位置。

获取元素(ZRANGE、ZREVRANGE):

    有序集合中的获取元素操作主要依赖于跳跃表。ZRANGE 操作从跳跃表的头部开始,按照给定的评分范围返回符合条件的元素。ZREVRANGE 操作则从跳跃表的尾部开始,按照给定的评分范围返回符合条件的元素。

删除元素(ZREM):

    删除元素操作首先通过哈希表找到对应元素,然后在跳跃表中删除该元素。Redis 只需要删除哈希表中的指向该元素的指针,跳跃表中的元素会自动上移。

更新元素评分(ZINCRBY):

    更新元素评分操作仅需修改哈希表中对应元素的评分,然后重新计算跳跃表中元素的位置。

获取有序集合长度(ZCARD):

有序集合长度的操作直接查询哈希表中的键值对数量。

随机获取元素(ZRANDMEMBER):

    随机获取元素操作首先从哈希表中随机选择一个元素,然后在该元素所在的跳跃表区间内随机选择一个元素。

通过以上操作,Redis 实现了高效有序集合(Sorted Set)的数据结构,提供了高性能的排序和范围查找功能。

2、实战

要使用 Spring Boot 和 Redis 实现排行榜功能,你可以遵循以下步骤:


引入依赖

在你的 Spring Boot 项目的 pom.xml 文件中,添加以下依赖:

<dependencies>      <dependency>          <groupId>org.springframework.boot</groupId>          <artifactId>spring-boot-starter-data-redis</artifactId>      </dependency>  </dependencies> 

 

配置 Redis

    在 application.properties 或 application.yml 文件中配置 Redis 连接信息:

# application.properties  spring.redis.host=localhost  spring.redis.port=6379  
# application.yml  spring:    redis:      host: localhost      port: 6379  

创建 Redis 模板

创建一个 RedisTemplate Bean:

import org.springframework.context.annotation.Bean;  import org.springframework.context.annotation.Configuration;  import org.springframework.data.redis.connection.RedisConnectionFactory;  import org.springframework.data.redis.core.RedisTemplate;  import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;  import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration  public class RedisConfig {    @Bean      public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {          RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();          redisTemplate.setConnectionFactory(redisConnectionFactory);          redisTemplate.setKeySerializer(new StringRedisSerializer());          redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());          redisTemplate.setHashKeySerializer(new StringRedisSerializer());          redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());          return redisTemplate;      }  }

创建排行榜实体类

创建一个排行榜实体类,包含用户 ID、分数等信息:

import java.io.Serializable;public class RankingEntity implements Serializable {    private String userId;      private double score;    // 构造方法、getter 和 setter 

 


实现 Redis 排行榜操作

创建一个服务类,实现排行榜的相关操作,如添加分数、查询排名等:

import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.data.redis.core.RedisTemplate;  import org.springframework.data.redis.core.ValueOperations;  import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;@Service  public class RankingService {    @Autowired      private RedisTemplate<String, Object> redisTemplate;    private static final String RANKING_KEY = "ranking_list";    /**       * 添加分数       * @param userId 用户 ID       * @param score 分数       */      public void addScore(String userId, double score) {          ValueOperations<String, RankingEntity> valueOperations = redisTemplate.opsForValue();          valueOperations.set(RANKING_KEY + ":" + userId, score, 60, TimeUnit.SECONDS);      }    /**       * 查询排名       * @return 排名列表       */      public List<Object> getRankingList() {          List<Object> rankingList = redisTemplate.opsForList().range(RANKING_KEY, 0, -1);          return rankingList;      }    /**       * 查询用户排名       * @param userId 用户 ID       * @return 用户排名       */      public Object getUserRanking(String userId) {          return redisTemplate.opsForValue().get(RANKING_KEY + ":" + userId);      }     * @param userId 用户 ID       */      public void deleteUserScore(String userId) {          ValueOperations<String, RankingEntity> valueOperations = redisTemplate.opsForValue();          valueOperations.delete(RANKING_KEY + ":" + userId);      }    /**       * 更新用户分数       * @param userId 用户 ID       * @param score 新的分数       */      public void updateUserScore(String userId, double score) {          ValueOperations<String, RankingEntity> valueOperations = redisTemplate.opsForValue();          valueOperations.set(RANKING_KEY + ":" + userId, score, 60, TimeUnit.SECONDS);      }    /**       * 获取用户排名列表的长度       * @return 用户排名列表的长度       */      public long getUserRankingListSize() {          return redisTemplate.opsForList().size(RANKING_KEY);      }    /**       * 在用户排名列表中插入用户分数       * @param userId 用户 ID       * @param score 分数       * @param index 插入位置,0 表示插入到列表头部,负数表示插入到列表尾部       */      public void insertUserScore(String userId, double score, long index) {          List<Object> rankingList = redisTemplate.opsForList().range(RANKING_KEY, 0, -1);          redisTemplate.opsForList().leftPush(RANKING_KEY, score, index);      }    /**       * 在用户排名列表中删除用户分数       * @param userId 用户 ID       * @param index 删除位置,0 表示删除第一个元素,1 表示删除第二个元素,依此类推       */      public void deleteUserScore(String userId, long index) {          List<Object> rankingList = redisTemplate.opsForList().range(RANKING_KEY, 0, -1);          redisTemplate.opsForList().rightPop(RANKING_KEY, index);      }    /**       * 获取用户排名列表中的最后一个元素       * @return 用户排名列表中的最后一个元素       */      public Object getLastUserScore() {          return redisTemplate.opsForList().rightPop(RANKING_KEY);      }    /**       * 获取用户排名列表中的第一个元素       * @return 用户排名列表中的第一个元素       */      public Object getFirstUserScore() {          return redisTemplate.opsForList().leftPop(RANKING_KEY);      }    /**       * 在用户排名列表中添加元素       * @param score 添加的分数       */      public void addUserScore(double score) {          redisTemplate.opsForList().rightPush(RANKING_KEY, score);      }    /**       * 在用户排名列表中删除元素       * @param index 删除位置,0 表示删除第一个元素,1 表示删除第二个元素,依此类推       */      public void deleteUserScore(long index) {          redisTemplate.opsForList().rightPop(RANKING_KEY, index);      }    /**       * 获取用户排名列表       * @return 用户排名列表       */      public List<Object> getUserRankingList() {          return redisTemplate.opsForList().range(RANKING_KEY, 0, -1);      }    /**       * 设置用户排名列表的长度       * @param length 用户排名列表的新长度       */      public void setUserRankingListLength(long length) {          redisTemplate.opsForList().setSize(RANKING_KEY, length);      }    /**       * 获取用户排名       *       * @param userId 用户 ID       * @return 用户排名,如果用户不存在,返回 -1       */      public int getUserRanking(String userId) {          List<Object> rankingList = getRankingList();          Object userScore = getUserRanking(userId);          if (userScore == null) {              return -1;          }                     int rank = 0;            for (Object score : rankingList) {                if (score.equals(userScore)) {                    break;                }                rank++;            }            return rank;        }    /**       * 获取用户排名列表中的指定位置的元素       *       * @param index 指定位置,从 0 开始       * @return 用户排名列表中的指定位置的元素       */      public Object getUserRankingListElement(long index) {          return redisTemplate.opsForList().range(RANKING_KEY, 0, -1).get(index);      }    /**       * 获取用户排名列表中的用户分数       *       * @param userId 用户 ID       * @return 用户排名列表中的用户分数,如果用户不存在,返回 null       */      public Object getUserRanking(String userId) {          ValueOperations<String, RankingEntity> valueOperations = redisTemplate.opsForValue();          return valueOperations.get(RANKING_KEY + ":" + userId);      }    /**       * 是否存在用户       *       * @param userId 用户 ID       * @return 是否存在用户       */      public boolean existsUser(String userId) {          ValueOperations<String, RankingEntity> valueOperations = redisTemplate.opsForValue();          return valueOperations.hasKey(RANKING_KEY + ":" + userId);      }    /**       * 清除所有用户排名数据       */      public void clearAllUserRankingData() {          redisTemplate.delete(RANKING_KEY);      }  }

好了,今天的小知识你学会了吗?


链接:https://python-basketball.blog.csdn.net/article/details/134236814

(版权归原作者所有,侵删)


微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
Redis 和 SpringBoot 的绝佳组合:Lua 脚本的黑科技!11月必看!“新世界三大男高音”Juan Diego Flórez首次亮相澳洲!英语句子什么时候用to do something,什么时候用doing something?Rains Revive Poyang Lake, Offering Hope After Record Dry Season【JetBlue收购Spirit被判违反反垄断法】Frontier Airlines和Spirit Airlines宣布将合并!How a Red-Haired Chatbot Became China’s Favorite English TutorChatbots, Self-Care, Alter Egos: What Young Chinese Want in 2024《湖天一览楼》1部6章(1.1)高邮光复(上)SpringBoot AOP + Redis 延时双删功能实战Spring Boot实战 之 MongoDB分片或复制集操作Java近期新闻:Spring Framework 6.1、Spring Data 2023.1、Payara Platform中国航天承认自己大而不强,和SpaceX有差距,主要体现在四方面Bose产品上新,发布Bose QuietComfort系列新品Citing Safety, Beijing Bans Unregistered Electric ScootersNetty+SpringBoot 打造一个 TCP 长连接通讯方案普契尼艺术节After Delayed Winter, China’s Ski Resorts Swing into High Gear部分旧版Chase Freedom用户被强制“暖心升级”成Chase Freedom UnlimitedOld Markets, New Appeal: Young Chinese Rediscover Wet Markets在RTX 4090被限制的时代下,让大模型使用RLHF更高效的方法来了昨天顺手给人治了一个腰病忽视日志可要吃大亏,手把手教你玩转 SpringBoot 日志Kubernetes 实战:使用 k8s+jenkins 实现 CICDSpring Boot虚拟线程的性能还不如Webflux?悬赏十几万元以用Rust重写PrettierSpringBoot 采用 JsonSerializer 和 Aop 实现可控制的数据脱敏450刀BenQ GV1 Smart Portable Projector with Bluetooth SpeakerNature Metabolism | 基础医学院陈建国教授团队发现内源性氨的有益新功能In Northeast China, Tourists From the South Spark a Winter BoomRedis实现分页+多条件模糊查询组合方案!解忧咖啡馆如何用Kubernetes实战快速搭建typecho个人博客?忽视日志吃大亏,手把手教你玩转 SpringBoot 日志\'我不要伤亡数字,我只要塔山\' 林彪 vs Elon Musk assholeSpringBoot 实现动态切换数据源,这样做才更优雅!
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。