目录
序言
Redis客户端选型
Redis配置
Redis实现排行榜
Redis实现延迟队列
Redis LRU(Least Recently Used)使用
Redis实现消息已读未读
总结
在之前的开发中,我使用redis只用来实现分布式锁和对常用方法的查询数据缓存,再就是对登录验证码的一个缓存。数据类型也只用到了String(五种基本数据类型:String、List、Hash、Set、ZSet),这篇文章主要写怎么用Redis实现排行榜功能。
了解过的小伙伴应该知道,我前面一篇文章也提到过Redis的三个客户端,它们各有各的优劣,下面对比一下这几种客户端:
本篇文章我选用Redisson客户端来使用
1、添加依赖
org.redisson
redisson-spring-boot-starter
3.13.6
org.redisson
redisson-spring-data-23
org.redisson
redisson-spring-data-21
3.13.6
org.springframework.boot
spring-boot-starter-data-redis
2、在application.yml文件加上配置,我们知道redis有四种工作模式——单点(single)、主从、哨兵(sentinel)、集群(cluster),本机单机测试,采用的是单点模式,如下:
###### redis ######
redis:
clients:
default:
mode: single
address: redis://${redission.basicHost}:${redission.basicPort}
password: ${redission.baiscPwd}
3、在application-local.yml文件上加上上面引用的配置,这里配置的密码是大家自己设置的,如下:
redission:
basicHost: localhost
basicPort: 6379
baiscPwd: 123456
4、使用redis-server命令启动本机Redis服务器,基本命令如下,
5、使用redis-cli连接客户端,使用config set requirepass 123456设置密码,使用auth 123456检测给定的密码和配置文件中的密码是否相符,config get requirepass获取配置中的密码
6、使用Medis连接redis服务器看是否正常,
我们都知道使用ZSet数据结构(有序集合元素数量<128且所有元素长度小于64字节则为zipList数据结构,否则为skipList数据结构)来存储所需要排序的值,下面就来看一下如何实现:
首先看官网API文档:https://github.com/redisson/redisson/wiki/7.-distributed-collections#74-sortedset,知道ZSet如何使用后就可以开撸代码了,
1、服务已经搭好,在Test写单元测试,BaseTest是SpringJUnit测试类,
@Slf4j
public class UserLogicTest extends BaseTest {
//注入门面类
@Resource
private Facade facade;
//注入Redis客户端
@Resource
private RedissonClient redissonClient;
//redis排行榜单测
@Test
public void redisRankTest() throws ClassNotFoundException {
//通过反射拿到Service层的方法名作为存储的SetName
Class clazz = Class.forName("com.hust.zhang.service.logic.impl.UserLogicImpl");
Method method = clazz.getDeclaredMethods()[0];
String SetName = Constants.REDIS_CACHE_ID + ":" + getMethodName(method);
//拿到redis的ScoredSortedSet集合
try {
RScoredSortedSet set = redissonClient.getScoredSortedSet(SetName);
//从数据库拿到User集合
List list = facade.getDataFacade().getUserService().list();
//把集合数据异步存到redis服务器中
list.stream().forEach(user -> set.addAsync(user.getScore().doubleValue(), user));
}catch (Exception e){
log.info("redis客户端操作失败,异常信息:", e);
}
}
/**
* 获取包含方法参数路径的方法名
* @param method
* @return
*/
private static String getMethodName(Method method) {
StringBuilder sb = new StringBuilder();
sb.append(method.getName()).append("(");
Class[] var2 = method.getParameterTypes();
int var3 = var2.length;
for (int var4 = 0; var4 < var3; ++var4) {
Class> type = var2[var4];
sb.append(type.getName()).append(",");
}
if (method.getParameterTypes().length > 0) {
sb.delete(sb.length() - 1, sb.length());
}
sb.append(")");
return sb.toString();
}
}
这里从数据库拿数据,我的数据库原始数据如下图所示,User实体类中的score为各个对象的分数(有需求可能会要对不同值进行加权求平均分),这里简单起见只用score分数就行,
2、打开终端输入redis-cli打开命令客户端,输入monitor监控redis服务器,
3、跑完单测,可以看到redis客户端执行的命令,如下图
4、medis查看存入redis服务器的数据,可以看到存入的数据类型就是ZSET且进行了排序
这就是一个简单的使用ZSET数据结构进行排名,当然各位大佬可能会有更优雅的方式。
延迟队列单元测试如下:
@Test
public void redisQueueTest() {
try {
//获取一个阻塞队列
RBlockingQueue blockingQueue = redissonClient.getBlockingQueue("my_queue");
//根据阻塞队列获取一个延时队列
RDelayedQueue delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
//创建一个子线程,阻塞队列有数据就返回,否则wait
Thread thread = new Thread(() -> {
while (true) {
try {
System.err.println(blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
// 每秒向延迟队列放入数据,共执行5此
for (int i = 1; i <= 5; i++) {
delayedQueue.offer("test" + i, 10, TimeUnit.SECONDS);
}
}catch (Exception e){
log.info("redis客户端操作失败,异常信息:", e);
}
}
上面使用了两个队列,阻塞队列和延时队列,下面简单介绍一下这两个队列,
阻塞队列(BlockingQueue)通常最先想到的是它是一个队列,不过队列除了FIFO还有LIFO的。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。
在阻塞队列不可用时,针对下面两种情况提供了4种处理方式:
处理方式 | 抛出异常 | 返回特殊值 | 一直阻塞 | 超时退出 |
---|---|---|---|---|
插入方法 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除方法 | remove() | poll() | take() | poll(time,unit) |
检查方法 | element() | peek() | 不可用 | 不可用 |
使用monitor监控redis执行的命令,如下,
可以看到Redis执行顺序:
具体可以参看文末链接,本质上是发布订阅模型。
参看官网上的API后,看到了常用的页面置换算法,选择最近最久未使用的页面予以淘汰。Redis可以看一下是怎么使用的,
Redisson提供了基于Redis的以LRU为驱逐策略的分布式LRU有界映射对象。顾名思义,分布式LRU有界映射允许通过对其中元素按使用时间排序处理的方式,主动移除超过规定容量限制的元素。
@Test
public void RedisLRUTest() {
try {
RMapCache map = redissonClient.getMapCache("map");
// 尝试将该映射的最大容量限制设定为10
map.trySetMaxSize(10);
// 将该映射的最大容量限制设定或更改为10
map.setMaxSize(10);
for (int i = 0; i < 20; i++) {
map.put(String.valueOf(i), String.valueOf(i), 30, TimeUnit.SECONDS);
}
}catch (Exception e){
log.info("redis客户端操作失败,异常信息:", e);
}
}
可以看到Redis客户端里存放的是最后几个键值对,还可以看到Encoding是使用的zipList(数据量较小时的数据结构)。
实现消息已读未读功能的思路是使用Hash存储用户上次看过的时间,另外使用ZSet存储每个模块的每个信息产生的时间,
Redis可以用到的地方还是挺多的,需要我们大家自己去摸索,别人给了API文档,只要花时间去看去了解,都是可以用上的。不过需要深入的地方还有很多,加油!
另外补充两点:
参考链接:
1、https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95
2、https://zhuanlan.zhihu.com/p/343811173