使用Redis实现消息队列

使用Redis实现消息队列

消息队列

什么是消息队列?

  • 消息队列就是一个保存消息的容器,它具有先进先出的特性

为什么会出现消息队列?

  • 异步:场景的B/S架构下,客户端向服务器发送请求,但是服务器处理这个消息需要花费很长的时间,如果客户端一直等到服务器处理完消息,会造成客户端的系统资源浪费;而使用消息队列后,服务器直接将消息推送到消息队列中,由专门的处理消息程序处理消息,这样客户端就不必花费大量时间等待服务器的响应了。
  • 解耦:传统的软件开发模式,模块之间的调用时直接调用,这样的系统很不利于系统的扩展,同时,模块之间的相互调用,数据之间的共享问题也很大,每个模块都要时时刻刻考虑其他模块会不会挂历;使用消息队列之后,模块之间不直接调用,而是通过数据,而且当某个模块挂了以后,数据仍旧会保存在消息队列中。最典型的就是生产者-消费者模式。
  • 削峰填谷:某一时刻,系统的并发请求暴增,原因超过了系统的最大处理能力后,如果不做任何处理,系统会崩溃;使用消息队列以后,服务器把请求推送到消息队列中,由专门的处理消息程序以合理的速度消费消息,降低服务器的压力。

springboot集成redis

  • 引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.10.0</version>
</dependency>
  • 使用redis作为消息队列,看到以前有人将消息插入到redis中,再进行读取,但这样就没有发送消息这一步骤了。我们这里使用了RedisTemplate.convertAndSend()方法和一个MessageListener实现类。

RedisTemplate配置

import com.jack.redismq.consumer.RedisListener;
import com.jack.redismq.util.RedisObjectSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Created by zhang_j on 2019/12/3
 */
@Configuration
public class RedisConfig {

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisConfig.class);

    /**
     * RedisTemplate解决乱码问题
     */
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){
        LOGGER.info("redis序列化配置开始");
        RedisTemplate<String,Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        //string序列化方式
//        RedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
        RedisObjectSerializer redisObjectSerializer = new RedisObjectSerializer();

        //设置默认序列化方式
        template.setDefaultSerializer(redisObjectSerializer);
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(redisObjectSerializer);
        LOGGER.info("redis序列化配置结束");

        return template;
    }

    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory factory){
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(factory);
        container.addMessageListener(new RedisListener(),new PatternTopic("demo-channel"));
        return container;
    }
}
  • 我们这边使用自己的序列化和反序列化方式,因为感觉使用GenericJackson2JsonRedisSerializer不是特别友好。
  • 代码如下:
public class RedisObjectSerializer implements RedisSerializer<Object> {

    static final byte[] EMPTY_ARRAY = new byte[0];

    @Override
    public Object deserialize(byte[] bytes) {
        if (isEmpty(bytes)) {
            return null;
        }
        ObjectInputStream oii = null;
        ByteArrayInputStream bis = null;
        bis = new ByteArrayInputStream(bytes);
        try {
            oii = new ObjectInputStream(bis);
            Object obj = oii.readObject();
            return obj;
        } catch (Exception e) {

            e.printStackTrace();
        }
        return null;
    }

    @Override
    public byte[] serialize(Object object) {
        if (object == null) {
            return EMPTY_ARRAY;
        }
        ObjectOutputStream obi = null;
        ByteArrayOutputStream bai = null;
        try {
            bai = new ByteArrayOutputStream();
            obi = new ObjectOutputStream(bai);
            obi.writeObject(object);
            byte[] byt = bai.toByteArray();
            return byt;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    private boolean isEmpty(byte[] data) {
        return (data == null || data.length == 0);
    }

}

消费者

  • 我们实现的MessageListener接口便是监听到队列中消息的回调方法。

    • message:redis消息类,该类中仅有两个方法

      • byte[] getBody():以二进制形式获取消息体
      • byte[] getChannel(): 以二进制形式获取消息通道
    • pattern:二进制形式的消息通道,和message.getChannel()返回值相同

  • 简单Redis队列监听器代码如下:

public class RedisListener implements MessageListener {
    private static final Logger LOGGER = LoggerFactory.getLogger(RedisListener.class);

    @Override
    public void onMessage(Message message, byte[] pattern) {
        LOGGER.info("从消息通道={}监听到消息",new String(pattern));
        LOGGER.info("从消息通道={}监听到消息",new String(message.getChannel()));
        LOGGER.info("元消息={}",new String(message.getBody()));

        /**
         * 新建一个用于反序列化的对象,注意这里的对象和前面配置的一样
         * 因为我前面设置的默认序列化方式为GenericJackson2JsonRedisSerializer
         * 所以这里的实现方式为GenericJackson2JsonRedisSerializer
         */
//        RedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
        RedisObjectSerializer redisObjectSerializer = new RedisObjectSerializer();
        LOGGER.info("反序列化后的消息={}",redisObjectSerializer.deserialize(message.getBody()));
    }
}
  • 注意这里的序列化方式需要与配置的一致
  • 在实现完队列监听器之后,需要将这个监听添加到redis队列监听器容器中,注意这里需要放到配置中,代码就是RedisConfig中的container方法。
  • 注意container方法中的管道名要和推送消息的管道名一致

生产者

@Service
public class Producer {
    @Autowired
    private RedisTemplate redisTemplate;

    public void produce(Object msg){
        redisTemplate.convertAndSend("demo-channel",msg);
    }
}

测试

  • 这里我们新建了一个User类用于测试
public class User implements Serializable {

    private static final long serialVersionUID = -8289770787953160443L;

    public User(Integer userId, String userName, String password, String phone) {
        this.userId = userId;
        this.userName = userName;
        this.password = password;
        this.phone = phone;
    }

    private Integer userId;
    private String userName;
    private String password;
    private String phone;


    public Integer getUserId() {
        return userId;
    }
    public void setUserId(Integer userId) {
        this.userId = userId;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getPhone() {
        return phone;
    }
    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", password='" + password + '\'' +
                ", phone='" + phone + '\'' +
                '}';
    }
}
  • 测试代码如下:
@SpringBootTest
class ProducerTest {
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Test
    void redisConfig() {
        redisTemplate.opsForValue().set("user1",new User(1,"yj","123","111"));
        User user1 = (User)redisTemplate.opsForValue().get("user1");
        Assert.assertNotNull(user1);
    }

    @Test
    void produce(){
        redisTemplate.convertAndSend("demo-channel",new User(1,"yj","123","111"));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值