背景
在未引入rocketMq,kafka等消息中间件的情况下,如何用redis实现消息机制,就此封装了一个redis消息工具包。
一、前期准备
需要引入二方包
二、消息工具包中的代码 1.定义消息体等基本对象redis.clients jedis3.6.3 org.apache.commons commons-lang33.12.0 com.alibaba fastjson1.2.79 org.springframework.data spring-data-redis2.6.0
TkMessage 消息体
public class TkMessage extends AbstractEntry {
private GroupTopicEnum groupTopic;
private String tag;
private String key;
private String content;
public GroupTopicEnum getGroupTopic() {
return groupTopic;
}
public void setGroupTopic(GroupTopicEnum groupTopic) {
this.groupTopic = groupTopic;
}
public String getTag() {
return tag;
}
public void setTag(String tag) {
this.tag = tag;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public static Builder create() {
return new Builder();
}
public static class Builder {
private GroupTopicEnum groupTopic;
private String tag;
private String key;
private String content;
public TkMessage build() {
TkMessage message = new TkMessage();
message.setGroupTopic(this.groupTopic);
message.setTag(this.tag);
message.setKey(this.key);
message.setContent(this.content);
return message;
}
public Builder setGroupTopic(GroupTopicEnum groupTopic) {
this.groupTopic = groupTopic;
return this;
}
public Builder setTag(String tag) {
this.tag = tag;
return this;
}
public Builder setKey(String key) {
this.key = key;
return this;
}
public Builder setContent(String content) {
this.content = content;
return this;
}
public GroupTopicEnum getGroupTopic() {
return groupTopic;
}
public String getTag() {
return tag;
}
public String getKey() {
return key;
}
public String getContent() {
return content;
}
}
}
AbstractEntry
public class AbstractEntry implements Serializable {
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
GroupTopicEnum
public class GroupTopicEnum extends AbstractEntry {
private String group;
private String topic;
public GroupTopicEnum() {
}
public GroupTopicEnum(String group, String topic) {
this.group = group;
this.topic = topic;
}
public String getGroup() {
return group;
}
public void setGroup(String group) {
this.group = group;
}
public String getTopic() {
return topic;
}
public void setTopic(String topic) {
this.topic = topic;
}
public boolean equals(GroupTopicEnum obj) {
boolean r = super.equals(obj);
if (!r) {
r = this.group.equals(obj.getGroup()) && this.topic.equals(obj.getTopic());
}
return r;
}
}
2、消息生产者封装
MessageProducer
package io.shulie.jmeter.tool.redis.message;
import io.shulie.jmeter.tool.redis.RedisConfig;
import io.shulie.jmeter.tool.redis.domain.TkMessage;
import io.shulie.jmeter.tool.redis.util.JsonUtil;
import io.shulie.jmeter.tool.redis.util.MessageUtil;
import io.shulie.jmeter.tool.redis.util.RedisUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.stream.*;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.*;
public class MessageProducer {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private static final Map
3、消息消费者封装
IMessage
public interface IMessageConsumerListener {
public GroupTopicEnum getGroupTopic();
public String group();
public String getConsumer();
public List getTags();
public boolean receive(TkMessage message);
}
AbstractMessageConsumerListener
public abstract class AbstractMessageConsumerListener implements IMessageConsumerListener {
@Override
public String group() {
return "default";
}
@Override
public String getConsumer() {
return "consumer-1";
}
}
MessageConsumer
package io.shulie.jmeter.tool.redis.message; import io.shulie.jmeter.tool.redis.RedisConfig; import io.shulie.jmeter.tool.redis.domain.TkMessage; import io.shulie.jmeter.tool.redis.util.JsonUtil; import io.shulie.jmeter.tool.redis.util.MessageUtil; import io.shulie.jmeter.tool.redis.util.RedisUtil; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.Range; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.stream.*; import org.springframework.data.redis.core.StreamOperations; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.stream.StreamListener; import org.springframework.data.redis.stream.StreamMessageListenerContainer; import org.springframework.util.CollectionUtils; import java.time.Duration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.*; public class MessageConsumer implements StreamListener3、redis配置链接工具> { private final Logger logger = LoggerFactory.getLogger(this.getClass()); public static long DEATH_LINE = 300; private static ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); private static final Map
package io.shulie.jmeter.tool.redis.util;
import io.shulie.jmeter.tool.redis.RedisConfig;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.stream.StreamInfo;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class RedisUtil {
private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class);
private final static Map jedisConnectionFactoryMap = new HashMap<>();
private final static Map stringRedisTemplateMap = new HashMap<>();
public static void checkAndCreateStreamGroup(StringRedisTemplate template, String streamKey, String group) {
StreamInfo.XInfoGroups groups = null;
try {
groups = template.opsForStream().groups(streamKey);
} catch (Exception e) {
logger.error("groups方法执行失败", e);
}
if (null == groups || groups.isEmpty() || groups.stream().noneMatch(g -> g.groupName().equals(group))) {
template.opsForStream().createGroup(streamKey, group);
}
}
public static JedisConnectionFactory getJedisConnectionFactory(RedisConfig config) {
JedisConnectionFactory factory = jedisConnectionFactoryMap.get(config);
if (null == factory) {
factory = buildJedisConnectionFactory(config);
if (null != factory) {
jedisConnectionFactoryMap.put(config, factory);
}
}
return factory;
}
public static StringRedisTemplate getStringRedisTemplate(RedisConfig config) {
JedisConnectionFactory factory = getJedisConnectionFactory(config);
return getStringRedisTemplate(factory);
}
public static StringRedisTemplate getStringRedisTemplate(RedisConnectionFactory factory) {
if (null == factory) {
return null;
}
StringRedisTemplate template = stringRedisTemplateMap.get(factory);
if (null == template) {
template = buildStringRedisTemplate(factory);
if (null != template) {
stringRedisTemplateMap.put(factory, template);
}
}
return template;
}
private static JedisConnectionFactory buildJedisConnectionFactory(RedisConfig config) {
if (null == config) {
return null;
}
JedisConnectionFactory factory;
if (StringUtils.isNotBlank(config.getClusterNodes())) {
factory = buildClusterJedisConnectionFactory(config);
} else if (StringUtils.isNotBlank(config.getNodes())) {
factory = buildSentineJedisConnectionFactory(config);
} else {
factory = buildStandaloneJedisConnectionFactory(config);
}
return factory;
}
private static StringRedisTemplate buildStringRedisTemplate(RedisConnectionFactory factory) {
if (null == factory) {
return null;
}
StringRedisTemplate template = new StringRedisTemplate();
template.setDefaultSerializer(new StringRedisSerializer());
template.setConnectionFactory(factory);
template.afterPropertiesSet();
return template;
}
private static JedisConnectionFactory buildSentineJedisConnectionFactory(RedisConfig redisConfig) {
if (StringUtils.isBlank(redisConfig.getNodes())) {
return null;
}
RedisSentinelConfiguration configuration = new RedisSentinelConfiguration();
configuration.setMaster(redisConfig.getMaster());
List redisNodes = Stream.of(redisConfig.getNodes()).filter(StringUtils::isNotBlank)
.map(s -> s.split(","))
.flatMap(Arrays::stream)
.filter(StringUtils::isNotBlank)
.map(s -> s.split(":"))
.map(a -> new RedisNode(a[0], NumberUtils.toInt(a[1], 6379)))
.distinct()
.collect(Collectors.toList());
configuration.setSentinels(redisNodes);
if (StringUtils.isNotBlank(redisConfig.getPassword())) {
configuration.setPassword(redisConfig.getPassword());
}
if (null != redisConfig.getDatabase()) {
configuration.setDatabase(redisConfig.getDatabase());
}
// JedisClientConfiguration clientConfiguration = buildJedisClientConfiguration(redisConfig);
// JedisPoolConfig config = buildJedisPoolConfig(redisConfig);
JedisConnectionFactory factory = new JedisConnectionFactory(configuration);
// if (null != clientConfiguration) {
// factory = new JedisConnectionFactory(configuration, clientConfiguration);
// } else {
// factory = new JedisConnectionFactory(configuration);
// }
// if (null != config) {
// factory.setPoolConfig(config);
// }
factory.afterPropertiesSet();
return factory;
}
private static JedisConnectionFactory buildClusterJedisConnectionFactory(RedisConfig redisConfig) {
if (StringUtils.isBlank(redisConfig.getClusterNodes())) {
return null;
}
List redisNodes = Stream.of(redisConfig.getClusterNodes()).filter(StringUtils::isNotBlank)
.map(s -> s.split(","))
.flatMap(Arrays::stream)
.filter(StringUtils::isNotBlank)
.map(s -> s.split(":"))
.map(a -> new RedisNode(a[0], NumberUtils.toInt(a[1], 6379)))
.distinct()
.collect(Collectors.toList());
RedisClusterConfiguration configuration = new RedisClusterConfiguration();
configuration.setClusterNodes(redisNodes);
if (StringUtils.isNotBlank(redisConfig.getPassword())) {
configuration.setPassword(redisConfig.getPassword());
}
// JedisClientConfiguration clientConfiguration = buildJedisClientConfiguration(redisConfig);
// JedisPoolConfig config = buildJedisPoolConfig(redisConfig);
JedisConnectionFactory factory = new JedisConnectionFactory(configuration);
// if (null != clientConfiguration) {
// factory = new JedisConnectionFactory(configuration, clientConfiguration);
// } else {
// factory = new JedisConnectionFactory(configuration);
// }
// if (null != config) {
// factory.setPoolConfig(config);
// }
factory.afterPropertiesSet();
return factory;
}
private static JedisConnectionFactory buildStandaloneJedisConnectionFactory(RedisConfig redisConfig) {
//单机模式
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
if (StringUtils.isNotBlank(redisConfig.getHost())) {
configuration.setHostName(redisConfig.getHost());
}
if (null != redisConfig.getPort()) {
configuration.setPort(redisConfig.getPort());
}
if (StringUtils.isNotBlank(redisConfig.getPassword())) {
configuration.setPassword(redisConfig.getPassword());
}
if (null != redisConfig.getDatabase()) {
configuration.setDatabase(redisConfig.getDatabase());
}
JedisConnectionFactory factory = new JedisConnectionFactory(configuration);
factory.afterPropertiesSet();
return factory;
}
// private static JedisClientConfiguration buildJedisClientConfiguration(RedisConfig redisConfig) {
// JedisClientConfiguration.JedisClientConfigurationBuilder builder = null;
// if (null != redisConfig.getTimeout()) {
// builder = JedisClientConfiguration.builder();
// builder.connectTimeout(Duration.ofSeconds(redisConfig.getTimeout()));
// }
// if (null != redisConfig.getSoTimeOut()) {
// if (null == builder) {
// builder = JedisClientConfiguration.builder();
// }
// builder.readTimeout(Duration.ofSeconds(redisConfig.getSoTimeOut()));
// }
// if (null != builder) {
// return builder.build();
// }
// return null;
// }
// private static JedisPoolConfig buildJedisPoolConfig(RedisConfig redisConfig) {
// JedisPoolConfig config = null;
// if (null != redisConfig.getMaxTotal()) {
// config = new JedisPoolConfig();
// config.setMaxTotal(redisConfig.getMaxTotal());
// }
// if (null != redisConfig.getMaxIdle()) {
// if (null == config) {
// config = new JedisPoolConfig();
// }
// config.setMaxIdle(redisConfig.getMaxIdle());
// }
// return config;
// }
}
redis配置文件,该配置文件内容较少,可能不能满足redis多中链接需要,有需要同学自己扩展
public class RedisConfig {
private String nodes;
private String master;
private String host;
private Integer port;
private Integer database;
private String password;
private String clusterNodes;
private Integer maxIdle;
private Integer maxTotal;
private Integer timeout;
private Integer soTimeOut;
}
4、其他工具类
*
* @param defValue 默认值
* @param t 取值对象
* @param func 取值方法,从对象中取值的方法
* @param 取值对象类型
* @param 返回值对象类型
*/
public static R getValue(R defValue, T t, Function func) {
R result = defValue;
if (null != t) {
R r = func.apply(t);
if (null != r) {
if (r instanceof String) {
if (StringUtils.isNotBlank((String)r)) {
result = r;
}
} else if (r instanceof List) {
if (!CollectionUtils.isEmpty((List>)r)) {
result = r;
}
} else if (r instanceof Map) {
if (((Map, ?>) r).isEmpty()) {
result = r;
}
} else {
result = r;
}
}
}
return result;
}
public static boolean matchTags(IMessageConsumerListener listener, TkMessage message) {
if (StringUtils.isBlank(message.getTag())) {
return true;
}
List tags = listener.getTags();
if (CollectionUtils.isEmpty(tags)) {
return false;
}
try {
return tags.stream().filter(Objects::nonNull).anyMatch(s -> matches(message.getTag(), s));
} catch (Exception e) {
log.error("", e);
}
return false;
}
private static boolean matches(String s, String match) {
if (s.equals(match)) {
return true;
}
if ("*".equals(match)) {
return true;
}
if (match.startsWith("*") && match.endsWith("*")) {
match = match.substring(1, match.length()-1);
return s.contains(match);
} else if (match.startsWith("*")) {
match = match.substring(1);
return s.endsWith(match);
} else if (match.endsWith("*")) {
match = match.substring(0, match.length()-1);
return s.startsWith(match);
}
return false;
}
}
public class JsonUtil {
private static final Logger log = LoggerFactory.getLogger(JsonUtil.class);
public static String toJson(Object o) {
if (null == o) {
return null;
}
try {
return JSON.toJSONString(o);
} catch (Exception e) {
log.error("toJson failed!o="+o);
}
return null;
}
public static JSONObject parse(String text) {
if (StringUtils.isBlank(text)) {
return null;
}
JSONObject json = null;
try {
json = JSON.parseObject(text);
} catch (Exception e) {
log.error("parse json failed!text="+text);
}
return json;
}
public static T parseObject(String text, Class clazz) {
if (StringUtils.isBlank(text)) {
return null;
}
T result = null;
try {
result = JSON.parseObject(text, clazz);
} catch (Exception e) {
log.error("parse json to object class failed!text="+text);
}
return result;
}
public static T parseObject(String text, TypeReference type) {
if (StringUtils.isBlank(text)) {
return null;
}
T result = null;
try {
result = JSON.parseObject(text, type);
} catch (Exception e) {
log.error("parse json to object type failed!text="+text);
}
return result;
}
public static JSONArray parseArray(String text) {
if (StringUtils.isBlank(text)) {
return null;
}
JSONArray result = null;
try {
result = JSON.parseArray(text);
} catch (Exception e) {
log.error("json parseArray failed!text="+text);
}
return result;
}
public static List parseArray(String text, Class clazz) {
if (StringUtils.isBlank(text)) {
return null;
}
List result = null;
try {
result = JSON.parseArray(text, clazz);
} catch (Exception e) {
log.error("json parseArray failed!text="+text);
}
return result;
}
}
三、使用
1、消费者
@Slf4j
@Service
public class MessageConsumerService extends AbstractMessageConsumerListener {
public static final GroupTopicEnum groupTopic = new GroupTopicEnum("yanbatest3", "default-test");
// @Resource
// private StringRedisTemplate stringRedisTemplate;
@Resource
private RedisConfig redisConfig;
private MessageConsumer messageConsumer;
@PostConstruct
private void init() {
messageConsumer = MessageConsumer.getInstance(redisConfig);
messageConsumer.setThreadPool(Executors.threadPool);
messageConsumer.register(this);
}
@Override
public GroupTopicEnum getGroupTopic() {
return groupTopic;
}
@Override
public List getTags() {
return Lists.newArrayList("*");
}
@Override
public boolean receive(TkMessage message) {
log.info("message="+ JsonUtil.toJson(message));
return true;
}
}
2、生产者
@Slf4j
@Service
public class MessageProducerService {
// @Resource
// private StringRedisTemplate stringRedisTemplate;
@Resource
private RedisConfig redisConfig;
private MessageProducer messageProducer;
@PostConstruct
public void init() {
messageProducer = MessageProducer.getInstance(redisConfig);
}
public boolean send(String key, String content) {
TkMessage message = TkMessage.create()
.setGroupTopic(MessageConsumerService.groupTopic)
.setTag("tagA")
.setKey(key)
.setContent(content)
.build();
return send(message);
}
public boolean send(TkMessage message) {
try {
return messageProducer.send(message);
} catch (Exception e) {
log.error("send message error", e);
}
return false;
}
}
总结
这里要特别说明一下,消费者IMessageConsumerListener定义多个方法,其中group方法表示消费组,
如果多个消费者返回的值相同表示消费者共同竞争,只会有一个消费者收到消息,即消息单点消费;
如果多个消费者返回的值不同表示消费者都需要收到消息,即消息广播,所有消费者都进行消费;



