持久化SSE对象

SpringBoot整合SSE,实现后端主动推送DEMO

前些日子写了整合SSE得demo。但是SSE对象是存储在ConcurrentHashMap<String, SseEmitter>中。在正式环境明显就不行了,服务重启一下的话都没有了。

那么要持久化,第一选择放redis

1、写了一个redis操作组件

SseEmitterStore

/**
 * 不考虑redis 连接异常问题
 * @author cmy
 * @date 2024/8/21 10:55
 */
@Component
public class SseEmitterStore {

    private ConcurrentHashMap<String, SseEmitter> emitters = new ConcurrentHashMap<>();
    @Resource
    private RedisTemplate<String, Object> redisTemplate;


    public void addEmitter(String key, SseEmitter emitter) {
        emitters.put(key, emitter);
        redisTemplate.opsForHash().put("sse-emitters", key, emitter);
    }

    public void removeEmitter(String key) {
        emitters.remove(key);
        redisTemplate.opsForHash().delete("sse-emitters", key);
    }

    @PostConstruct
    private void init() {
        Map<Object, Object> temp = redisTemplate.opsForHash().entries("sse-emitters");
        temp.forEach((key, value) -> {
            if (value instanceof SseEmitter) {
                emitters.put(key.toString(), (SseEmitter) value);
            }
        });
    }

    public ConcurrentHashMap<String, SseEmitter> getEmitters() {
        return emitters;
    }
}

Controller修改

public class SseController {


    @Resource
    SseEmitterStore sseEmitterStore;

    @GetMapping("/subscribe/{id}")
    @CrossOrigin(origins = "*")
    public SseEmitter subscribe(@PathVariable String id) {
        SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
        sseEmitterStore.addEmitter(id,emitter);
        emitter.onCompletion(() -> sseEmitterStore.removeEmitter(id));
        emitter.onError(e -> sseEmitterStore.removeEmitter(id));
        return emitter;
    }

    @GetMapping("/unbind/{id}")
    @CrossOrigin(origins = "*")
    public ServerResponse deleteItem(@PathVariable String id) {
        this.sseEmitterStore.removeEmitter(id);
        return ServerResponse.success(true);
    }
}

异步发送消息service

    @Async
    public void broadcastMessage(String message) {
        List<String> keysToDelete = new ArrayList<>();

        this.sseEmitterStore.getEmitters().forEach((k, v) -> {
            try {
                v.send(message);
            } catch (Throwable t) {
                keysToDelete.add(k);
            }
        });
        keysToDelete.forEach(this.sseEmitterStore::removeEmitter);
    }

2、无法序列化的问题

跑起来之后,结果报错

DefaultSerializer requires a Serializable payload but received an object of type [org.springframework.web.servlet.mvc.method.annotation.SseEmitter]

错误信息已经很明显了

因为 SseEmitter 并不是一个实现了 Serializable 接口的类,因此不能被默认的序列化器正确处理。

问了AI

        

3、解决无法序列化问题

3.1自定义redis自定义序列化器

public class CustomJackson2JsonRedisSerializer<T> implements RedisSerializer<T> {

    private static final long serialVersionUID = -7649863253433761554L;
    private final ObjectMapper objectMapper;

    public CustomJackson2JsonRedisSerializer() {
        this.objectMapper = new ObjectMapper();
        this.objectMapper.registerModule(new JavaTimeModule());
        this.objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        try {
            return objectMapper.writeValueAsBytes(t);
        } catch (JsonProcessingException e) {
            throw new SerializationException("Could not write JSON: " + e.getMessage(), e);
        }
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        try {
            return (T) objectMapper.readValue(bytes, SseEmitter.class);
        } catch (IOException e) {
            throw new SerializationException("Could not read JSON: " + e.getMessage(), e);
        }
    }
}

3.2redis配置,使序列化器生效

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        CustomJackson2JsonRedisSerializer<Object> jacksonSerializer = new CustomJackson2JsonRedisSerializer<>();

        // 根据实际情况,自行修改
        template.setKeySerializer(stringSerializer);
        template.setValueSerializer(jacksonSerializer);
        template.setHashKeySerializer(stringSerializer);
        template.setHashValueSerializer(jacksonSerializer);

        template.afterPropertiesSet();
        return template;
    }
}

再次启动服务,即生效。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
05-25
PHP SSE (Server-Sent Events) 是一种在 Web 浏览器和服务器之间实现实时通信的技术。它基于 HTTP 协议,可以在客户端浏览器与服务器之间建立持久化的连接,从而允许服务器实时向客户端推送数据。 使用 SSE 技术可以轻松地创建实时更新的 Web 应用程序,例如:实时股票报价、在线聊天、实时推送新闻等。 在 PHP 中实现 SSE 技术,可以使用 EventSource 对象来处理从服务器推送的事件。在服务器端,可以使用 header() 函数设置响应头,以便浏览器了解服务器发送的数据类型是 text/event-stream,并且设置连接保持时间。 以下是一个简单的 PHP SSE 示例: ``` <?php header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); while (true) { // 从数据库或其他数据源获取最新的数据 $data = get_latest_data(); // 发送事件 echo "data: " . json_encode($data) . "\n\n"; // 刷新输出缓冲区 ob_flush(); flush(); // 等待一段时间再发送下一个事件 sleep(1); } ?> ``` 在客户端,可以使用 JavaScript 来监听服务器发送的事件,例如: ``` var source = new EventSource('sse.php'); source.onmessage = function(event) { var data = JSON.parse(event.data); // 处理从服务器接收到的数据 }; ``` 需要注意的是,SSE 技术虽然可以实现实时通信,但它并不是 WebSocket 技术的替代品。WebSocket 技术还提供了双向通信的能力,并且在性能和可扩展性方面也更优秀。因此,在选择实现实时通信功能时,应该根据具体的需求和应用场景选择合适的技术。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值