redis实现博客排行榜功能
redis介绍
redis是一种高效存取的key-value存取系统, 实现了丰富的数据类型用于不同的应用场景并且支持高效的读取操作.
数据结构
String类型: 信息缓存、计数器、分布式锁
list类型: 消息队列 朋友圈的点赞列表、评论列表、排行榜
hash: 购物车 存储对象
set: 收藏夹
zset: 实时排行榜
zset的实现
zset 在内容数量大于64的时候使用了hash和skiplist两种数据结构来存储, 实现了O(log(n))的增删, O(1)的查找
跳表是一种利用空间换时间的数据结构, 通过建立多级索引加速查找;
关于增删的索引重建 使用了随机晋升的思想--randomLevel() 方法,该方法会随机生成 1~MAX_LEVEL 之间的数, 通过概率算法告诉我们这个元素需要插入到几级索引中.
(https://www.jianshu.com/p/9d8296562806, https://redisbook.readthedocs.io/en/latest/internal-datastruct/skiplist.html)
(一些面试题 https://www.cnblogs.com/shoshana-kong/p/14832434.html )
spring项目中集成redis
redis常用的客户端: Jedis Lettuce(基于Netty 线程安全的)
- 项目加入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>2.0</version>
</dependency>
- 配置redis
##redis配置 Lettuce 和 Jedis 的都是连接 Redis Server的客户端。
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.lettuce.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout= 1000ms
- 自定义redis template
package com.william.blog.config;
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
RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
- 创建redis template操作类
package com.william.blog.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import reactor.util.annotation.Nullable;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key, long time, TimeUnit t) {
try {
if (time > 0) {
redisTemplate.expire(key, time, t);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
}
}
}
/**
* 有序集合添加之前没有的元素
*/
public boolean zAdd(String key,Object value,double score) {
return redisTemplate.opsForZSet().add(key, value,score);
}
/**
* 获取集合中元素的排名(从大到小排序)
* @param key
* @param value
* @return
*/
public long zGetRank(String key,Object value) {
return redisTemplate.opsForZSet().reverseRank(key, value);
}
/**
* 若集合中已有此元素,则此元素score+传入参数
* 若没有此元素,则创建元素。
* @param key
* @param value
* @param score
*/
public void zIncreamentScore(String key,Object value,double score) {
redisTemplate.opsForZSet().incrementScore(key, value, score);
}
/**
* 对集合按照分数从小到大排序(默认)
* 指定位置区间0,-1指排序所有元素
* 得到的值带有score
* @param key
* @return
*/
public Set<ZSetOperations.TypedTuple<Object>> zRangeWithScore(String key) {
return redisTemplate.opsForZSet().rangeWithScores(key, 0, -1);
}
/**
* 对集合按照分数从大到小排序
* @param key
* @return
*/
public Set<ZSetOperations.TypedTuple<Object>> zReverseRangeWithScore(String key){
return redisTemplate.opsForZSet().reverseRangeWithScores(key, 0, -1);
}
/**
* 获取有序集合的大小
* @param key
* @return
*/
public Long zGetSize(String key) {
return redisTemplate.opsForZSet().size(key);
}
/**
* 获取key集合里面,value值的分数
* @param key
* @param value
* @return
*/
public double zGetScoreByValue(String key,Object value) {
return redisTemplate.opsForZSet().score(key, value);
}
/**
* 指定分数区间,从大到小排序
* @param key
* @param start
* @param end
* @return
*/
public Set<ZSetOperations.TypedTuple<Object>> zReverseRangeByScoreWithScores(String key, double start, double end){
return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, start, end);
}
}
实现周排行榜功能(博客)
使用的排行榜一天刷新一次, 并且每周都会清零, 同时每次都会提供当周点击量前6的文章
每次获取系统的天数作为redis的key值 从而实现一周一刷.
- 用户点击 增加访问量 同时设置key的ttl
long hour=System.currentTimeMillis()/(1000*60*60*24);
redisUtil.zIncreamentScore(String.valueOf(hour), articleInfo.getId(),1);
redisUtil.expire(String.valueOf(hour), 40, TimeUnit.DAYS);
- 查询热点文章
long hour=System.currentTimeMillis()/(1000*60*60*24);
Set<ZSetOperations.TypedTuple<Object>> res= redisUtil.zReverseRangeWithScore(String.valueOf(hour));
前端展示:
前端使用了layui的timeline组件
本文作者:wwilliam
本文链接:https://www.cnblogs.com/wjwilliam/p/15885065.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步