目录

序言

Redis客户端选型

Redis配置

Redis实现排行榜

Redis实现延迟队列

Redis LRU(Least Recently Used)使用

Redis实现消息已读未读

总结


序言

在之前的开发中,我使用redis只用来实现分布式锁和对常用方法的查询数据缓存,再就是对登录验证码的一个缓存。数据类型也只用到了String(五种基本数据类型:String、List、Hash、Set、ZSet),这篇文章主要写怎么用Redis实现排行榜功能。

Redis客户端选型

了解过的小伙伴应该知道,我前面一篇文章也提到过Redis的三个客户端,它们各有各的优劣,下面对比一下这几种客户端:

  • Jedis:Jedis中的方法调用是比较底层的暴露的Redis的API,也即Jedis中的Java方法基本和Redis的API保持着一致。Jedis使用阻塞的I/O,且其方法调用都是同步的,程序流需要等到sockets处理完I/O才能执行,不支持异步。Jedis客户端实例不是线程安全的,所以需要通过连接池来使用Jedis。Jedis仅支持五种基本数据结构(String、Hash、List、Set、ZSet)。
  • Redisson:Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。而Redisson中的方法则是进行比较高的抽象,每个方法调用可能进行了一个或多个Redis方法调用。Redisson使用非阻塞的I/O和基于Netty框架的事件驱动的通信层,其方法调用是异步的。Redisson的API是线程安全的,所以可以操作单个Redisson连接来完成各种操作。Redisson不仅提供了一系列的分布式Java常用对象,基本可以与Java的基本数据结构通用,还提供了许多分布式服务。
  • Lettuce:Lettuce是一个高性能基于Java编写的Redis驱动框架,底层集成了Project Reactor提供自然的反应式编程,通讯框架集成了Netty使用了非阻塞IO,5.x版本以后融合了JDK1.8的异步编程特性,在保证高性能的同时提供了十分丰富易用的API。用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。主要在一些分布式缓存框架上使用比较多。

本篇文章我选用Redisson客户端来使用

Redis配置

1、添加依赖

<!--redisson-->
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.13.6</version><exclusions><exclusion><groupId>org.redisson</groupId><artifactId>redisson-spring-data-23</artifactId></exclusion></exclusions>
</dependency>
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-data-21</artifactId><version>3.13.6</version>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、在application.yml文件加上配置,我们知道redis有四种工作模式——单点(single)、主从、哨兵(sentinel)、集群(cluster),本机单机测试,采用的是单点模式,如下:

###### redis ######
redis:clients:default:mode: singleaddress: redis://${redission.basicHost}:${redission.basicPort}password: ${redission.baiscPwd}

3、在application-local.yml文件上加上上面引用的配置,这里配置的密码是大家自己设置的,如下:

redission:basicHost: localhostbasicPort: 6379baiscPwd: 123456

4、使用redis-server命令启动本机Redis服务器,基本命令如下,

  • redis-server  -------Redis服务器
  • redis-cli         -------Redis命令行客户端
  • redis-benchmark ---------Redis性能测试工具
  • redis-check-aof ----------AOF文件修复工具
  • redis-check-dump --------RDB文件检查工具
  • redis-cli —> auth认证 —> shutdown --------关闭redis服务器
  • redis-cli —> auth认证 —> monitor --------监控redis执行了哪些命令(线上环境慎用,比较耗redis服务器资源)

5、使用redis-cli连接客户端,使用config set requirepass 123456设置密码,使用auth 123456检测给定的密码和配置文件中的密码是否相符,config get requirepass获取配置中的密码

6、使用Medis连接redis服务器看是否正常,

Redis实现排行榜

我们都知道使用ZSet数据结构(有序集合元素数量<128且所有元素长度小于64字节则为zipList数据结构,否则为skipList数据结构)来存储所需要排序的值,下面就来看一下如何实现:

首先看官网API文档:7. Distributed collections · redisson/redisson Wiki · GitHub,知道ZSet如何使用后就可以开撸代码了,

1、服务已经搭好,在Test写单元测试,BaseTest是SpringJUnit测试类,

@Slf4j
public class UserLogicTest extends BaseTest {//注入门面类@Resourceprivate Facade facade;//注入Redis客户端@Resourceprivate RedissonClient redissonClient;//redis排行榜单测@Testpublic void redisRankTest() throws ClassNotFoundException {//通过反射拿到Service层的方法名作为存储的SetNameClass 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<User> set = redissonClient.getScoredSortedSet(SetName);//从数据库拿到User集合List<User> 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数据结构进行排名,当然各位大佬可能会有更优雅的方式。

Redis实现延迟队列

延迟队列单元测试如下:

