redis stream数据类型

介绍 Redis 5.0 引入的 Stream 数据类型,作为高效的消息队列解决方案,可替代传统基于 List 的发布订阅模式,具备持久化特性,适用于常规 MQ 场景。文章详解了 Stream 命令,包括数据添加、消费组创建、消息读取及确认等操作,并提供了 Spring Boot 集成示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

redis5新加了stream流类型,可以代替之前基于list的发布订阅,采用类似kafka的设计方式,而且支持持久化,常规场景mq代替品,是广大码农的福音,如果不是具有巨大流量,则可以完全弃用其他单独

(因为是key设计模式,所以基本是单节点承受压力,所以单个topic流量不能过于巨大,不过万级不是问题)

此处也可以通过key规则指定到不同节点,以分摊stream带来的压力  ${数字} eg. ${0}aa ${0}bb,这两个key都会落到一个节点上,此时只会按0计算 

以下是基本命令:

增加一条数据: xadd mzvd:vedio * data aaa

创建一个消费组: XGROUP create mzvd:vedio g1 $

通过组读取数据: XREADGROUP group g1 cons1 count 1 block 0

处理完消息进行回执:XACK  mzvd:vedio group 1000001

如果采用spring-data-redis,springboot版本到2.2.7,redisTemaplet已经有了封装,我这提供了3个类,分别为消费者、提供者、属性配置类



import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.apache.commons.collections.map.HashedMap;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
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.Record;
import org.springframework.data.redis.connection.stream.RecordId;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;

import com.alibaba.fastjson.JSONObject;

import cn.stylefeng.guns.base.utils.SpringUtil;
import io.lettuce.core.XReadArgs.StreamOffset;
import lombok.extern.slf4j.Slf4j;

/**
 * @author yy
 * @date 2020年5月12日
 * @description redis队列
 *
 */
@Slf4j
public abstract class RedisMQConsumer implements InitializingBean{
	
	private RedisTemplate<String, String> redisTemplate;
	private RedisMQProperty mqProperty;
	private static String key="";
	
	public void setMqProperty(RedisMQProperty mqProperty) {
		this.mqProperty = mqProperty;
	}

	/**
	 * @author yy
	 * @date 2020年5月12日
	 * @description 初始化信息
	 */
	public void initConsumer(RedisTemplate<String, String> redisTemplate,RedisMQProperty mqProperty) {
		
		this.redisTemplate=redisTemplate;
		this.mqProperty=mqProperty;
		
		synchronized(key) {
			
			log.info("MQ初始化信息开始");
			//初始化队列
			key = RedisMQProperty.prex+mqProperty.topic;
			RecordId recordId = redisTemplate.boundStreamOps(key).add(new HashedMap() {{put("-1", "-1");}});
			this.redisTemplate.boundStreamOps(key).delete(recordId.getValue());
			//初始化组
			this.redisTemplate.boundStreamOps(key).destroyGroup(mqProperty.group);
			redisTemplate.boundStreamOps(key).createGroup(ReadOffset.latest(), mqProperty.group);
			log.info("MQ初始化信息完毕");
			
			try {
				TimeUnit.SECONDS.sleep(1L);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//触发
			new Thread(new Runnable() {
				@Override
				public void run() {
					// TODO Auto-generated method stub
					while(true) {
						event();
					}
				}
			}).start();
			
		}
		
	}
	
	private void event() {
		List<MapRecord<String, Object, Object>>  list=
				this.redisTemplate.boundStreamOps(RedisMQConsumer.key).read(Consumer.from(mqProperty.group, mqProperty.consname),ReadOffset.lastConsumed());
		
		if(list==null || list.isEmpty())
			return;
		
		try {
			message(list.get(0).getValue().get("data").toString());
		} catch (Exception e) {
			handlerException(list.get(0).getValue(), e);
		}
		
		this.redisTemplate.boundStreamOps(key).acknowledge(mqProperty.group, list.get(0).getId().getValue());
	}
	/**
	 * @author yy
	 * @date 2020年5月12日
	 * @description 处理消息
	 * @param o
	 */
	public abstract void message(String json) ;
	
	/**
	 * @author yy
	 * @date 2020年5月12日
	 * @description 处理异常
	 * @param map
	 * @param exception
	 */
	protected void handlerException(Map<Object, Object> map,Exception exception) {
		log.info("mq消费异常:"+JSONObject.toJSONString(map),exception);
	}
	
}

import java.util.Map;

import javax.annotation.Resource;

import org.apache.commons.collections.map.HashedMap;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSONObject;

import lombok.extern.slf4j.Slf4j;

/**
 * @author yy
 * @date 2020年5月12日
 * @description redis队列
 *
 */
@Component
public class RedisMQProvier {
	@Resource
	private RedisTemplate<String, String> redisTemplate;

	private String prex=RedisMQProperty.prex;
	

	
	/**
	 * @author yy
	 * @date 2020年5月12日
	 * @description 发布队列
	 * @param <T>
	 * @param topic
	 * @param obj
	 */
	public <T> void pub(String topic,T obj) {
		
		Map<Object,Object> data = new HashedMap();
		data.put("data", JSONObject.toJSONString(obj));
		redisTemplate.boundStreamOps(prex+topic).add(data);
		redisTemplate.boundStreamOps(prex+topic).trim(20000);
	}
	
	
	
	int i=0;
//	@Scheduled(fixedDelay = 10)
	private void pubSomething() {
	
		pub("vedio", i);
		i++;
		System.out.println(i);
//		pub("vedio", System.currentTimeMillis()+"====>");
	}
}

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.stereotype.Component;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
/**
 * @author yy
 * @date 2020年5月12日
 * @description redismq配置
 *
 */
@Component
@NoArgsConstructor
@AllArgsConstructor
public class RedisMQProperty {


	public static final String prex="mq:";
	public String topic;
	public String group;
	public String consname;
	
	public void setRedisMQProperty(String topic, String group, String consname) {
		this.topic = topic;
		this.group = group;
		this.consname = consname;
	}
	
	
}

配置文件

#redismq
redis.mq.vedio.topic=vedio
redis.mq.vedio.group=g1
redis.mq.vedio.consname=c1

ps.

以上配置是 按分组进行消费,自建Consumer继承RedisMQConsumer即可,可以建立多个Consumer,默认设置为 不同分组互斥消费。如需组之间重复消费,设置ReadOfset从0开始消费即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值