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开始消费即可