Redis基于Stream实现消息队列
先上效果图
需要使用redis5.0以上版本,使用了redis5.0新增的数据类型Stream,使用block表示阻塞等待,直到有新的数据添加
这里不需要再redis新增Stream和消息组
pom文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.java</groupId> <artifactId>redis-study</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.3</version> <relativePath /> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
配置文件
server.port=8080 spring.redis.database=0 spring.redis.host=192.168.0.101 spring.redis.port=6379 spring.redis.password=123
代码结构
package com.java; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; 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.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * @Description: * @Author: qiuxie * @Create: 2023/6/15 18:38 */ @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); template.setConnectionFactory(factory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用jackson template.setValueSerializer(jackson2JsonRedisSerializer); // hash的value序列化方式采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } }
package com.java.service.impl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.connection.stream.MapRecord; import org.springframework.data.redis.core.StreamOperations; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.stream.StreamListener; import org.springframework.stereotype.Component; import java.util.Map; /** * @Description: 消费者 * @Author: qiuxie * @Create: 2023/6/15 18:30 */ @Component public class RedisConsumer implements StreamListener<String, MapRecord<String,String,String>> { private final Logger log= LoggerFactory.getLogger(RedisConsumer.class); public static final String streamName= "qiuxieStream"; public static final String groupName= "qiuxieGroup"; public final String consumerName= "emailConsumer"; @Autowired private StringRedisTemplate stringRedisTemplate; @Override public void onMessage(MapRecord<String, String, String> message) { log.warn("【消费者】Stream名称:{},消息内容:{}",streamName,message.getValue()); //获取生产者消息 Map<String, String> msgMap = message.getValue(); log.info("msgMap:{}",msgMap); //业务逻辑 略 //ps:通过 msgMap.get("key") 拿到需要参数执行业务逻辑 StreamOperations<String, String, String> streamOperations = stringRedisTemplate.opsForStream(); //消息应答 streamOperations.acknowledge( streamName,groupName,message.getId() ); } }
package com.java.service.impl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.stream.Consumer; import org.springframework.data.redis.connection.stream.MapRecord; import org.springframework.data.redis.connection.stream.ReadOffset; import org.springframework.data.redis.connection.stream.StreamOffset; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.data.redis.stream.StreamMessageListenerContainer; import java.time.Duration; import java.util.Collections; /** * @Description: * @Author: qiuxie * @Create: 2023/6/15 18:34 */ @Configuration public class RedisStreamConfig { @Autowired private RedisConsumer emailConsumer; @Autowired private RedisTemplate<String,Object> redisTemplate; private final Logger log= LoggerFactory.getLogger(RedisStreamConfig.class); @Bean public StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, MapRecord<String,String,String>> emailListenerContainerOptions(){ StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); return StreamMessageListenerContainer.StreamMessageListenerContainerOptions .builder() //block读取超时时间 .pollTimeout(Duration.ofSeconds(3)) //count 数量(一次只获取一条消息) .batchSize(1000) //序列化规则 .serializer( stringRedisSerializer ) .build(); } /** * 开启监听器接收消息 */ @Bean public StreamMessageListenerContainer<String, MapRecord<String,String,String>> emailListenerContainer(RedisConnectionFactory factory, StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, MapRecord<String,String,String>> streamMessageListenerContainerOptions){ StreamMessageListenerContainer<String,MapRecord<String,String,String>> listenerContainer = StreamMessageListenerContainer.create(factory, streamMessageListenerContainerOptions); //如果 流不存在 创建 stream 流 if( !redisTemplate.hasKey(RedisConsumer.streamName)){ redisTemplate.opsForStream().add(RedisConsumer.streamName, Collections.singletonMap("", "")); log.info("初始化stream:{} success", RedisConsumer.streamName); } //创建消费者组 try { redisTemplate.opsForStream().createGroup(RedisConsumer.streamName, RedisConsumer.groupName); } catch (Exception e) { log.info("消费者组:{} 已存在", RedisConsumer.groupName); } //注册消费者 消费者名称,从哪条消息开始消费,消费者类 // > 表示没消费过的消息 // $ 表示最新的消息 listenerContainer.receive( Consumer.from(RedisConsumer.groupName, emailConsumer.consumerName), StreamOffset.create(RedisConsumer.streamName, ReadOffset.lastConsumed()), emailConsumer ); listenerContainer.start(); return listenerContainer; } }
package com.java.controller.front; import com.java.service.impl.RedisConsumer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.connection.stream.ObjectRecord; import org.springframework.data.redis.connection.stream.RecordId; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StreamOperations; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @Description: 生产者 * @Author: qiuxie * @Create: 2023/6/15 17:02 */ @RestController public class RedisProducerController { @Autowired private RedisTemplate<String,Object> redisTemplate; @GetMapping("/generateData") public String generateData(String message) { StreamOperations<String, Object, Object> streamOperations = redisTemplate.opsForStream(); RecordId recordId = streamOperations.add(ObjectRecord.create(RedisConsumer.streamName, message)); System.out.println("Published message with record ID: " + recordId); return "Success"; } }
package com.java; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @Description: 设置年轻代和老年代的比例 1:4 * E:S0:S1 8:1:1 * @Author: Yourheart * @Create: 2023/4/21 11:27 */ @SpringBootApplication() public class MysqlServiceApplication { public static void main(String[] args) { SpringApplication.run(MysqlServiceApplication.class, args); } }
使用postman测试,127.0.0.1:8080/generateData?message=aaaaa