    @Testpublic void redisQueueTest() {try {//获取一个阻塞队列RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue("my_queue");//根据阻塞队列获取一个延时队列RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);//创建一个子线程,阻塞队列有数据就返回,否则waitThread 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执行顺序:

  1. Monitor一直ping命令Redis服务器
  2. 订阅(SubScribe)了一个固定的队列 redisson_delay_queue_channel:{my_queue}, 就是为了开启进程里面的延时任务。
  3. zrangebyscore key min max [WITHSCORES] [LIMIT offset count]:分页获取指定区间内(min-max),带有分数值(可选)的有序集成员的列表。
  4. redisson_delay_queue_timeout:{my_queue} 是一个zset,当有延时数据存入Redisson队列时,就会在此队列中插入数据,排序分数为延时的时间戳。
  5. zrange,取出第一个数,也就是判断上面的还有不有下一页。
  6. BLPOP,移出并获取 my_queue列表的第一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止,这里显然没有元素,就会一直阻塞。
  7. zadd,往ZSet里面添加数据。
  8. rpush,同步一份数据到list队列。
  9. zrange+publish,取出排序好的第一个数据,也就是最临近要触发的数据,然后发送通知 (之前订阅了的客户端,可能是微服务就有多个客户端),内容为将要触发的时间。客户端收到通知后,就在自己进程里面开启延时任务(HashedWheelTimer),到时间后就可以从redis取数据发送。

具体可以参看文末链接,本质上是发布订阅模型。

Redis LRU(Least Recently Used)使用

参看官网上的API后,看到了常用的页面置换算法,选择最近最久未使用的页面予以淘汰。Redis可以看一下是怎么使用的,

Redisson提供了基于Redis的以LRU为驱逐策略的分布式LRU有界映射对象。顾名思义,分布式LRU有界映射允许通过对其中元素按使用时间排序处理的方式,主动移除超过规定容量限制的元素。

    @Testpublic void RedisLRUTest() {try {RMapCache<String, String> map = redissonClient.getMapCache("map");// 尝试将该映射的最大容量限制设定为10map.trySetMaxSize(10);// 将该映射的最大容量限制设定或更改为10map.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(数据量较小时的数据结构)。

Redis实现消息已读未读

实现消息已读未读功能的思路是使用Hash存储用户上次看过的时间,另外使用ZSet存储每个模块的每个信息产生的时间,

  1. 当有新信息产生,向相关模块添加时间:根据当前时间添加到ZSet数据集中。
  2. 当用户点击某个模块时,更新用户查看该模块的上次时间:更新当前点击模块的时间到Hash集合中。
  3. 查看时,从ZSet数据集中拿到当前模块的时间值,如果为空则表明是新消息,否则看用户上次看过的时间到当前时间是否有新消息产生。

总结

Redis可以用到的地方还是挺多的,需要我们大家自己去摸索,别人给了API文档,只要花时间去看去了解,都是可以用上的。不过需要深入的地方还有很多,加油!

另外补充两点:

  1. 如果在开发中如果是把Java对象转换成JSON格式存入到Redis中,我们取出的时候也需要把JSON格式转换成我们所需的Java对象(可能需要借助阿里巴巴的fastjson的JSONObject.parseObject()方法)。
  2. Mysql存的数据也可以通过Order By score语句(默认ASC)进行检索,也可以达到相同的效果。当时Mysql的检索性能远不及Redis,不光光是因为Mysql的数据结构是B+树Redis的数据结构是跳表,更重要的是Redis是基于内存操作。

参考链接:

1、目录 · redisson/redisson Wiki · GitHub

2、Redisson 延时队列 原理 详解 - 知乎

Redis实现排行榜、延迟队列、LRU、消息已读未读(Redisson客户端实现)相关推荐

  1. redis延迟队列 实现_灵感来袭,基于Redis的分布式延迟队列(续)

    背景 上一篇(灵感来袭,基于Redis的分布式延迟队列)讲述了基于Java DelayQueue和Redis实现了分布式延迟队列,这种方案实现比较简单,应用于延迟小,消息量不大的场景是没问题的,毕竟J ...

  2. mysql消息已读未读_Redis实现信息已读未读状态提示

    本文为大家分享了Redis实现信息已读未读状态提示的关键代码,希望可以给大家一些启发,具体内容如下 前提: 假如现在有2个模块需要提示消息:只要存在用户在上个时间点之后没有看过的信息就提示用户有新的信 ...

  3. Redis 实现热度统计和已读未读功能

    Redis 实现销量统计和已读未读功能 本文主要讨论,通过Redis实现:1. 商品的销量统计.2. 页面内容的已读/未读功能 销量统计 背景 实现统计商品的购买量(热度). 假设有如下商品信息: 要 ...

  4. IM群聊消息的已读未读功能在存储空间方面的实现思路探讨

    1.引言 IM系统中,特别是在企业应用场景下,消息的已读未读状态是一个强需求. 以阿里的钉钉为例,钉钉的产品定位是用于商务交流,其"强制已读回执"功能,让职场人无法再"假 ...

  5. Web聊天室消息[已读未读]的实现

    聊天室快速访问 继上次完成聊天室的历史记录功能后,我又想着实现聊天记录的已读未读功能.(轻喷..) 开始之前 首先我看了抖音和钉钉这两款应用的消息已读未读功能的呈现效果.首先是抖音,在聊天界面,给好友 ...

  6. 大公司面试考细节,设计群聊消息的已读未读功能你说说怎么做?

    一朋友和我讨论他前段时间面试某大公司的一题目 : 企业IM比如企业微信.钉钉里面的群消息的有个已读未读的功能,发送者刚发出消息时,当前群里其他群成员都是未读状态,陆陆续续有人看了这个消息,这时候消息的 ...

  7. 面试官:群聊消息的已读未读功能,你来设计一个?

    欢迎关注方志朋的博客,回复"666"获面试宝典 一朋友和我讨论他前段时间面试某大公司的一题目 : 企业IM比如企业微信.钉钉里面的群消息的有个已读未读的功能,发送者刚发出消息时,当 ...

  8. 群聊消息“已读”/“未读” 功能解决方案!

    一朋友和我讨论他前段时间面试某大公司的一题目: 企业IM比如企业微信.钉钉里面的群消息的有个已读未读的功能,发送者刚发出消息时,当前群里其他群成员都是未读状态,陆陆续续有人看了这个消息,这时候消息的详 ...

  9. 面试题:群聊消息的已读未读设计

    点击上方"Java之间",选择"置顶或者星标" 你关注的就是我关心的! 作者:小猿学习笔记 一朋友和我讨论他前段时间面试某大公司的一题目 : 企业IM比如企业微 ...

最新文章

  1. 填写各类表格时有时在多个选择前有小方框 在其中打勾
  2. Java的Socket通信(多Clients/Server模型)
  3. 老男孩教育每日一题-第86天-nfs客户端挂载信息写入/etc/fstab中,系统重启,没有自动挂载是什么原因?...
  4. Redis常见配置redis.conf
  5. python温度转换代码分析_Python温度转换实例分析
  6. C# winForm 定时访问PHP页面小工具
  7. 分布式系统开发注意事项
  8. 连接和关闭资源工具类
  9. eclipse maven tomcat 部署
  10. java下载文件加速_使用Java优化下载速度
  11. 人心是暖的,眼泪是苦的,杜鹃花真的很香
  12. 元宇宙大杀器来了!小扎祭出4款VR头显,挑战视觉图灵测试
  13. 项目管理中团队合作有多重要
  14. 牛客练习赛53 老瞎眼 pk 小鲜肉[思维+离线+线段树]
  15. 如何在不清空原有配置的情况下修改路由器密码??????
  16. 图像处理 Matlab GUI系统(全)
  17. mx450属于什么档次的显卡
  18. jquery 即点即改.......
  19. 乐吾乐2D可视化为智慧水务可视化赋能(二)
  20. 辨别MagicKeyboard的真伪(序列号验证版)

热门文章

  1. Tableau-prep概览
  2. 实现在科汛CMS会员中心调用指定栏目文章方法【已解决】
  3. html中加图片相对路径,以html中插入图片为例,聊一聊绝对路径和相对路径的区别...
  4. 物联卡是正规卡吗?物联网卡长什么样?
  5. 攻防演练前 临战阶段:战前动员,鼓舞士气
  6. 雅虎美女CEO梅耶尔
  7. 标签背景透明以及文字重叠问题
  8. Cocos2d-JS 环境搭建 for mac
  9. 大数据文档PDF开放下载
  10. 2021-06-27 五星好评的实现