Redis批量操作是什么?怎么实现延时队列及订阅模式+LRU
mhr18 2024-10-29 14:35 38 浏览 0 评论
前言
这次的内容是我自己为了总结Redis知识而扩充的,但是Redis的强大,以及适用范围之广可不是单单一篇博文就能总结清的。所以这次准备继续总结,因为第一个问题,Redis的批量操作,是我在面试过程中被真实问到的,当时没答上来,也是因为确实没了解过Redis的批量操作。
当时的问题,我还记得比较清晰:Redis执行批量操作的功能是什么?使用场景就是搞促销活动时,会做预缓存,会往缓存里放大批数据,如果直接放的话那么会很慢,怎么能提高效率呢?
Redis的批量操作-管道(pipeline)
首先Redis的管道(pipeline)并不是Redis服务端提供的功能,而是Redis客户端为了减少网络交互而提供的一种功能。
正常的一次Redis网络交互如下:
pipeline主要就是将多个请求合并,进行一次提交给Redis服务器,Redis服务器将所有请求处理完成之后,再一次性返回给客户端。
下面我们分析一下 pipeline 的原理
pipeline的一个交互过程是这样的:
- 客户端进程调用 write 命令将消息写入到操作系统内核为套接字分配的 发送缓冲区send buffer 。
- 客户端操作系统通过网络路由,将 send buffer 中的数据发送给服务器操作系统为套接字分配的接 收缓冲区 receive buffer 。
- 服务端进程调用 read 命令从 receive buffer 中取出数据进行处理,然后调用 write 命令将相应信息写入到服务端的 send buffer 中。
- 服务端操作系统通过网络路由,将 send buffer 中的数据发送给客户端操作系统的 receive buffer 。
- 客户端进程调用read命令将数据从 receive buffer 中取出进行业务处理。
在使用 pipeline 时需要注意:
- pipeline执行的操作,和mget,mset,hmget这样的操作不同,pipeline的操作是不具备原子性的。
- 还有在集群模式下因为数据是被分散在不同的slot里面的,因此在进行批量操作的时候,不能保证操作的数据都在同一台服务器的slot上,所以集群模式下是禁止执行像mget、mset、pipeline等批量操作的,如果非要使用批量操作,需要自己维护key与slot的关系。
- pipeline也不能保证批量操作中有命令执行失败了而中断,也不能让下一个指令依赖上一个指令,如果非要这样的复杂逻辑,建议使用lua脚本来完成操作。
Redis实现消息队列和延时队列
消息队列
Redis的实现消息队列可以用list来实现,通过lpush与rpop或者rpush与lpop结合来实现消息队列。
但是若是list为空后,无论是lpop还是rpop都会持续的获取list中的数据,若list一直为空,持续的拉取数据,一是会增加客户端的cpu利用率,二是也增高了Redis的QPS,解决方案是使用 blpop或 brpop 来代替lpop或rpop。
其实blpop和brpop的作用是bloking pop,就是阻塞拉取数据,当消息队列中为空时就会停止拉取,有数据后立即恢复拉取。
但是当没有数据的时候, 阻塞拉取 ,就会一直阻塞在那里,时间久了就成了空闲连接,那么Redis服务器一般会将时间闲置过久的连接直接断掉,以减少连接资源。所以还要检测 阻塞拉取 抛出的异常然后进行重试。
另外一点,就是Redis实现的消息队列,没有ACK机制,所以想要实现消息的可靠性,还要自己实现当消息处理失败后,能继续抛回队列。
延时队列
用Redis实现延时队列,其实就是使用zset来实现,将消息序列化成一个字符串(可以是json格式),作为为 value ,消息的到期处理时间做为 score ,然后用多线程去查询zset来获取到期消息进行处理。
多线程轮询处理,保证了可用性,但是要做幂等或锁处理,保证不要重复处理消息。
主要的实现代码如下。
/**
* 放入延时队列
* @param queueMsg
*/
private void delay(QueueMsg queueMsg){
String msg = JSON.toJSONString(queueMsg);
jedis.zadd(queueKey,System.currentTimeMillis()+5000,msg);
}
/**
* 处理队列中从消息
*/
private void lpop(){
while (!Thread.interrupted()){
// 从队列中取出,权重为0到当前时间的数据,并且数量只取一个
Set<String> strings = jedis.zrangeByScore(queueKey, 0, System.currentTimeMillis(), 0, 1);
// 如果消息为空,就歇会儿再取。
if(strings.isEmpty()){
try {
//休息一会儿
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
continue;
}
String next = strings.iterator().next();
// 如果抢到了消息
if(jedis.zrem(queueKey,next)>0){
// 反序列化后获取到消息
QueueMsg queueMsg = JSON.parseObject(next, QueueMsg.class);
// 进行消息处理
handleMsg(queueMsg);
}
}
}
订阅模式
Redis的主题订阅模式,其实并不想过多总结,因为由于它本身的一些缺点,导致它的应用场景比较窄。
前面总结的用Redis的list实现的消息队列,虽然可以使用,但是并不支持消息多播的场景,即一个生产者,将消息放入到多个队列中,然后多个消费者进行消费。
这种消息多播的场景常用来做分布式系统中的解耦。用哦 publish 进行生产者发送消息,消费者使用 subscribe 进行获取消息。
例如:我向jimoerChannel发送了一条消息 b-tree
127.0.0.1:6379> publish jimoerChannel b-tree
(integer) 1
订阅这个渠道的消费者立马收到了一条b-tree的消息。
127.0.0.1:6379> subscribe jimoerChannel
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "jimoerChannel"
3) (integer) 1
1) "message"
2) "jimoerChannel"
3) "b-tree"
我前面也说到了,Redis的pub/sub订阅模式,其实最大的缺点就是,消息不能持久化,这样就导致,若是消费者挂了或是没有消费者,那么消息就会被直接丢弃。因为这个原因,所以导致他的使用场景比较少。
IO模型
Redis的过期策略
Redis的过期策略是适用于所有数据结构的。数据一到过期时间就自动删除,Redis会将设置了过期时间的key 放置在一个字典表里。
定期删除
Redis会定期遍历字典表里面数据来删除过期的Key。
Redis默认的定期删除策略是每秒进行10次过期扫描,即每100ms扫描一次。并不是扫描全部设置了过期时间的key,而是随机扫描20个key,删除掉已经过期的key,如果过期的比率超过25%,那么就继续进行扫描。
惰性删除
因为定期删除是随机抽取一些key来进行过期删除,所以如果key并没有被定期扫描到,那么过期的key就不会被删除。所以Redis还提供了惰性删除的策略, 就是当去查询某些key的时候,若是key已经过期了,那么就会删除key,然后返回null。
另外一点当在集群条件下,主从同步情况中,主节点中的key过期后,会在aof中生成一条删除指令,然后同步到从节点,这样的从节点在接收到aof的删除指令后,删除掉从节点的key,因为主从同步的时候是异步的所以,短暂的会出现主节点已经没有数据了,但是从节点还存在。
但是若是定期删除也没有扫描到key,而且好长时间也没去去使用key,那么这部分过期的key就会一直占用的内存。
所以Redis又提供了内存淘汰机制。
内存淘汰机制
当Redis的内存出现不足时,就会持续的和磁盘进行交互,这样就会导致Redis卡顿,效率降低等情况。这在线上是不允许发生的,所以Redis提供了配置参数 maxmemory 来限制内存超出期望大小。
当内存使用情况超过maxmemory的值时,Redis提供了以下几种策略,来让使用者通过配置决定该如何腾出内存空间来继续提供服务。
- noeviction 不会继续提供写请求(del请求可以),读请求可以,写请求会报错,这样保证的数据不会丢失,但是业务不可用,这是默认的策略。
- volatile-lru 会将设置了过期时间的key中,淘汰掉最近最少使用的key。没有设置过期时间的key不会被淘汰,保证了需要持久化的数据不丢。
- volatile-ttl 尝试将设置了过期时间的key中,剩余生命周期越短,越容易被淘汰。
- volatile-random 尝试将从设置了过期时间的key中,随机选择一些key进行淘汰。
- allkeys-lru 从所有key中,淘汰掉最近最少使用的key。
- allkeys-random 从所有key中,随机淘汰一部分key。
那么具体设置成哪种淘汰策略呢?
这就是要看在使用Redis时的具体场景了,如果只是用Redis做缓存的话,那么可以配置allkeys-lru或allkey-random,客户端在写缓存的时候并不用携带着过期时间。若是还想要用持久化的功能,那么就应该使用volatile-开头的策略,这样可以保证每月设置过期时间的key不会被淘汰。
内存淘汰策略的配置如下:
# 最大使用内存
maxmemory 5m
# 内存淘汰策略 The default is:noeviction
maxmemory-policy allkeys-lru
LRU算法
LRU算法的实现,其实可以靠一个链表。链表按照使用情况来进行排序,当空间不足时,会剔除掉尾部的数据。当某个元素被访问时它会被移动到链表头。
在真实的面试中,若是让写出LRU算法,我认为可以使用Java中的LikedHashMap来实现,因为LikedHashMap已经实现了基本的LRU功能,我只需要封装一下就改造成了自己的了。
/**
* @author Jimoer
* @description
*/
public class MyLRUCache<K,V> {
// lru容量
private int lruCapacity;
// 数据容器(内存)
private Map<K,V> dataMap;
public MyLRUCache(int capacity){
this.lruCapacity = capacity;
// 设置LinkedHashMap的初始容量为LRU的最大容量,
// 扩容因子为默认的0.75,第三个参数是否将数据按照访问顺序排序。
dataMap = new LinkedHashMap<K, V>(capacity, 0.75f, true){
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
// 当数据量大于lruCapacity时,移除掉最老使用的数据。
return super.size()>lruCapacity;
}
};
}
public V get(K k){
return dataMap.get(k);
}
public void put(K key, V value){
dataMap.put(key,value);
}
public int getLruCapacity() {
return lruCapacity;
}
public Map<K, V> getDataMap() {
return dataMap;
}
}
测试代码:
@Test
public void lruTest(){
// 内存容量为3,即存储3条数据后,再放入数据,就会将最老使用的数据删除
MyLRUCache myLRUCache = new MyLRUCache(3);
myLRUCache.put("1k","张三");
myLRUCache.put("2k","李四");
myLRUCache.put("3k","王五");
// 容量已满
System.out.println("myLRUCache:"+JSON.toJSONString(myLRUCache.getDataMap()));
// 继续放入数据,该删除第一条数据为第四条数据腾出空间了
myLRUCache.put("4k","赵六");
// 打印出结果
System.out.println("myLRUCache:"+JSON.toJSONString(myLRUCache.getDataMap()));
}
运行结果:
myLRUCache:{"1k":"张三","2k":"李四","3k":"王五"}
myLRUCache:{"2k":"李四","3k":"王五","4k":"赵六"}
总结
好了,Redis的相关知识,就总结到这里了,算上前面两篇博文( Redis基础数据结构总结 、 你说一下Redis为什么快吧,怎么实现高可用,还有持久化怎么做的 ),这是Redis的第三篇了,这一篇博文也是新年的第一篇,元旦假期在家花了两天时间,自己学习自己总结。元旦假期结束后,我要继续面试了,后面我会继续将我面试中遇到的各种问题,总结出来,一是增加自己的知识面,二也将知识进行的传播。
希望可以对大家学习Redis有帮忙,喜欢的小伙伴可以帮忙转发+关注,感谢大家!
原文链接:https://www.tuicool.com/articles/VNNFVrf
相关推荐
- Redis合集-使用benchmark性能测试
-
采用开源Redis的redis-benchmark工具进行压测,它是Redis官方的性能测试工具,可以有效地测试Redis服务的性能。本次测试使用Redis官方最新的代码进行编译,详情请参见Redis...
- Java简历总被已读不回?面试挂到怀疑人生?这几点你可能真没做好
-
最近看了几十份简历,发现大部分人不是技术差,而是不会“卖自己”——一、简历死穴:你写的不是经验,是岗位说明书!反面教材:ד使用SpringBoot开发项目”ד负责用户模块功能实现”救命写法:...
- redission YYDS(redission官网)
-
每天分享一个架构知识Redission是一个基于Redis的分布式Java锁框架,它提供了各种锁实现,包括可重入锁、公平锁、读写锁等。使用Redission可以方便地实现分布式锁。red...
- 从数据库行锁到分布式事务:电商库存防超卖的九重劫难与破局之道
-
2023年6月18日我们维护的电商平台在零点刚过3秒就遭遇了严重事故。监控大屏显示某爆款手机SKU_IPHONE13_PRO_MAX在库存仅剩500台时,订单系统却产生了1200笔有效订单。事故复盘发...
- SpringBoot系列——实战11:接口幂等性的形而上思...
-
欢迎关注、点赞、收藏。幂等性不仅是一种技术需求,更是数字文明对确定性追求的体现。在充满不确定性的网络世界中,它为我们建立起可依赖的存在秩序,这或许正是技术哲学最深刻的价值所在。幂等性的本质困境在支付系...
- 如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享
-
如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享在高流量场景下。首先,我需要回忆一下常见的优化策略,比如负载均衡、缓存、数据库优化、微服务拆分这些。不过,可能还需要考虑用户的具体情况,比...
- Java面试题: 项目开发中的有哪些成长?该如何回答
-
在Java面试中,当被问到“项目中的成长点”时,面试官不仅想了解你的技术能力,更希望看到你的问题解决能力、学习迭代意识以及对项目的深度思考。以下是回答的策略和示例,帮助你清晰、有说服力地展示成长点:一...
- 互联网大厂后端必看!Spring Boot 如何实现高并发抢券逻辑?
-
你有没有遇到过这样的情况?在电商大促时,系统上线了抢券活动,结果活动刚一开始,服务器就不堪重负,出现超卖、系统崩溃等问题。又或者用户疯狂点击抢券按钮,最后却被告知无券可抢,体验极差。作为互联网大厂的后...
- 每日一题 |10W QPS高并发限流方案设计(含真实代码)
-
面试场景还原面试官:“如果系统要承载10WQPS的高并发流量,你会如何设计限流方案?”你:“(稳住,我要从限流算法到分布式架构全盘分析)…”一、为什么需要限流?核心矛盾:系统资源(CPU/内存/数据...
- Java面试题:服务雪崩如何解决?90%人栽了
-
服务雪崩是指微服务架构中,由于某个服务出现故障,导致故障在服务之间不断传递和扩散,最终造成整个系统崩溃的现象。以下是一些解决服务雪崩问题的常见方法:限流限制请求速率:通过限流算法(如令牌桶算法、漏桶算...
- 面试题官:高并发经验有吗,并发量多少,如何回复?
-
一、有实际高并发经验(建议结构)直接量化"在XX项目中,系统日活用户约XX万,核心接口峰值QPS达到XX,TPS处理能力为XX/秒。通过压力测试验证过XX并发线程下的稳定性。"技术方案...
- 瞬时流量高并发“保命指南”:这样做系统稳如泰山,老板跪求加薪
-
“系统崩了,用户骂了,年终奖飞了!”——这是多少程序员在瞬时大流量下的真实噩梦?双11秒杀、春运抢票、直播带货……每秒百万请求的冲击,你的代码扛得住吗?2025年了,为什么你的系统一遇高并发就“躺平”...
- 其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。
-
其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。比如上周有个小伙伴找我,五年经验但简历全是'参与系统设计''优化接口性能'这种空话。我就问他:你做的秒杀...
- PHP技能评测(php等级考试)
-
公司出了一些自我评测的PHP题目,现将题目和答案记录于此,以方便记忆。1.魔术函数有哪些,分别在什么时候调用?__construct(),类的构造函数__destruct(),类的析构函数__cal...
- 你的简历在HR眼里是青铜还是王者?
-
你的简历在HR眼里是青铜还是王者?兄弟,简历投了100份没反应?面试总在第三轮被刷?别急着怀疑人生,你可能只是踩了这些"隐形求职雷"。帮3630+程序员改简历+面试指导和处理空窗期时间...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)