在软件开发的世界里,消息队列就像是一场奇妙的表演,每个演员都有自己的任务,而消息则是剧本中的情节。而要打造一场精彩的表演,我们需要一个强大的舞台工具。在这篇文章中,我们将带你走进 Redis 的神奇世界,揭示它是如何通过 List 这个神奇的工具,帮助我们搭建起高效的消息队列,让消息传递变得更加有趣。
Redis中的List是一种有序、可重复的数据结构,它实际上是一个字符串链表。每个节点都包含一个字符串值,而这些节点按照插入顺序排列。
以下是Redis List的一些关键特性和操作:
有序集合: List中的元素是有序的,每个元素都有一个索引位置,允许按索引进行访问和操作。
可重复元素: List中的元素可以重复,同样的值可以存在于不同的位置。
左右两端操作: 可以从List的左端(头部)或右端(尾部)进行元素的添加和移除。
范围操作: 可以获取List中的一定范围的元素。
长度操作: 可以获取List的长度。
其他操作: 还有其他一些操作,如插入、删除、获取指定索引位置的元素等。
总体而言,Redis List适用于需要维护有序数据集合的场景,例如实现消息队列、任务队列等。在进行代码实现时,可以使用各种编程语言的Redis客户端库,根据需求调用相应的List操作,并确保代码中有适当的注释来解释每个操作的目的和影响。
使用Redis List实现消息队列具有一些优势,尤其在一些特定的场景下。以下是一些优势以及与其他专业消息队列系统的比较:
轻量级:
简单易用:
无需额外组件:
高性能:
支持持久化:
适用范围:
功能丰富性:
可扩展性:
管理和监控:
在选择是否使用Redis List实现消息队列时,需要权衡项目的规模、复杂性以及对消息队列功能的具体需求。对于简单的应用场景,Redis List提供了一种轻量级、简单易用的解决方案。然而,在面对复杂、大规模的系统需求时,专业消息队列系统可能更为适用。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
package fun.bo.config;
import lombok.extern.slf4j.Slf4j;
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 java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author xiaobo
*/
@Configuration
@Slf4j
public class RedisConfiguration {
@Bean
public <K,V> RedisTemplate<K,V> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
log.info("开始创建redis模板对象");
RedisTemplate<K, V> redisTemplate = new RedisTemplate<K, V>();
// 设置redis连接工厂对象
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 设置 redis key 的序列化器
// redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setKeySerializer(new GenericJackson2JsonRedisSerializer());
// 设置 redis 值的序列化器
// redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
@Bean
public ExecutorService messageProcessorExecutor() {
return Executors.newSingleThreadExecutor();
}
}
解释一下上面的代码
嘿,这是一个有趣的 Redis 配置文件!这里就像是搭建一座小型 Redis 王国的蓝图。首先,我们有一个名为 RedisConfiguration
的大臣(类),他负责整个 Redis 的建设。
在这个王国里,最重要的是创建一个 Redis 模板对象。这就像是设计一张神奇的宝藏地图,告诉你在 Redis 中如何寻找和存放宝藏(数据)。当我们开始创建这个宝藏地图时,就打印了一条消息,告诉大家“开始创建redis模板对象”,这样大家就知道有人在设计新的宝藏地图了。
然后,我们看到了一个名为 redisTemplate
的特殊工具,它是一种用来挖掘和存储宝藏的神奇工具。这个工具有两个设置,一个是告诉大家如何处理宝藏地图上的坐标(Key),另一个是告诉大家如何保存宝藏的外表和内涵(Value)。我们使用了一种叫做 GenericJackson2JsonRedisSerializer
的神奇卷轴,它可以帮我们把宝藏地图上的信息转换成可读的 JSON 格式,这样大家就能轻松理解宝藏的内容了。
最后,还有一个名为 messageProcessorExecutor
的小使者,他负责处理消息的传递。他就像是王国里的信使,专门负责将重要的消息传递给其他领地。为了确保他能够高效地完成任务,我们给他提供了一个单线程的小驿站(ExecutorService),这样他就不会在处理消息的路上迷路了。
总的来说,这段代码就是在告诉大家如何建设一个充满幽默和智慧的 Redis 王国,确保我们的宝藏得以妥善保管和传递。希望你喜欢这个 Redis 王国的建设计划!
解释为何我们使用泛型,而不是使用确定的类型
首先,泛型就像是一把神奇的钥匙,可以打开各种类型的宝藏宝盒。如果我们硬性规定只能用
这样的具体类型,那就好比在宝藏地图上写上:“只有字符串类型的宝藏坐标才能打开!” 这样一来,其他类型的宝藏就会感到被歧视了。
使用泛型,比如
,就像是一种宽容的设计。这让我们的 Redis 王国更加灵活,不再关心宝藏的类型是什么,只要是合法的类型,都可以自如地在宝藏地图上标记和挖掘。
另外,泛型还能提供一种类型安全的感觉,就像在挖掘宝藏的时候,我们知道自己挖到的是什么类型的财富。这就避免了在宝藏使用过程中出现强制类型转换的尴尬场面,确保我们能够安心地享受宝藏带来的快乐。
总而言之,使用泛型就是为了让我们的 Redis 王国更加灵活、友好,让不同类型的宝藏都能够在这个王国里找到自己的位置。泛型就像是为宝藏地图设计的通用钥匙,打开了各种各样的珍宝之门!
/**
* 推送消息到源List
*
* @param message 消息内容
*/
public void pushMessage(String message) {
redisTemplate.opsForList().rightPush(sourceList, message);
}
public void popMessagePersistence() {
String rightPopAndLeftPush = null;
try {
rightPopAndLeftPush = redisTemplate.opsForList().leftPop(sourceList, 3, TimeUnit.SECONDS);
if (rightPopAndLeftPush != null) {
log.info("处理消息: {}" , rightPopAndLeftPush);
}
}catch (Exception e) {
// 处理超时异常,进行持久化操作,消息没处理可进行后续处理
if (rightPopAndLeftPush != null) {
redisTemplate.opsForList().leftPush(backupList, rightPopAndLeftPush);
}
log.error(e.getMessage());
}
}
重要代码详解
leftPop(sourceList, 3, TimeUnit.SECONDS):这是一个有趣的操作,它执行了两个动作:
所以,这行代码就像是在 Redis 王国中进行一场左手右手的宝藏传递游戏,左手拿着一个宝藏,右手把它传递给另一个地方。如果在3秒内没有成功传递,就算了,毕竟宝藏也不能等太久。希望这个解释能帮助你更好地理解这段代码!
BRPOP命令也称为阻塞式读取,客户端在没有督导队列数据时,自动阻塞,直到有新的数据写入队列,再开始读取新数据。和消费者程序自己不停地调用RPOP命令相比,这种方式能节省CPU开销
对于消费者的调用,可以使用while(true),或者ScheduledExecutorService
public void startProcessingMessages() {
messageProcessorExecutor.execute(() -> {
while (true) {
popMessagePersistence();
}
});
}
public void startProcessingMessagesUseScheduled() {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(this::popMessagePersistence, 0, 1, TimeUnit.SECONDS);
}
第一个方法:
这个方法启动了一个消息处理的执行器(messageProcessorExecutor),并在其中创建了一个无限循环。在每次循环中,它调用了 popMessagePersistence() 方法。这就像是一个永不停歇的消息处理机器,不断地从某个地方弹出消息并进行处理。由于使用了线程池(messageProcessorExecutor,可能是之前配置的单线程池),这样的设计可以在后台异步地处理消息,而不会阻塞主线程。
第二个方法:
这个方法使用了 ScheduledExecutorService
,它是一个用于定时执行任务的执行器。在这个方法中,它创建了一个调度器(scheduler
),并使用 scheduleAtFixedRate
方法来定期执行 popMessagePersistence()
方法。
具体来说,它的调度规则是:
this::popMessagePersistence
:表示要执行的任务,即调用 popMessagePersistence()
方法。0
:表示首次执行任务的延迟时间,这里是0,即立即执行。1
:表示每次执行任务的时间间隔,这里是1秒。TimeUnit.SECONDS
:表示时间间隔的单位,这里是秒。这个方法的设计更加精确,按照固定的时间间隔执行任务,比起无限循环的方式更加灵活。它适用于需要定期执行任务的场景,比如轮询某个资源或者定时处理一些事务。
深深感谢你阅读完整篇文章,希望你从中获得了些许收获。如果觉得有价值,欢迎点赞、收藏,并关注我的更新,期待与你共同分享更多技术与思考。