redis zset 使用场景
前文,我们讨论过redis 的数据结构及使用场景。可参考:
参考: 总结篇4:redis 核心数据存储结构及核心业务模型实现应用场景 https://www.cnblogs.com/yizhiamumu/p/16566540.html
一:zset(sorted set:有序集合)
Redis zset和Set一样也是String类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。Redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。
Redis zset类似Java里的LinkedSet(不重复有序),当zset中最后一个value 被移除后,数据结构自动删除,内存被回收。
常用操作
zadd key score member [[score member]...] // 往有序集合key 中加入带分值元素
zrem key member [member ...] // 从有序集合中删除元素
zcore key member // 返回有序集合key 中元素member 的分值
zincrby key increment member // 为有序集合key 中元素member的分值加上increment
zcard key // 返回有序集合key 中元素个数
zrange key start stop [withscores] // 正序获取有序集合key 从start 下标到stop 下标的元素Zrevrange key start stop [withscores] // 倒序
集合操作
zunionstore destkey numkeys key [key ...] 并集计算
zinterstore destkey numkeys key [key ...] 交集计算
应用场景
1 点击新闻
zincrby hotnews:202008 1 name
2 单日排行榜
zrevrange hotnews:202208 0 9 withscores
3 七日搜索榜单计算
zunionstore hotnews:20220801-20220807 7
4 展示七日榜前十
zrevrange hotnews:20220801-20220807 0 9 withscores
二:zset 应用场景
因为Rediszset底层的数据结构是skipList,最底层链表有序,所有可以有以下使用场景:
1. 延时队列
score作为时间戳,自动按照时间最近的进行排序,启一个线程持续poll并设置park时间,完成延迟队列的设计,可参考Executors.newScheduledThreadPool中的DelayedWorkQueue
2. 排行榜
score作为浏览次数,自动进行排序,但要注意冷数据。
3. 滑动窗口限流
score作为时间戳,可统计最近一段时间内内的成员数量,实现滑动窗口限流。
下面我们简单实现一下应用场景。
三:代码实现
1. 延时队列
zset 会按 score 进行排序,如果 score 代表想要执行时间的时间戳。在某个时间将它插入zset集合中,它变会按照时间戳大小进行排序,也就是对执行时间前后进行排序。
起一个死循环线程不断地进行取第一个key值,如果当前时间戳大于等于该key值的score就将它取出来进行消费删除,可以达到延时执行的目的。
发送消息
代码如下:
public void sendMessage(long messageId, String message) {
System.out.println("发送消息");
Jedis client = jedisPool.getResource();
Pipeline pipeline = client.pipelined();
// score 设置成当前时间戳 + 延迟时间
pipeline.zadd(DELAY_QUEUE, System.currentTimeMillis() + DELAY_TIME * 1000,
String.format(MEMBER_PREFIX, messageId));
Map<String, String> map = new HashMap<>();
map.put(String.format(MEMBER_PREFIX, messageId), message);
pipeline.hset(DELAY_MESSAGE, map);
pipeline.syncAndReturnAll();
pipeline.close();
client.close();
System.out.println("======发送消息 over");
}
采用 pipeline 的方式,同时写入 zset 和 hash 中
消费消息
代码如下:
public void consumer() {
System.out.println("======消费消息开始");
Jedis client = jedisPool.getResource();
Set<Tuple> tupleSet = client.zrangeByScoreWithScores(DELAY_QUEUE, 0, System.currentTimeMillis());
for (Tuple t : tupleSet) {
long messageId = Long.valueOf(t.getElement().replaceAll("[^0-9]", ""));
messageHandler(messageId);
}
client.close();
System.out.println("------消费消息 over");
}
public void messageHandler(long messageId) {
System.out.println("===---===");
pool.execute(() -> { // 放到线程池处理
Jedis client = jedisPool.getResource();
String message = client.hget(DELAY_MESSAGE, String.format(MEMBER_PREFIX, messageId));
System.out.println("处理消息体" + message);
System.out.println("处理消息体成功");
Pipeline pipeline = client.pipelined();
pipeline.multi();
pipeline.hdel(DELAY_MESSAGE, String.format(MEMBER_PREFIX, messageId));
pipeline.zrank(DELAY_QUEUE, String.format(MEMBER_PREFIX, messageId));
pipeline.exec();
pipeline.close();
client.close();
});
}
问题
没有 ack 机制,当消费失败的情况下队列如何处理?
这是 topic 模式,广播模式如何搞?
2. 排行榜
经常浏览技术社区的话,应该对 “1小时最热门” 这类榜单不陌生。如何实现呢?
如果记录在数据库中,不太容易对实时统计数据做区分。我们以当前小时的时间戳作为 zset 的 key,把贴子ID作为 member ,点击数评论数等作为 score,当 score 发生变化时更新 score。利用 ZREVRANGE 或者 ZRANGE 查到对应数量的记录。
记录回复数
代码如下:
/**
* 模拟每次针对贴子的回复数加 1
*
* @param id
*/
public void post(long id) {
Jedis client = jedisPool.getResource();
client.zincrby(POSTLIST, 1, String.format(MEMBER_PREFIX, id));
client.close();
}
获取列表
代码如下:
/**
* 获取 Top 的贴子列表 ID
*
* @param size
* @return
*/
public List<Integer> getTopList(int size) {
List<Integer> result = new ArrayList<>();
if (size <= 0 || size > 100) {
return result;
}
Jedis client = jedisPool.getResource();
Set<Tuple> tupleSet = client.zrevrangeWithScores(POSTLIST, 0, size - 1);
client.close();
for (Tuple tuple : tupleSet) {
String t = tuple.getElement().replaceAll("[^0-9]", "");
result.add(Integer.valueOf(t));
}
return result;
}
模拟用户发帖的行为
代码如下:
public void test() throws InterruptedException {
int threadSize = 200;
long[] ids = {100, 102, 103, 104, 105, 106, 101, 108, 107, 200, 109, 201, 202};
CountDownLatch countDownLatch = new CountDownLatch(threadSize);
for (int i = 0; i < threadSize; i++) {
pool.execute(() -> {
for (int j = 0; j < 3; j++) {
Random r = new Random();
int index = (int) (r.nextDouble() * ids.length);
post(ids[index]);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
}
问题
数量过大时会占用大量内存,需要清理很多冷数据
适合处理点击数、访问量之类,处理发帖回复这种还需要考虑,帖子审核不通过的情况
3. 限流策略
滑动窗口是限流常见的一种策略。如果我们把一个用户的 ID 作为 key 来定义一个 zset ,member 或者 score 都为访问时的时间戳。我们只需统计某个 key 下在指定时间戳区间内的个数,就能得到这个用户滑动窗口内访问频次,与最大通过次数比较,来决定是否允许通过。
代码如下:
/**
*
* @param userId
* @param period 窗口大小
* @param maxCount 最大频次限制
* @return
*/
public boolean isActionAllowed(String userId, int period, int maxCount) {
String key = String.format(KEY, userId);
long nowTs = System.currentTimeMillis();
Jedis client = jedisPool.getResource();
Pipeline pipe = client.pipelined();
pipe.multi();
pipe.zadd(key, nowTs, String.format(MEMBER, userId, nowTs));
pipe.zremrangeByScore(key, 0, nowTs - period * 1000);
Response<Long> count = pipe.zcard(key);
pipe.expire(key, period + 1);
pipe.exec();
pipe.close();
client.close();
return count.get() <= maxCount;
}
思路是每一个请求到来时,将时间窗口外的记录全部清理掉,只保留窗口内的记录。zset 中只有 score 值非常重要,value 值没有特别的意义,只需要保证它是唯一的就可以了
问题
需要清理额外的数据
限制的请求量过大时,会占用大量内存
redis zset 使用场景的更多相关文章
- redis的适应场景
redis应用场景: 1.对数据高并发读写 2.对海量数据的高效存储和访问 3.对数据的高可扩展性和高可用性 做分布式扩展很简单,因为没有固定的表结构 redis介绍: redis是一个key-val ...
- redis 发展史 应用场景
引言 在Web应用发展的初期,那时关系型数据库受到了较为广泛的关注和应用, 原因是因为那时候Web站点基本上访问和并发不高.交互也较少. 而在后来,随着访问量的提升,使用关系型数据库的Web站点多多少 ...
- redis系列-redis的使用场景
redis越来越受大家欢迎,提升下速度,做下缓存,完成KPI之利器呀.翻译一篇文章<<How to take advantage of Redis just adding it to yo ...
- Redis 数据结构使用场景
转自http://get.ftqq.com/523.get 一.redis 数据结构使用场景 原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的 ...
- Lua脚本在redis分布式锁场景的运用
目录 锁和分布式锁 锁是什么? 为什么需要锁? Java中的锁 分布式锁 redis 如何实现加锁 锁超时 retry redis 如何释放锁 不该释放的锁 通过Lua脚本实现锁释放 用redis做分 ...
- redis的使用场景和基本数据类型
一:redis使用的场景 redis是一个高性能的NoSQL数据库,特点是高性能,持久存储,适应高并发的应用场景. 下面看看它的使用场景1.取最新N个数据的操作比如取网站的最新文章,通过下面方式,我们 ...
- redis作为缓存场景使用,内存耗尽时,突然出现大量的逐出,在这个逐出的过程中阻塞正常的读写请求,导致 redis 短时间不可用
redis 突然大量逐出导致读写请求block 内容目录: 现象 背景 原因 解决方案 ref 现象 redis作为缓存场景使用,内存耗尽时,突然出现大量的逐出,在这个逐出的过程中阻塞正常的读写请 ...
- 实现排行榜神器——redis zset
需求:假如现在需要搞个 “运动消耗卡路里排行榜”,例似微信步数排名,显示排名前20人的信息和消耗的卡里路,怎样实现排序? 一般思路:存储信息,然后数据库查询,排序?(假如有几十万人参与排名,这样查my ...
- redis的使用场景和优缺点
使用场景和优缺点: 2 Redis用来做什么? 通常局限点来说,Redis也以消息队列的形式存在,作为内嵌的List存在,满足实时的高并发需求.而通常在一个电商类型的数据处理过程之中,有关商品,热销, ...
- 延时任务-基于redis zset的完整实现
所谓的延时任务给大家举个例子:你买了一张火车票,必须在30分钟之内付款,否则该订单被自动取消.订单30分钟不付款自动取消,这个任务就是一个延时任务. 我之前已经写过2篇关于延时任务的文章: <完 ...
随机推荐
- CF414B
这道题dp状态表示需要一点思维,而且会卡到时间复杂度 之前题主用的是试除法,时间复杂度为n^2.5,然后被卡了,但是换一种写法就是对的 #include <iostream> #inclu ...
- 解决方案 | pywintypes.com_error: (-2147221005, '无效的类字符串', None, None) --Python连接CAD报错真正解决思路!
1 背景 import pythoncom import win32com.client import math wincad = win32com.client.Dispatch("Aut ...
- 【.bat】IISExpress配置通过IP访问程序
本页只记录便携运行方式脚本 详细IISExpress配置方法请看: VS的IISExpress配置通过IP访问程序 网络信息:192.168.1.45:8378 Run.bat :: run as a ...
- find命令在根目录查找文件
find命令在根目录查找文件 find命令口诀是: find 路 名 含 一,首先看看路径的表示方法 . 表示当前目录 .. 表示上一级目录 cd .. 表示返回上一级目录cd ../ ...
- 【Java-GUI】04 菜单
--1.菜单组件 相关对象: MenuBar 菜单条 Menu 菜单容器 PopupMenu 上下文菜单(右键弹出菜单组件) MenuItem 菜单项 CheckboxMenuItem 复选框菜单项 ...
- 使用 C# 和 ONNX 來玩转Phi-3 SLM
LLM 席卷世界刷新 AI 的认知之后,由于 LLM 需要的硬件要求实在太高,很难在普通设备上运行,因此 SLM 逐漸受到重視,Phi-3 SLM 是由 Microsoft 所开发的模型,可以在你的电 ...
- 机器人操作系统ROS (学习视频)—— 学习ROS,安装Ubuntu,虚拟机和双系统如何选择
分享一个ROS入门视频: https://www.bilibili.com/video/BV1BP4y1o7pw/
- 深度学习框架theano下的batch_norm实现代码——强化学习框架rllab
深度学习框架theano下的batch_norm实现代码--强化学习框架rllab # encoding: utf-8 import lasagne.layers as L import lasagn ...
- MindSpore中使用model.train,在每一步训练结束后自动进行调用自定义函数 —— from mindspore.train.callback import Callback
在MindSpore中使用model.train训练网络时我们难以处理间断性的任务,为此我们可以考虑使用MindSpore中的Callback机制.Callback 函数可以在 model.train ...
- 老代码报错:scipy.misc.imresize报错: AttributeError: module 'scipy.misc' has no attribute 'imresize'
运行老代码报错: image = misc.imresize(image, [Config.IMAGE_HEIGHT, Config.IMAGE_WIDTH], 'bilinear')Attribut ...