Redis 消息实例+DeferredResult 异步长轮询

Redis发布订阅

配置消息监听器

package com.example.demo.common;

import java.util.concurrent.CountDownLatch;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisMessageListener {

    private static final String TOPIC = "TOPIC";

    /**
     * redis消息监听容器
     * @param redisConnectionFactory redis连接工厂
     * @param messageListenerAdapter redis消息监听适配器
     * @return
     */
    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory redisConnectionFactory,
        MessageListenerAdapter messageListenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(redisConnectionFactory);
        container.addMessageListener(messageListenerAdapter, new PatternTopic(TOPIC));
        return container;
    }

    /**
     * @param receiver  适配器处理器(defaultListenerMethod 默认的监听器处理方法)
     * @param stringRedisSerializer
     * @return
     */
    @Bean
    public MessageListenerAdapter adapter(ReceiverRedisMessage receiver, StringRedisSerializer stringRedisSerializer) {
        //绑定监听方法 messageReceive
        MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(
            receiver, "messageReceive");
        //设置序列化
        messageListenerAdapter.setStringSerializer(stringRedisSerializer);
        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        messageListenerAdapter.setSerializer(genericJackson2JsonRedisSerializer);
        return messageListenerAdapter;
    }


    @Bean
    public ReceiverRedisMessage receiver(CountDownLatch latch) {
        return new ReceiverRedisMessage(latch);
    }
    @Bean
    public CountDownLatch latch() {
        return new CountDownLatch(1);
    }
    @Bean
    public StringRedisSerializer stringRedisSerializer(){
        return new StringRedisSerializer();
    }
}

消息处理器

package com.example.demo.common;

import java.util.concurrent.CountDownLatch;
import lombok.extern.slf4j.Slf4j;

/**
 * @author wh
 * @date: 2019/9/26 0026 11:26 
* @desc: */
@Slf4j public class ReceiverRedisMessage { private CountDownLatch latch; public ReceiverRedisMessage(CountDownLatch latch) {this.latch = latch;} public void messageReceive(String jsonString) { //具体消息处理 try { log.info("处理消息:{}",jsonString); }finally { latch.countDown(); } } }

测试

package com.example.demo;

import com.alibaba.fastjson.JSON;
import com.example.demo.common.RedisMessageService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisMessageApplicationTests {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Test
    public void contextLoads() {
        redisTemplate.convertAndSend("TOPIC",JSON.toJSONString("发送消息"));
    }

}

在这里插入图片描述

Redis 配合异步长轮询

异步消息容器


import com.ngc.common.web.http.HttpResponseEntity;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.request.async.DeferredResult;

@Slf4j
public class MessageContainer {

    /**
     * 用户DeferredResult绑定
     */
    private static ConcurrentHashMap<String, DeferredResult<HttpResponseEntity>> userDeferredResultMap = new ConcurrentHashMap<>();


    /**
     * 缓存需要推送消息DeferredResult
     *
     * @param key 用户Id
     * @param value DeferredResult
     */
    public static void put(String key, DeferredResult<HttpResponseEntity> value) {
        if (StringUtils.isBlank(key) || Objects.isNull(value)) {
            return;
        }
        log.info("异步消息新增=={}", key);
        userDeferredResultMap.put(key, value);
    }

    /**
     * 移除
     */
    public static void remove(String key, DeferredResult<HttpResponseEntity> deferredResult) {
        if (StringUtils.isBlank(key)) {
            return;
        }
        log.info("异步消息移除=={}", key);
        if (userDeferredResultMap.containsKey(key) &&
            userDeferredResultMap.containsValue(deferredResult)) {
            //移除
            boolean remove = userDeferredResultMap.remove(key, deferredResult);

            if (remove == true) {
                log.info("异步消息移除成功=={}", key);
            } else {
                log.info("异步消息移除失败=={}", key);
            }
        }
    }

    /**
     * 设置返回结果
     */
    public static void setResult(HttpResponseEntity result) {
        if (Objects.isNull(result)) {
            return;
        }
        Iterator<Entry<String, DeferredResult<HttpResponseEntity>>> iterator = userDeferredResultMap
            .entrySet().iterator();

        //循环遍历,setResult,并移除已经推送过
        while (iterator.hasNext()) {
            Entry<String, DeferredResult<HttpResponseEntity>> next = iterator.next();

            log.info("异步消息发送消息=={}", next.getKey());
            DeferredResult<HttpResponseEntity> deferredResult = next.getValue();
            deferredResult.setResult(result);
            iterator.remove();
        }
    }
}

Controller 层



import com.ngc.bs.common.listener.MessageContainer;
import com.ngc.common.web.http.HttpResponseCodeEnum;
import com.ngc.common.web.http.HttpResponseEntity;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.jeecgframework.core.util.ResourceUtil;
import org.jeecgframework.web.system.pojo.base.TSUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;


@Slf4j
@RestController
@RequestMapping("/message")
public class MessageController {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 工作台消息
     */
    @ResponseBody
    @RequestMapping(value = "/workbench")
    public DeferredResult<HttpResponseEntity> workench(HttpServletRequest request) {
        log.info("轮询接口进入....");
        //https://blog.csdn.net/yzf913214/article/details/53915378

        request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
        DeferredResult<HttpResponseEntity> deferredResult = new DeferredResult<>(30000L,
            HttpResponseEntity.error(HttpResponseCodeEnum.NOT_FOUND.getCode(), "no message")
        );

        //检查用户登录信息,如果未登录直接返回,不进行消息订阅
        TSUser user = ResourceUtil.getSessionUser();
        if (Objects.isNull(user)) {
            deferredResult.setResult(HttpResponseEntity.error("请重新登录"));
            return deferredResult;
        }

        String key = user.getId() + "_" + System.currentTimeMillis();
        //异步完成时调用,为了防止超时,网络故障,增加第二套保证
        deferredResult.onCompletion(() -> MessageContainer.remove(key, deferredResult));
        MessageContainer.put(key, deferredResult);
        // log.info("轮询接口结束返回....deferredResult.getResult():"+deferredResult.getResult()+"    deferredResult.hasResult():      "+deferredResult.hasResult()+"deferredResult.setErrorResult(\"erro\")  "+deferredResult.setErrorResult("erro"));
        return deferredResult;
    }
}

1.在使用注解时使用@RequestMapping不要使用@GetMapping
2.Spring boot 项目使用t摩擦他启动
3. 配置参数 如下 或者使用实例request设置

 <async-supported>trueasync-supported>

测试

log:
在这里插入图片描述
前端返回

Redis 消息实例+DeferredResult 异步长轮询_第1张图片
当超时会返回默认超时结果
Redis 消息实例+DeferredResult 异步长轮询_第2张图片

注意

1 不建议多个长轮询 chrome最多六个线程处理 长轮询最好只存在一个即可
2.在使用注解时使用@RequestMapping不要使用@GetMapping
3.Spring boot 项目使用t摩擦他启动
4. 配置参数 如下 或者使用实例request设置

你可能感兴趣的:(Spring,Redis,java)