使用Redis實(shí)現(xiàn)輕量級消息隊列
使用消息中間件如RabbitMQ或kafka雖然好,但也給服務(wù)器帶來很大的內(nèi)存開銷,當(dāng)系統(tǒng)的業(yè)務(wù)量,并發(fā)量不高時,考慮到服務(wù)器和維護(hù)成本,可考慮使用Redis實(shí)現(xiàn)一個輕量級的消息隊列,實(shí)現(xiàn)事件監(jiān)聽的效果。下面介紹下Redis實(shí)現(xiàn)消息隊列的三種形式。
方式一 Redis Pub/Sub(適用于廣播通知)
Redis Pub/Sub 適用于 實(shí)時消息推送,但不支持消息持久化,如果消費(fèi)者掉線,消息會丟失。
(1) 發(fā)布消息(生產(chǎn)者)
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final StringRedisTemplate redisTemplate;
public OrderService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void createOrder(Long orderId) {
System.out.println("訂單創(chuàng)建成功: " + orderId);
// 發(fā)布消息
redisTemplate.convertAndSend("order.channel", orderId.toString());
}
}
(2) 訂閱消息(消費(fèi)者)
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Service;
@Service
public class NotificationService implements MessageListener {
@Override
public void onMessage(Message message, byte[] pattern) {
String orderId = message.toString();
System.out.println("【通知服務(wù)】收到訂單創(chuàng)建消息:" + orderId);
}
}
(3) 注冊 Redis 監(jiān)聽器
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
@Configuration
public class RedisPubSubConfig {
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(listenerAdapter, new PatternTopic("order.channel"));
return container;
}
@Bean
public MessageListenerAdapter listenerAdapter(NotificationService receiver) {
return new MessageListenerAdapter(receiver, "onMessage");
}
}
缺點(diǎn)
- 無持久化,消費(fèi)者掉線后無法重新獲取消息。
- 不支持消費(fèi)組,多個消費(fèi)者同時訂閱時,所有都會收到消息(無法負(fù)載均衡)。
方式二:Redis List(適用于任務(wù)隊列)
使用 Redis List(LPUSH + RPOP)可以實(shí)現(xiàn)簡單的任務(wù)隊列,適用于任務(wù)異步處理,但不支持回溯消費(fèi)。
(1) 生產(chǎn)者(推送任務(wù))
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final StringRedisTemplate redisTemplate;
public OrderService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void createOrder(Long orderId) {
System.out.println("訂單創(chuàng)建成功: " + orderId);
// 推送到隊列
redisTemplate.opsForList().leftPush("order.queue", orderId.toString());
}
}
(2) 消費(fèi)者(輪詢獲取任務(wù))
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Service
public class NotificationService {
private final StringRedisTemplate redisTemplate;
public NotificationService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Scheduled(fixedDelay = 5000) // 每5秒輪詢一次
public void processOrderQueue() {
String orderId = redisTemplate.opsForList().rightPop("order.queue");
if (orderId != null) {
System.out.println("【通知服務(wù)】處理訂單:" + orderId);
}
}
}
要想消費(fèi)者能監(jiān)聽到消息并進(jìn)行處理,需要在方法上添加@Scheduled注解,同時在服務(wù)啟動類中添加@EnableScheduling注解,或者在配置類添加
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableScheduling
public class SchedulingConfig {
}缺點(diǎn)
- 無消費(fèi)組,多個消費(fèi)者時需要自行分配任務(wù),可能會造成任務(wù)重復(fù)消費(fèi)或丟失。
- 無持久化保障,如果任務(wù)未處理完,Redis 發(fā)生故障,任務(wù)可能會丟失。
方式三:Redis Stream(推薦,支持持久化 + 消費(fèi)組)
Redis Stream 是 Redis 6.0 之后的特性,類似于 Kafka,支持持久化、消費(fèi)組、多消費(fèi)者模式。
(1) 生產(chǎn)者(推送事件)
import org.springframework.data.redis.connection.stream.ObjectRecord;
import org.springframework.data.redis.connection.stream.StreamRecords;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final StringRedisTemplate redisTemplate;
public OrderService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void createOrder(Long orderId) {
System.out.println("訂單創(chuàng)建成功: " + orderId);
// 推送到 Redis Stream
ObjectRecord<String, String> record = StreamRecords.newRecord()
.ofObject(orderId.toString())
.withStreamKey("order.stream");
redisTemplate.opsForStream().add(record);
}
}
(2) 消費(fèi)者(監(jiān)聽事件)
import org.springframework.data.redis.connection.stream.MapRecord;
import org.springframework.data.redis.connection.stream.ReadOffset;
import org.springframework.data.redis.connection.stream.StreamMessageListenerContainer;
import org.springframework.data.redis.connection.stream.StreamOffset;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.stream.StreamListener;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.time.Duration;
import java.util.Collections;
@Service
public class NotificationService implements StreamListener<String, MapRecord<String, String, String>> {
private final StringRedisTemplate redisTemplate;
private final StreamMessageListenerContainer<String, MapRecord<String, String, String>> listenerContainer;
public NotificationService(StringRedisTemplate redisTemplate,
StreamMessageListenerContainer<String, MapRecord<String, String, String>> listenerContainer) {
this.redisTemplate = redisTemplate;
this.listenerContainer = listenerContainer;
}
@PostConstruct
public void startListening() {
listenerContainer.receive(StreamOffset.fromStart("order.stream"), this);
}
@Override
public void onMessage(MapRecord<String, String, String> message) {
String orderId = message.getValue().values().iterator().next();
System.out.println("【通知服務(wù)】訂單 " + orderId + " 創(chuàng)建成功!");
}
}
優(yōu)點(diǎn)
- 持久化存儲,即使 Redis 重啟,消息不會丟失。
- 支持消費(fèi)組,多個消費(fèi)者可以負(fù)載均衡地消費(fèi)消息。
- 支持回溯,可以讀取歷史消息。
總結(jié)
| 方案 | 適用場景 | 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|---|---|
| Pub/Sub | 即時消息通知 | 低延遲 | 無持久化,消費(fèi)者掉線丟消息 |
| List | 簡單任務(wù)隊列 | 輕量級 | 無消費(fèi)組,任務(wù)可能丟失 |
| Stream | 高級事件流處理 | 持久化、消費(fèi)組 | 復(fù)雜度較高 |
如果需求是輕量級隊列,推薦 Redis Stream,它類似 Kafka,支持消費(fèi)組和持久化,比 Redis List 更穩(wěn)定。
到此這篇關(guān)于使用Redis實(shí)現(xiàn)輕量級消息隊列的文章就介紹到這了,更多相關(guān)Redis 輕量級消息隊列內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Golang實(shí)現(xiàn)Redis分布式鎖解決秒殺問題
這篇文章主要給大家介紹了使用Golang實(shí)現(xiàn)Redis分布式鎖解決秒殺問題,文中有詳細(xì)的代碼示例供大家參考,具有一定的參考價值,需要的朋友可以參考下2023-08-08
golang協(xié)程池模擬實(shí)現(xiàn)群發(fā)郵件功能
這篇文章主要介紹了golang協(xié)程池模擬實(shí)現(xiàn)群發(fā)郵件功能,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-05-05
linux下通過go語言獲得系統(tǒng)進(jìn)程cpu使用情況的方法
這篇文章主要介紹了linux下通過go語言獲得系統(tǒng)進(jìn)程cpu使用情況的方法,實(shí)例分析了Go語言使用linux的系統(tǒng)命令ps來分析cpu使用情況的技巧,需要的朋友可以參考下2015-03-03
golang的HTTP基本認(rèn)證機(jī)制實(shí)例詳解
這篇文章主要介紹了golang的HTTP基本認(rèn)證機(jī)制,結(jié)合實(shí)例形式較為詳細(xì)的分析了HTTP請求響應(yīng)的過程及認(rèn)證機(jī)制實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-07-07
Go語言中的Web服務(wù)器開發(fā)詳細(xì)指南
go提供了一系列用于創(chuàng)建web服務(wù)器的標(biāo)準(zhǔn),而非常簡單,下面這篇文章主要給大家介紹了關(guān)于Go語言中Web服務(wù)器開發(fā)的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-07-07
golang語言http協(xié)議get拼接參數(shù)操作
這篇文章主要介紹了golang語言http協(xié)議get拼接參數(shù)操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12

