实时消息推送(二)SSE+Redis

一.SSE

实现消息推送的方式有很多:轮询、长轮询、SSE、WebSocket等等,上篇文章已经介绍了WebSocket,但在有些时候并不需要客户端利用WebSocket发送数据,即客户端通过HTTP协议握手后,只负责接收服务端数据。此时,使用SSE不需引入其他依赖,有效降低开发成本。
SSE基于HTTP协议,一般意义上的 HTTP 协议是无法做到服务端主动向客户端推送消息的,但SSE变换了一种思路,就是服务器向客户端声明,发送的是流信息,本质上,这种通信就是以流信息的方式。SSE在服务器和客户端之间打开一个单向通道,服务端响应的不再是一次性的数据包而是text/event-stream类型的数据流信息,在有数据变更时从服务器流式传输到客户端。
整体的实现思路有点类似于在线视频播放,视频流会连续不断的推送到浏览器,你也可以理解成,客户端在完成一次用时很长(网络不畅)的下载。

二.SSE+Redis实时消息推送

需求分析:

后台实时获取Redis里写入的数据,前端实时展示。我这里应用场景是MQTT服务推送消息,经过计算后将消息存入Redis,后端将消息实时推送前端页面进行展示。
代码:SpringBoot+SSE+Redisson

1.SseEmitter

@RestController
@RequestMapping("/sse")
public class SseController {
    private static final Logger logger = LogManager.getLogger(SseController.class);
    private static final Map<String, SseEmitter> sseCache = new ConcurrentHashMap<>();

    public static void remove(String key) {
        sseCache.remove(key);
        logger.info("移除连接:{}", key);
    }

    private static Runnable completionCallBack(String key) {
        return () -> {
            logger.info("结束连接:{}", key);
            remove(key);
        };
    }

    private static Runnable timeoutCallBack(String key) {
        return () -> {
            logger.info("结束连接:{}", key);
            remove(key);
        };
    }

    private static Consumer<Throwable> errorCallBack(String key) {
        return throwable -> {
            logger.info("连接异常:{}", key);
            remove(key);
        };
    }

    @GetMapping(path = "/subscribe", produces = TEXT_EVENT_STREAM_VALUE)
    public SseEmitter subscribe(String id) {
        if (sseCache.get(id) != null) {
            return sseCache.get(id);
        }
        // 超时时间设置为1小时
        SseEmitter sseEmitter = new SseEmitter(3_600_000L);
        sseCache.put(id, sseEmitter);
        sseEmitter.onTimeout(timeoutCallBack(id));
        sseEmitter.onCompletion(completionCallBack(id));
        sseEmitter.onError(errorCallBack(id));
        return sseEmitter;
    }

    public void sendMessage(List<RMap<Object, Object>> message) {
        for (SseEmitter sseEmitter : sseCache.values()) {
            try {
                sseEmitter.send(message);
            } catch (IOException e) {
                logger.error("sse send error.", e);
            }
        }
    }
}

2.Redis配置

1)导入Redisson依赖,也可使用Jedis实现

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.23.1</version>
</dependency>

2)配置application.yml文件

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    database: 3
    password: 123456
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        max-wait: -1ms
        min-idle: 0
      shutdown-timeout: 100ms
#    消息订阅主题
    subscribe: sse

3)RedissonConfig,配置Redis的连接

@Configuration
public class RedissonConfig {
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private String port;
    @Value("${spring.redis.password}")
    private String password;

    @Bean
    RedissonClient redisson() {
        Config config = new Config();
        config.setCodec(new StringCodec())
                .useSingleServer()
                .setAddress("redis://" + host + ":" + port);
        if (!StringUtils.isEmpty(password)) {
            config.useSingleServer().setPassword(password);
        }
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}

4)RedisConfig监听Redis的发布订阅消息

@Configuration
public class RedisConfig {
    @Value(value = "${spring.redis.subscribe}")
    private String subscribe;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private SseController sseController;

    @Bean
    public RedisMessageListenerContainer listenerContainer(RedisConnectionFactory factory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(factory);
        //监听设备MQTT频道并发送SSE消息
        container.addMessageListener((a, b) -> {
            String msg = new String(a.getBody());
            //根据订阅消息查询Hash
            List<RMap<Object, Object>> list = new ArrayList<>();
            list.add(redissonClient.getMap(msg));
            sseController.sendMessage(list);
        }, new PatternTopic(subscribe));
        return container;
    }
}

三.postman测试

1.新建Http请求
请添加图片描述
2.创建一个Hash作为模拟存入的消息,然后使用Redis的发布。实际项目中,消息存入或变更时,自动触发发布命令。
请添加图片描述
3.此时postman持续接收消息
请添加图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值